沿着 N-Gram 到 RLHF 的连续构建流程,理解 GPT 每一步为什么非得这样做。来源:《GPT 图解:大模型是怎样构建的》(黄佳,人民邮电出版社)。
两者都在预训练模型之上继续训练,但改的不是同一件事。监督微调提供的是“标准答案”:模型照着写,学会的是应答的形式与风格。对齐提供的是“好坏排序”:没有唯一正确答案,只有相对偏好,因此无法直接做有监督学习,必须先把人类排序训练成一个能自动打分的奖励模型,再用强化学习依据奖励调整模型的输出策略。前者解决“会不会答”,后者解决“答得好不好、该不该答”。
两者用的是同一套计算:查询与键定权重,权重作用于值。区别只在三个张量各自从哪来。自注意力把被注意的对象换成序列自身,因此每个位置都能看到全句其他位置,编码器用它来建模输入内部关系,解码器也用它但必须加掩码限制为只看已生成部分。编码器-解码器注意力则把查询交给解码器、把键和值交给编码器输出,作用是在生成每个词时决定该回看输入的哪些位置。
循环结构把信息传递限制在一条串行的时间链上:隐藏状态一步步改写,梯度也要沿这条链反向连乘,因此既无法并行,也难以学会长距离依赖,还伴随计算效率低与信息传递时的梯度消失爆炸。Transformer 用自注意力取代循环,让编码器和解码器可以同时处理输入序列中的所有元素、让它们互相作用,从而在不同长度的序列之间并行计算。代价是它不再按顺序处理序列,必须额外引入位置编码把顺序信息补回去。
两者在词向量学习上十分相似,都通过捕捉上下文的语义信息,把词的离散表示转换为连续向量表示。分界线在于目标:神经概率语言模型进一步用神经网络学习词与词之间的关系,从而计算目标词在给定上下文条件下出现的概率,它是在做语言建模;而词向量算法虽然也能预测词,但那只是训练嵌入的手段,训练完只取嵌入矩阵、丢掉预测头。因此一个属于语言模型谱系,一个属于表示学习谱系。
两者都试图把离散符号映射到连续向量空间,让原本没有意义的词汇编码变成蕴含语言信息的表示,因此都可以看作分布式表示的应用实例。但实现方式与表达能力差别很大:词袋只是把词频摆进一个由词表决定长度的向量里,每一维严格对应一个词;词向量则通过学习单词在上下文中的共现关系生成低维密集向量,每一维承载的是特征而非某个词。前者是计数,后者是学习。
两者基本结构都是 Transformer,差别在训练任务与由此决定的语境方向。一个像做填空题,遮住词让模型依据双向上下文补全;一个像做文字接龙,只依据上文往下续。前者更擅长理解,后者更擅长生成。原书进一步给出判断:GPT 这条路更接近语言模型的本质,因为它的预训练过程紧凑而有效地再现了自然语言生成的过程——语言模型的核心任务本就是为给定上下文生成合理的概率分布。
训练损失只能说明模型在见过的数据上拟合得如何。做法是把语料切成三份:训练集用于更新参数,评估集用于训练过程中监控,测试集留到最后。每一轮训练结束后,在评估集上以不更新梯度的方式算一次损失;持续跟踪最低验证损失,只有当本轮验证损失低于历史最低时才保存当前权重,最终得到的就是验证表现最好的那份模型。评估时切到推理模式并关闭梯度计算,评估结束再切回训练模式。
两者都是从模型给出的词概率分布中挑选序列的策略。贪婪搜索在每个时间步只保留概率最高的那一个词,计算效率最高,但容易陷入局部最优,典型症状是某个 token 反复出现、或无意义的词组循环出现。集束搜索每个时间步同时保留 K 个候选序列,用整个序列的累积概率排序,每轮再从中留下得分最高的 K 条,直到达到预定长度或全部候选遇到结束符;它能更好地平衡全局与局部最优、通常生成质量更高,代价是每步要处理 K 条序列,计算复杂度与耗时都上升。
生成不能像训练那样一次算完,必须写成循环:把当前已有的 token 序列整体送进模型,模型对每个位置都给出词表上的分数,只取最后一个位置的分数选出下一个词;把选出的词追加到序列末尾,再进入下一轮。循环需要两个停止条件——达到设定的最大生成长度,或者生成了结束符。如果一次把所有位置的结果都当成输出、并且不追加新词,模型就没有在自回归生成,只是在并行复述训练时的模式。
给每个句子加上起始符与结束符,得到一条完整序列,然后用一次错位切出两份数据:去掉最后一个元素作为输入序列,从第二个元素开始作为目标序列。目标序列正好是输入序列向右移动一位的结果,于是每个位置的任务都变成“看着前文预测下一个词”。训练时把真实目标序列直接喂给解码器,而不是喂模型自己生成的词,这就是教师强制,它能帮助模型更快收敛。批内长度不一致时用填充符补齐,并在损失里忽略填充位置。
搭网络时最省时间的做法是给每一层都标注期望形状,并在实现过程中逐步核对,而不是等报错再回溯。原因很直接:输入形状与层要求的形状不符时,要么程序无法运行,要么能跑但得到错误结果——后者更危险。具体做法是在每个子层出口写下“批次、序列长度、特征维度”三元组,注意力处补上头数维度,多头合并处确认展平回原特征维度,输出头处确认最后一维等于词表大小。任何一处对不上,问题就锁定在这一层。
改动量小到只有一处:保持词嵌入层与输出层不变,把中间那个“接收展平向量”的线性层换成循环层,让它直接吃嵌入层输出的三维张量,逐时间步处理并输出每个时间步的隐藏状态;然后只取最后一个时间步的隐藏状态送进输出层生成预测。数据集、训练参数、训练循环全部沿用。改完之后,控制上下文长度的那个参数会从模型结构里彻底消失,因为不再需要展平,输入序列长度不再影响网络结构。
独热向量长度等于词表大小,只有一个位置是一、其余全为零;把它乘上权重矩阵,实际效果等同于从矩阵里取出对应的那一行。既然结果一样,就可以省掉造独热向量和做矩阵乘法这两步:把第一层从线性层换成专用嵌入层,输入直接给词的整数索引,由嵌入层按索引查表返回向量。三处需要同时改动——层定义、训练数据的输入格式、以及从权重矩阵取向量的下标方式,因为权重形状从“嵌入维度乘词表”变成了“词表乘嵌入维度”。
六步即可跑通:构建语料并生成词汇表与双向索引字典;按滑动窗口生成训练对,窗口大小决定“周围词”的范围;把输入侧编成独热向量、标签侧只保留词表索引;定义一个两层网络,第一层从词表大小映射到嵌入维度,第二层映射回词表大小,两层都不带偏置;用交叉熵作为损失、逐样本做梯度清零、反向传播、参数更新;训练结束后从第一层权重矩阵里按索引取出每个词的向量,维度设成二就能直接画散点图检查聚类效果。
词袋把文本表示成词频向量:先分词并建立词汇表,向量长度等于词汇表大小,每一维记录对应词在文本中出现的次数,词序被完全丢弃。有了定长向量就能比较文本:用点积除以两个向量的范数得到余弦相似度,取值在负一到一之间,越接近一越相似。对全部句子两两计算即可得到相似度矩阵,再画成热图,颜色越深表示语义越接近。因为余弦关注方向而非长度,句子长短差异带来的影响会被抵消。
不需要梯度也能搭出一个语言模型,全流程只有计数与归一化:先把语料切成单个元素,滑动窗口取出所有二元组,以前一个元素为键、后一个元素为值统计频次,得到一张“前缀到后继计数”的字典;再对每个前缀把计数除以该前缀下的总计数,得到条件概率;生成时接收一个前缀,取其概率最大的后继返回,把这个后继拼回前缀末尾,循环若干次即可产出连续文本。整套东西就是一张归一化后的计数字典。
预训练只优化一件事:让下一个词的预测尽量接近语料中的真实词。这个目标里没有“回答用户问题”“语气友好”“拒绝有害请求”这些约束,所以基础模型擅长续写却不擅长应答,给它一个问题它可能续写出另一个问题。要补上这层,需要引入语料里不存在的信号——人类偏好。做法是让标注员依据一份指导方针评价模型回答,把这些评价转化为可规模化使用的奖励信号,再用强化学习按奖励更新模型,使其目标与人类目标一致。
随着参数数量增加和训练语料扩充,大模型逐渐展现出一系列新能力,这些能力并非通过显式编程引入,而是在训练过程中自然呈现出来,研究者称之为涌现能力。机制上可以这样理解:预测下一个词这个目标本身不变,但要在大语料上把它做好,模型必须顺带学会词法、句法、事实关联乃至推理模式,规模越大,能被压进参数的规律就越复杂。原书还提到一项研究结论:参数超过某个量级后推理能力才开始显现。
自回归的核心思想是基于已有的序列预测下一个元素,但它在两个阶段含义不同。训练阶段:把固定长度的输入序列交给模型,让它预测下一个词,与实际词比较算损失并更新参数;靠目标序列右移一位来批量构造样本,靠因果掩码保证每个位置只看到前文,因此整个序列可以一次并行算完,自回归只体现在训练目标上。生成阶段:给一段种子文本,模型产出一个词,把这个词拼回输入再次送入模型,循环往复直到达到长度上限或遇到结束符,此时依赖的是模型自己刚生成的内容。
生成任务里,模型在预测某个位置的词时本来就看不到之后的内容;如果训练时让它看到,就等于用答案做题,学到的规律在推理时全部失效。做法是在算完注意力得分之后、归一化之前,把一个上三角掩码矩阵与得分相加,被遮位置的得分变成接近负无穷,归一化后权重接近零。于是解码器在每个位置只能关注当前及之前的位置。这也解释了为什么训练时明明可以全并行,仍然要把目标序列右移一位。