python神经网络编程入门(二十九)——模型优化、测试评估与知识体系收官

发布时间:2026/8/8 20:31:07
python神经网络编程入门(二十九)——模型优化、测试评估与知识体系收官 引言赢了比赛还要再进一步上一章的三足鼎立比出了结果GRU 用 73.06% 的测试准确率拿下了头名。但最后一节留了个尾巴——第 21 轮之后训练损失还在往下掉验证准确率却不涨反跌。这说明冠军模型已经开始背答案而不是学方法了我们称之为过拟合。这一章做三件事先给模型穿上防作弊装甲Dropout让它不再死记硬背再趁机把记忆容量调大、多训练几轮看看冠军能不能更上一层楼最后在真正的考场上2.5 万条没见过的影评做一次全面体检用混淆矩阵看得清清楚楚。结尾把整个系列的知识点串成一张地图算是给这段学习之旅收个官。本章目标理解过拟合用Dropout 正则化给模型防作弊调大隐藏维度、加长训练把测试准确率从 73.06% 提升到79.80%在测试集上画混淆矩阵搞懂精确率、召回率、F1 的关系把第 1-16 章知识串成一张认知树完成收官。一、过拟合背答案而不是学方法先想清楚冠军到底出了什么问题。模型训练时看的是一组影评目标是让见过的这组影评判断得越来越准。可越来越准有两种实现方式学方法从影评里提炼出真正的规律比如出现 great、amazing 多半是好评这种能力能迁移到没见过的新影评上背答案把每一句训练影评原封不动地记下来见到一模一样的就答对。可新影评从没见过背的答案用不上。过拟合就是模型从学方法滑向了背答案。训练损失一路跌到接近 0训练集全答对了验证准确率却停在原地——因为它对没见过的数据束手无策。怎么判断一个模型过拟合了看训练损失和验证指标之间的缝隙。训练损失只管训练集背得好不好验证准确率才代表新数据上真正厉不厉害。两者差距越拉越大就越说明模型在钻训练集的空子。回到第 14 章那组真实数据就能看清这个剪刀差训练轮10152024训练损失0.3960.1850.0930.054验证准确率59.9%68.6%73.5%73.05%从第 15 轮到第 24 轮训练损失还在大幅下降0.185 → 0.054训练集几乎被一字不差背下来了可验证准确率从峰值 73.85% 回落、停在 73% 一线不动。损失在降、验证不涨这两个信号一撞就是过拟合的典型特征。把这个剪刀差画出来更直观过拟合的剪刀差训练损失狂降验证却走平回落0.00.20.40.60.81.005101520Epoch训练损失一路跌向 0验证准确率先升后走平缝隙越拉越大 过拟合训练损失红越走越低、验证准确率蓝先升后平两条线之间张开一个大口子——缝得越开模型背答案越严重。怎么逼它只能学方法、不许背答案思路是训练时故意给数据加点干扰让它没那么容易记住原文。这就是Dropout。二、Dropout随机关掉一部分神经元Dropout 的做法非常朴素每次训练时随机让一部分神经元罢工输出置 0下次再换另一批。相当于每次都在练一个残缺的模型。打个比方老师出卷子时每次都随机遮住几道题的答案逼学生必须理解原理而不是靠死记硬背某个标准答案。因为不知道哪部分会被遮住学生只能把每个知识点都真正弄懂这样学出来的本事换个新卷子也用得上。更妙的是因为每次关掉的是不同的一批相当于同时训练了很多个不同的残缺模型最后大家一投票取平均——单个模型的奇思怪想对训练集的死记被平均掉了保留下来的反而是大家一致认同的通用规律。这就是 Dropout 的集成直觉。数学上训练时对隐藏向量hhh逐元素乘一个随机掩码rrr其中每个分量独立服从伯努利分布ri∼Bernoulli(1−p),h~iri⊙hi1−pr_i \sim \mathrm{Bernoulli}(1 - p), \qquad \tilde{h}_i \frac{r_i \odot h_i}{1 - p}ri​∼Bernoulli(1−p),h~i​1−pri​⊙hi​​这里ppp是关掉比例Dropout 率除以1−p1-p1−p是为了让h~\tilde hh~的期望值保持和hhh一样不影响整体的信号强度。可以快速验证一下因为E[ri]1−p\mathbb{E}[r_i] 1-pE[ri​]1−p所以E[h~i]E[ri] hi1−p(1−p) hi1−phi\mathbb{E}[\tilde h_i] \frac{\mathbb{E}[r_i]\,h_i}{1-p} \frac{(1-p)\,h_i}{1-p} h_iE[h~i​]1−pE[ri​]hi​​1−p(1−p)hi​​hi​期望不变模型平均下来没被削弱只是多了随机扰动。注意只在训练时关评估时全开——否则模型在考试时也会掉链子。在 PyTorch 里就一行nn.Dropout(p)插入到该加的位置classSentimentGRU(nn.Module):def__init__(self,vocab5002,embed64,hidden96,dropout0.3):super().__init__()self.embnn.Embedding(vocab,embed,padding_idx0)self.dropnn.Dropout(dropout)# 防作弊装甲self.grunn.GRU(embed,hidden,batch_firstTrue)self.fcnn.Linear(hidden,1)defforward(self,x):eself.drop(self.emb(x))# 查表后先随机关一批_,hself.gru(e)# 通读returnself.fc(self.drop(h[-1])).squeeze(-1)# 打分前再关一批Dropout 放在两处Embedding 之后打乱词向量、最后一个线性层之前打乱浓缩记忆。关键提醒评估时 PyTorch 会自动识别model.eval()并关闭 Dropout所以训练/评估切换时别忘了model.eval()。三、调参升级73.06% → 79.80%有了 Dropout 这把保险锁模型不那么容易背答案了就可以放心把手脚放开——把记忆容量从 64 加到 96训练轮数从 12 加到 16。超参数对比如下超参数第 15 章基线第 16 章调参模型GRUGRU隐藏维度6496Dropout无0.3训练轮数1216词向量维度 / 学习率 / Batch64 / 1e-3 / 64不变最大化序列长度200200训练代码和上一章几乎一样唯一区别是隐藏维度和 Dropouttorch.manual_seed(42);np.random.seed(42)modelSentimentGRU(hidden96,dropout0.3)# 换了型号opttorch.optim.Adam(model.parameters(),lr1e-3)forepinrange(16):forstinrange(0,8000,64):x,ypack(train_idx[st:st64])lolossf(model(x),y)opt.zero_grad();lo.backward()nn.utils.clip_grad_norm_(model.parameters(),5.0)opt.step()vaccevaluate(model,val_idx)# 每轮验证集打分真实跑出来的验证准确率曲线GRU 调参前后验证准确率IMDB0.450.500.550.600.650.700.750246810121416Epoch调参后 hidden96 Dropout79.70%第15章基线 hidden64 无Dropout73.65%验证峰值 79.70%第 15 章基线橙色虚线峰值验证准确率 73.65%训练 12 轮测试集 73.06%调参后蓝色实线隐藏维度 96 Dropout 0.3训练 16 轮验证峰值79.70%测试集79.80%。光是加一个 Dropout、调大一点隐藏维度验证准确率就抬高了整整6 个百分点。更关键的是曲线后半段依然稳稳上扬没有出现上一章那根涨到顶就回落的尾巴——过拟合被明显压住了。最终在 2.5 万条从未见过的测试影评上拿到了79.80%。把两代模型的成绩摆在一起模型验证峰值测试集准确率参数量说明第15章 GRUhidden6473.65%73.06%345,153基线后期过拟合第16章 GRUhidden96 Dropout79.70%79.80%366,881调参稳定上扬消融实验确定 Dropout 到底贡献了多少能涨 6 个点是 hidden 变大还是 Dropout 的功劳为了分清这笔账做一次消融实验把 hidden 固定成 96其他不变只去掉 Dropout 再跑一遍同样的 16 轮。真实结果配置验证峰值测试集准确率结论hidden96无Dropout76.55%75.74%Dropout 的作用被单独剥离hidden96带Dropout0.379.70%79.80%完整配置同样是 hidden96、同样是 16 轮光是把 Dropout 加上去测试准确率就从 75.74% 涨到 79.80%净增约 4 个百分点。这说明提升主要不是靠记忆容量变大而是靠 Dropout 压住了过拟合——模型从背答案真正转向了学方法。这也解释了为什么第 14 章末尾故意留了个过拟合的伏笔那时候没有 Dropout加容量只会背得更彻底反而更糟。四、混淆矩阵看清模型到底错在哪测试准确率 79.80% 是个好数字但它只告诉我们答对了八成没说清楚错在哪、偏向了谁。情感分类是个二分类问题答案只有好评(1)和差评(0)两种。把预测和真相交叉一摆就得到一张混淆矩阵Confusion Matrix四格刚好对应四种情况TP真阳性真实好评预测也是好评TN真阴性真实差评预测也是差评FP假阳性真实差评却被判成好评FN假阴性真实好评却被判成差评。在测试集 2.5 万条上统计出来的真实数字测试集混淆矩阵2.5万条全新影评预测好评(1)预测差评(0)真实好评(1)真实差评(0)TP 9339答对好评FN 3161好评漏判FP 1889差评误判TN 10611答对差评对角线 TP TN 19950占总数 25000 的 79.8%FP 只占 7.6%FN 占 12.6% —— 漏判好评明显偏多对角线TP、TN判对的部分共 9339 10611 19950条占总数的79.8%正好对上准确率FP差评误判成好评只有 1889 条最少。这类往往踩在讽刺上——比如这电影烂得恰到好处表面词是好的模型就上当了FN好评漏判成差评有 3161 条最多。这类多是欲扬先抑的写法——开头抱怨一堆结尾才转折夸好读到最后一步时模型把转折给丢了。从这两格各挑一条真实误判的影评看看模型是怎么被骗的差评被判成好评FP│ 真实差评 预测好评 影评里反复出现 great nice works 等褒义词 但整段其实在挖苦演员的表演与剧情——模型只抓了表面词。 好评被判成差评FN│ 真实好评 预测差评 影评先花大量篇幅抱怨节奏慢、人物扁平 结尾才转折说自己看得很享受——模型没抓住最后的转折。第一类说明模型容易被单个褒义词误导讽刺的坑第二类说明模型只重视开头、丢了结尾的转折长尾依赖的坑。这两类错误正是纯 GRU 这类顺序读完最后一步的模型最典型的短板。从矩阵还能推出三个更细的指标PrecisionTPTPFP933993391889≈0.832\text{Precision} \frac{TP}{TPFP} \frac{9339}{93391889} \approx 0.832PrecisionTPFPTP​933918899339​≈0.832RecallTPTPFN933993393161≈0.747\text{Recall} \frac{TP}{TPFN} \frac{9339}{93393161} \approx 0.747RecallTPFNTP​933931619339​≈0.747F12⋅Precision⋅RecallPrecisionRecall≈0.787\text{F1} \frac{2\cdot \text{Precision}\cdot \text{Recall}}{\text{Precision}\text{Recall}} \approx 0.787F1PrecisionRecall2⋅Precision⋅Recall​≈0.787这三个指标各看一个侧面精确率 0.832模型说这是好评时有 83.2% 真的说对了。高说明它很少冤枉好评召回率 0.747所有真实好评里它只捞回来了 74.7%有近 1/4 漏掉了。相对偏低正是那 3161 条欲扬先抑式好评被误杀F1 0.787把两者调和成一个数兼顾别冤枉和别漏掉是更公允的单一排行指标。用个生活场景区分精确率和召回率精确率像挑苹果挑出来的苹果里坏果越少越好精挑细选召回率像捞鱼网兜越密能捞到的鱼越多但也会捞进一堆杂草宁多勿漏。两者天然此消彼长F1 就是它们的调和平均数偏大者被惩罚、偏小者被拉高最终给出一个平衡分。三种模型最终的 F1 对比如下模型精确率召回率F1RNN第15章0.510.510.51LSTM第15章0.620.620.62GRU 调参后第16章0.8320.7470.787五、常见坑与自查评估时忘了model.eval()不切回评估模式Dropout 还在随机关神经元验证/测试准确率会被打折扣测出来偏低。训练循环结束、开始打分前务必model.eval()。Dropout 加错位置加在输入层之前或输出层之后没有意义正确位置是 Embedding 之后和线性层之前即信息流动的中段。Dropout 率过大设成 0.5 以上模型残废得太狠学不动准确率反而下降。0.2~0.3 是常见稳妥区间。只看准确率不看矩阵79.8% 的准确率掩盖了漏判好评偏多这个倾向。要想知道模型是爱冤枉还是爱漏掉必须看混淆矩阵和 Precision/Recall。测试集和调参混着用如果在测试集上反复试参数、越试越高测试集就被污染了测出来的数字不再可信。调参只看验证集测试集只碰一次。调只加容量、不加 Dropout第 14 章的教训——没有正则化时把 hidden 从 64 加到 96模型会背得更彻底很可能更早过拟合。加容量一定要和 Dropout 搭配。六、收官一串知识点一张认知树到这里第 1-16 章的完整链路就算走通了。回头看一眼其实整个系列就回答了一个问题怎么让机器读懂一段文字的感情。从原理到实战恰好是一根越走越坚实的主线读懂一段文字的感情原理推导第1-5章循环思想 · BPTT · 梯度消失三兄弟进化第6-10章RNN → LSTM → GRU数据处理第11-13章词嵌入 · 词表 · 填充掩码实战对比第14-15章三模型同台 · GRU 胜出优化评估第16章Dropout · 调参 · 混淆矩阵最终成果测试准确率 79.80%RNN 家族原理 → 手撕 → 数据 → 实战 → 优化一条主线读懂文本序列建模这条主线的四个阶段正好对应四个问题第 1-5 章 · 原理为什么文字要用 RNN循环到底是什么手撕一遍前后向摸清梯度消失的病根第 6-10 章 · 进化直面病根从 LSTM 的细胞状态到 GRU 的双门简化理解传送带 收费站如何救场第 11-13 章 · 数据把文字变成数字把长短不一的影评装进统一模具学会填充与掩码第 14-16 章 · 实战搭模型、三兄弟对决、调参优化、混淆矩阵评估最终拿到 79.80% 的测试准确率。回头看这套方法还能迁移到哪这一路学到的远不止一个 79.80% 的数字而是一套可复用的方法链数据工程把原始文本变成规整的张量词表、填充、掩码任何文本任务都用得上模型选型任务规模中等、追求效率时选 GRU长序列高精度要求时选 LSTM先跑个 RNN 当基线训练技巧固定种子保证可复现、梯度裁剪防爆炸、Dropout 防过拟合、只看验证集调参评估思维准确率之外用混淆矩阵 精确率/召回率/F1 看清模型的性格。这套思路稍加改动就能平移到机器翻译、文本生成、语音识别、时间序列预测等任务上——结构换成更大模型管线依然成立。学会从原理到实战的完整闭环比记住任何一个具体数字都更有价值。小结这一章既是优化也是收官认识过拟合训练损失狂降、验证走平是模型在背答案用 Dropout 随机关掉部分神经元逼它学方法调参升级隐藏维度 64→96、加 Dropout、练 16 轮测试准确率从73.06%提升到79.80%过拟合尾巴消失消融验证固定 hidden96 只去 Dropout准确率掉到 75.74%——证明提升主要来自 Dropout 压过拟合净增约 4 个点混淆矩阵评估TP9339 / TN10611 / FP1889 / FN3161精确率0.832、召回率0.747、F10.787看清模型判差评很准、漏好评偏多知识收官把原理、进化、数据、实战串成一条主线完成从零到 79.80% 的完整闭环。至此从循环是什么到让机器读懂影评感情一段完整的旅程画上了句号。三大循环网络、完整的数据管线、从手撕到实战的方法论都可以迁移到机器翻译、文本生成、语音识别等其他序列任务上。理解原理、动手实现、用数据说话——这条路上学到的远不止一个数字。感谢一路读到这里的每一位读者。