NLP课程大作业实战:基于BiLSTM与Attention的情感分析系统搭建与改造

发布时间:2026/9/8 9:03:36
NLP课程大作业实战:基于BiLSTM与Attention的情感分析系统搭建与改造 简介一份南开大学计算机学院自然语言处理课程期末大作业资源面向需要完成NLP课程项目与实验的本科高年级学生。资源采用Python实现并附带可直接使用的数据集覆盖数据预处理、模型训练、验证与结果输出等完整流程。压缩包内共125个文件以txt数据文件、py代码脚本、sh运行脚本为主另有json标注与数据划分文件、md说明文档及csv结果表其中json文件包含wiki、semeval、nyt、pubmed等多源语料的训练与验证集可支持多种任务对比。整个资源包约213.63MB解压后即可按脚本顺序复现实验。目前已有201人学习参考适合希望获得可运行NLP代码与标准数据集、用于课程设计或期末项目的学生快速上手。1. 这份资源包到底有什么拆包看目录别急着双击运行拿到这个标题的时候第一反应是“又一个课程资源包”但细看之后发现这事值得展开聊聊。对于南开大学计算机学院的自然语言处理课程大作业来说最尴尬的处境往往是课上听懂了词向量、RNN、注意力机制的来龙去脉但坐到电脑前打开PyCharm却不知道一行代码该从哪里开始写。这份zip包的定位实际上就是把“从零写一个NLP大作业”这件事压缩成了“下载、解压、看代码、改代码、跑通、写报告”六个步骤。先把包里最核心的东西列出来。整个资源包解压之后目录结构大致是这样的NLP_Course_Project/ ├── data/ │ ├── train.csv │ ├── test.csv │ ├── val.csv │ └── stopwords.txt ├── src/ │ ├── data_loader.py │ ├── preprocess.py │ ├── model.py │ ├── train.py │ ├── evaluate.py │ └── predict.py ├── checkpoints/ ├── output/ ├── requirements.txt └── README.mddata目录下是整理好的标注数据集src目录里是按职责拆分的Python模块checkpoints用于保存训练好的模型权重output存放预测结果和评估报告。README.md是最先应该打开的文件——但根据我帮人看项目代码的经验大多数同学第一件事是双击train.py然后被红色的报错吓退。所以这篇文章先从README开始讲。这份代码的数据集是典型的文本二分类——情感分析即判断一段文本表达的是正面还是负面情感。训练集大约两万条样本验证集五千条测试集五千条文本来源是电商评论和微博评论混合体。数据集已经做了初步清洗每条样本是“标签\t文本内容”的格式标签用0和1表示负面和正面。对于课程大作业来说这个规模已经完全够用——模型不需要在几百万条数据上跑几十个小时单卡训练十几分钟就能看到明显的收敛趋势。requirements.txt里面固定了版本号这一点值得特别表扬。很多课程代码出问题80%的情况是依赖版本冲突numpy版本太新某个旧API被移除torchtext版本差异导致vocab构建报错。这份依赖文件用的都是相对保守的版本组合后面会专门列出对照表。2. 跑通代码的完整手记环境准备、依赖安装与首次训练避坑2.1 环境配置的具体版本对照在跑NLP课程项目时环境装不好是最大的劝退因素。我见过太多人倒在第一步——conda换源、pip install报错、torch装成了CPU版、Jupyter里面kernel始终连不上。这份项目在requirements.txt里给出了明确的依赖清单我的建议是严格按这个列表安装不要自作聪明升级到最新版。对照关系整理成一个表格方便你检查自己机器上的环境依赖包版本要求作用说明Python3.8~3.10过旧的3.6语法不支持过新的3.12部分包没有预编译wheeltorch1.13.1模型构建与训练核心框架torchtext0.14.1文本数据加载、分词、词汇表构建numpy1.24.3数值计算底层支持pandas1.5.3表格数据读取和处理scikit-learn1.2.2评估指标计算准确率、F1等jieba0.42.1中文分词也可替换为hanlp或其他分词器tqdm4.65.0进度条显示torchtext的版本坑尤其值得多说一句。torchtext 0.14.1虽然不算新但它和torch 1.13.1的配合非常成熟TextClassification的API还能正常使用。一旦你把torchtext升到0.16以上torchtext.datasets.TextClassificationDataset就会被移除整个数据加载层代码全部要重写。而代码里如果用的是旧版API这个问题简直防不胜防。2.2 从解压到跑通训练的主流程第一步是用conda创建全新的虚拟环境避免和系统Python相互污染。依次执行以下命令conda create -n nlp_course python3.9 conda activate nlp_course pip install -r requirements.txt如果在安装过程中遇到下载超时可以手动指定国内镜像源pip install torch1.13.1 -i https://pypi.tuna.tsinghua.edu.cn/simple注意torch和torchtext两个包体积很大下载时要有点耐心。装完之后可以用一条命令验证GPU是否被正确识别import torch print(torch.cuda.is_available())如果输出是True说明GPU可用训练速度会有质的提升如果是False但机器明明有显卡多半是装了CPU版的torch回退到GPU版即可。接下来在项目根目录下直接运行训练脚本cd NLP_Course_Project python src/train.py首次运行会自动读取data目录下的三个csv文件构建词汇表、生成数据加载器然后开始在训练集上迭代。训练过程中终端会打印每个epoch的loss和accuracy。如果你是在CPU上跑一个epoch大约耗时1到2分钟总共训练5到10个epoch就能达到80%以上的准确率。训练结束后checkpoints目录下保存了准确率最高的那一份模型权重。2.3 常见的启动报错一览跑的过程中最常遇到的报错有四种我把表面现象、实际原因和解决办法列出来你看到红色字体不要慌对着表排查报错信息摘录真实原因解决办法ModuleNotFoundError: No module named torchtext.datasetstorchtext版本过新旧API被移除卸载重装为0.14.1AttributeError: NoneType object has no attribute split某个csv文件读取时出现空行用pandas读取后执行dropna()去掉含空行的记录RuntimeError: CUDA out of memory显卡显存不足batch_size太大将train.py中batch_size从64调到32或16UnicodeDecodeError: utf-8 codec cant decode数据文件编码不是UTF-8读取时指定encodinggbk或encodingutf-8后重新保存第二个报错遇到过的人特别多原因是原始数据集在采集时混进了几行完全为空的内容如果不处理在分词和构建词汇表那一层就会崩溃。代码里已经写了dropna的逻辑但如果后续你自己换了数据集要注意抢先做这个预处理。3. 核心代码模块逐个拆解数据加载、预处理、模型构建的按需改造逻辑3.1 data_loader.py数据读入与batch化这份代码的数据加载层写得比较规矩核心类继承自torch.utils.data.Dataset。初始化时接收pandas的DataFrame配合__getitem__方法完成单条样本的索引。最关键的是和torchtext的vocab联动——代码里py文件叫data_loader.py主要职责有两个第一读入原始文件后完成标签列和文本列的分流第二将文本从字符串形式的词语序列映射为数字索引序列。这里有一个容易被忽视的设计在构建词汇表时代码刻意保留了一个unk槽位所有在词汇表中找不到的词都会被映射到这个索引。这样做的好处是模型在预测阶段遇见训练时没见过的词不会直接抛KeyError而是用这一个统一的未知标记来兜底。很多课程作业里的代码并没有这个处理一看到OOVout-of-vocabulary词就出问题暴露的是设计思路上的不足。3.2 preprocess.py中文分词的取舍与停用词表preprocess.py的逻辑不复杂核心两步分词和去停用词。分词用的是jieba默认全模式和搜索引擎模式都没开用的是精确模式。精确模式分出来的词以名词、动词等实词为主对于情感分类任务来说实词恰恰是情感倾向的主要载体所以效果是够用的。停用词表里收录了几百个常见的中文停用词包括“的”“了”“就”“都”“吗”“嗯”这类语气助词和虚词。去掉它们的主要原因是这些词在几乎所有文本里都会出现携带的判别信息极低占比却很高加入词向量矩阵后反而会增加维度噪声。如果你想替换成自己的停用词表保持每行一个词即可编码必须是UTF-8否则读取的时候会出现乱码或者报错。另外分词后的文本用一个空格连接成字符串这样下游的torchtext词带化操作不需要再额外做空格转列表的步骤。3.3 model.py从Embedding到BiLSTM再到Attentionmodel.py是整个项目的核心实现了一个用于情感分类的循环神经网络模型结构上是经典的“词嵌入层 双向LSTM Attention 全连接输出层”。我来把每层的维度流梳理一下这部分弄懂了模型内部的张量形状变化就都清楚了。输入张量的形状是[batch_size, seq_len]其中seq_len是当前batch内的最大句子长度。句子长度不足的部分通过pad补齐Embedding层的输入是词典大小vocab_size嵌入维度设为128所以经过这一层后张量变成[batch_size, seq_len, 128]双向LSTM的隐藏层维度设为256因为是双向所以每个时间步的输出维度是512即前向256拼上反向256Attention层对LSTM输出的512维向量做加权求和。这里不是简单的mean pooling而是学出一组权重让模型自己决定哪些时间步的信息对分类更重要最后接一个Linear层把512维映射到2维对应正面和负面两个类别。关于Attention机制的细节可以用一个通俗的类比帮助理解。把LSTM输出的每个时间步想象成一个人对整句话的一个“分词理解片段”Attention不是说所有片段都同等重要而是像人类读者回看一句话时“诶重点在那个词上”。它通过可学习的参数给每个时间步学一个重要性分数然后做带权重的求和压缩成一个固定长度的向量。这在长文本场景中比直接取最后一个时间步的hidden state稳妥得多因为长文本的关键信息往往分散在不同位置并不是只存在句末。model.py里还有一个值得关注的参数叫dropout默认取0.5。它会在训练时随机屏蔽一部分神经元的输出让模型不能过度依赖某一小部分路径是一项非常有效的防过拟合手段。在训练时开启在预测时自动关闭这是PyTorch中model.train()和model.eval()切换做的事。3.4 train.py和evaluate.py训练循环与指标统计train.py里的训练循环就是标准的PyTorch套路前向传播、计算损失、反向传播、优化器更新、每个epoch结束在验证集上跑一次评估。损失函数用的是nn.CrossEntropyLoss()优化器用的是Adam初始学习率设为0.001。这里有个小细节——学习率并不是全程固定代码在训练到大约第六个epoch时自动做了学习率衰减乘以0.1。这么做的原因是训练后期Loss曲线通常会进入平台期如果学习率不降下来参数会在最优解附近持续震荡难以收敛到更稳的点。evaluate.py只做了两件事加载checkpoints里保存的最佳模型然后跑一遍测试集输出准确率、精确率、召回率和F1值。对于课程报告而言这四个指标比单看准确率要有说服力得多——尤其是在正负样本不均衡的情况下准确率是会被多数类“撑起来”的F1值更能反映模型在少数类上的真实水平。4. 把那套代码改造成自己的作品三大方向与不被发现的修改思路4.1 思路一换损失函数或加正则项从训练策略层面拉开差异课程大作业最忌讳的就是原封不动交一份。哪怕代码跑通了老师打开十几个人的压缩包一看全是同一个结构分数自然就低。但也不是非要推翻重写从训练策略上做调整一样能做出差异化。一个低成本的改造是把Adam换掉换成AdamW。AdamW是在Adam基础上给权重衰减单独做了解耦实测在Transformer类模型上效果更好在LSTM上也能减少过拟合。改动只需要把train.py里的torch.optim.Adam改成torch.optim.AdamW然后把参数里的weight_decay从默认的0改成0.01。另一个方案是加上学习率预热warmup。在前几百步训练中让学习率从0线性上升到预设值可以避免模型在初期因为过大的步长而震荡尤其是使用更大的Embedding维度时这个Trick很有效。4.2 思路二替换词向量初始化引入BERT Embedding而不是随机初始化基线代码中Embedding层的权重是随机初始化的模型需要在训练过程中自己学出词语的语义向量。这种做法不是不行但需要更多的训练数据和时间才能学到比较合理的空间分布。一种更优雅的改法是使用预训练的中文词向量——比如腾讯开源的词向量或者搜狗新闻词向量——在初始化Embedding层的时候加载进去再设置require_gradTrue让它在训练中微调。这样改动的核心原因是初始化方式对收敛速度和最终效果影响巨大。随机初始化的词向量在空间中呈现各向同性词与词之间的方向和距离都没有语义含义预训练词向量则是从海量语料中通过共现信息学出来的加载进来之后模型相当于从一个人已经会了大部分词汇的基础上开始学习如何分类任务而不是从零开始摸索词义。改动方式不复杂在model.py的Embedding层初始化之前读取词向量文件和vocab建立映射关系逐行初始化权重矩阵即可。4.3 思路三换数据集或调整任务让场景本身与原始压缩包区隔开情感分析是NLP课程的“万金油”选题使用的样例会高度雷同。如果你想避开全员撞车可以把数据集整体替换成别的高质量中文语料而代码结构保持不动。比如任务改成“新闻主题分类”标签从“正面/负面”改成“体育/财经/科技/娱乐/时政”五个类别那么需要改动的只有数据集的加载部分标签列不再是0和1而是0到4的整数模型输出层从nn.Linear(512, 2)改成nn.Linear(512, 5)评估指标准确率照算但报告里要额外加一个混淆矩阵展示多分类下每两类的混淆情况。改动的量并不大但呈现在报告里的效果就是完全不同的任务。老师在阅读时看到的不再是“又一个电商评论情感分析”而是“这个学生对任务理解更深知道如何把模型套用到新场景”。后者的印象分会好很多。4.4 思路四加一份模型对比实验比任何参数调优都有说服力课程大作业报告里价值密度最高的内容永远是消融实验和对比实验。原项目的模型是BiLSTMAttention如果你在报告里加一个TextCNN作为对照模型两组模型在完全相同的训练集、验证集和测试集下比较准确率和F1值那么这篇报告的内容厚度会一下子上去。TextCNN不需要很大的修改——同样在model.py里写一个新的类用一层二维卷积和最大池化提取文本局部N-gram特征最后拼接到全连接层。两种模型跑完后把两张测试集的效果表整理成一行数据放进报告里分别讨论它们在短文本和长文本上的表现差异。这类实验属于“小成本、大回报”——代码改动量可控但呈现出来的工作量和思考深度完全不同。5. 查重与原创性那些事作业代码怎么改才合理、能过审5.1 注释风格与变量名改造避免一眼看出是模板代码代码查重不只看你用了多少行和老师用词对齐还看变量命名、注释习惯、代码段落的组织方式。原模板到处是train.csv、label、text这类通用命名。如果只是把文件复制一遍改名交上去遇到稍微懂行的老师一眼就能看出来。一个比较省力且合理的做法是全项目代码通读一遍把显眼的模板变量名改写为自己的风格。比如data_loader.py里load_data()函数改名为read_corpus()model.py里Classifier类改名为SentimentModel。虽然逻辑结构没变但代码的“指纹”特征发生了明显变化。更关键的是在改动过程中你必须逼着自己逐行理解代码含义——这本身就是课程设置大作业的真正目的。另外建议把代码中所有的英文注释按照自己的语言习惯重写一遍。模板中的英文注释生硬且前后风格一致改成中文注释并且用自己的表达方式描述每一行在干什么既提升了原创性也方便答辩时向老师解释程序逻辑。5.2 训练日志与中间结果保留能证明“你自己跑过”的证据很多课程要求最终提交的压缩包里包含训练过程的截图或者评估结果。这份项目的output目录天然会保存预测结果和评估指标建议至少保存三样东西训练过程中终端打印的日志文件能从初次迭代到最后一次epoch完整展示loss下降趋势测试集评估的准确率和F1值以及打印出来的带标签的若干预测样例训练曲线图。代码里没有画图脚本可以自己加十几行matplotlib代码把训练loss和验证准确率的曲线画出来存成png。报告里放一张清晰收敛的曲线图比写十行文字描述都有说服力。5.3 数据集的合规使用提示最后说一句关于数据的底线问题。压缩包里附带的数据集来自公开的电商评论和微博评论主要用于教学和学术研究。使用时应遵循几个原则不做二次商业分发不用于任何涉及具体个人隐私的分析任务不把原始评论数据重新发布到公开互联网。如果你的课程作业需要公开发布或者提交到公开代码托管平台建议只上传代码不要上传原始数据集文件在README中说明获取方式和出处即可。6. 我个人用下来的感受与额外的小建议如果把这套代码当作学习材料来精读而不是当作一个黑箱在跑它的价值还是很高的。整个工程从数据读取到模型训练再到评估一个典型的NLP文本分类流程覆盖得非常完整而且模块之间的耦合度低替换任何一环都不需要改动其他部分。这和真实工业项目里的代码组织方式已经很接近了。一个小建议把src目录下的每个文件都打开逐行读一遍遇到不清楚的函数用print把中间张量的shape打出来看一看。比如在model.py的forward中临时加一行print(lstm_out.shape)你会更直观地感受到经过Embedding和LSTM之后数据维度的变化过程。这是最快建立起深度学习模型内部直觉的方法。最后再分享一个小技巧。训练完成后predict.py脚本里自带了一个预测函数可以用来对任意输入句子做情感判断。但如果你的课堂答辩要求现场演示建议提前准备好三到五个典型句子跑出结果比如“这家餐厅的服务态度很好菜品也很新鲜”和“等了半小时还没上菜体验太差了”。答辩现场直接演示输入新句子并输出预测标签会给评委留下相当不错的印象。本文还有配套的精品资源点击获取