一张从「文本喂进模型之前要做什么」一路推到「BERT/GPT 为什么长成那样」的 NLP 全栈主干图,把 预处理 → 文本张量表示 → 序列建模 → Attention → Transformer → 预训练+迁移学习 串成一条因果链。
一、这份材料解决什么问题
NLP 的所有工程细节,说到底都在回答同一个问题:怎么把离散的语言符号变成神经网络能算的、长度统一的、带顺序和上下文的数字矩阵。
这张导图按「数据流水线的顺序」把这个问题的答案切成了几段,每一段对应一个具体的工程约束:
| 约束 | 现象 | 导图里给出的解法 |
|---|---|---|
| 文本要先变成词 | 中文没有空格分隔符 | 分词(jieba 三模式)、自定义词典 |
| 词要先变成数字 | 词是离散符号,梯度算不了 | one-hot → Word2Vec → Embedding 层 |
| 序列长度不固定 | 神经网络吃不了变长输入 | 长度规范(截断/补齐)、padding_mask |
| 顺序有语义 | 「狗咬人」≠「人咬狗」 | Positional Encoding |
| 长距离依赖会丢 | 传统 RNN 短期记忆、梯度消失 | LSTM/GRU 门控 → Attention 直接建模任意距离 |
| 序列方向不能并行 | RNN 必须沿时间步循环 | Transformer 的矩阵并行 + 因果掩码 |
| 标注数据永远不够 | 每个任务从零训代价太高 | 预训练 + 微调(AR / AE / Seq2Seq 三种骨架) |
在新闻分类项目里的位置:这份图不是某一个模型的说明书,而是整个项目技术迭代路线的底层解释器。项目的四个模型,TF-IDF+随机森林 → FastText → BERT 微调 → LLM 零样本,正好对应这张图里四个不同的知识簇:文本表示、n-gram+词向量、Transformer+BERT 架构、自回归语言模型。被追问「你为什么从 TF-IDF 换到 FastText 又换到 BERT」,答案的每一层都落在这张图上。
二、核心结论
- 文本表示的全部演进都是在修 one-hot 的两个洞:一是维度等于词表大小(内存爆炸),二是任意两个词的向量内积都是 0(语义完全割裂)。Word2Vec 用「上下文相似的词向量也相似」把洞补上,Embedding 层则把它变成网络里可训练的普通参数。
- RNN 的两个通病里,LSTM/GRU 只治好了一个。门控机制缓解了梯度消失(长期记忆),但「不能沿 seq_len 并行」是循环架构的结构性缺陷,谁改内部结构都救不了,这是 Transformer 存在的唯一理由。
- Attention 的三步永远不变:打分 → 归一化 → 加权求和。Q/K/V 只是这套动作的参数化外壳;缩放因子 \sqrt{d_k} 的作用是把内积的量级拉回来,防止 softmax 进入梯度极小的饱和区。
- Transformer 的两个杀手锏:沿序列方向的矩阵并行(靠因果掩码保证不偷看未来),以及多头注意力(在不同子空间里表达同一个序列的不同特征维度)。工业实现与原始论文在子层顺序上已经分道扬镳(LSDA vs SDAL)。
- 预训练语言模型只有三种骨架:自回归 AR(GPT 系,只能看上文,天生会生成)、自编码 AE(BERT 系,双向看,理解强,但 [MASK] 造成预训练/微调输入不一致)、Seq2Seq(BART/T5,生成与理解缝合)。选基座模型就是选骨架。
三、知识地图
导图的 8 个一级分支与展开深度如下。权重标注说明:原材料(思维导图节点)本身没有标注【掌握】/【理解】/【了解】,下表的分级是我按节点的展开深度推断的,有子节点、有 API 名、有代码块的判为【掌握】,只有一两级概念说明的判为【理解】,仅有名称或一句话的判为【了解】,便于你分配复习时间。
NLP基础总结
├── 1. NLP入门 【了解】
│ ├── 什么是NLP(让计算机理解/生成/与人类语言交互)
│ ├── 两个分水岭:CV 2012(AlexNet)/NLP 2017(Transformer)
│ └── 应用场景(语音助手、机器翻译、搜索引擎、搜索广告推荐)
├── 2. 文本预处理 【掌握】
│ ├── 基本方法
│ │ ├── 分词(定义/为什么分词/jieba 三模式 + 自定义词典)
│ │ ├── 命名实体识别 NER(七类实体、应用、工具 UIE)
│ │ └── 词性标注 POS(以分词为基础,jieba 实现)
│ ├── 文本张量的表示方法
│ │ ├── one-hot 编码(实现 + 优劣势)
│ │ ├── Word2Vec 静态词向量(CBOW / skip-gram,fastText 训练)
│ │ └── Word Embedding 动态词向量(embedding 层参数 + tensorboard 可视化)
│ ├── 文本数据分析(标签分布 / 句长分布 / 词频词云) 【理解】
│ ├── 文本特征处理(n-gram 特征、长度规范) 【掌握】
│ └── 文本数据增强(回译法 + 其缺陷与解法) 【理解】
├── 3. RNN及其变体 【掌握】
│ ├── 按输入输出结构分类:N vs N / N vs 1 / 1 vs N / N vs M
│ ├── 传统RNN(结构、优点、短期记忆 + 不能并行)
│ ├── LSTM(三门一状态、Bi-LSTM)
│ ├── GRU(重置门/更新门、Bi-GRU)
│ └── 人名分类案例(sequence2label 四步工作流)
├── 4. 注意力机制 【掌握】
│ ├── 由来与定义(学会"看重点")
│ ├── 分类(软/硬/普通/自注意力)
│ └── 计算(统一公式 → 打分 → 归一化 → 加权求和 → 缩放点积)
├── 5. Transformer 【掌握】
│ ├── 是什么(完全基于 Attention;特征抽取 + 训练速度两个卖点)
│ ├── 模型架构(输入/掩码/编码器/解码器/输出五段)
│ ├── 优势(并行训练 + 多头)
│ └── PyTorch 实现(nn.Transformer / nn.Embedding / nn.Linear)
├── 6. 英译法案例(Transformer/RNN) 【掌握】
│ └── 六步工作流:词表 → 数据集 → 网络 → 训练 → 测试 → 预测
├── 7. FastText 工具 【掌握】
│ ├── 工具介绍(三层结构、n-gram、层次化 softmax、负采样)
│ ├── 有监督文本分类 train_supervised
│ ├── 无监督词向量 train_unsupervised
│ └── 词向量迁移 load_model
└── 8. transformers 迁移学习 【掌握】
├── 迁移学习两种方式 / 预训练模型分类(AR / AE / Seq2Seq)
├── HuggingFace 三种调用方式(Pipeline / AutoModel / SpecificModel)
├── 微调任务头总结(句子级 vs token 级)
└── 三个微调实践(中文分类 / 中文填空 / 中文句子关系)
结构性观察:第 2~5 章是「原理层」,第 6~8 章是「工程层」。导图的重心明显偏后,FastText 和 transformers 两章占据了全部 23 张原图中的 13 张,说明这份材料是为动手改模型写的,不是为背定义写的。
四、原理详解
4.1 文本预处理:分词是第一性操作
直觉。词是人类理解文本时的最小语义单元,模型也必须站在这个粒度上。但中文没有天然分隔符(英文有空格),所以中文 NLP 的第一行代码永远是分词。
机制。分词 = 把连续字序列按一定规范重新组合成词序列,切出来的每个元素叫词(token)。jieba 提供三种模式,选择依据是下游任务:
| 模式 | 行为 | 适用 |
|---|---|---|
| 精确模式 | 最精确地切开句子 | 文本分析、分类(项目默认选它) |
| 全模式 | 扫描出所有可能成词的组合,不能消除歧义 | 快速召回、候选词挖掘 |
| 搜索引擎模式 | 在精确模式基础上对长词再切分 | 搜索引擎分词,提高召回率 |
jieba 另外支持繁体中文分词,以及通过 jieba.load_userdict("./userdict.txt") 加载用户自定义词典,按词典里的词切分。这个功能在业务场景里非常关键:领域专有名词(如「大模型」「深度学习」)如果不在词典里会被切碎。
注意一个容易被忽略的事实:导图把 NER 和 POS 放在「文本处理的基本方法」里与分词并列,但它们都以分词为前提(词性标注尤其明确写了「以分词为基础」)。所以预处理链路的真实顺序是:分词 → POS/NER。NER 识别的是人名、地名、机构名、时间、日期、货币、百分比这七类实体,它们是句子关键信息的载体,也是关键词抽取等 NLP 高阶任务的基础环节,典型落地场景是知识图谱、文本理解和舆情分析。工具方面导图提到了百度的 UIE。
4.2 文本张量表示:从 one-hot 到词向量
这是整份材料的理论核心,也是最值得深挖的一段。
(1)one-hot 编码
把每个词表示成一个 n 维向量,其中只有一个元素是 1,n = 整个语料中不同词汇的总数。
one_hot = torch.zeros(len(vocab), dtype=torch.float32)
one_hot[vocab[token]] = 1.0
优点只有一条:操作简单、容易理解。缺点有两条,而且都是致命的:
- 丢失语义信息,完全割裂词与词之间的联系,任意两个不同词的向量内积恒为 0,\cos 相似度恒为 0,模型从「股票」身上学不到任何关于「股市」的东西;
- 维度爆炸。假设词表 5 万,每个词向量 5 万维,其中 49999 个是 0。一个 batch 的输入张量就是
[batch, seq_len, 50000]的稀疏矩阵,占内存且算得慢。
(2)Word2Vec 静态词向量
Word2Vec 是一种将词汇表示成向量的无监督训练方法,核心假设是「上下文相似的词,语义也相似」。它有两种训练模式:
- CBOW:根据上下文词汇预测目标词汇,选定窗口大小;
- skip-gram:使用目标词汇预测上下文词汇。
导图用两张模型结构图把这两种模式的张量流讲得很清楚。

图注:这张图标的是 CBOW 的完整前向通路,参数写在图内,vocab_size = 5、embed_dim = 3。左侧是两个上下文词的 one-hot 向量(图中示例词为 Hope、Set),分别经过权重矩阵 W\ (5\times3)(对应 PyTorch 的 nn.Embedding)得到两个 1\times3 的 embedding;这两个 embedding 汇合成 h,标注为 mean/sum,即对上下文向量取平均或求和,维度仍是 1\times3;再由 W’\ (3\times5)(对应 nn.Linear(3,5))映射回词表维度,得到 1\times5 的 logits,最后经 softmax 得到在 5 个候选词上的概率分布,与目标词的 one-hot 计算交叉熵。注意两点:一是 W 和 W’ 都参与训练,词向量就是 W 的列;二是 CBOW 把「多个上下文」压成了一个 h,信息在聚合时被平均掉,这就是它比 skip-gram 快、但精度往往略低的原因。

图注:同一套参数的 skip-gram(跳元模型)版本,方向与 CBOW 恰好相反:输入是中心词的 one-hot(图中示例为 can),经 W\ (5\times3) 得到该词的 embedding 1\times3,中间量记作 h;再经 W’\ (3\times5) 得到 logits → softmax,但输出端要分别对窗口内每一个上下文词算一次损失,图中画了两条支路,分别对应 Hope 的 loss1 和 set 的 loss2,并给出两种聚合方式:loss = loss1 + loss2(求和)或 loss = mean(loss1, loss2)(求均值)。这就是 skip-gram 计算量随窗口大小线性增长的原因,也是它在大语料上质量通常优于 CBOW 的原因:一个中心词被训练了 K 次(K = 窗口内上下文词数),而 CBOW 只训练了 1 次。
(3)Word Embedding 动态词向量
Word Embedding 不再把词向量当成外挂的语料统计产物,而是在神经网络里加一个 embedding 层,随整个网络一起训练,产生的 embedding 矩阵(词向量矩阵)就是这个层的参数。好处是:向量会针对下游任务的任务目标被优化,而不是只对「预测上下文」负责。训练完成后可用 TensorBoard 可视化:
tensorboard --logdir=runs --host 127.0.0.1
# 浏览器访问 http://127.0.0.1:6006 查看词向量可视化效果
导图的一个盲区:文本张量表示这一支只讲了 one-hot / Word2Vec / Embedding,没有覆盖 Bag-of-Words 和 TF-IDF。而这两个正好是新闻分类项目第一个基线模型(TF-IDF + 随机森林)用的表示法,也是几乎所有 NLP 的必问题。建议把它们当作这张图的补充章节单独补(本报告第七节给了例子)。
4.3 文本数据分析与特征处理
数据分析的作用是「理解语料、快速查出语料问题、指导超参数选择」,导图给了三种方法,每一种都有明确的下游决策:
| 分析方法 | 看什么 | 决定什么 |
|---|---|---|
标签数量分布(sns.countplot) |
类别是否均衡 | 要不要做数据增强或数据删减 |
句子长度分布(sns.histplot) |
句子长度范围 | 截断/补齐的 max_length 取多少 |
| 词频统计 + 关键词词云 | 语料质量 | 人工审核与修正 |
文本特征处理做两件事:
- 添加 n-gram 特征:一段文本序列中,n 个词或字的相邻共现特征即 n-gram 特征,常用的是 bi-gram 和 tri-gram。它的价值在于给「词袋类」模型补上一点局部顺序信息(「不 好看」和「看 不 好」在 unigram 下无法区分)。
- 文本长度规范:一般模型输入需要等尺寸矩阵,因此必须截断补全。
文本数据增强最常用的是回译法:基于翻译接口把文本译成另一种语言再译回来,得到新语料。优势是操作简便、新语料质量高。但它有一个真实的坑:短文本回译后与原语料重复率很高,并不能有效扩大样本的特征空间,解法是进行连续的多语言翻译(如 中→英→日→中),让中间语言把表述「搓」开。
4.4 RNN 及其变体
(1)RNN 的定义与结构分类
RNN 是通过循环计算隐藏状态来处理序列数据的神经网络,它的循环结构天然承载了句法结构和语义信息的顺序关系。导图按两个维度做了分类,这两个维度要能立刻说出:
按输入输出结构分类(决定这个模型能做什么任务):
| 类型 | 特点 | 典型应用 |
|---|---|---|
| N vs N | 输入输出等长 | 生成等长度的合辙诗句 |
| N vs 1 | 输入是序列,输出是一个值 | 文本分类、情感分析 |
| 1 vs N | 输入不是序列,输出为序列 | 图片生成文字(图生文) |
| N vs M | 输入输出均不受限 | 机器翻译、阅读理解、文本摘要 |
按内部构造分类:传统 RNN / LSTM / GRU。
(2)传统 RNN

图注:传统 RNN 的单个时间步单元。图中有两条水平通路:下面一条从左向右输入 X_t,上面一条从左向右输入上一时刻的隐藏状态 h_{t-1}(图上标为 h t-1);两者进入中间的 tanh 激活框后输出本时刻的隐藏状态 h_t,并继续向右传给下一时间步;h_t 同时向下分出一支作为该时间步的 output。整个单元只有一次线性变换加一次 tanh,没有任何门控,这就是它「内部结构简单、参数量少、对计算资源要求低」的来源,也是它「短期记忆」的来源:h_{t-1} 每过一个时间步都要乘一次权重矩阵,连乘很快就会让远距离信息衰减掉。
传统 RNN 的两个缺点必须背下来:短期记忆问题,以及不能并行,RNN 架构的通病,只能沿着序列方向循环计算隐藏状态,第 t 步必须等第 t-1 步算完。
(3)LSTM

图注:LSTM 单元的「三门一状态」结构。图中间一条水平贯穿的通道是细胞状态(cell state,累积更新长期记忆),它只经过逐元素乘法和加法(图中 × 与 + 节点),因此梯度可以近乎无损地沿时间步回传。三个门都由 sigmoid(图中 σ)产生 0~1 的门控信号:遗忘门决定遗忘或保留多少旧记忆,作用在细胞状态的乘法节点上;输入门决定写入多少新记忆,与一个 tanh 生成的新候选记忆相乘后,加到细胞状态上;输出门决定输出哪些信息,细胞状态先过 tanh 再与输出门相乘,得到本时刻的 h_t。下方同样有 X_t 与 h_{t-1} 两个输入并汇入门控计算。与上一张传统 RNN 单元对比,结构复杂度明显上升,这就是 LSTM 有效的原因,也是它更慢的原因。
LSTM 的优缺点:与经典 RNN 相比能有效捕捉长序列之间的语义关联、缓解梯度消失;但它只能缓解梯度问题,依然不能 seq_len 并行。
Bi-LSTM:即双向 LSTM,没有改变 LSTM 任何内部结构,只是把 LSTM 应用两次且方向不同,再把两次结果拼接作为最终输出。
(4)GRU

图注:GRU 单元结构,把 LSTM 的三个门压成两个门、并取消了独立的细胞状态。图中左下输入 x_t(图上标 X t)、左上输入 h_{t-1}:左侧 r_t 是重置门,它与 h_{t-1} 相乘(×)后参与候选隐状态 \tilde{h}_t 的计算,作用是把上一时刻的隐状态「清掉一部分」再用来算候选;上方 z_t 是更新门,图中同时画出了 1- 的分支,说明 z_t 与 1-z_t 构成一对互补的插值权重:一路控制把候选隐状态 \tilde{h}_t(经 tanh 得到)写入多少,另一路控制保留多少 h_{t-1},两路在 ⊕ 处相加得到 h_t。整个 GRU 没有任何独立的记忆通道,这就是它比 LSTM 参数少、计算复杂度小的原因。
GRU 的评价:作用与 LSTM 相同,捕捉长序列语义关联、缓解梯度消失,效果优于传统 RNN,且计算复杂度比 LSTM 小;缺点同样两条,只能缓解梯度问题、不能 seq_len 并行。Bi-GRU 同理:只是把 GRU 应用两次、方向不同、结果拼接。
(5)人名分类案例
导图用它演示 sequence2label 任务:根据人名预测所属国家,三个模型(RNN/LSTM/GRU)走同一套四步工作流,准备数据集 → 搭建神经网络 → 模型训练 → 模型预测。这个案例的价值在于它是极简的「批量分类」模板,和项目里的新闻标题分类在工程结构上完全同构(都是 N vs 1),只是少了分词和停用词处理。
4.5 注意力机制
直觉。Attention 要解决的问题是让模型学会「看重点」,自动关注重要信息,弱化不重要部分。放在英译法里就是:翻译第 i 个法语词时,不是把整个英文句子压成一个固定长度的向量(那是 RNN 编码器的瓶颈),而是让模型在每个解码步自己决定回头看哪些英文词。
分类:软注意力 / 硬注意力 / 普通注意力 / 自注意力。区分维度是「可微性」和「Q 与 K 是否同源」:软注意力对所有位置分配连续权重(可微、可训练),硬注意力只挑一个位置(不可微,需强化学习);自注意力的特点是 Q=K=V,即序列自己看自己。
统一公式与三步计算。导图把 Attention 抽象成一个统一公式,并强调必须先指定三个输入 Q、K、V,再通过计算公式得到结果:

图注:这就是导图给出的 Attention 统一公式 \mathrm{Attention}(Q,K,V)=\mathrm{softmax}(\mathrm{score}(Q,K))\cdot V。公式有三层含义:score(Q,K) 是一个待替换的抽象打分函数(换成点积就是点积注意力,换成加性网络就是 Bahdanau 注意力),softmax 完成归一化,最后与 V 相乘完成加权求和。也就是说,所有注意力变体的差别只在于 score 怎么定义。

图注:计算过程第 1 步,打分。图中只有一行式子 attention_score = score(Q,K),输出一个形状为「查询数 × 键数」的分数矩阵,矩阵元素 (i,j) 表示第 i 个 query 对第 j 个 key 的关注程度,此时还是未归一化的原始分数。

图注:计算过程第 2 步,归一化。图中式子为 \alpha = \mathrm{softmax}(\mathrm{attention\_score})。softmax 按行(对每个 query 在所有 key 上)做归一化,使每一行权重和为 1,\alpha 的每一行就是该 query 在所有 key 上的注意力分布。

图注:计算过程第 3 步,加权求和。图中式子是 context 由注意力权重 \alpha_i 与对应 value 向量 v_i 相乘后求和得到,即 \mathrm{context}=\sum_i \alpha_i v_i。输出的 context 向量维度与 value 维度相同,它与 query 数量一致:每个 query 都被替换成「它自己认为重要的那些 value 的加权平均」。
缩放点积注意力。这是 Transformer 实际用的具体形式:

图注:图中公式为 \mathrm{Attention}(Q,K,V)=\mathrm{Softmax}\!\left(\dfrac{QK^{T}}{\sqrt{d_k}}\right)V。它把抽象 score 具体化为点积 QK^T(矩阵乘,天然可并行),并额外除以 \sqrt{d_k}。导图对缩放的动机写得很明确:避免内积过大导致 softmax 梯度过小。直观理解是 d_k 越大,点积的方差越大,原始分数越容易落进极端区;softmax 一旦饱和(某个位置接近 1,其余接近 0),梯度就趋近于 0,训练卡住。
带数字的小例子。取 d_k=2、一个 query q=[1,0,1,0] 和两个 key k_1=[1,0,1,0]、k_2=[0,1,0,1]:
- 原始点积:q\cdot k_1=2,q\cdot k_2=0;
- 除以 \sqrt{2}\approx1.414 后:1.414 和 0;
- softmax 结果:\alpha\approx[0.804,\ 0.196]。
如果不缩放,同样两个分数 [2,0] 的 softmax 是 [0.881,\ 0.119],仅仅 d_k=2 时注意力分布就已经明显更尖锐了。真实模型里 d_k 常取 64,标准差随 \sqrt{d_k} 放大 8 倍,不缩放会直接把 softmax 推到饱和区。
4.6 Transformer
是什么:一个完全基于 Attention 进行序列建模的神经网络(不再需要循环结构)。导图概括了它的两个卖点,正好对应它取代 RNN 的两个理由:
- 特征抽取质量:多头注意力机制,捕捉句法结构与语义信息;
- 模型训练速度:矩阵并行计算,沿着序列方向并行训练。
作用:完成 NLP 领域典型任务(机器翻译、文本生成等);以及构建预训练语言模型,用于不同任务的迁移学习,这一句是 BERT/GPT 那一章的前置铺垫。

图注:Transformer 的经典整体架构图,分左右两半。左半是 Encoder:底部 Inputs → Input Embedding 与 Positional Encoding 相加进入编码器栈,栈内 N× 重复的两层结构是「Multi-Head Attention → Add & Norm」和「Feed Forward → Add & Norm」,输出送到右侧解码器的交叉注意力。右半是 Decoder:底部 Outputs (shifted right) → Output Embedding + Positional Encoding 进入解码器栈,栈内 N× 重复的三层结构是「Masked Multi-Head Attention → Add & Norm」「Multi-Head Attention → Add & Norm」「Feed Forward → Add & Norm」,在这里可以看到解码器的中间层有一条从 Encoder 跨过来的连线(交叉注意力的 K/V 来源)。顶部是 Linear → Softmax → Output Probabilities,把解码器输出映射到词表上的概率分布。读图要点:编码器两层、解码器三层,多出来的那一层就是带掩码的自注意力,它是「不能偷看未来」的唯一执行者。
(1)输入部分:Word Embedding + Positional Encoding
自注意力本身是位置无关的(打乱输入顺序,输出只是跟着打乱),所以必须显式注入位置信息。Positional Encoding 的作用就是把「词汇位置不同会产生不同语义」这个信息加进词嵌入张量,弥补位置信息的缺失,导图给出的实现方式是三角函数。
(2)掩码张量
导图对掩码的定义很值得背:掩代表遮掩,码就是张量中的数值,尺寸不定,里面一般只有 1 和 0,代表位置被遮掩或不遮掩,至于是 0 遮掩还是 1 遮掩可以自定义。它让另一个张量中的一些数值被遮蔽或替换。两类掩码用途完全不同:
| 掩码 | 遮什么 | 为什么必须存在 |
|---|---|---|
| 填充掩码 padding_mask | PAD 填充位 | 补齐到等长后,PAD 是无意义占位,不能让它参与注意力 |
| 因果掩码 causal_mask | 未来位置 | 未来的信息可能被提前利用,因此需要遮掩 |
因果掩码的实现形式是给分数矩阵填一个极小值再 softmax:
scores = scores.masked_fill(mask == 0, -1e9)
-1e9 经 softmax 后趋近于 0,被遮的位置权重被真正抹掉。
(3)编码器部分:两个子层
每个编码器层由两个子层连接结构组成,导图明确点出「工业实现:+LayerNorm」:
- 多头自注意力 + AddNorm
- 前馈神经网络 + AddNorm

图注:多头注意力的内部实现。底部三路输入 V、K、Q 各自经过一个 Linear 投影,然后进入 Scaled Dot-Product Attention 完成上一节的缩放点积注意力计算;多个这样的分支(图中画了两个并行的注意力头)的输出在 Concat 处拼接,最后再过一个 Linear 得到最终输出。这张图解释了一个常被问到的细节:多头不是「多个独立的注意力」,而是把 d_{model} 维拆成 h 份分别做注意力、算完再拼回 d_{model} 维,所以总参数量与单头相差不大,但每个头能学不同的行为。导图对多头作用的表述是:基于相同的注意力机制学习到不同的行为,再将不同行为作为知识组合起来,捕获序列内各种范围的依赖关系;每个注意力机制去优化每个词汇的不同特征部分,均衡同一种注意力机制可能产生的偏差,让词义有更多元的表达,实验表明可以提升模型效果。形式上 Q=K=V,对三个变量分别做不同的线性变换。
层归一化的动机(导图原文):随着网络层数增加,多层计算后参数可能出现过大的小的情况,导致学习过程异常、收敛非常慢,因此在一定层数后接层归一化进行数值规范化,使特征数值落在合理范围内。
残差连接的动机:为了将信息传递得更深,增强模型的可训练性和拟合能力。
前馈全连接层:具有两层线性层的全连接网络。存在理由是「考虑注意力机制可能对复杂过程的拟合程度不够,通过增加两层网络来增强模型的能力」,注意力做的是加权聚合(线性操作),它本身没有非线性变换能力,FFN 就是补上的那个非线性。
(4)解码器部分:三个子层
- 带掩码的多头自注意力 + AddNorm
- 多头交叉注意力 + AddNorm,注意:这里 Q \neq K = V,Q 来源于 target,K,V 来源于 source
- 前馈神经网络 + AddNorm
导图还给了一个非常容易被被追问的工程细节,子层连接顺序:工业主流是 LSDA(LayerNorm → Sublayer → Dropout → Add),即 Pre-LN;原始论文是 SDAL(Sublayer → Dropout → Add → LayerNorm),即 Post-LN。这是原论文与工业实现之间最著名的一处分歧。
(5)输出部分:线性层 + softmax。
(6)Transformer 优势(导图总结的两条)
- 并行训练,沿序列方向并行训练,基于注意力机制建模,用因果掩码确保不会看到未来位置;
- 采用 Multi-head Attention 机制,拥有更强大的特征抽取能力。
(7)PyTorch 实现:nn.Transformer 实现了完整的编码器-解码器架构;输入部分是 nn.Embedding 加上可学习的位置编码(nn.Parameter)或旋转位置编码;输出部分是线性层 nn.Linear。注意这里是「可学习位置编码 / RoPE」与上面「三角函数」并列,说明实现层面有两条路线可选。
4.7 英译法案例:一条完整的 NLP 工作流
这个案例是整张导图里最值得完整背下来的一节,因为它是所有 NLP 任务的通用骨架(转成分类任务只需改最后一步)。
设定:机器翻译,source 英文句子 → target 法文句子,样本量约 64k。
六步工作流:
- 构建词表:需要两个词表,source 词表与 target 词表(两种语言不能共用)。
- 准备数据集:拆成 train/val/test → 查看句子长度分布 → 把输入 source 与输出 target 分别截断填充到固定长度。
- 搭建神经网络:
Encoder编码器:输出 source 序列的语义表示,获取 K, V;Attention层:根据 QKV 计算缩放点积注意力;Decoder解码器:输出 target 序列的语义表示并给出最终预测结果,获取 Q;- 整体
EncoderDecoder网络:实现总体前向传播/模型预测逻辑。训练时可以获取完整的 target 序列,预测时只能自回归地循环预测下一个词。 - 模型训练:损失函数用多分类交叉熵,优化器用 Adam,训练模式
model.train()。每个轮次内走前向传播 → 计算损失 → 梯度清零 → 反向传播 → 更新参数。损失计算有一个关键细节:这是 token 级别的损失,更新总损失时要乘以 token 数量,且要去掉 PAD 之后的 token 数量,否则长句会被 PAD 稀释、短句被相对放大。 - 模型测试:两种测试方式,teacher forcing 测试(使用真实标签预测下一个 token)和自回归测试(逐个预测下个 token)。评估用 BLEU 指标(实际工程中翻译任务的通用指标),同时也看多分类交叉熵损失和准确率。
- 模型预测:自回归推理。
注意第 5 步的双轨测试设计:teacher forcing 下的指标会虚高(因为每步都喂真值,误差不累积),自回归测试才反映真实推理时的表现。被问「BLEU 很高但线上效果差」,答案通常就藏在这里。
4.8 FastText 工具
FastText 是同一套库、两种用法:文本分类(有监督学习)和训练词向量(无监督学习)。它的优点极其直白:速度快、效果好、处理 OOV。为什么能同时做到这三点?导图给了三个原因:
原因一:模型简单,三层结构
- 输入数据:输出词 + 添加了 n-gram 特征;
- 词嵌入层:获取词向量;
- 平均池化层:计算平均 embedding;
- 输出层:线性层,映射到输出类别。
和上一节 CBOW 的图对比:FastText 分类模型就是把 CBOW 的「中间词预测两边」改成「平均词向量预测标签」,这就解释了为什么它快。
原因二:添加 n-gram 特征,弥补模型缺陷、提升精度。由于中间只有平均池化,FastText 天然丢失词序、是「词袋」式的;把 subword n-gram 加进输入,既补回一部分局部顺序信息,也让没见过的词因为其字符 n-gram 曾被训练过而得到合理向量,这是它「处理 OOV」的主要机制。
原因三:输出层优化
- 层次化 softmax(文本分类):降低计算量,提升超多类别下的训练效率。为什么比普通 softmax 快,使用了哈夫曼编码,把计算量由 O(\text{vocab\_size}) 降到 O(\log_2 \text{vocab\_size});求每个单词的概率分布,转化为求单词的哈夫曼编码路径的概率乘积。
- 负采样 + 迁移学习(训练词向量)。
安装与 API:
pip install fasttext-wheel
(1)有监督文本分类 fasttext.train_supervised
先区分三种分类形态,常考:
| 种类 | 定义 |
|---|---|
| 二分类 | 文本被分到两个对立类别中,如判断一句评论是好评还是差评 |
| 单标签多分类 | 文本被分入多个类别,且每条文本只能属于某一个类别(项目属于这一类) |
| 多标签多分类 | 文本被分到多个类别,且每条文本可以属于多个类别 |
五步流程:获取数据 → 划分训练集与验证集 → 训练模型 fasttext.train_supervised() → 预测并评估 → 模型调优 → 保存与加载。
模型优化方法(导图列了 6 条):原始数据处理、脏数据处理;增加训练轮数;调整学习率;增加 n-gram 特征;修改损失计算方式;自动超参数调优。
其中两条要能写出 API:
# 改损失:层次化 softmax —— 提高训练速度,但会损失一部分精度,要看工程需要
model4 = fasttext.train_supervised(input="cooking.train", lr=1.0, epoch=30,
wordNgrams=2, loss='hs')
# 实际生产中多标签多分类问题的损失计算方式:ova = one-vs-all,本质是多个二分类
model6 = fasttext.train_supervised(input="cooking.train", lr=0.2, epoch=30,
wordNgrams=2, loss='ova')
# 自动超参数调优
model5 = fasttext.train_supervised(input='cooking.train',
autotuneValidationFile='cooking.valid',
autotuneDuration=600)
保存与加载:model.save_model(path) / fasttext.load_model(path)。
(2)无监督词向量 fasttext.train_unsupervised
作用是用向量表示文本中的词汇。五步流程:获取数据 → 训练词向量 → 设定超参数 → 效果检验 → 保存与加载。实现方式两种:skip-gram(跳词模型,中间预测两边)和 CBOW(连续词袋模型,两边预测中间)。
(3)词向量迁移 fasttext.load_model
词向量迁移就是使用在大型语料库上已经训练完成的词向量模型,避免从零训练。导图给出了非常具体的可用资源(这两个数字要记住,因为很难临时编):
| 语料 | 语言数 | 训练模式 | 维度 |
|---|---|---|---|
| Common Crawl + Wikipedia | 157 种 | CBOW | 300 维 |
| Wikipedia | 294 种 | skip-gram | 300 维 |
使用四步:下载 bin.gz → 解压为 bin → 加载并取词向量 → 用邻近词检验效果:
model = fasttext.load_model("cc.zh.300.bin")
model.get_word_vector("the") # 获取词向量
model.get_nearest_neighbors("音乐") # 用邻近词检验效果
4.9 transformers 迁移学习与 BERT 微调
(1)迁移学习的两种方式
- 使用预训练模型、开箱即用,针对通用任务;
- 微调方式,训练部分参数或添加输出头,以适应特定任务。
预训练模型的定义:一般都是大型模型、复杂网络结构、参数量大,在大数据集上训练得到。
(2)预训练语言模型分类,这张导图最关键的一张分类表
| 类别 | 概念 | 优点 | 缺点 | 代表模型 |
|---|---|---|---|---|
| 自回归 AR(文本生成) | 根据上文预测下一个词 | 从左向右处理文本,天然适合文本生成 | 只能利用上文,不能同时利用上文和下文 | GPT、GPT2、Transformer-XL、XLNET |
| 自编码 AE(语言理解) | 对输入句子随机 Mask,根据上下文预测被 Mask 的词 | 融入双向语言模型,同时看到上文和下文,预测能力强 | 训练时使用 [MASK] 标记,与 Fine-tuning 阶段输入不一致 | BERT、ALBERT、RoBERTa、ELECTRA |
| Seq2Seq | 生成与理解结合 | — | — | BART、T5 |
XLNET 出现在 AR 一列是个容易被问倒的点:XLNET 用的是排列语言建模(permutation LM),本质仍是自回归的从左到右预测,只是排列打乱了表面顺序,所以它属于 AR 阵营,不是 AE。
(3)HuggingFace Transformers 的三种调用方式

图注:这张图用倒金字塔表达三种使用粒度的关系。最上层最窄的是管道(Pipeline),高度集成的极简使用方式,几行代码即可实现一个 NLP 任务;中层是自动模型(AutoModel),在使用时需要明确指定具体模型(如 BERT、GPT2、T5、DistilBERT、GPT3、PEGASUS 等);最下层最宽的是具体模型(SpecificModel),直接调用某个确定类的模型。越往下越灵活、代码越多,越往上越省事、定制空间越小。图右侧还给出了 BERT、GPT2/GPT3、T5、DistilBERT、PEGASUS 等具体模型名作为示例。
Pipeline 用法:三步,from transformers import pipeline → 实例化 pipeline 并传入参数 task、model → 将文本输入模型对象完成处理。

图注:Pipeline 方式的一段完整可运行代码截图。可以看到 def demo_pipeline(text) 函数内部先做条件判断,再用 pipeline(task=..., model=...) 实例化管道并直接调用返回结果。这段代码的工程价值在于:它是验证「某个预训练模型能不能干这个任务」的最快手段,十几行就能跑通情感分类等任务,不用碰 tokenizer、不用写 collate_fn。
Pipeline 能做的任务分两类:
- 带头任务(根据任务类型输出相应类型结果):文本分类、完形填空、阅读理解、文本摘要、NER;
- 不带头任务(只做文本张量的表示):特征提取任务。
AutoModel 用法:四步,导入 AutoModel, AutoTokenizer 等 → 加载对应任务的模型和 tokenizer → 将文本输入模型对象处理 → 将模型输出转为预测结果。

图注:自动模型方式的代码截图。图中可见 AutoConfig / AutoModel / AutoTokenizer 的导入与加载,以及 AutoModelForSequenceClassification、AutoModelForMaskedLM、AutoModelForSeq2SeqLM、AutoModelForTokenClassification 等带任务头的 Auto 类的并列使用。这段代码说明了 AutoModel 的真正价值:同一个模型可以换不同的任务头,切换任务只需换一个类名,主干权重不必重写。
具体模型方式:步骤与自动模型一样,只是类名从 Auto* 换成确定的模型类。
(4)微调任务头总结,这张表要能默写
这是整张导图里学习性价比最高的一张图,它把「什么任务接什么头、用什么损失」一次性讲清:

图注:这是一张按「预测粒度」组织的任务头对照表,共 6 行 6 列。列依次为:预测粒度、任务类型(示例)、用于预测的特征表示、Linear 输出维度、激活函数 + 损失函数、备注。句子级的三行,① 单标签多分类(情感分类、意图识别、新闻主题分类):特征取 [CLS] 最终隐藏状态或 pooler_output,Linear 输出类别数 C,Softmax + CrossEntropy,备注「标准单标签分类」;② 多标签多分类(多标签主题分类、标签打标):特征同样取 [CLS],Linear 输出 C,Sigmoid + BCEWithLogits,备注「每个标签独立预测」;③ 单标签二分类(语义匹配、句对相似度、同义判断):特征取两句的 [CLS] 表示(拼接/差值/点乘/余弦),Linear 输出 1,Sigmoid + BCEWithLogits,备注「也可用 2 个 logits + Softmax + CE」。token 级的三行,④ 单标签多分类(NER、词性标注):特征取每个 token 的最终隐藏状态,Linear 输出标签数 C,Softmax + CrossEntropy,备注「对每个 token 单标签预测」;⑤ 二标签多分类(抽取式 QA、阅读理解):同样取每个 token 的隐藏状态,Linear 输出 2(start/end),损失为 Softmax + CE(start) + CE(end),备注「对 start/end token 分别计算 CE」;⑥ N 标签多分类(完形填空、MLM):特征取被 mask 的 token 隐藏状态,Linear 输出词表大小 V,Softmax + CrossEntropy,备注「未 mask token 通常 ignore_index=-100」。读表要点:句子级任务只用一个向量([CLS]),token 级任务用每个位置的向量;句子级换的是输出维度,token 级换的是特征选取方式。
(5)三个微调实践
导图给了中文分类、中文填空、中文句子关系三个实践,每个都是「数据处理 → 模型搭建 → 模型训练」三件套。三者的差异全部集中在数据处理和大头上,值得逐个看:
中文分类,数据处理:完成文本张量表示;模型搭建;模型训练。

图注:中文分类任务的自定义 collate_fn1(data)。图中可见它调用 my_tokenizer.batch_encode_plus(...) 做批量化文本数值化,参数为 truncation=True、padding='max_length'、max_length=500、return_tensors='pt';随后从返回的字典里取出 input_ids、attention_mask、token_type_ids 三个键,并把标签一并返回。max_length=500 是这里最需要解释的数字,它来自 4.3 节「句子长度分布」分析的结果,是数据驱动而非拍脑袋定的。

图注:中文分类的下游模型 MyModel。结构上先调用预训练 BERT 编码,取 last_hidden_state[:, 0],即每个样本的第 0 个位置,也就是 [CLS] 向量,送入 nn.Linear(768, 2) 得到 2 类 logits(768 是 BERT-base 的隐藏维度,2 就是新闻二分类/正负例的类别数)。这是「句子级分类」的最小实现:一个 CLS 向量 + 一个 Linear。
中文填空,数据处理;模型搭建;模型训练(同分类)。

图注:中文填空(MLM 预训练任务复现)的自定义 collate_fn2(data)。前段与分类相同,batch_encode_plus + truncation=True + padding='max_length' + max_length=32;关键差异在标签构造:图中注释写明「把第 16 个词固定替换为 mask」,代码先 labels = input_ids[:, 16].reshape(-1).clone() 把原先第 16 个位置的词 id 取出来存成标签,再把第 16 个位置改写为 tokenizer 的 mask token,最后把 labels 转成 torch.LongTensor。这就是「先存答案、再挖空」的经典写法,且图中保留了被注释掉的形状打印语句(torch.Size([8]),说明 batch size 是 8)。注意 max_length=32 比分类任务短得多,因为这是自造的填空数据集,句子本身短。

图注:中文填空的下游模型 MyModel。与分类版的唯一区别在这里暴露得非常清楚:它取的是 last_hidden_state[:, 16],被 mask 的那个固定位置,送入 nn.Linear(768, vocab_size, bias=False) 得到词表维度 V 的 logits。图中特别写了一句「decoder.bias 初始化为 0」,这对应 BERT 论文里 MLM 输出头的 bias 初始化技巧;bias=False 的写法也说明这一层的偏置是单独管理的。对照上一张分类图:同样一个 BERT 主干,取 [:, 0] 就是分类,取 [:, 16] 就是填空,特征选取位置决定任务。

图注:直接用 bert-base-chinese 做填空推理的代码截图。图中给了一句带 [MASK] 的输入,「我想明天去[MASK]家吃饭」,传入 BertForMaskedLM 后取 output.logits,图中打印出 torch.Size([1, 11, 21128]):1 是 batch,11 是这条输入的 token 数(含首尾特殊 token),21128 就是中文 BERT 的词表大小(与图中 tokenizer.vocab_size 一致)。随后在 mask 位置取 argmax,用 convert_ids_to_tokens 把 id 转回文字。这个 21128 值得记,它同时是 MLM 输出头 Linear 的输出维度,也是「不做任务头裁剪的多分类」在词表上的直观规模。
中文句子关系,数据处理;模型搭建(同分类);模型训练(同分类)。

图注:中文句子关系(句对分类)的自定义 collate_fn3(data)。差异在输入从单句变成句对:data 是三元组列表,代码用 sents = [i[:2] for i in data] 取出每条的前两个元素作为句对,labels = [i[2] for i in data] 取第三个元素作为标签;随后用 my_tokenizer.batch_encode_plus(batch_text_or_text_pairs=sents, ...),注意参数名是 text_or_text_pairs,这是句对任务和单句任务在 API 上的分水岭;配置 max_length=50,图中注释给出了这个数字的推导:「44 + cls + sep + sep + other = 44 + 3 = 47」,即两句话共 44 个 token 加上 [CLS]、两个 [SEP] 共 3 个特殊 token。返回部分依旧取 input_ids、attention_mask、token_type_ids,注释明确提醒 token_type_ids 是句对任务独有的(区分两个句子),并且「labels 不要忘记转成 tensor 一维数组」。
(6)中文分类的训练脚本关键片段

图注:微调训练脚本的开头几行,注释和代码一同给出了三个关键决策。第一段是「不训练预训练模型,只让预训练模型计算数据特征,不需要计算梯度」,代码为 for param in my_model_pretrained.parameters(): param.requires_grad_(False),这是冻结主干(feature extraction 模式);第二段 epochs = 3,一个很小的轮次数,正是微调的典型取值(预训练已经提供了好的初始化);第三段 my_model.train() 切到训练模式,下一行注释「外层 for 循环 + 控制轮数」,与「斌子法则」的训练框架一致。这三行合起来就是「迁移学习」三个字在代码里的全部含义。
(7)BERT 与 GPT 的架构对照(扩展章)
导图最后用一小节把两者的骨架拉到一起对比:
Transformer 基础架构(见 4.6 节)作为共同底座。
BERT:
- 模型架构:嵌入层 3 类(token / segment / position);主干是 Transformer-Encoder(双向上下文,不带因果掩码);顶部接任务头 / 预微调层;
- 预训练任务:MLM 掩码语言建模 + NSP 下一句预测;
- 定位:文本理解类任务。
BERT 变体:RoBERTa、MacBERT、DeBERTa、ALBERT。
GPT:
- 结构:单向 Transformer-Decoder,由
Feed Forward和Masked Multi-Head Attention组成(带因果掩码); - 定位:文本生成类任务。
对照记忆法:BERT 是 Transformer 砍掉 Decoder 只留 Encoder,GPT 是砍掉 Encoder 只留 Decoder;一个双向不带掩码、一个单向带掩码,这一句话把 4.6 节的架构图、4.9 节的语言模型分类、以及 BERT/GPT 的差别全串起来了。
五、易错点与常见误解
- 关于 Attention 的缩放
常见说法:「除以 \sqrt{d_k} 是为了让数值稳定、避免溢出。」
实际上,「缩放是为了避免内积过大导致 softmax 梯度过小。」
为什么:1/\sqrt{d_k} 不是数值防溢出手段(那是 -1e9 掩码和 log-sum-exp 的事),它是梯度层面的修正。点积的方差随 d_k 线性增长,d_k 变大时分数进入 softmax 的饱和区,反向梯度趋近 0。
- 关于 LSTM 能不能并行
有人以为「LSTM 比 RNN 高级,所以训练更快。」
其实「LSTM 只缓解了梯度消失/短期记忆,不能 seq_len 并行;它比传统 RNN 更慢,因为门控参数更多、计算更重。」
原因在于,并行是循环结构的结构性问题,换门控解决不了。导图在 LSTM 和 GRU 两处都明确写了「不能 seq_len 并行」。
- 关于 Bi-LSTM / Bi-GRU
常听到的一种说法是:「Bi-LSTM 是改进版的 LSTM 单元结构。」
真实情况是,「没有改变 LSTM 任何内部结构,只是把 LSTM 应用两次、方向不同,再把两次结果拼接。」
背后的道理是,这是导图的原文表述,也是理解「双向 = 两个独立编码器 + 拼接」这个实现的关键,它不共享参数、不改变单元。
- 关于 [CLS] 向量
容易想岔的地方:「BERT 输出每个 token 都能表示整句,用哪个都行。」
事实正相反,「句子级任务取 [CLS](last_hidden_state[:, 0])或 pooler_output;token 级任务(NER、QA)取每个位置的隐藏状态。」
之所以如此,见任务头总结表。取错位置是新手最常见的一类 bug,用 mean pooling 或最后一个 token 也可能 work,但那是另一套设计,混用会让「为什么效果好」无法解释。
- 关于句子关系的
token_type_ids
先说一个误解:「token_type_ids 可有可无,input_ids 加 attention_mask 就够了。」
实际上,「句对任务(NSP / 句子关系 / 语义匹配)必须用 token_type_ids 区分两个句子;max_length 也要按 cls + sep + sep + 两句长度 算。」
为什么:见 collate_fn3 的注释,「44 + cls + sep + sep + other = 44 + 3 = 47」。丢了 segment id,模型无法区分哪部分是句 A、哪部分是句 B。
- 关于 [MASK] 造成的预训练/微调不一致
常见说法:「BERT 双向看上下文,所以它比 GPT 全面更好。」
其实「BERT 的双向能力来自 [MASK],而 [MASK] 只存在于预训练阶段,Fine-tuning 时输入里根本没有 mask token,这就是它作为 AE 的固有缺陷,也是 RoBERTa 等变体要修的地方。」
原因在于,导图在 AE 的缺点里写得很直接:「训练时使用 [MASK] 标记,与 Fine-tuning 阶段输入不一致」。
- 关于 XLNET 的阵营
有人以为「XLNET 名字里有『NET』又双向,应该归 AE。」
真实情况是,「导图把 XLNET 归在自回归 AR 一列(GPT、GPT2、Transformer-XL、XLNET)。」
背后的道理是,XLNET 用排列语言建模,本质仍是「根据已知预测未知」的自回归形式。
- 关于 FastText 的 loss 选择
常听到的一种说法是:「loss='hs'(层次化 softmax)又快又好,无脑用。」
事实正相反,「loss='hs' 可提高训练速度,但会损失一部分精度,要看工程需要;多标签多分类场景应改用 loss='ova'。」
之所以如此,导图原文就是这么写的。这是「速度 vs 精度」的显式权衡,不是默认最优解。
- 关于 TF-IDF 与词向量能否叠加
容易想岔的地方:「用了 Word2Vec/Embedding,TF-IDF 就没用了。」
实际上,「二者解决的不是同一个问题:TF-IDF 是可解释的稀疏统计特征 + 强基线,词向量是稠密语义特征。项目里 TF-IDF 的定位是「最便宜的基线 + 可解释性对照」,不是被淘汰的旧方案。」
为什么:这是项目技术迭代叙事的核心,见第七节。
- 关于「不能并行」到底指哪一维
先说一个误解:「Transformer 完全并行,什么都能并行。」
其实「Transformer 并行的是序列方向(seq_len);batch 方向谁都能并行。但自回归解码时生成方向仍必须串行,第 t 个 token 要等第 t-1 个算完。」
原因在于,训练时靠因果掩码 + teacher forcing 一次算整句,推理时只能逐个吐 token。导图在英译法一节也说了预测时「只能自回归预测下一个词,循环预测下一个词」。
六、横向对比与研究脉络
6.1 文本表示方法对比
| 方法 | 原理 | 优点 | 缺点 | 适用场景 | 复杂度/维度 |
|---|---|---|---|---|---|
| one-hot | 词表位置上置 1 | 实现极简、无需训练 | 完全割裂词间联系;维度 = 词表大小、极稀疏 | 教学演示、极小词表 | 维度 =V,稀疏 |
| TF-IDF(导图未覆盖,项目用到) | 词频 × 逆文档频率 | 无需训练、可解释、强基线 | 丢词序、丢语义、稀疏 | 文本分类基线 | 维度 =V,稀疏 |
| Word2Vec CBOW | 上下文词预测中心词 | 训练快 | 多上下文被平均,信息有损 | 大语料预训练词向量 | 训练 O(V)/层次 softmax O(\log_2 V) |
| Word2Vec skip-gram | 中心词预测各上下文词 | 每词被训练 K 次,质量更好 | 计算量随窗口线性增长 | 追求词向量质量、小语料 | 同上 |
| Word Embedding(动态) | embedding 层随网络一起训 | 面向下游任务优化 | 需标注数据;无预训练时易欠拟合 | 端到端训练 | 可指定,如 300 |
| BERT 上下文向量 | 双向 Transformer 编码 | 同一个词在不同句子得到不同向量 | 推理成本高;[MASK] 不一致 | 理解类任务 | 768(base) |
6.2 序列建模方法对比
| 模型 | 核心机制 | 长依赖 | 并行 | 参数量 | 典型定位 |
|---|---|---|---|---|---|
| 传统 RNN | 单 tanh 循环单元 | 差(短期记忆) | 不能(seq_len) | 最少 | 教学基线、极短序列 |
| LSTM | 三门一状态(遗忘/输入/输出 + 细胞状态) | 好(缓解梯度消失) | 不能(seq_len) | 中 | 长序列建模、NER |
| GRU | 两门(重置/更新),无独立细胞状态 | 好(与 LSTM 同效) | 不能(seq_len) | 少于 LSTM | 长序列建模、算力受限 |
| Bi-LSTM / Bi-GRU | 上述模型两次 + 拼接 | 好(双向) | 不能(seq_len) | 2 倍 | 需全句信息的理解类任务 |
| Transformer | 多头自注意力 + FFN | 好(任意距离直达) | 能(seq_len) | 大 | 预训练语言模型的底座 |
6.3 FastText 两种损失函数的取舍
| loss | 机制 | 速度 | 精度 | 适用 |
|---|---|---|---|---|
softmax(默认) |
全词表归一化 | 慢 | 高 | 类别数少 |
loss='hs' |
哈夫曼编码层次化 softmax,O(V)\to O(\log_2 V) | 快 | 有损失 | 类别超多、追求训练效率 |
loss='ova' |
one-vs-all,多个二分类 | — | — | 多标签多分类(生产常用) |
6.4 预训练语言模型三种骨架
| 骨架 | 建模方式 | 看上下文 | 预训练任务 | 天然擅长 | 代表 |
|---|---|---|---|---|---|
| AR 自回归 | 根据上文预测下词 | 仅上文 | 语言建模 | 生成 | GPT、GPT2、Transformer-XL、XLNET |
| AE 自编码 | 随机 Mask 后按上下文补 | 双向 | MLM(+NSP) | 理解 | BERT、ALBERT、RoBERTa、ELECTRA |
| Seq2Seq | 编码-解码 | 双向编码 + 自回归解码 | 去噪/填空式生成 | 生成 + 理解结合 | BART、T5 |
6.5 演进脉络(谁解决了谁的什么问题)
one-hot ──(语义割裂 + 维度爆炸)──▶ Word2Vec / Embedding
│
├─ CBOW:快,但多上下文被平均
└─ skip-gram:慢,但一个词被训练 K 次
│
n-gram 补词序 + 平均池化 + 层次化 softmax
▼
FastText(快、效果好、抗 OOV)
│
传统 RNN ──(短期记忆 + 梯度消失)──▶ LSTM/GRU ──(仍不能 seq_len 并行)──▶ Attention
│
(Q=K=V 的自注意力 + 多头 + 位置编码)
▼
Transformer
┌───────────┴───────────┐
只留 Encoder 只留 Decoder
双向、无因果掩码 单向、带因果掩码
+ MLM / NSP + 自回归语言建模
│ │
BERT GPT ──▶ LLM
│
(+任务头) 分类 / NER / QA / 填空
这条链的三次「换刀」:① 表示层从稀疏人工特征换成稠密学习特征(one-hot → 词向量);② 结构层从循环换成注意力(RNN → Transformer);③ 训练范式从任务内从零训换成预训练 + 微调(模型自己学通用语言知识)。第 ③ 步之后才出现了「LLM + 提示词/少样本」的第四个范式,也就是项目技术路线的最后一站。
七、在新闻分类项目中的落地
7.1 这张图在你项目技术迭代中的位置(TF-IDF → FastText → BERT → LLM)
项目的四个模型不是四个孤立尝试,而是沿着这张导图从下往上爬的四步;每一步都在修上一步的一个具体缺陷,而且每一步都能在这张图里找到对应的知识节点:
| 迭代阶段 | 对应导图章节 | 修的是上一步的什么问题 | 该阶段的核心知识点 |
|---|---|---|---|
| ① TF-IDF + 随机森林 | 第 2 章的”盲区” | 无(基线) | 分词 + 749 个停用词 + TfidfVectorizer(stop_words=...)。缺陷:只统计词,丢词序、丢语义、稀疏 |
| ② FastText | 第 2 章 n-gram + 第 7 章全章 | ①丢词序、①丢语义、①稀疏 | __label__标签名 文本 格式;wordNgrams=2 用 n-gram 补词序;词嵌入 + 平均池化得到稠密语义;层次化 softmax 加速;autotuneValidationFile 自动调参;test() 返回 (样本数, 精确率, 召回率)。缺陷:仍是词袋式平均池化,没有上下文 |
| ③ BERT 微调 | 第 4、5、8 章 | ②没有上下文、②丢词序 | Transformer-Encoder 双向注意力给出上下文相关向量;按第 8 章的任务头表接 [CLS] + Linear(768, 10) + Softmax + CrossEntropy(这就是句子级单标签多分类那一行);batch_encode_plus(truncation=True, padding='max_length', max_length=…);冻结主干 + epochs=3。缺陷:需要标注数据、推理重、类别改动要重训 |
| ④ LLM(DeepSeek API)零样本/少样本 | 第 8 章 AR 一列 | ③需要标注、③改动成本高 | 自回归模型「根据上文预测下一个词」的能力直接转化为分类能力;无需训练样本即可跑通;类别名写在 prompt 里就能改品类,这是对 ③ 最大的工程优势 |
这张图的定位一句话:它是①②③ 三个阶段的技术说明书,同时是④ 的理论起点(第 8 章的 AR/AE 分类直接决定了「为什么 GPT 系能靠 prompt 分类,而 BERT 系必须微调」)。但它不覆盖 ④ 之后的内容(提示工程、RAG、Agent),也不覆盖版本末期的模型压缩,压缩属于「把 ③ 做小」的工程,与本图的原理层正交。
7.2 具体落点
第 2 章 → 项目第一步(数据与基线)
- 分词与停用词:749 个停用词正是喂给
TfidfVectorizer(stop_words=停用词)的,对应导图「分词 + 自定义词典 + 停用词」这条线。 - 数据分析:18w/1w/1w 的数据切分之后,必须先做标签数量分布(10 个类是否均衡 → 决定要不要做数据增强)和句子长度分布(决定 BERT 的
max_length取多少)。导图明确写了句长分布的作用是「选取合理范围值进行句子截断补齐」,这个数字直接变成batch_encode_plus的max_length,参考 4.9 节中文分类用的就是max_length=500(那是长文本场景;新闻标题通常远小于它)。 - 数据增强:如果某个类别(比如冷门频道)样本过少,就用回译法补充;但要注意导图指出的坑,短标题回译后重复率高,需要连续多语言翻译(中→英→日→中)来拉开表述。
第 2 章 → TF-IDF 基线的补充说明(导图未覆盖,但项目要用)
TF-IDF = 词频 × 逆文档频率,直观含义是「一个词在当前文档里出现得越多、在整个语料里出现得越少,它对这个文档的区分度就越高」。取 N=3 个文档 d1=股票 上涨 股票 利好、d2=股票 下跌、d3=天气 晴朗,用平滑式 \mathrm{idf}=\ln\frac{1+N}{1+df}+1 算:
| 词 | 文档 | tf | idf | tf-idf |
|---|---|---|---|---|
| 股票 | d1 | 0.5 | 1.2877 | 0.6438 |
| 上涨 | d1 | 0.25 | 1.6931 | 0.4233 |
| 天气 | d3 | 0.5 | 1.6931 | 0.8466 |
可以看到「天气」虽然在 d3 里只出现一次(tf 与「股票」在 d1 里的 tf 相同),但因为 df 更小、idf 更大,最终权重反而更高。这个机制的代价是:TF-IDF 把每个词当成独立信号,句子被彻底拆散成词袋,「利好」和「利空」在特征空间里的距离,与「利好」和「天气」的距离一样远。这就是第 ② 步 FastText 要修的东西。
第 7 章 → FastText 阶段
- 数据格式:
__label__标签名 文本,fasttext.train_supervised()训练,autotuneValidationFile+autotuneDuration自动调参,save_model()保存,test()返回(样本数, 精确率, 召回率)。 - 类别数很少(10 类)时不要急着开
loss='hs':层次化 softmax 的收益在超多类别下才明显,10 类开 hs 只会白白损失精度(导图原文:可提高训练速度,但会损失一部分精度)。 - 10 类单标签分类用默认 softmax 或
loss='ova';只有当任务改成「一条新闻打多个频道标签」时才必须切到loss='ova'。 wordNgrams=2是性价比最高的一档调参:它对应导图「添加 n-gram 特征,弥补模型缺陷提升精度」,FastText 的中层只有平均池化,加 bi-gram 是补词序信息的唯一低成本手段。
第 4~8 章 → BERT 微调阶段
- 直接用第 8 章的任务头表定位:新闻标题 10 分类 = 句子级 / 单标签多分类,特征取
[CLS](last_hidden_state[:, 0]),nn.Linear(768, 10),Softmax + CrossEntropy。注意表里备注的「标准单标签分类」,这是最简单的一行,不需要任何花哨设计。 - 数据处理:
batch_encode_plus(truncation=True, padding='max_length', max_length=...),返回input_ids / attention_mask / token_type_ids。单句分类其实用不到token_type_ids(它全为 0),但数据集里保留它无害;一旦任务变成句对(如「标题+正文是否一致」)就必须用上。 - 训练骨架:照「斌子法则」写,同时照 4.9 节训练片段做两件事,
param.requires_grad_(False)冻结预训练主干(先只训任务头,验证数据管线没问题再解冻全量微调),epochs=3。先冻结后解冻是省时间的关键:数据管线有 bug 时,冻结跑一轮就能暴露,不必等全量微调。 - 评估口径:必须和 FastText 阶段用同一套指标(精确率/召回率/F1 + 混淆矩阵),否则「BERT 比 FastText 好多少」这个结论无法成立。
第 8 章 → LLM 阶段的原理依据
导图把 AR 和 AE 的优点缺点并排写出来,正好解释了项目最后一步为什么选 LLM 做零/少样本:自回归模型「根据上文预测下一个词」的训练目标,天然等价于「给定一段包含类别清单的 prompt,续写出最可能的类别名」。而 BERT 系的 AE 骨架做不到这点,它的强项是被 Mask 位置的填补和微调,不微调就没有输出类别名的能力。这也是第 ③④ 两步的本质差别不是模型大小,而是训练目标。
模型压缩(与全图正交,但同属版本末期)
导图完全不涉及量化/蒸馏/剪枝。这里只需要记住一条接口级对应:BERT 微调产出的就是一个普通 PyTorch 模型,所以量化(torch.quantization.quantize_dynamic())、剪枝(torch.nn.utils.prune.global_unstructured())、蒸馏(loss = (1-alpha)*hard_loss + alpha*(T*T)*soft_loss,F.kl_div())都能直接作用在它身上。被追问时可以把「导图讲清了 BERT 是什么、压缩讲的是怎么把它做小」这两件事并排说,位置清晰,不会串。
7.3 实际会踩的坑
max_length定错:先看句长分布再定,不要抄别人的 500 或 32。定了之后如果改数据,要重新看分布。- 损失被 PAD 稀释:翻译任务里导图专门强调「更新总损失时要乘以 token 数量,去掉 PAD 之后的 token 数量」。分类任务虽然一般只用一个 CLS 向量、不受 PAD 影响,但只要换成 token 级任务(NER)就必须用
ignore_index=-100(任务头表第 6 行也写了这条),否则 PAD 位置会一起算损失。 [MASK]在微调阶段不会出现:BERT 的 MLM 输出头只在预训练/填空实践里用。微调分类时如果照抄填空的取特征方式(取某个中间位置而不是[CLS]),性能会莫名其妙地差,这是「取错位置」的典型表现。- FastText 忘了加
__label__前缀:数据格式错误时 FastText 会静默训出一个没用的模型(无法识别任何标签),test()出来的精确率会立刻暴露问题。 - BERT 训练时忘了
model.train()/ 评估时忘了model.eval():dropout 和 LayerNorm 行为不同,指标会不稳定。
八、重点问答
Q1:为什么要分词?中文分词和英文有什么不同?
A:因为词是人类理解文本的最小语义单元,模型也得站在这个粒度上,所以分词就是把连续的字序列按一定规范重新组合成词序列,切出来的每个元素叫 token。中文和英文最大的差别在于:英文空格是天然的分隔符,中文没有明显的单词分解符,所以中文 NLP 的第一行代码通常是分词。工程上我用的是 jieba,它有精确、全、搜索引擎三种模式,分类任务用精确模式;另外还加载了用户自定义词典,把领域词固定切出来。分好词之后的每个 token 就是后面词表、TF-IDF、embedding 的输入单位。
Q2:one-hot 编码有什么问题?词向量是怎么解决的?
A:one-hot 把每个词表示成一个词表大小的向量,只有一个位置是 1。它有两个硬伤:一是维度等于整个语料的词汇总数,5 万词表就是 5 万维稀疏向量,内存和计算都吃不消;二是任意两个不同词的内积恒为 0,也就是完全割裂了词与词之间的联系,模型从「股票」上学不到任何关于「股市」的东西。Word2Vec 解决的是第二个问题,它的假设是「上下文相似的词语义也相似」,用无监督方法把每个词映射到一个低维稠密向量,语义相近的词在向量空间里就靠得近。具体有 CBOW 和 skip-gram 两种模式,CBOW 是上下文预测中心词、快但上下文被平均掉了,skip-gram 是中心词预测上下文、一个词被训练多次所以质量更好但更慢。再往后还有 Embedding 层这种方式,把词向量当成网络里可训练的参数,随下游任务一起优化。
Q3:LSTM 和 GRU 有什么区别?它们解决了 RNN 的什么问题?
A:LSTM 是「三门一状态」,遗忘门决定遗忘或保留多少旧记忆,输入门决定写入多少新记忆,细胞状态负责累积长期记忆,输出门决定输出哪些信息;细胞状态这条通道只经过加法和逐元素乘法,梯度可以沿时间步近乎无损地传播,所以它缓解了传统 RNN 的短期记忆和梯度消失。GRU 把三个门压成两个,重置门和更新门,并取消了独立的细胞状态,直接用更新门和「1 减更新门」这对互补权重在旧隐状态和候选隐状态之间做插值,所以效果与 LSTM 相当而参数更少、计算更小。但有一点必须说清楚:它们都只能缓解梯度问题,不能沿 seq_len 并行,因为本质还是循环计算隐藏状态,这要靠 Transformer 解决。另外 Bi-LSTM 和 Bi-GRU 没有改单元内部结构,只是把同一个模型跑两遍、方向不同再拼接。
Q4:Attention 的计算过程是什么?为什么要除以根号 d_k?
A:先指定三个输入 Q、K、V,然后走三步:第一步打分,用 score(Q,K) 算出未归一化的注意力分数;第二步归一化,对分数做 softmax 得到注意力权重 α,每行和为 1;第三步加权求和,用 α 去乘对应的 V 再求和,得到 context 向量。写成统一公式就是 Attention(Q,K,V) = softmax(score(Q,K)) · V,所有变体的差别只在于 score 怎么定义。Transformer 用的是缩放点积,score 就是 QK 转置,再除以根号 d_k,再乘 V。缩放的目的是避免内积过大导致 softmax 梯度过小:点积的方差随 d_k 线性增长,d_k 越大分数越容易落进极端区,softmax 一旦饱和(某个位置接近 1、其余接近 0),反向梯度就趋近于零,训练就卡住了。举个极小的例子,d_k 等于 2、两个原始分数是 2 和 0,缩放后 softmax 是 0.80 比 0.20,不缩放是 0.88 比 0.12,d_k 越大差距越夸张。
Q5:Transformer 为什么能取代 RNN?
A:两个层面。第一是训练速度,Transformer 完全基于注意力建模,用矩阵运算实现,可以沿序列方向并行训练,靠因果掩码保证不会看到未来位置;RNN 必须循环计算隐藏状态,第 t 步要等第 t-1 步,没法并行。第二是特征抽取质量,多头注意力基于同一套注意力机制学出不同的行为,再把这些行为组合起来,能捕获序列内各种范围的依赖关系,而且每个头优化的是词汇的不同特征部分,可以均衡单一注意力机制可能产生的偏差,让词义有多元的表达。还有一个容易被忽略的点:RNN 的长期依赖要逐时间步传递,距离越远衰减越厉害;自注意力是任意两个位置直接计算,路径长度是 1,长依赖不用「走」过去。不过要补一句,Transformer 并行的只是序列方向,自回归解码时生成方向仍然必须串行。
Q6:BERT 和 GPT 的架构区别是什么?为什么一个擅长理解、一个擅长生成?
A:BERT 用的是 Transformer-Encoder,也就是双向上下文、不带因果掩码,嵌入层有三类(token、segment、position),预训练任务是 MLM 掩码语言建模加 NSP 下一句预测,所以它能同时看到上文和下文,擅长文本理解类任务。GPT 用的是单向的 Transformer-Decoder,由掩码多头注意力和前馈网络组成,带因果掩码,训练目标就是根据上文预测下一个词,所以它天然适合文本生成。可以记成一句话:BERT 是砍掉 Decoder 只留 Encoder,GPT 是砍掉 Encoder 只留 Decoder。不过 BERT 有个固有缺陷:它的双向能力来自 [MASK],而 [MASK] 只存在于预训练阶段,Fine-tuning 时输入里根本没有它,这就造成了预训练和微调输入不一致,也是 RoBERTa 这类变体要修的地方。
Q7:BERT 做不同类型的任务,输出任务头怎么接?
A:先分两级:句子级任务还是 token 级任务。句子级任务取 [CLS] 的最终隐藏状态,也就是 last_hidden_state[:, 0],或者用 pooler_output。如果是单标签多分类,比如新闻主题分类,输出维度是类别数 C,接 Softmax 加交叉熵;如果是多标签多分类,输出维度还是 C,但要换成 Sigmoid 加 BCEWithLogits,因为每个标签是独立预测的;如果是句对匹配类的二分类,特征要把两个句子的信息合起来,拼接、差值、点乘或余弦都行,输出维度是 1,Sigmoid 加 BCEWithLogits。token 级任务取每个 token 的隐藏状态:NER 和词性标注是每个 token 单标签分类,输出维度是标签数 C,Softmax 加交叉熵;抽取式问答输出维度是 2,代表 start 和 end,损失是 start 和 end 各算一次交叉熵再相加;完形填空这种 MLM 任务,取被 mask 的那个位置的隐藏状态,输出维度是词表大小 V,剩下的 token 一般用 ignore_index 等于 -100 忽略掉。总结一句话:句子级换的是输出维度,token 级换的是特征怎么取。
Q8:FastText 为什么快?它的损失函数怎么选?
A:它的快有三个原因。第一是模型简单,只有三层,输入是词加 n-gram 特征,中间一个词嵌入层加平均池化得到句子的平均 embedding,输出层一个线性层映射到类别。说到底是把 CBOW 的「中间词预测两边」改成了「平均词向量预测标签」,所以非常轻。第二是输出层用了层次化 softmax,通过哈夫曼编码把每个单词的概率分布转化成求哈夫曼编码路径的概率乘积,计算量从 O(词表大小) 降到 O(log2 词表大小),在超多类别场景下提速非常明显。第三是它加了 subword n-gram 特征,既补回了一部分词序信息,也让没见过的词因为字符 n-gram 被训练过而能得到合理向量,这就是它能处理 OOV 的原因。损失函数选择上,默认的全词表 softmax 精度最高;loss='hs' 是层次化 softmax,能提高训练速度但会损失一部分精度,要看工程需要;如果是多标签多分类,实际生产里用的是 loss='ova',也就是 one-vs-all,本质是多个二分类。
九、延伸阅读
论文
- Attention Is All You Need(Vaswani 等,2017),Transformer 原始论文,本报告 4.6 节的架构图与所有子层细节的出处。
- BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding(Devlin 等,2018),MLM + NSP 预训练任务、[CLS] 用法与 21128 中文词表的来源。
- Efficient Estimation of Word Representations in Vector Space(Mikolov 等,2013),word2vec 的 CBOW 与 skip-gram 两种模式。
- Bag of Tricks for Efficient Text Classification(Joulin 等,2016),FastText 分类的原始论文,平均池化 + 层次化 softmax 的设计动机。
- Enriching Word Vectors with Subword Information(Bojanowski 等,2016),subword n-gram 如何让词向量处理 OOV。
官方文档与工具
- jieba 分词:https://github.com/fxsjy/jieba (三种模式、
load_userdict、POS 接口) - fastText:https://fasttext.cc/ 和 https://github.com/facebookresearch/fastText (
train_supervised/train_unsupervised/autotuneValidationFile;预训练词向量下载页确实提供cc.zh.300.bin这类 bin.gz 文件) - HuggingFace Transformers:https://huggingface.co/docs/transformers (Pipeline / AutoModel / 具体模型三种用法;
bert-base-chinese模型卡) - HuggingFace 官方 NLP 课程:https://huggingface.co/learn/nlp-course (按任务组织的微调教程,与本导图第 8 章结构一致)
- PyTorch 文档:
torch.nn.Transformer、torch.nn.Embedding、torch.quantization.quantize_dynamic、torch.nn.utils.prune.global_unstructured - TensorBoard 词向量可视化:https://www.tensorflow.org/tensorboard (导图中
tensorboard --logdir=runs的官方说明) - 百度 UIE:https://github.com/PaddlePaddle/PaddleNLP (导图提到的 NER 工具;PaddleNLP 的 UIE 系列模型)
数据集与基准
- 导图英译法案例使用的英法平行语料(课程切片约 64k 句对;评估指标 BLEU)。
- 导图 FastText 分类示例用的是 fastText 官方仓库自带的
cooking.train/cooking.valid数据文件,可直接从 fastText 仓库data/目录获取。 - 中文 NER / 句对任务的常用公开数据集可在 HuggingFace Datasets(https://huggingface.co/datasets)中按任务名检索。
- fastText 预训练词向量资源(导图给出的两组):157 种语言的 Common Crawl + Wikipedia 词向量(CBOW,300 维)、294 种语言的 Wikipedia 词向量(skip-gram,300 维),下载页见 https://fasttext.cc/docs/en/crawl-vectors.html。
附:本报告使用的原图(共 23 张,已按内容插入对应小节)
| 小节 | 原图 |
|---|---|
| 4.2 文本张量表示 | 附件 1(CBOW)、附件 2(skip-gram) |
| 4.4 RNN 及其变体 | 附件 3(传统 RNN 单元)、附件 4(LSTM 单元)、附件 5(GRU 单元) |
| 4.5 注意力机制 | 附件 6(统一公式)、附件 7(打分)、附件 8(归一化)、附件 9(加权求和)、附件 10(缩放点积注意力) |
| 4.6 Transformer | 附件 11(整体架构图)、附件 12(多头注意力结构) |
| 4.9 transformers 迁移学习 | 附件 13(三种调用方式)、附件 14(pipeline 代码)、附件 15(AutoModel 代码)、附件 16(collate_fn1)、附件 17(分类下游模型)、附件 18(collate_fn2)、附件 19(填空下游模型)、附件 20(BERT 填空推理)、附件 21(collate_fn3)、附件 22(训练脚本片段)、附件 23(任务头总结表) |
全部 23 张附件原图均为技术内容(架构图 / 公式图 / 代码截图 / 对照表),无装饰性 logo 或背景图,因此一张未删。