尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
语言模型训练与预测:从n-gram到神经网络实战笔记
学习NLP的第14天我把焦点放在语言模型的训练与预测上。所谓语言模型简单说就是“让机器学会判断哪句话更像人话”的模型它既能给整句打分也能预测下一个词。今天我用一份小型中文语料同时跑通了 n-gram 计数模型和一个小型神经网络模型对比了两种路线的训练过程、预测效果和典型坑点。如果你是刚开始学 NLP或者被大模型的前置概念搞得有点晕这篇笔记应该能帮你把“训练”和“预测”这两个动作彻底拆明白。我会按数据准备、n-gram 训练、神经网络训练、预测解码、问题排查的顺序往下写每一步都附带实际操作参数和体会。1. 第14天先解决核心问题语言模型在学什么1.1 语言模型到底在学什么一个预测下一词的游戏我正式开始写这一天的笔记时脑子里冒出来的第一句话是语言模型本质上是在做一个“猜下一个词”的游戏。你给它前面的上下文它输出对下一个词的预测概率。比如“今天天气很好我准备出门____”大多数人会填“散步”“跑步”“购物”。语言模型做的就是把这个“大多数人会填什么”变成概率分布同时让正确词的概率尽量高。这个过程没有人工标注因为语料本身就是答案每个句子的下一个词就是监督信号。所以我们常说语言模型训练是自监督的根本原因就在这里。训练时我们喂入大量文本让模型不断根据前文预测真实的下一个词用交叉熵更新参数。预测时给定一个开头模型每步输出概率分布再用各种解码策略选词接到后面。所谓的“训练与预测”拆开看就是这么两件事但里面的坑远比字面意思多。我今天花了大半天时间不是因为概念难而是因为每一步都有太多“看起来没问题一跑就露馅”的细节。1.2 用条件概率链把一个句子“拆”到底要从数学上理解语言模型关键是整句概率的链式分解。对于句子“我 喜欢 学习 NLP”可以写成P(我) × P(喜欢 | 我) × P(学习 | 我喜欢) × P(NLP | 我喜欢学习)也就是用“每一步条件概率的连乘”表示整句的合理性。这样做有一个直接收益我们不需要一次性预测整句话只需要建模一个通用的“下一个词预测器”。这种思路贯穿传统 n-gram 和现代神经网络语言模型。训练数据是普通文本不需要额外标注所以语言模型天然适合大规模预训练但同时它也会学到语料里的偏见这是后话。条件概率链越往后依赖的前文越长。传统 n-gram 因为统计表太大最多保留两三个词神经网络可以用嵌入和隐状态把长上下文压缩成向量。这是两者最本质的区别。今天我先从 n-gram 入手就是想把这种“拆解”落到每一张计数表上而不是停留在公式里。1.3 第14天为什么同时跑n-gram和神经网络没有背景的人第一次学语言模型很容易直接跳到 Transformer。我的想法是把两种路线都跑一遍先从 n-gram 感受“概率表怎么从计数中产生”再写一个小型循环神经网络体会“梯度怎么把概率表变成参数矩阵”。前者轻量、可解释后者更接近现代生产环境。两条路线的预测阶段都可以用同样一套逻辑去看给上下文拿分布选词。今天早上我先用一个 50MB 左右的中文语料搭建 baseline然后用同样语料训练了一个嵌入维度 128、隐藏维度 128 的小网络最后对比两者生成结果。这个流程花不了太多时间但对理解后面积累的概念很有帮助。尤其是当你以后看到“预训练”“大模型微调”这些词时会发现它们的底层逻辑和今天的训练循环并没有本质区别只是规模和工程复杂度不一样。2. 训练前必做数据清洗、分词与样本构造2.1 语料质量怎么定先像洗菜一样洗文本我踩过的第一个坑是拿原始文本直接训练。那段语料里有超链接、乱码、全半角标点混用、大量英文内容统计出来的相邻词对经常是“http 的”“了 。.”这种奇怪组合。清洗的第一步是分段和分句把一篇文章拆成一个个自然句太长的句子截断太短的过滤掉。第二步是统一标点中文逗号、句号保留英文标点统一成半角多余空白删除。第三步是按需去除噪声如果场景是通用文本去 URL、邮箱如果场景是对话反而要保留口语词。清洗不是越干净越好。过度清洗会把语气词和标点规律都删掉模型生成出来会变得死板。我第一版数据处理时把所有标点都删了结果模型把“。”也当成需要预测的目标整句话没有节奏。后来我保留句末标点只删掉无意义的特殊符号生成结果才正常。数据清洗的本质是保留语言规律去掉干扰项这个度需要反复试。2.2 分词与词表设计决定模型词汇量的天花板中文没有天然空格分词会直接影响后续所有统计。第 14 天我用的是词级而非字符级。字符级实现简单每个汉字都认识很少遇到未登录词但序列会变长很多并且丢失了词义边界词级表达更紧凑但分词错误会引入噪声还要处理词表外的词。构建词表时我统计全部词频只保留 top 20000同时加入占位符pad用来补齐短句bos表示句子开头eos表示结束unk表示未登录词。token作用pad填充批次中较短的序列避免维度不一致bos标记句子开始让模型知道从哪里开始预测eos标记句子结束也可以作为训练目标unk兜底所有不在词表中的词减少零概率问题词表大小的核心权衡是词表太大会让输出层参数爆炸推理速度变慢词表太小又会出现大量unk模型学不到低频词。小实验里 2 万到 5 万是比较常用的区间。我今天先选了 2 万就是想让输出层不至于成为训练瓶颈。2.3 滑窗构造样本输入和标签永远错开一个位置神经网络语言模型的训练样本并不是把完整句子丢进去而是用固定长度窗口截取。给定词序列 [我喜欢学习NLP处理]窗口为 3我会生成两对样本输入 [我喜欢学习]目标是 NLP输入 [喜欢学习NLP]目标是处理如果前面不够长度用bos补齐。这里一定要记住一个原则预测第 t 个词时只能看到第 1 到 t-1 个词绝不能把 t 位之后的词也放进输入。我第一次写代码时不小心把“整句编码后”直接丢给模型训练损失降得飞快但生成阶段完全不会说人话因为模型在训练时提前看到了答案。这就是数据泄漏。排查了一整天才发现数据构造时把输入序列和目标序列错位错了最后把标签整体右移一位问题才解决。3. n-gram计数模型从统计词表到条件概率3.1 bigram 训练核心就是一个计数器n-gram 模型的“训练”听着很高级实际就是数数。以 bigramn2为例我遍历整个语料统计每个二元组 count(w_{i-1}, w_i) 和每个一元组 count(w_{i-1})然后计算条件概率P(w_i | w_{i-1}) count(w_{i-1}, w_i) / count(w_{i-1})如果语料里“我”一共出现 100 次其中“我 喜欢”出现 20 次那 P(喜欢 | 我) 就是 0.2。这个比例就是模型的全部知识。做完这一步预测阶段就非常简单给定上一个词查表找出条件概率最大的下一个词。训练过程快得惊人几百万词的语料在几秒内能统计完但能力也很有限它能捕捉“钱”后面的常搭配词却完全记不住上次出现过的远距离信息。3.2 平滑处理不能允许任何概率为 0n-gram 最大的敌人是数据稀疏。即使语料有几十 MB词表 2 万很多“理论上可行”的相邻组合一次都没出现过。没有平滑处理时这些组合概率为 0整句概率也会变成 0模型直接露馅。我用的第一个方案是加 1 平滑P(w_i | w_{i-1}) (count(w_{i-1}, w_i) 1) / (count(w_{i-1}) V)其中 V 是词表大小。这样原来的零概率组合会得到一点点概率不会出现整句概率为 0 的情况。但加 1 平滑会让高频组合的概率被稀释效果一般后来我又试了回退backoff和插值即优先用 bigram如果没统计到就回退到 unigram。对小实验来说加 1 平滑已经能保证“能跑”但别指望它带来质的提升。3.3 选 n2 还是 n3上下文长度和稀疏性打架理论上 n 越大条件概率越准确因为能利用更多前文。但实际试下来n3 的计数表增长得非常快词表 2 万时 trigram 理论组合数高达 8 万亿小语料里绝大多数组合根本没出现过。即便有平滑这些未出现组合也会把概率摊得很薄让模型生成变得“和稀泥”。所以我的经验是在个人小语料上bigram 是最稳的起点想试 trigram建议先把语料规模提上去或者只对高频词使用更长上下文。第 14 天我先跑通 bigram把平滑、评估、解码这套流程摸清楚再用神经网络接手更长依赖。这样做的另一个好处是如果后续模型效果不如 bigram你至少能定位是数据问题还是模型问题。3.4 n-gram如何做预测查表、取数、选词用 n-gram 做预测说白了是“查条件概率表”。给定词 a找出 P(w|a) 最高的几个词。动手试的时候我会打印每个上下文对应的 top-5 候选词比如上下文是“我 喜欢”的时候top 可能有“学习”“吃”“看”“你”“这个”。这一步看着原始但价值很大它能让你直观感受到“语料里哪些词经常搭配”。如果 top-5 全是“的、了、在”这种功能词就说明语料还是太小或者泛化不够。之后我会用随机采样而不是取最大值否则生成结果很容易陷入“我 喜欢 学习 学习 学习……”这样的循环。这里我补充一句n-gram 的预测虽然简陋但它把“语言模型 条件概率分布”这个核心概念展示得特别清楚后续所有复杂模型都是对条件概率分布的更精细逼近。4. 神经网络语言模型训练循环与关键超参数4.1 一个可以直接跑的结构嵌入层、循环层、输出层我的 mini 网络结构可以拆成三块。第一块是嵌入层Embedding把词 ID 映射成一个 128 维向量相当于给每个词学习一个表征第二块是循环层这里用普通 GRU比 LSTM 参数少把当前词向量和上一步的隐状态融合形成对前文的“记忆”第三块是全连接输出层把最后的隐状态映射到词表大小得到每个词作为下一个词的得分。可以把这个结构理解成一个人读句子的过程每读到当前词马上结合刚才记住的内容猜下一个词。最终输出层给每个词打分再经过 softmax 变成概率分布。训练结束后嵌入层和循环层的参数里就压缩了语料的统计规律。和第 3 节查表相比神经网络不再显式保存每一个组合的计数而是用连续向量表达“语义相似”的词这也是它能缓解稀疏性的关键。4.2 训练流程一步步拆前向、交叉熵、反向、更新神经网络语言模型训练的大循环是这样先把一批输入序列转成 ID输入网络得到 logits再用交叉熵损失比较“模型预测的分布”和“真实下一个词的分布”反向传播算出梯度用优化器更新参数。为了稳定要加梯度裁剪。伪代码大概长这样for epoch in range(epochs): for batch in data_loader: logits model(batch[input_ids]) # [batch, seq_len, vocab_size] loss cross_entropy(logits.view(-1, vocab_size), batch[target_ids].view(-1)) optimizer.zero_grad() loss.backward() clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step()交叉熵听起来复杂实际上就是在说如果真实下一个词是 w模型给的 P(w) 越大损失越小。第 14 天我每跑一个 epoch 就打印一次平均损失观察到损失从最开始大概 7 点几一路往下掉最后稳定在 2 到 3 左右。损失降到 2 到 3相当于模型对下一个词的预测平均只在 exp(2) 到 exp(3) 个候选里犹豫已经比随机猜 2 万个词好太多了。4.3 超参数到底怎么定序列长度、批大小、学习率、梯度裁剪序列长度我用 16。太短只能看到眼前两三个词太长则循环网络计算量大、训练慢batch size 我选 32显存友好梯度噪声也能接受学习率用 Adam 时从 0.001 开始如果 loss 震荡就降到 0.0005。梯度裁剪设成 max_norm1.0这个动作能防止 RNN 因为长序列梯度乘法累积而爆炸。很多初学者只调学习率不调序列长度和梯度裁剪结果模型永远训不收敛。生活化类比序列长度像人的记忆窗口太大反而容易把无关细节也记进来梯度裁剪像给学习率加保险丝防止一次更新把参数推到沟里去。参数建议值调整方向embedding_size128语料大时可升到 256/512hidden_size128同上但要考虑显存和训练时间seq_len16观察生成质量可尝试 8/32batch_size32小显存可降到 16learning_rate0.001震荡时降 0.0005max_grad_norm1.0类似取值 0.5~5.0这些参数之间会互相影响。比如序列长度加大后batch size 往往要减小否则单次计算量会明显上升。我的经验是先固定一组中等参数跑通再每次只改一个变量不要同时调三个以上否则出了问题很难归因。4.4 从第14天的小模型到预训练大模型的连接点现代大语言模型在训练本质上和上面这段流程是一致的只是把 GRU 换成更强的 Transformer 解码器把几 MB 语料换成几 TB 语料把单卡训练换成大规模分布式训练。理解小模型的意义在于很多困扰大模型的训练、预测问题在小模型上早就有原型——过拟合、数据泄漏、解码时重复、评估指标失真都属于这类。第 14 天的重点不是复现一个大规模预训练流程而是把“训练循环 — 损失 — 预测 — 评估”这条主线走通后面读到预训练、微调这些概念时才不会觉得全是黑话。我自己在把 bigram 换成 GRU 之后才真正理解了为什么训练损失曲线和验证集指标要分开看。很多人以为训练就是“loss 下降”其实那些在训练集上驴头不对马嘴的生成样例才是真正帮助判断模型好坏的材料。5. 预测阶段把概率变成一句能读的话5.1 贪心解码最快但最容易掉进重复循环模型预测时最简单的方法是每步都选概率最大的那个词这就是贪心解码。我在 bigram 模型上试过给定“我 喜欢”模型可能会逐步生成“我 喜欢 学习 我 喜欢 学习”陷入重复循环。原因很直接高频搭配在局部概率最大但整个序列已经绕死了。贪心的优点是无脑、快、适合确定性场景缺点是在开放式生成中缺乏多样性。真实产品里很少裸用贪心通常要配合重复惩罚或者改用采样。所以我建议学习阶段把贪心当成“最差基线”用来直观展示解码策略的重要性而不是直接把它用到实际生成里。5.2 温度采样给概率分布加一点点“混乱”比贪心稍微讲究一点的是从概率分布中采样而温度参数 T 控制采样的“陡峭程度”。具体做法是先把 logits 除以 T再做 softmaxp_i exp(z_i / T) / sum_j exp(z_j / T)T 越小分布越尖锐接近贪心T 越大分布越平滑随机性越强。我的实测T0.7 到 0.9 生成的句子相对自然T1.2 以上开始出现语法破碎。如果是对话助手这类要求稳定的场景T 往往调得很低如果是诗歌、故事创作可以调高一点。温度不是越高越有创意太高就是胡说八道。实际写代码时我习惯先算 logits再除以 T然后 softmax最后用随机采样。这里有个细节logits 通常已经过了概率对数化直接除以 T 会改变分布形状这也是“温度”这个名称的由来。生成多样化问题时我一般把 T 放在 0.8 到 1.1 之间再根据结果微调。5.3 束搜索用多条候选路径换取质量束搜索beam search是另一种常用解码策略。它同时维护 k 条概率最高的候选序列每一步从 k 条序列分别展开保留累计概率最高的 k 条。这个做法能避免贪心只走一条路导致的局部最优。我在小模型上试过 k3 和 k5k5 生成更流畅但明显更偏向高频词句子很长时会变得平庸。束搜索适合机器翻译、摘要这类“答案相对确定”的任务创意写作则更多用温度采样。解码策略没有银弹要看任务对确定性还是多样性的要求。跑一次束搜索比贪心慢 k 倍所以工程上如果能在效果和延迟之间接受贪心就不一定非要用大 k。第 14 天我主要是为了理解“为什么生成结果不同”所以三种策略都各跑了几条样例对比。5.4 除了看损失还要看困惑度PPL 与生成样例训练和预测中间需要一个评估标尺。最常用的是困惑度perplexityPPL把模型在每个位置的交叉熵求平均再做指数运算。可以理解为模型平均在多少个词之间“犹豫不决”数值越低越好。第 14 天我把 bigram 和 GRU 放在同一份验证集上对比。模型验证集PPL生成样例bigram 加1平滑约 420“我 喜欢 学习 学习 学习 自然 语言”GRU(128) 训练3轮约 88“我喜欢学习自然语言处理的基本方法”PPL 低了生成未必一定好因为困惑度只衡量“词级别的不确定性”不关心句子是否语义连贯。所以我每次评估都会额外打印两组新句子人工看一遍。只有困惑度和生成样例一起变好才说明训练是有效的。如果只盯着 PPL 调参很可能得到一个“看起来很自信、说出来的话却毫无逻辑”的模型。6. 踩坑实录语言模型训练中的四个常见问题6.1 损失一直降生成却很空泛这个问题我调了两天才想明白。原因往往是语料里高频词集中在“的、了、是、在”上模型学会了用安全词把句子填满但对实词没有信心。对策有三一是观察验证集损失确认是否真的在拟合分布二是生成时把高频功能词的概率压低一点或做重复惩罚三是检查分词结果如果分词粒度太粗很多实词被切碎了模型根本学不到。第 14 天“训练和预测”都跑通之后单独看训练损失很容易被骗必须回到文本样例里去验证。我后来养成一个习惯训练过程中每两个 epoch 就生成几段文本哪怕只是简单打印也比只看 loss 曲线更能发现问题。尤其是模型开始疯狂输出unk或者句号的时候通常是词表或者解码设置出了问题。6.2 训练损失下降验证损失反弹过拟合还是学习率问题模型在验证集上困惑度不降反升第一个怀疑的就是过拟合。小语料小模型很容易把训练集特有的搭配背下来却没学到泛化规律。做法是加 dropout、减小 hidden size、增加数据量或者早停。另一种情况是学习率太大loss 在验证集上震荡不是真正的过拟合。我后来习惯用“每 5 个 epoch 在验证集上算一次 PPL同时保留最优 checkpoint”而不是只看最后一个 epoch。这样能同时监控训练和验证两条曲线定位更清晰。如果训练损失还在降、验证损失已经开始升基本可以断定为过拟合如果两条曲线一起震荡那就要先调学习率或 batch size。6.3 未登录词、OOV 和unk泛滥中文分词系统不能覆盖所有词任何词表之外的新词都会被映射成unk。如果语料里unk太多模型就会频繁输出unk生成结果没法看。我做的三件事第一词表尽量覆盖语料中出现至少 2 次的词只有绝对的低频词才归入unk第二解码时直接把unk的概率置 0不允许生成第三选择更好的分词方式减少碎词。对 n-gram 来说unk属于必要兜底但数量必须控制否则概率表里最大的一个“词”就是unk模型整天只会输出它。神经网络虽然用嵌入表达所有词但也扛不住大量低质量分词。遇到这个问题时不要急着换大模型先回去统计数据里unk的占比通常在 1% 以下才比较健康。6.4 先跑一个最笨的基线再换大模型这是我最想分享的一条经验。很多人第 14 天就开始调大规模预训练模型结果问题满天飞原因是他们跳过了最小可行模型。先跑 bigram 基线的好处有三个一是明白了数据分布里到底有什么二是拿到一个最低标准后面神经网络模型才显得有意义三是遇到 bug 时基线可以帮你确认是数据问题还是模型问题。比如我的 GRU 一开始损失降不下去回头和 bigram 对比才发现是分词结果里有大量空串清洗漏了一步。基线的价值不是效果而是坐标。没有这个坐标你很难判断一个模型到底是真的学会了还是只是把训练数据背下来了。我在实际学习过程中还有一个习惯每天结束前用同样一批提示词让两个模型各生成 5 句话记录特征和失败案例。第 14 天跑完 n-gram 和 GRU 后我把“上下文窗口”和“稀疏性”这两个抽象概念彻底看明白了。如果你也在学 NLP建议不要急着堆模型结构先让最简单的概率表跑起来再让梯度流动起来。卡住的时候回到语料和基线重新拆问题比换一个更大的模型更管用。
RELATED

相关推荐

Pod端口声明与监听错位:Kubernetes假健康排障实录

Pod端口声明与监听错位:Kubernetes假健康排障实录

做 Kubernetes 排障的人大概都经历过这种场面:客户端访问超时,打开控制面一看,Pod 全部 Running,CPU、内存指标也正常,日志里没几条 ERROR,Service 的 Selector 更是精确匹配。这种“资源都正常”的现象最迷…

📅 2026/10/16 16:35:54
Hive数据倾斜全解析:从Map端到Reduce端的调优实战

Hive数据倾斜全解析:从Map端到Reduce端的调优实战

1. 数据倾斜的本质与判断方法1.1 什么是数据倾斜数据倾斜这个词听起来很学术,但说白了就一句话:本该大家一起干的活,全压到了少数几个人身上。在 Hive 里,这个“人”就是 task。一个 Hive SQL 提交后会被拆成多个 stage&#xff0…

📅 2026/10/16 16:35:54
非对称纳什谈判下多微网电能共享优化策略与实现

非对称纳什谈判下多微网电能共享优化策略与实现

干微电网优化的人应该都有印象:一个园区里,A栋屋顶光伏在午间发得多了,消纳不掉,逆变器被迫限功率;隔壁B栋是商业综合体,午间恰恰是负荷高峰,正在按尖峰电价从网上买电。两边物理上只隔几百米&a…

📅 2026/10/16 16:35:54
MORE NEWS

更多资讯

📰

Proteus直流电机闭环仿真:SPICE建模与PID调参实战

简介:本资源是一套基于Proteus平台的直流电机控制仿真工程,面向电子类专业学生、嵌入式初学者及自动化课程实践者,聚焦电机调速与正反转控制的核心能力训练。压缩包共18个文件,涵盖Proteus原理图(.dsn)、Ke…

📰

C++实现影像金字塔:重采样核选型与避坑指南

简介:C图像重采样与影像金字塔构建的完整工程源码,适合具备基础C语法、希望深入图像处理或遥感应用的开发者和学习者。项目针对8位和24位Windows位图实现22双线性内插重采样,并通过逐层下采样构造多分辨率影像金字塔,清晰展示从像…

📰

数据结构初探:从基本概念到算法复杂度,一篇理清第一天重点

数据结构day1基本概念,这堂课我前后看了两遍。第一遍觉得都是名词解释,数据、数据元素、数据项、逻辑结构、物理结构,看一眼就觉得自己会了;第二遍才发现,第一天其实把整个学科的骨架全摆出来了,后面学的栈…

📰

JVM Class文件常量池全解析:17种cp_info类型与字节码运行原理

写Java的人,天天和class文件打交道,但愿意把Class文件当成一坨字节去逐行读懂的人其实不多。常量池(Constant Pool)是Class文件里信息密度最高的一块区域,也是连接字节码与运行时数据的“中枢神经”。JVM规范里定义了1…

📰

STM32+MPU6050姿态解算源码详解:I2C通信与DMP固件实战

简介:面向嵌入式入门与进阶开发者,一套基于STM32F103C8T6与MPU6050的Keil完整工程可直接编译下载使用。工程自带标准外设库源码与头文件,并配套完整示例与说明文档,覆盖I2C读取MPU6050原始数据、姿态解算、串口输出及定时器、ADC、…

📰

Windows下开箱即用的Ceres Solver预编译库(VS2019/2022)

简介:本资源为Windows平台下预编译完成的Ceres Solver数值优化库及配套测试工程,面向计算机视觉、SLAM、参数估计等领域的C开发者与高校科研人员,解决在Windows环境中部署Ceres时常见的编译门槛高、依赖配置复杂、VSCMake联调困难等痛点。压缩…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬