资讯动态

从NLP期末提纲到实战:词向量、注意力与Transformer避坑指南

发布时间:2026/10/15 7:36:24 来源:尧图企业网站定制
简介国科大自然语言处理NLP课程胡玥老师的2022期末复习提纲面向备考NLP期末的国科大研究生与高年级本科生系统梳理神经网络基础与序列建模两大模块覆盖第一至第七章的高频考点。提纲按章节浓缩核心内容常见激活函数Sigmoid/Tanh/ReLU的作用梯度下降与随机/小批量梯度下降CNN卷积层、池化层、全连接层结构RNN的LSTM/GRU/BiLSTM改进思路与BPTT训练方法概率语言模型部分汇总one-hot、词袋表达及NNLM、RNNLM、CW、CBOW、Skip-gram的参数学习方法注意力机制详解传统模块结构、软/硬/全局/局部注意力及其在求和场景中的优势。文本分类与匹配涉及马尔科夫模型、HMM前向/后向与维特比算法、CRF解决输出独立问题Seq2seq按生成式、选择式、选择-生成式分类并梳理Transformer、BERT、GPT等预训练模型要点。每个知识点均给出简洁记忆口径适合考前快速翻阅与查漏补缺资源为1份485KB的PDF文件已有419人学习电子版便于打印或平板批注直击常考计算题与概念题要点。1. 把一份 NLP 期末复习提纲当成项目需求文档来拆但凡是在某个 AI 实验室或者大模型公司呆过一阵子的人看到 NLP 期末复习提纲 这几个字第一反应不应该是划重点背一背而应该是这又是一份藏着大量边界条件的工程需求文档。2022 年的这份提纲我印象很深它表面上是给学生划重点但如果你真的接触过后续部署落地会发现里面的高频考点——词向量、序列标注、注意力机制——全都是生产环境里必然会踩坑的原始设计约束。适合谁来看不只是要过关的学生更应该是那些刚从图像方向转过来做文本、或者自学了一堆 Transformer 理论却不会调参的工程师。这份提纲真正考验的不是你记住了多少公式而是你能不能把公式里那个隐层的维度变化跟你电脑上一次真实的矩阵相乘对上号。很多人复习的时候喜欢死磕推导到了动手写代码的时刻就翻车原因恰恰是把理解和复现切成了两半。所以这一篇我打算顺着提纲的路子把概念、代码、以及考试里最容易埋钩子的地方揉开来讲宗旨只有一个让你真正能给别人讲明白而不只是对着提纲打勾。2. 词向量与语言模型提纲里的高频考点与工程落地2.1 为什么 NNLM 和 Word2Vec 总被放在一起考混淆点到底在哪只要是 2022 年那会儿的 NLP 期末提纲开头三章基本跑不掉词向量。但老师特别喜欢把 NNLM 和 Word2Vec 横向对比着出题因为这两者表面长得像实际的设计动机差了十万八千里。NNLM 的核心目标是预测下一个词语言模型本身是主菜词向量只是训练过程中的副产物。而 Word2Vec 则反过来了它把所有精力都放在学词向量上语言模型的任务只是让向量学得更好。很多同学在这里混淆的深层原因是没搞清楚输入输出到底谁是谁。NNLM 的输入是一段定长上下文输出是一个覆盖整个词表大小的 Softmax 分布Word2Vec 的 CBOW 和 Skip-gram 则把这个问题简化成了一个二分类任务——尤其是在负采样版本里。所以说考试如果问你为什么 Word2Vec 训练更快但它本质上仍然是在做语言模型任务你要回答的不是它性能多好而是它通过负采样把多分类硬生生转化成了二分类从而绕开了整个词表大小的矩阵运算。从工程复现的角度看这个区别怎么验证你去读当年那些开源出来的训练代码看它的损失函数。CBOW 的损失函数里做矩阵乘法的是中心词向量跟上下文向量而 NNLM 的损失函数里动不动就出现一个[vocab_size, hidden_size]的大矩阵。这也就是为什么后来人们在部署轻量词向量模型时几乎不会选 NNLM因为隐层到输出层的那个矩阵太占内存了线上服务根本扛不住。2.2 用 30 行代码复现 CBOW 的真正难点参数更新细节关于复现我最常被问到的一个问题是为什么我照着书上的公式写了 CBOWloss 就是不降答案百分之百出在参数更新上。纸质教材里给你画的是前向传播和反向传播公式但实践里你必须搞清楚哪个参数是输入向量矩阵的更新哪个参数是输出向量矩阵的更新。如果只更新输入矩阵你的训练会非常慢如果只更新输出矩阵训练到最后得到的向量根本不是你以为的那些。下面这段是带核心注释的精简版你可直接照改import numpy as np from collections import Counter # 设定基础的超参数这些值都可以后续用网格搜索调优 VOCAB_SIZE 1000 EMBED_DIM 50 CONTEXT_SIZE 2 # 左右各看两个词 # 假设语料已经被整理成 (上下文词id列表, 目标词id) 的格式 training_data [] # 实际需要自行从语料里构造 # 初始化两个矩阵输入向量和输出向量 # 这是 CBOW 的最核心区别两个矩阵不能共用一份权重 W_in np.random.normal(0, 0.01, (VOCAB_SIZE, EMBED_DIM)) W_out np.random.normal(0, 0.01, (VOCAB_SIZE, EMBED_DIM)) for context_ids, target_id in training_data: # 1. 上下文词向量求平均得到一个隐层向量 context_vectors W_in[context_ids, :] # shape: (context_size, embed_dim) hidden np.mean(context_vectors, axis0) # 得到一个隐藏层向量 # 2. 通过输出矩阵计算每个词作为目标词的概率 scores W_out hidden # 等价于 W_out.dot(hidden) exp_scores np.exp(scores - np.max(scores)) # 防止数值溢出 probs exp_scores / np.sum(exp_scores) # 3. 计算损失关于 score 的梯度然后反传 delta probs.copy() delta[target_id] - 1.0 # 这是 softmax 交叉熵损失的标准梯度 # 4. 对输出矩阵的梯度梯度直接乘隐藏向量 grad_W_out np.outer(delta, hidden) # 5. 对输入矩阵的梯度先把梯度通过输出矩阵映射回隐藏层再平均到上下文词 grad_hidden W_out.T delta grad_W_in np.zeros_like(context_vectors) for i, ctx_id in enumerate(context_ids): grad_W_in[i, :] grad_hidden / CONTEXT_SIZE # 6. 进行梯度下降更新 lr 0.05 W_out - lr * grad_W_out W_in[context_ids, :] - lr * grad_W_in这段代码值得注意的参数点是delta[target_id] - 1.0这行把 softmax 层的反向传播化简得非常干净。很多教程里让你先求前向的 loss 再用链式法则但实际操作中softmax 加交叉熵的梯度可以直接写成这个形式省掉一长串的中间变量计算也不容易出错。另一个点是grad_W_in必须累加到上下文词向量上而且每个上下文词分到的梯度是一样的因为前向传播用的是平均池化。如果你的模型选择的是求和而不是求平均这里的梯度就要相应变化否则更新量会随着窗口大小变化而不稳定。为什么我要在期末复习里强调这段代码因为不管是 CBOW 还是 Skip-gram只要你真正动手写过矩阵相乘和梯度回传你对词表大小对内存的影响、窗口大小对语义粒度的作用这些考试题的感知就不是靠背的。比如考题问你为什么负采样能大幅降低训练复杂度立刻能反应到是W_out矩阵梯度里只更新了极少数行而不是全部词表。这比死记一句负采样解决计算瓶颈要有说服力得多。3. 序列标注与句法分析把公式变成能跑的代码才叫掌握3.1 BERT 时代还考 HMM/CRF考的不是落伍是对齐问题到了序列标注这一章很多同学会觉得强烈的不真实感现在线上系统都已经用 BERT 加 CRF 做实体抽取了期末提纲里还整页整页地在考 HMM 的三类基本问题——评估、解码、学习。某高校的这份提纲里明确写着Viterbi 算法的解码细节必须掌握当时不少人觉得这知识没用。但等真正到了做 NER 落地的时候你就会发现 HMM 里的那套状态转移逻辑跟你调试 BIO 标注序列的 Bug 时的心智模型完全一致。最大的混淆点在于怎么理解 CRF 和 HMM 的共同出发点是序列对齐。HMM 假设观测之间条件独立就因为这一个小小的独立性假设导致它在处理中文长句的远距离依赖时完全抓瞎。所以期末考试如果让你对比 HMM 和 CRF 的差异你必须从归一化方式入手HMM 是生成模型在每一步单独做归一化CRF 是判别模型在整个序列上做全局归一化。这个差异的直接后果就是 CRF 能解决标签偏置问题而 HMM 天生有。复习这块内容的时候我习惯建议别人用状态分数 转移分数的视角来看所有序列标注模型。无论后面接的是 LSTM 还是 TransformerCRF 层真正在算的只有三件事发射分数、转移分数、路径分数归一化。也就是说如果你能自己实现一个 Viterbi 解码透过那层 BERT 的外壳看到底层的序列约束逻辑期末那些抽象的计算题就会变成可推理的图搜索问题。3.2 用 Python 跑通维特比算法看透解码和考试大题维特比算法是期末考试的固定大分题。有些题目会让你手工推导状态路径有些直接让你在考卷上写递推方程。这里我用一段极简的 Python 来实现它跑通的流程跟你在纸笔上推的依次填表过程完全一致但对回溯这一步骤的清晰度要求更高import numpy as np # 定义隐藏状态集合比如实体标注中的 BIO states [B, I, O] # 观测序列对应的发射分数实际中来自分类模型如BERT # 假设序列长度为3每个状态3个分值 emit_scores np.array([ [0.1, 0.1, 0.8], # 第一个词 [0.2, 0.7, 0.1], # 第二个词 [0.6, 0.3, 0.1] # 第三个词 ]) # 转移分数从行状态转移到列状态这里简单手写 # B I O trans np.array([[0.3, 0.5, 0.2], [0.1, 0.6, 0.3], [0.4, 0.2, 0.4]]) # 初始化动态规划表格 T len(emit_scores) N len(states) dp np.zeros((T, N)) backptr np.zeros((T, N), dtypeint) # 第一列只考虑发射分数和初始概率这里默认初始概率均等 dp[0, :] emit_scores[0, :] (1.0 / N) backptr[0, :] -1 # 开始递推填充 for t in range(1, T): for j in range(N): # 到当前状态j的最大概率 上一时刻任意i的最大概率 * 转移概率(i-j) * 发射概率 prob dp[t-1, :] trans[:, j] emit_scores[t, j] backptr[t, j] np.argmax(prob) dp[t, j] np.max(prob) # 回溯找到最优路径 best_path np.zeros(T, dtypeint) best_path[-1] np.argmax(dp[-1, :]) for t in range(T-2, -1, -1): best_path[t] backptr[t1, best_path[t1]] # 输出对应状态 path_states [states[i] for i in best_path] print(最优状态序列:, path_states) print(每步的最大分数:, dp[np.arange(T), best_path])注意这里有一个非常容易被忽视的参数点trans[:, j]表示的是从任何上一时刻状态 i 转移到当前状态 j的分数但 dp 表中存的是在此之前的总分所以求最大概率时是做prob dp[t-1, :] trans[:, j]这就完成了累加操作。而np.argmax(prob)只返回第一个最大值下标。考试时手推表格的结果跟这个代码的输出是一一对应的唯一需要留神的是维特比算法里有一个标准化normalization的细节代码里为了展示方便忽略了。我在某次模拟面试里就见过这样的例子A同学代码完全跑通但问他如果发射分数里掺杂了几个特别大的值导致数据中小数下溢怎么办他当场卡住。这就是把复习提纲只当数学题做、没当算法题做的典型症状。应对策略也很简单可仿照 Softmax 稳定性方案给每一帧的发射分数都减去当前帧的最大值再做递推回溯路径完全不受影响因为减掉常数不会改变argmax的位置。4. 注意力机制与 Transformer从论文到参数的三个必调点4.1 自注意力机制中的 Mask 矩阵它保护了谁又坑了谁复习提纲进入注意力机制画面的风格就从概率图模型变成了矩阵操作。Transformer 里最容易被低估的细节就是矩阵里的 0 到底在哪个位置。很多面试官和期末考老师最喜欢问的就是多头注意力里面为什么要缩放点积原来是怎样处理大数值的。如果不把 Mask 矩阵的结构想明白后面顺着代码往里走几乎就是黑匣子状态。用大白话来讲Padding Mask 是给无效位置做标记防止注意力把分数分配给填充位置的 token。Causal Mask因果则是给未来位置做标记因为在做生成任务时当前词不能看到后续的词。这两者经常被放在一起使用但它们在矩阵乘法里加的 bias 模式截然不同。编写代码时最常见的翻车点是忘了 mask 和 attention logits 相加的位置——你必须在执行softmax之前把 mask 加到 score 矩阵上而不是在 softmax 之后才对输出做置零操作。import numpy as np def attention_logits(q, k, maskNone): # q, k 的 shape 是 (batch_size, num_heads, seq_len, d_k) d_k q.shape[-1] # 缩放是稳定训练的关键参数这就是论文里除的那个 sqrt(d_k) scores np.matmul(q, k.transpose(0, 1, 3, 2)) / np.sqrt(d_k) if mask is not None: # 关键点这里是加一个极小的负数不是置为0 # 因为后续要 softmax0 会被赋予正常概率只有 -inf 才能变成0概率 scores np.where(mask, scores, -1e9) probs np.exp(scores - np.max(scores, axis-1, keepdimsTrue)) probs probs / np.sum(probs, axis-1, keepdimsTrue) return probs这里的参数d_k是必考知识点为什么除以根号 d_k因为当 embedding 维度增大时点积结果的方差会随之线性增大导致 softmax 函数进入梯度极小的饱和区。你想想两个维度为 1000 的向量相乘即使每个元素都很小加起来也可能得到几十甚至上百的值这在 softmax 里基本就把梯度杀没了。所以这行scores / np.sqrt(d_k)本质上是一个数值稳定性工程策略同时也影响了注意力权重的平滑度。Mask 矩阵本身是一个bool数组或者(batch_size, 1, seq_len, seq_len)的矩阵它不注意就会变形。在生产项目的 TensorFlow 或 PyTorch 代码里我见过不止一次mask维度和scores维度对不上导致广播报错。排查方法只有一条把 mask 的shape跟 score 的shape从头到尾打印出来看到 seq_len 维度出现两个不同长度的那一帧基本就是预填充或者 prefix 长度拼接时出了错。4.2 多头注意力的 concat 与 head 数设定期末常考的 4 个参数多头注意力考的其实是一个降维再拼接的空间变换问题。很多人记忆里只剩下多几个头效果更好但真的要解释num_heads为什么要设置为 8 或者 12又讲不出本质。这里的本质是头数决定了每个子空间的分辨率。例如把维度 512 切成 8 头每头只能看到 64 维的交互模式头数越多单头能表述的特征维度越窄但组合起来能表征更多不同的关系。这跟卷积神经网络里多个核是一个道理。期末大题常常让你手算多头注意力在拼接后的维度和参数量其实只要看透一个公式output concat(head_1, ..., head_h) * W_o。下面的代码展示了最容易被忽略的 reshape 和 permute 动作import numpy as np def multi_head_split(q, num_heads): # 假设 q 的 shape 是 (batch_size, seq_len, d_model) # 例如 (2, 5, 64)num_heads8所以每个 head 维度为 8 batch_size, seq_len, d_model q.shape d_k d_model // num_heads # 第一步把模型的维度切块 q q.reshape(batch_size, seq_len, num_heads, d_k) # 第二步把 head 维度移到 seq_len 之前 q q.transpose(0, 2, 1, 3) # (batch_size, num_heads, seq_len, d_k) return q考试和刷题时容易记错的就是这个名字叫transpose的操作它仅仅调整轴的顺序没有加任何可学习参数。也就是说多头注意力的参数量全部集中在W_q、W_k、W_v、W_o四个矩阵里而reshape和concat只是结构的改变。当题目给你一个d_model512、num_heads8的 Transformer 编码层让你算单头注意力里q k^T的结果大小是多少它就该是(seq_len, 64) * (64, seq_len) (seq_len, seq_len)而不是充满困惑的(64, seq_len)。期末复习里面对这个章节不该只背 多头让模型关注不同位置 这种套话。你要能做到给定一份模棱两可的代码截图准确指出哪一行是reshape、哪一行是transpose、哪一行是在对d_k做缩放。这才是提纲真正想考察的也是你在实际构建模型的时候唯一需要动脑子调整的部分。5. 期末避坑复盘公式推导与代码实现的五个经典翻车现场5.1 公式推导背得熟代码里却算不对 Log-Sum-Exp现象明明已经把 Softmax 的定义写得滚瓜烂熟一旦把损失函数写到代码里面对一个几千维的向量时计算过程直接报overflow encountered in exp。原因数学公式里用自然指数是理想化的但工程数据里分数往往不是 0 和 1而是几十上下的大数。在 32 位浮点数的表示范围内exp(1000)直接就变成 inf 了而这个现象在期末复习题里往往被直接用自然对数优雅地消掉只留一个漂亮的表达式不实际运行验证。解决所有涉及指数求和的地方都要执行一个安全减法。具体做法是先把 score 向量减去其最大值再做exp和log最终计算结果不变但数值范围被控制在 1 以内。写代码的时候别图省事凡是出现np.exp第一行基本就该是x x - np.max(x)这不仅是乐观技巧更是必须写进复习笔记里的安全模板。5.2 BERT 的 embedding 向量和 Word2Vec 向量用错导致迁移学习翻车现象把预训练 BERT 的最后一层隐藏状态向量当成传统词向量拿去算两个词的余弦相似度结果发现相似度普遍偏高且相似词排序的语义相关性远不如直接加载 Word2Vec。于是开始怀疑模型是不是加载错了。原因Word2Vec 强化的是静态词语义它给每个词一个固定向量而 BERT 是上下文相关的同一个词在不同句子里对应不同向量。你把 BERT 输出当静态向量相当于把高维语境信息压进了同一条向量自然会产生语义重叠余弦相似度区分度低。解决期末复习里这一条往往是隐性考点。真正做迁移学习落地时要看你要解决的任务是什么。如果是文本分类这种句子级任务建议取[CLS]向量接一个分类头做微调如果是 Token 级任务就直接用 Token 输出过 CRF 层千万别绕到词向量那个思路上去拿余弦相似度说事。写代码时可以打印这个向量在相似句子中的变化幅度用来验证你的使用姿势到底对不对。5.3 解码评测时忘记去掉特殊符号分数直线下跌现象用生成模型跑完一批测试句子算 BLEU 值的时候发现分数普遍比论文报的基准低 5~10 个百分点不论怎么调解码参数都毫无起色。原因预处理时没把[UNK]、[PAD]、[SEP]等特殊 token 从生成结果里剔除。这些符号虽然不影响人类阅读但在计算 BLEU / ROUGE 时它们都会被当成正常的词干去匹配导致精确匹配分数被严重拉低。解决在解码结束后的后处理脚本里必须定义一份 STOP_TOKEN 集合对所有输出序列做截断再解析。设置截断和过滤逻辑时务必统一参考训练时的 tokenizer 配置否则 special token 打印出来是s还是[CLS]会对不齐。期末如果碰上代码填空题考察你对生成结果清洗的路径判断这一趴往往就是送分题可实际工作中坑了不少人。5.4 CRF 实现里初始转移向量跟填充状态耦合导致识别全乱现象自己用 CRF 尝试复现命名实体识别时单独看每一项转移分数都很正常但整体解码结果乱成一团连续出现多处从I跳到B的非法跳转。原因CRF 层在解码时需要同时考虑初始状态概率和转移矩阵。很多人实现时直接把转移矩阵初始化为零没有显式指定起始状态从哪里来结果序列开头被强加了一个不合理的状态。解决务必在模型中增加一个显式的start_trans向量并且在计算路径总分时把初始维度跟转移矩阵分开。用代码表示就是score start_trans[first_tags] [发射分数] [后续转移分数]初始终止概率都属于可学习参数而不是零填充。调试时把这两个向量打印出来只要发现 start 概率分布的初始值分布过于均匀就要回头检查参数初始化逻辑。5.5 注意力权重可视化时忘记用真实文本对齐导致分析结论下反现象拿到一张漂亮的注意力热力图发现某个位置权重特别高据此推断模型学到了某种关键语义关系结果一对应原文发现高亮位置其实是填充位。原因可视化脚本里没有处理 mask把填充位置的注意力权重也画进去了。实际填充位置在 logits 阶段就已经被置成极小值但因为输出概率非零所以如果只用概率值做热力图它还会呈现看似高亮的底噪。解决正确做法是先提取 mask 矩阵让热力图脚本在填充区域直接覆盖为灰白色并排除在统计范围外。同时再来个验证步骤用同一批句子在原始代码和可视化代码中对齐 token ids保证 index 一一对应避免因为分词粒度不同而错位。这一条在面试里经常隐藏作为项目小细节提问能主动说出来基本能证明你不只是调了库。6. 进考场前最该确认的 3 个实战技巧第一个技巧是先跑通一段最小代码再回去看公式。针对提纲里的每个经典模型比如 CBOW、Viterbi、Multi-Head Attention你考前在电脑上从头敲一遍能跑出来的最小实现别用框架的高级封装跑完就完。只要你能亲手把 q 和 k 的维度在 reshape 前后打印出来并把 loss 曲线的一次下降过程截图保存面对卷子上那些请写出反向传播过程的题你的手感就远比死记硬背扎实。第二个技巧是心里始终锁定一个序列标准答案的参考模板。比如序列标注输出BIO时非法跳转有哪几类、Viterbi 回溯后得到的路径字符串怎么还原这些只要提前 10 分钟在草稿纸上画一遍状态表格考场上就能按图索骥。我当年复习这章的时候就把一张 A4 纸对折四次正面画 HMM 的三类问题关系图背面写维特比递推方程进了考场根本不需要翻书心里就有底。第三个技巧是遇到注意力机制的大题先把Q、K、V的 shape 写出来再进行操作。很多失分不在于你不会算而在于算到一半把(seq_len, d_k)和(b, h, s, d_k)混为一谈最后矩阵乘法维度配不上公式推导也跟着崩。合理习惯是先把batch_size、num_heads、seq_len、d_k这四个数字旁若无人地写在试卷角落然后所有演算都围绕这四个字母转。去年我在某个项目里带队做文本解析模块遇到一个跟提纲里 Viterbi 边界条件几乎一致的实体识别问题。当时组里新来的同学把注意力全部扑在调整 BERT 的随机种子和学习率上最后发现所有指标上不去的原因不过是后处理时转移约束写反了。那种感觉就像是期末复习时明明背过却硬生生写错了公式里的下标。从那以后我就养成一个习惯每次面对一个看似玄学的模型问题都先回头检查边界向量和维度变换而不是盲目调整超参。这既是我被期末提纲训练出来的思维惯性也确实成了后续工作里最能救命的一条行事准则。希望这篇提纲拆解能帮你在复习时抓大放小把代码里的坑提早填平考场上自然就能稳住。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑