简介:《ChatGPT大模型技术发展与应用》是刊于《数据采集与处理》2023年第5期的综述论文PDF,由南京航空航天大学团队撰写,面向自然语言处理方向的研究生、算法工程师与技术决策者,用于系统梳理大模型技术脉络、理清从统计方法到预训练范式的知识链条。全文沿语言模型、语义表示与预训练三条线索展开:从N-gram统计方法到神经网络语言模型,机器翻译研究催生Transformer与自注意力机制;从TF-IDF、pLSA、LDA到Word2Vec词向量,再到ELMo、BERT、GPT-2预训练框架;并剖析GPT-3生成不可控、知识谬误与逻辑推理薄弱等问题,说明ChatGPT如何借助指令学习、监督微调与人类反馈强化学习实现与人类期望的对齐。压缩包内含1个PDF文件,约2.15MB,含中英文摘要、关键词与参考文献,便于通读或按章节检索。已有526人学习,可作为论文写作、技术综述与组会汇报的参考底稿。
1. 反直觉的结论先摆在这
很多人默认 ChatGPT 的对话能力是参数规模堆出来的,这个判断只对了一半。一篇 2023 年发表的中文综述把大模型的技术脉络从头捋了一遍,给出的结论有点反直觉:GPT-3 已经证明大模型能生成通顺文本,但它同时暴露出三类硬伤——生成不可控、知识谬误、逻辑推理差。真正把大模型从“能说”推到“能聊”的,是在 GPT-3.5 上追加的指令学习、监督微调和基于人类反馈的强化学习。换句话说,预训练决定能力上限,对齐决定这份能力能不能被人用起来。搞清这条分界线划在哪、怎么划出来的,比背几个模型名字有用得多。
2. 语言模型演进:N-gram 的稀疏、RNN 的梯度与自注意力的并行
语言模型的数学定义朴素得不像话:给一个词序列 $S = W_1, W_2, \dots, W_k$,算出它的联合概率 $P(S) = P(W_1)P(W_2|W_1)\cdots P(W_k|W_1,\dots,W_{k-1})$,然后拿这个概率去预测下一个词。公式几十年没变过,变的是每一项条件概率到底怎么估。从统计方法到神经网络再到自注意力,每一次换代都是被前一代的某个结构性缺陷逼出来的,而不是谁灵机一动发明了新架构。
2.1 N-gram 的马尔科夫假设:把无限上下文砍成 n-1
按上面的定义直接算,第 k 个词要依赖前面所有词,参数空间和语料规模根本撑不住。N-gram 的做法是引入马尔科夫假设:当前词只跟前 n-1 个词相关,于是联合概率退化成
$$P(S)=\prod_{i=1}^{k}P(W_i \mid W_{i-n+1},\dots,W_{i-1})$$
每一项用极大似然估计,直接数语料里的频次:分子是这 n 个词连续出现的次数,分母是前 n-1 个词连续出现的次数。n=2 就是二元模型,n=3 是三元模型,工程上常用的是二元和三元。
这套方法的死结在于 n 的两难。n 调大,每个词能依赖更多上文,预测更准;但 n 一调大,绝大多数 n 元组在语料里压根没出现过,分子为 0,整个句子的概率被算成 0,这显然和语言事实不符。同时参数个数随词表大小指数增长,计算代价也跟着爆。业界的补救办法是数据平滑:拉普拉斯平滑最简单,不管出现几次都加 1,把 0 概率抬起来;更精细的有 Good-Turing、Kneser-Ney 和插值平滑,本质都是用低频事件的统计规律去补贴没见过的组合。
平滑能救急,救不了根。N-gram 把词当成离散符号,两个词之间没有任何“相似性”可言,“汽车”和“轿车”在模型眼里是两个毫不相干的 ID,语料里没出现过“轿车抛锚”不代表模型该对“汽车抛锚”一无所知。这个缺陷不是调参能解决的,必须换表示方式。
2.2 RNN 与门控机制:梯度连乘的死结
2003 年出现的第一版神经网络语言模型,思路是把前 n-1 个词做 one-hot 编码,乘一个投影矩阵降维成稠密词向量,拼接后过 tanh 隐藏层,再进线性层加 softmax 输出词表上的概率分布。词向量这个副产品后来比模型本身更有名,因为它第一次让“语义相近 = 向量距离近”变得可计算。
但前馈结构决定了它只能看固定窗口的历史,窗口外的信息一律丢失。循环神经网络把结构改成“当前输入 + 上一时刻隐状态”一起算,历史信息可以沿着时间步一路传下去,理论上能建模任意长的依赖。
理论很美好,训练很骨感。反向传播时梯度要沿时间步连乘,连乘因子的范数只要持续小于 1,梯度就指数衰减到接近 0;持续大于 1 就指数爆炸。梯度爆炸有现成的解法——梯度裁剪,设一个阈值,把梯度范数压到阈值以下,超了就按比例缩放。梯度消失就没这么好办了,裁剪只能控制幅度,救不回“信息在传递途中已经丢了”这个事实。
真正的解法是改结构。LSTM 引入输入门、遗忘门、输出门三个门控,用 sigmoid 输出 0 到 1 之间的系数,决定哪些信息保留、哪些丢弃;GRU 把它简化成更新门和重置门两个门,参数量更少。门控的本质是给梯度修了一条“高速公路”,让重要信息可以跨很多时间步几乎无损地传回去。这一改动直接催生了一批落地成果:情感分类里的递归张量网络、字符级文本生成、能合成手写样本的序列生成、端到端语音识别,以及最关键的 Encoder-Decoder 框架和 Seq2Seq 模型。
Seq2Seq 又暴露了新问题:解码时只用了编码器最后一个时刻的向量,前面的信息全被压缩进一个定长向量里,长句子必然丢信息。注意力机制就是这时候补上的——解码每一步都回头看一眼编码器的所有时刻,按相关度加权取用。这个设计后来被单独拎出来放大,就成了 Transformer 的核心。
2.3 自注意力:把串行计算变成矩阵乘法
2017 年提出的 Transformer 干脆把循环结构整个扔掉,只保留注意力和全连接。编码器由自注意力层和前馈层堆叠而成,解码器在此基础上多一个带掩码的自注意力层,保证生成第 t 个词时只能看到前 t-1 个词。词序信息不再靠循环结构的先后顺序隐式表达,而是显式加位置编码进输入。
自注意力的计算可以拆成三步:把输入序列分别映射成查询 Q、键 K、值 V 三组向量;用 Q 和所有位置的 K 做点积,得到当前位置与其他位置的相关程度;softmax 归一化成权重后,对 V 加权求和。写成矩阵形式就是
$$\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\left(\frac{QK^{T}}{\sqrt{d_k}}\right)V$$
关键在于并行性:每个位置的计算只依赖其他位置的表示和权重,位置之间没有先后依赖,整个序列可以一次性算完。这跟 RNN 必须一个时间步一个时间步往前推是完全不同的计算图,也是后来能把模型规模推到千亿级别的物理前提——GPU 最擅长的就是大规模矩阵乘法。
import numpy as np def softmax(x, axis=-1): x = x - x.max(axis=axis, keepdims=True) # 减最大值,防止 exp 溢出 e = np.exp(x) return e / e.sum(axis=axis, keepdims=True) def attention(Q, K, V, causal_mask=False): # Q/K/V: (batch, heads, seq_len, d_k) d_k = Q.shape[-1] # 除以 sqrt(d_k):d_k 越大点积方差越大,softmax 容易饱和成 one-hot,梯度趋于 0 scores = Q @ K.transpose(0, 1, 3, 2) / np.sqrt(d_k) if causal_mask: seq = Q.shape[-2] # 上三角为 True 的位置属于“未来”,训练时必须屏蔽,否则等于让模型抄答案 mask = np.triu(np.ones((seq, seq), dtype=bool), k=1) scores = np.where(mask, -1e9, scores) weights = softmax(scores, axis=-1) # 沿 key 维度归一化 return weights @ V, weights这段代码对应的是单头注意力的最小实现。d_k是每个头的维度,缩放因子 $\sqrt{d_k}$ 不是玄学调参,而是为了把点积的方差拉回 1 附近,避免 softmax 进入饱和区后梯度消失。causal_mask只在解码器或自回归生成时打开,它保证了“预测下一个词时看不到下一个词”这个训练约束不被破坏,是语言模型能自监督训练的根本保证。函数同时返回注意力权重矩阵,实际排查模型行为时,看这张权重图往往比看 loss 曲线更能定位问题。
多头注意力是在这个基础上再做一层扩展:把 Q、K、V 拆成多组低维子空间分别算注意力,再拼接起来。不同头可以各自关注不同的语言现象,有的盯句法依存,有的盯指代关系,最后融合成更丰富的表示。这套结构在句子级语言建模上全面超过 RNN,成了后续所有大模型的共同底座。
3. 语义表示与预训练:为什么“一词一向量”注定走不到 ChatGPT
语言模型解决的是“给定前文预测下一个词”,语义表示解决的是另一个问题:怎么把一段文本变成一个能保留语义的向量。这两条线在预训练时代合流,才出现了“先在大规模无标注语料上学会语言,再到具体任务上微调”这个范式。理解这条线上每一步为什么被淘汰,比记住模型名字重要得多。
3.1 统计语义表示:从词频到主题模型
最早的做法是 TF-IDF:一个词在当前文档里出现得越频繁、在整个语料库里出现得越少见,就越能代表这篇文档。计算简单,检索效果好,但它是纯词频统计,处理不了多义词和同义词,“苹果”在公司财报和水果论文里的权重完全一样。
LSA 换了个思路,把文档-词矩阵做奇异值分解,投影到低维空间,用潜在语义维度来表示文档。这能缓解一词多义,但分解出来的维度没有可解释的含义,说不清到底代表什么主题。
pLSA 引入概率生成视角:文档以一定概率选择主题,主题又是一组词的概率分布,文档、主题、词三者通过概率关联起来。问题出在生成新文档上——模型学到的主题分布是绑在训练文档上的,来一篇新文档,没法确定它的主题分布。而且参数量随文档数增长,过拟合风险高。
LDA 在 pLSA 上加了贝叶斯先验,给文档的主题分布和主题的词分布各套一个 Dirichlet 先验。有了先验,新文档可以先采样主题分布再生成词,生成过程完整了,对数据的依赖也降下来了。这里的关键参数是:M 是语料文档数,N 是单篇文档词数,K 是主题数(由人指定),α 是文档主题分布 θ 的先验参数,β 是主题词分布 φ 的先验参数,Z 是从 θ 采样出的主题,W 是从 φ 采样出的词。这套东西在文本主题分析、信息检索、推荐系统里用了很多年,但它终究是词袋模型,词序和句法信息全部丢失。
3.2 Word2Vec:词向量好用了,但天花板也看得见了
2013 年提出的 Word2Vec 是语义表示的分水岭。它用神经网络在大规模语料上做无监督预训练,把词映射到低维连续向量空间,语义相近的词在空间里距离也近。两种结构:CBOW 用上下文词预测目标词,Skip-Gram 用目标词预测上下文词。前者训练快,后者对低频词更友好。
它的局限同样清晰。每个词只有一个固定向量,多义词没法处理,“银行”在河边和金融街是同一个向量;语料里没出现过的新词直接没有向量;只建模词级关系,短语和句子级语义捕捉不到;预训练完的向量是死的,换一个下游任务也没法调整。
3.3 预训练-微调范式:从单向到双向,从特征提取到整体微调
2015 年出现了“语言模型预训练 + 下游任务迁移”的思路:用单向 LSTM 在无标注语料上做无监督预训练,把预训练好的模型当固定特征提取器,下游任务只微调输出层。这验证了预训练带来的先验语言知识确实有用,但特征提取器的用法限制了模型对任务的适应能力。
ELMo 把结构做深,用双向 LSTM 分别训练前向和后向两个语言模型,各自抽特征再加权融合。词向量从此变成上下文相关的,同一个词在不同句子里可以得到不同表示,词义消歧这类任务效果明显提升。但要注意一个细节:前向和后向是分开训练的,两个方向的 LSTM 在训练时互相看不见,所谓“双向”并不彻底。
2018 年 GPT 来了,用 Transformer 替换 LSTM,做单向语言模型预训练,靠自注意力的并行优势把参数规模和数据规模同时拉上去,证明了“Transformer + 预训练 + 微调”这套框架对多种 NLP 任务都有效。但单向设计意味着模型只能看左侧上下文,而人理解语言从来是两边一起看的。
同一年 BERT 走了另一条路:用 Transformer 编码器做双向预训练,预训练任务是掩码语言建模加句子关系检测——按一定概率遮住输入里的词再让模型预测,外加判断两个句子是不是同一语义关系。这样能同时利用左右两侧上下文。代价是计算更复杂,对数据量和训练技巧也更敏感。BERT 的公开数据是 Books Corpus 约 8 亿词加 Wikipedia 约 25 亿词,Base 版 12 层 Transformer、1.1 亿参数,Large 版 24 层、3.4 亿参数。
2019 年的 GPT-2 把规模继续推大,最大版本 15.42 亿参数,预训练语料是从互联网爬取的高质量文本 WebText,约 40 GB。规模上去之后出现了一个质变:不做任何微调,仅靠预训练参数就能直接做下游任务,也就是 zero-shot 能力。
| 模型 | 主干结构 | 预训练目标 | 公开规模数据 | 关键特点 |
|---|---|---|---|---|
| ELMo | 双向 LSTM | 前向/后向语言模型分别训练 | 未在原文给出 | 上下文相关词向量,双向不彻底 |
| GPT | 单向 Transformer | 自回归语言模型 | 未在原文给出 | 预训练+微调框架成型 |
| BERT | 双向 Transformer 编码器 | 掩码语言建模 + 句子关系检测 | Base 12 层 1.1 亿;Large 24 层 3.4 亿;语料约 33 亿词 | 真正双向上下文 |
| GPT-2 | 单向 Transformer | 自回归语言模型 | 最大 15.42 亿参数;WebText 约 40 GB | zero-shot 直接做下游任务 |
3.4 GPT-3 的边界:规模能解决生成质量,解决不了可控性
GPT-3 把这条路推到极致,参数量和数据量再上一个量级,few-shot 和上下文学习能力随之出现——给几个示例,模型就能照着做新任务,不用改一个参数。这确实揭示了大语言模型的潜力。
但它同时暴露了三类问题,而且这三类问题不是“再大一点就好了”的那种。生成不可控:同一个输入,模型可能给出风格、长度、立场完全不同的输出,没有稳定预期。知识谬误:模型会用非常自信的语气说出错误事实,因为它优化的是“像人写的下一个词”,不是“正确的事实”。逻辑推理差:多步推理链条一长就容易断,中间某一步错后面全错。
这三条恰好是对话产品的致命伤。一个聊天机器人可以偶尔答不上来,但不能答得理直气壮却是错的,也不能每次回答风格都变。问题出在训练目标上:预训练优化的是语料分布的似然,而人想要的输出是“有帮助、诚实、无害”,这两个目标之间隔着一层,必须单独去对齐。
4. 对齐学习:RLHF 三阶段如何把“人觉得好”变成损失函数
ChatGPT 相对 GPT-3.5 的核心增量就在这里。官方没有公布详细方案,但那篇综述基于公开的 InstructGPT 论文做了合理推测,把整个流程拆成三个阶段:指令学习与监督微调、奖励模型训练、基于人类反馈的强化学习。三个阶段串起来,解决的其实是“怎么把一个只会续写文本的模型,调教成一个会按要求回答问题、并且回答符合人类偏好的助手”。
4.1 幻觉的来源:预训练目标本身就没打算保证真实
在讲对齐之前,得先说清幻觉为什么是结构性的。自回归语言模型的训练目标是最大化 $P(W_t \mid W_1,\dots,W_{t-1})$,也就是在给定前文的情况下让下一个词的条件概率最大。这个目标只要求生成的文本在统计上像训练语料,不要求它对应真实世界。当模型遇到训练分布之外的提问时,它不会说“我不知道”,而是按最像的续写方式补出一个答案,这个答案在语言上完全通顺,在事实上可能完全是编的。
所以幻觉不是 bug,是训练目标的直接产物。指望靠增大参数量消除它,方向就错了——参数越多,模型编得越流畅。可行的路径只有两条:一是让模型学会在不确定时表达不确定、在有据可依时优先依据,这需要人类偏好信号;二是把外部知识源接进来,让生成受约束。前者就是 RLHF 要干的事。
4.2 指令学习与监督微调:先教会模型“这是任务,不是续写”
第一阶段收集一批人工撰写的“指令-回答”对,格式上把任务描述作为 prompt,把人类写的高质量回答作为监督信号,用标准的自回归交叉熵做微调。这一步的意义不只是注入知识,更重要的是改变模型的输出模式:预训练模型看到“把下面这段话翻译成英文”时,第一反应是继续往下编一段类似格式的文本;经过指令微调后,它才会把这句话识别成一个待执行的任务,然后给出译文。
这一步的数据量不需要很大,但质量要求极高。回答的风格、长度、格式、语气都会在这里被固化下来,后续两个阶段主要是调整偏好,很难扭转基础风格。
4.3 奖励模型与 PPO:把“人觉得好”变成一个可以求导的标量
第二阶段解决“偏好没法直接当损失函数”的问题。做法是给同一个 prompt 采样多个回答,让人工对这些回答排序,然后训练一个奖励模型去拟合这种排序关系。损失函数用的是成对比较:对同一 prompt 下人类偏好的回答 $y_w$ 和较差的回答 $y_l$,让 $r(y_w)$ 尽量高于 $r(y_l)$,写成 $-\log\sigma(r_w - r_l)$。注意这里学的是相对好坏,不是绝对分数,这符合人类标注的实际能力——人能比较两个回答哪个更好,但很难给出稳定的绝对打分。
第三阶段用 PPO 做强化学习优化。策略模型对 prompt 采样回答,奖励模型打分,用这个分数作为回报去更新策略。这里有个关键约束:加一项与 SFT 模型的 KL 散度惩罚,防止策略模型为了拿高分把输出改得面目全非。没有这一项,模型很快会学会钻奖励模型的空子,输出一些人类看着莫名其妙但打分器给高分的文本——这在圈内叫 reward hacking。
# 阶段一:监督微调(SFT),数据是 (prompt, 人工撰写的回答) for prompt, answer in sft_dataset: loss = -log_prob(policy(prompt), answer) # 自回归交叉熵,逐 token 累加 loss.backward() # 阶段二:训练奖励模型(RM),数据是同一 prompt 下的人工排序 (y_good 优于 y_bad) for prompt, y_good, y_bad in pair_dataset: r_good = reward_model(prompt, y_good) r_bad = reward_model(prompt, y_bad) loss = -log(sigmoid(r_good - r_bad)) # 只约束相对大小,不约束绝对分数 loss.backward() # 阶段三:PPO,策略模型对着 RM 优化 for prompt in ppo_dataset: y = policy.sample(prompt) reward = reward_model(prompt, y) kl = kl_divergence(policy, sft_policy) # 与 SFT 模型的偏离程度 loss = -(reward - beta * kl) # beta 越大越保守 loss.backward()三段代码对应三个阶段的核心目标函数。beta是 KL 惩罚系数,它控制模型能在多大程度上偏离 SFT 模型去迎合奖励模型:设得太大,RL 基本不起作用;设得太小,模型会迅速过拟合到奖励模型的偏好上,输出开始变得机械、啰嗦或者刻意讨好。这个参数没有通用最优值,实际调的时候要看人工评估和奖励模型打分的背离程度,一旦发现打分涨了但人觉得变差了,就是 reward hacking 开始的信号。
整个三阶段走完,模型获得的是两种能力的叠加:指令学习让它知道该干什么,强化学习让它知道干成什么样更符合人类期待。这也解释了为什么 ChatGPT 的对话体验和 GPT-3 完全不是一个量级——差的不是参数,是训练目标。
5. 落地排查:把论文结论搬到工程里会踩的五个坑
那篇综述讲的是技术演进脉络,真到工程落地时,很多结论会被误读成操作指南,然后翻车。下面这五条是这类项目里出现频率最高的。
5.1 预训练与推理阶段的坑
坑一:拿双向理解模型去干生成任务。现象是生成的文本重复、不连贯、前后自相矛盾。原因是 BERT 这类模型用的是掩码语言建模,训练时两侧上下文都能看到,而自回归生成要求严格从左往右,两者在推理时的信息可见性完全不一致。解决方式很直接:生成任务选因果语言模型(带掩码自注意力),理解类任务再考虑双向编码器,别混用。
坑二:把 zero-shot 当成可以直接上生产的能力。现象是通用问题答得挺像样,一进专业领域就开始胡说。原因是预训练语料的分布和领域数据分布差得太远,模型在专业术语上根本没有足够统计支撑。解决办法不是继续换更大的模型,而是补领域数据做监督微调,或者上检索增强,把外部知识在推理时喂进去。
坑三:把上下文窗口当成记忆。现象是长文档问答时,模型对开头和结尾的内容回答准确,中间大段内容像没看见。原因在于注意力权重在长序列上会被稀释,加上训练时的有效长度往往短于推理时的输入长度,模型没有学会在超长上下文里定位信息。实践中的做法是分块加摘要再拼接,或用检索先定位相关段落,别指望把几万字一次性塞进去就万事大吉。
5.2 对齐与评估阶段的坑
坑四:把 RLHF 当成事实修正器。现象是模型变得“听话”了,格式规范、语气客气,但事实错误一个没少。原因很明确:奖励模型学的是人类偏好排序,不是事实真值,标注者能判断回答是否得体、是否有帮助,但不可能对每个事实点做核验。所以对齐能改善表达和可控性,改善不了知识正确性。真要把事实错误压下去,得靠外部知识源、约束解码和人工抽检。
坑五:只看困惑度、流畅度这类语言模型指标。现象是指标一路向好,上线后用户投诉不断。原因是语言模型指标衡量的是“像不像人话”,而产品要的是“任务有没有完成”。这两者在大模型上经常背离——一段流畅但答非所问的文本,困惑度可能非常低。评估体系必须包含任务级指标和人工偏好评估,两者缺一不可。
6. 设计取舍:为什么最终押注“对齐”而不是“继续变大”
把整条技术线拉直看,会看到一个很清晰的取舍:从 N-gram 到 RNN 到 Transformer,每一次换代的目标都是“让模型能更好地建模语言分布”;而从 GPT-3 到 ChatGPT,目标变了,变成“让模型的输出符合人的期待”。这两个目标不是同一件事,前者可以靠规模线性推进,后者不能。
这也解释了为什么那篇综述在讲完 GPT-3 之后,花了大篇幅去讲指令学习和 RLHF。如果只是继续堆参数,模型会越来越擅长续写,但不会自动学会“被问到不知道的事情时说不知道”。而 RLHF 这套机制的精妙之处在于,它没有改动预训练得到的任何知识,只是在输出分布上做了一次重新加权——把人类偏好的回答概率抬高,把不受欢迎的回答概率压下去。所以对齐的代价可控,收益却很直接。
但这条路也有明确的代价。第一是可控性和多样性的张力:KL 惩罚系数调大,输出稳定但变得套路化;调小,模型有创造力但容易跑偏,这个平衡点只能靠业务场景去定。第二是数据质量比数据量更关键,SFT 阶段几千条高质量样本的价值,可能超过几万条随意采集的对话。第三是评估成本,对齐效果没法用单一指标衡量,必须持续做人工评估,这部分投入省不掉。
我自己的习惯是,每次接触一个新的大模型能力,都先问一句:这个能力是预训练给的,还是对齐给的?如果是预训练给的,那它大概率不稳定,换个问法就可能失效;如果是对齐给的,那它相对可靠,但天花板受限于对齐数据的覆盖面。养成这个判断习惯之后,我在选型和排障时少走了很多弯路,也不会再对着一个流畅但错误的回答困惑半天。希望帮到你。
本文还有配套的精品资源,点击获取