多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

NLP-情感分析项目(四):训练评估 + 主函数

NLP-情感分析项目(四):训练评估 + 主函数 情感分析项目代码详解四train_eval_test.py 训练评估 main.py 项目总入口上一篇搭好了 TextRNN 模型这一篇进入实战环节。train_eval_test.py 包含训练、验证、测试三个函数负责让模型学会分类、及时保存最优版本、最后报告总成绩。一、导入库import torch import torch.nn as nn import numpy as np import torch.nn.functional as F from sklearn import metricsnumpy数值计算库这里用来收集和拼接预测结果数组。torch.nn.functional简写 F提供交叉熵损失等函数式接口只计算、不保存参数。sklearn.metrics机器学习评估工具包准确率、分类报告都有现成函数不用自己手写。二、evaluate 评估函数def evaluate(class_list, model, data_iter, testFalse): model.eval() loss_total 0 predict_all np.array([], dtypeint) label_all np.array([], dtypeint) with torch.no_grad():model.eval()把模型切换到评估模式。训练时用到的 dropout 等随机行为会被关闭保证评估结果稳定可复现。loss_total累加所有批次的损失最后取平均。predict_all、label_all两个空数组用来收集整个数据集上的预测值和真实标签。with torch.no_grad()评估阶段不需要反向传播关掉梯度计算能省大量显存、跑得更快。三、逐批计算损失与预测for texts, labels in data_iter: outputs model(texts) loss F.cross_entropy(outputs, labels) loss_total loss.item() labels labels.data.cpu().numpy() predic torch.max(outputs, 1)[1].cpu().numpy() label_all np.append(label_all, labels) predict_all np.append(predict_all, predic)data_iter 就是第二篇写的 DatasetIterator每次吐出一批 (texts, labels)。F.cross_entropy交叉熵损失衡量预测分数和真实标签差多少是多分类任务最常用的损失函数。loss.item() 把张量转成普通 Python 数字。torch.max(outputs, 1)[1]沿第 1 维类别方向找最大值[1] 取最大值的下标也就是预测的类别再用 .cpu().numpy() 转成 numpy 数组。np.append 把每批的结果拼进大数组循环结束后就得到全数据集的预测和标签。四、计算准确率与分类报告acc metrics.accuracy_score(label_all, predict_all) if test: report metrics.classification_report(label_all, predict_all, target_namesclass_list, digits4) return acc, loss_total / len(data_iter), report return acc, loss_total / len(data_iter)accuracy_score 直接算出整体准确率预测对的条数 ÷ 总条数。testTrue 时正式测试额外生成分类报告每种情绪各自的精确率、召回率、F1 值digits4 保留 4 位小数能看出模型对哪类情绪识别得好、哪类容易混淆。平均损失 总损失 ÷ 批数。评估在验证集上只返回准确率和损失在测试集上多返回一份报告。五、test 测试函数def test(model, test_iter, class_list): model.eval() test_acc, test_loss, test_report evaluate(class_list, model, test_iter, True) msg Test Loss: {0:5.2} , Test Acc: {1:6.2%} print(msg.format(test_loss, test_acc)) print(test_report)测试其实就是开全量报告的评估把 testTrue 传给 evaluate。msg 里的 {1:6.2%} 是格式控制符百分数保留 2 位小数、右对齐占 6 格输出整齐好看。最后打印测试集上的损失、准确率和完整分类报告。六、train 训练函数开头def train(model, train_iter, dev_iter, test_iter, class_list): model.train() optimizer torch.optim.Adam(model.parameters(), lr1e-3) total_batch 0 dev_best_loss float(inf) last_improve 0 flag False epochs 20model.train()切换到训练模式启用 dropout 等机制与 eval 正好相反。优化器选用 Adam学习率 1e-3即 0.001。Adam 是目前最常用的优化器收敛快、调参省心model.parameters() 把模型所有可训练参数交给它管理。几个记录变量total_batch 已训练批数dev_best_loss 历史最好验证损失初始化为正无穷——这样第一次评估一定会刷新它last_improve 上次成绩提升时的批数flag 早停标记epochs20 表示最多训练 20 轮一轮 完整过一遍训练集。七、训练核心循环for epoch in range(epochs): print(Epoch {}/{}.format(epoch 1, epochs)) for i, (trains, labels) in enumerate(train_iter): outputs model(trains) loss F.cross_entropy(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step()双层循环外层 20 轮内层遍历训练集的每一个批次。每个批次走标准四步曲①前向传播算输出②交叉熵算损失③zero_grad() 清空上一步的梯度PyTorch 的梯度默认累加不清会越积越乱④backward() 反向传播求梯度step() 用梯度更新参数。模型就是这样一批一批、一轮一轮学习的不断算错、求梯度、改参数损失越来越小。八、每 100 批评估一次if total_batch % 100 0: predic torch.max(outputs.data, 1)[1].cpu() train_acc metrics.accuracy_score(labels.data.cpu(), predic) dev_acc, dev_loss evaluate(class_list, model, dev_iter) if dev_loss dev_best_loss: dev_best_loss dev_loss torch.save(model.state_dict(), textrnn.ckpt) last_improve total_batch msg Iter:{0:6}, Train Loss:{1:5.2}, Train Acc:{2:6.2%}, Val Loss:{3:5.2}, Val Acc:{4:6.2%} print(msg.format(total_batch, loss.item(), train_acc, dev_loss, dev_acc)) model.train() total_batch 1每训练 100 个批次就在验证集上小考一次先算当前批次的训练准确率再调用 evaluate 在验证集上得到 dev_loss 和 dev_acc。如果这次验证损失打破了历史最低记录就把当前模型参数保存到 textrnn.ckptstate_dict 就是模型参数并更新 last_improve——这就是保存最优模型的常用做法。打印一行统计信息格式符保证对齐。最后记得 model.train() 切回训练模式——因为 evaluate 内部调用了 model.eval()不切回来的话 dropout 就一直关着了。循环末尾 total_batch 1记录总进度。九、早停机制if total_batch - last_improve 10000: print(No optimization for a long time, auto-stopping...) flag True break if flag: break test(model, test_iter, class_list)早停early stopping如果已经训练了 10000 个批次验证损失却一直没刷新最好成绩说明继续练下去多半只是浪费时间甚至过拟合直接中断。flag 配合两层 break先跳出内层批次循环再跳出外层轮次循环。训练全部结束后调用 test 在测试集上做最终大考打印损失、准确率和分类报告。训练流程讲完后顺便看看项目的总入口 main.py。它负责把前几篇写的所有零件组装起来代码不长一并放在本篇结尾讲完。十、main.py 导入与设备选择import torch import numpy as np import load_dataset, textrnn from train_eval_test import train if torch.cuda.is_available(): device cuda elif torch.backends.mps.is_available(): device mps else: device cpuload_dataset、textrnn 是同一个项目目录下的模块直接 import 就能用里面写好的函数和模型类from train_eval_test import train 只导入训练函数。接着判断用哪个设备计算cuda 代表 NVIDIA 显卡mps 代表苹果芯片的显卡都没有就退回 cpu。这个 device 字符串会传给迭代器和模型决定张量在哪里运算。十一、固定随机种子np.random.seed(1) torch.manual_seed(1) torch.cuda.manual_seed_all(1) torch.backends.cudnn.deterministic True随机种子相当于随机数的起点。训练中很多环节是随机的打乱数据、初始化参数、dropout 等固定种子后每次运行结果都一致方便调试和对比实验否则每次结果都略有差异出了问题很难排查。三行分别固定 numpy、CPU 和 GPU 的种子最后一行让 GPU 上的卷积运算也走确定性路径。十二、组装数据vocab, train_data, dev_data, test_data load_dataset.load_dataset(simplifyweibo_4_moods.csv) train_iter load_dataset.DatasetIterator(train_data, 128, device) dev_iter load_dataset.DatasetIterator(dev_data, 128, device) test_iter load_dataset.DatasetIterator(test_data, 128, device)调用第二篇写好的 load_dataset 加载词汇表和三个数据集再用 DatasetIterator 把每个数据集包装成迭代器batch_size 设为 128——模型每次吃 128 条数据。十三、加载预训练向量embedding_pretrained torch.tensor(np.load(embedding_Tencent.npz)[embeddings].astype(float32)) embed embedding_pretrained.size(1) if embedding_pretrained is not None else 300np.load 读取腾讯开源的中文词向量文件npz 压缩格式取出里面的 embeddings 数组astype(float32) 转成 32 位浮点最后用 torch.tensor 转成 PyTorch 张量。embed 取向量的维度 size(1)比如腾讯向量是 200 维如果没加载到预训练向量就退回默认 300 维。这个值对应第三篇 Model 里的 embed 参数。十四、定义类别并创建模型class_list [喜悦, 愤怒, 厌恶, 低落] num_classes len(class_list) model textrnn.Model(embedding_pretrained, len(vocab), embed, num_classes).to(device)class_list 列出四种情绪和标签 0~3 一一对应之后会作为分类报告每一行的名字。按第三篇 Model 的签名传入四个参数预训练向量、词汇表大小、向量维度、类别数最后 .to(device) 把模型整体搬到 GPU 上。到这里所有零件全部就位。十五、开始训练train(model, train_iter, dev_iter, test_iter, class_list)调用第四篇的 train 函数训练、验证、早停、保存最优模型、最终测试一气呵成。运行 main.py就等于把整个项目跑了一遍。小结整个项目至此全部讲完vocab_create.py 建词汇表 → load_dataset.py 数字化并分批 → textrnn.py 搭建模型 → train_eval_test.py 训练评估 → main.py 总调度。main.py 做的事就是选设备、定种子、组装数据、加载预训练向量、创建模型、开训把各模块像零件一样拼成一个完整的深度学习情感分类项目。
返回列表