上一篇收尾时留了一句话没兑现:最后一层的向量怎么变成词表上的概率。这一篇把它补上,因为“预测下一个词”这句话真正落地的地方就在这里。可实际动手去看,网络最后一层吐出来的东西跟“词”没有半点关系——它是一个数向量,比如 768 个实数。

一个 768 维的向量,凭什么指认出五万个词里的某一个? 而做这件事的那张矩阵,为什么常被说成“就是输入那张词向量表”?

要跨过去的是一段什么距离

前面几篇里,一句话进了模型,先变成一串词向量,经过几层注意力和前馈网络,出来的时候每个位置还是 dd 维向量(GPT-2 small 里 d=768d=768)。这个向量不是词,它是“这个位置上的上下文含义”:混合了前面所有词的信息,但没有任何一个坐标对应“猫”或“跑”。

而我们要的是一张有 50257 个格子的概率表,每格对应词表里的一个词,加起来等于 1。从 768 个数到五万个概率,中间必须有一台翻译机。

最笨但正确的想法:拿这个向量去表里挨个比像不像

先想输入那一端做了什么:词向量表 EE 是一张 V×dV \times d 的表(VV 是词表大小),第 kk 行就是第 kk 个词的向量;“把词变成向量”就是按编号取一行,也就是查表。

输出这一端做的是反过来的事:手里有一个向量,去表里找最像它的那一行。“像不像”可以直接用点积算:把隐藏向量 hh 和某个词向量 eke_k 逐维相乘再相加,方向越接近、长度越相当,点积越大。

于是整件事变成:拿 hh 和词表里每一行比一次,得到 50257 个分数,谁高就说明“下一个词更像谁”。输入是“词→向量”,输出是“向量→词”,两次都是同一张表在起作用——这个观察后面会变成一个真实的设计。

换成准确的说法:logits 与 softmax

把“挨个比”写成矩阵乘法:词向量表转置成 d×Vd \times V,一次乘完。

z=Wout h,Wout∈RV×d,z∈RVz = W_{\text{out}}\, h,\qquad W_{\text{out}} \in \mathbb{R}^{V \times d},\quad z \in \mathbb{R}^{V}

这个 zz 叫 logits,也就是“原始分数”。它不是概率,可以是负数,也不要求加起来等于某个数。想让分数变成概率,套 softmax:

pk=ezk∑j=1Vezjp_k=\frac{e^{z_k}}{\sum_{j=1}^{V} e^{z_j}}

这和上一篇掩码里用的是同一个函数:指数把分数变成正数,再按总和分配一个固定预算。它只保序、只归一,不改变谁大谁小。

四个词的小例子,手算一遍

设词表只有四个词,d=3d=3,某个位置的隐藏向量是 h=[1,0,1]h=[1,0,1]。WoutW_{\text{out}} 的四行恰好是:

词向量与 hh 的点积
猫[1,0,1][1,0,1]2
狗[1,0,0][1,0,0]1
鱼[0,1,1][0,1,1]1
跑[0,1,0][0,1,0]0

取指数:e2=7.389e^{2}=7.389,e1=2.718e^{1}=2.718(狗、鱼各一个),e0=1e^{0}=1,总和 13.82513.825。于是

p=[0.534, 0.197, 0.197, 0.072]p=[0.534,\ 0.197,\ 0.197,\ 0.072]

四项相加正好是 1。模型的意思是:下一个词有 53% 的可能是“猫”,7% 的可能是“跑”。注意最后一项不是零:softmax 给每个词都留了份额,只是份额小。如果这里的正确答案是“跑”,0.072 就太低了,训练要做的事就是把这个分数顶上去——那是下一篇的话题。

从概率到一个具体的词:取最高分,还是抽签

有了这串概率,生成下一个词有两条路。

一条是取最大(argmax,贪心):上例会选出“猫”,每次都一样。

一条是按概率抽签:随机数落在哪个区间就取哪个词,上例里约 53% 的机会出“猫”,偶尔蹦出“鱼”。

区别全在最后这一步,前面的计算一模一样。同一个模型、同一个输入,两种做法能给出不同的句子。怎么调抽签的“放肆程度”(温度)、怎么同时保留几条候选路径(beam search),下一篇再说。

那张表为什么真的能用两次

如果把 WoutW_{\text{out}} 直接设成词向量表 EE(形状正好是 V×dV \times d,尺寸对得上),那么

zk=ek⋅hz_k = e_k \cdot h

也就是说,每个词的分数就是“它的词向量与当前隐藏向量的点积”。这不是近似,是恒等式。让两块权重共用一份参数,叫 weight tying(权重共享)。

为什么说得通?因为输出端问的问题是“当前语境下,下一个词应该落在向量空间的哪个方向”,而输入端的词向量表已经把这个空间建好了:语义相近的词方向相近。同一张表既回答“这个词什么意思”,又回答“下一步该往哪个意思走”,本来就是一体两面。

第二笔账是参数。GPT-2 的词表被扩到 50257,d=768d=768,一张表就是

50257×768≈3,860 万50257 \times 768 \approx 3{,}860\text{ 万}

个参数。12 层的 GPT-2 small 总共约 1.2 亿参数,这一张表占了将近三分之一。共享就省下同样大的第二张表;不共享,参数量要涨到 1.6 亿左右。

还有一层更细的差别:只当输入表用时,第 kk 行只有在第 kk 个词真的出现时才被更新,稀有词轮不到几次;共享之后每个位置都要给所有词算分数,每一行受到的更新都变多了。Press 和 Wolf 在 2016 年正是从这条更新规则出发提出共享的:他们报告困惑度下降,机器翻译模型参数量减半而效果不降 1。

这个比喻在哪里会失真

“找最像的一行”不是真的只挑一行。 输出是给所有 VV 个词打分并归一化,每条尾巴都非零。语言模型能写出意料之外的句子,一部分原因就在这些尾巴上。

点积不是“纯相似度”。 它没有除以长度,hh 的模长一变长,所有分数一起变大,softmax 就更尖锐。所谓调温度,做的正是缩放这个尺度。

共享不是必须的。 它要求输入端和输出端是同一套词表、同一维度,翻译模型的源语言和目标语言词表不同,就不能简单共享。也有大模型为了让训练时的矩阵切分更方便,故意把两块矩阵分开 2;即便如此,GPT-2 本身是共享的 3。Press 和 Wolf 的结论是实验性的收益,不是一条定理。

走完这一步

链路到这里通了:一句话进来,经过注意力和前馈网络,每个位置得到一个隐藏向量;最后一个位置的向量乘输出矩阵得到 VV 个 logits,softmax 压成概率表,取最高或抽签得到下一个词,接回输入,再来一遍。

“预测下一个词”这句话里藏着的,就是这个乘法和这次归一化。而训练要做的,是让正确那格的概率变大、其余变小——这件事怎么写成可求导的损失,一个 batch 里几十个位置怎么同时算分,是下一篇的内容。