LSTM情感分析实战:从数据预处理到模型训练避坑指南 简介一套基于长短期记忆网络LSTM的中文文本情感分析毕业设计项目完整覆盖数据预处理、jieba分词、模型训练、评估与预测流程。数据集包含积极和消极中文文本各约八千条代码已测试通过直接运行即可复现训练结果。项目同时提供预训练模型、词向量、环境配置说明、效果预览图等既适合计算机专业学生完成毕业设计或课程设计也可作为教师授课和企业员工学习深度学习与自然语言处理的参考。压缩包共十一个文件涵盖程序脚本、模型权重、词向量、配置文件与说明文档整体约6.63MB便于下载后快速对照学习。目前已有七十九人浏览学习完整展示了从jieba分词到LSTM搭建的可执行思路并在README中注明使用方式与注意事项适合希望快速上手中文情感分析、搭建文本分类模型的学习者。1. 打开这份LSTM情感分析项目之前先想清楚你要的是什么一份号称“高分毕业设计”的LSTM文本情感分析项目正负面各8000条数据有代码、文档、模型、数据集这几乎是本领域最标准的选题配置。很多人在拿到这类项目后的第一反应是直接跑通、截图、写报告但真正让答辩老师认可的从来不是“模型能跑”而是“你清楚每一层在干什么数据为什么这样处理失败的时候怎么定位”。本文会在把LSTM文本情感分析这条技术链路拆开讲透的前提下给你一份可以直接照着复现的完整方案包含数据预处理、模型搭建、训练调参、避坑记录和进阶验证既适合拿来做毕业设计主体也适合作为情感分析方向的第一份动手实践。我默认你至少装好了Python 3.8以上版本并且会用pip安装依赖包如果还没准备好环境顺手把numpy、pandas、torch装上即可具体版本要求我会在对应小节说明。2. 基本原理与数据准备为什么是LSTM以及正负面各8000条怎么落到磁盘2.1 LSTM建模文本情感的基本逻辑从词向量到隐状态文本情感分析本质上是一个文本分类任务输入是一段文本输出是正面或负面。做这个任务最早期的方案是词袋模型加朴素贝叶斯或SVM靠统计词频来判断情感倾向。这类方法的局限很明显它丢掉了词语之间的顺序关系遇到“不好看”和“看不不好”这种语义反转或语序敏感的表达很容易翻车。循环神经网络RNN的引入解决了“按顺序读词”的问题而LSTM是RNN的一个变体专门用来缓解长序列训练中的梯度消失和梯度爆炸。LSTM的核心是一个记忆单元和三个门遗忘门决定上一时刻的细胞状态保留多少输入门决定新信息写入多少输出门决定当前隐状态输出多少。每一步读入一个词向量结合上一步的隐状态和细胞状态更新记忆最终把整个句子的信息压缩到最后一步的隐状态里。用这个隐状态接一个全连接层做二分类就是最经典的LSTM文本情感分析模型结构。这里需要先明确一个观点数据规模只有正负面各8000条属于典型的小数据集。LSTM在这种规模下不会展现出碾压BERT之类的优势但它的优势在于结构简单、训练快、可解释性强而且代码量少适合在毕业设计里把“原理—实现—实验—分析”讲完整。如果你在答辩中被问“为什么不用BERT”一个诚实的回答是BERT在这个数据规模下容易过拟合且训练成本高LSTM已经能达到可接受的准确率同时能清晰展示序列建模的过程。这个回答比吹嘘LSTM全面优于Transformer更让人信服。2.2 构建正负面各8000条的数据集采集、清洗与标注正负面各8000条听起来数据量不大但你要考虑的是这16000条文本是怎么来的、质量是否可靠。常见的数据来源有三种公开情感数据集如中文酒店评论、电商评论、爬取社交媒体评论后人工标注、用现成的评论数据做二次清洗。无论你用哪种方式最终落在磁盘上的格式都应该是一致的。我一般会把它整理成CSV文件两列一列是text一列是labellabel用1表示正面、0表示负面。这样做的好处是pandas读取后不需要额外解析文档里描述起来也直观。数据清洗是这个环节里最耗时也最容易忽略的步骤。原始评论里通常夹杂着HTML标签、URL、用户、多余的空格和换行、全角半角混乱、表情符号等问题。清洗的目标不是把文本变得越短越好而是去掉对情感判断没有帮助的噪音。以下是我常用的清洗函数直接可用在数据集构建阶段import re import pandas as pd def clean_text(text: str) - str: # 去掉HTML标签 text re.sub(r[^], , text) # 去掉URL text re.sub(rhttp[s]?://\S|www\.\S, , text) # 去掉用户和话题标签 text re.sub(r\S|#\S#, , text) # 统一全角半角全角字符转半角 text .join( chr(ord(c) - 0xFEE0) if ord(c) 0xFF00 and ord(c) 0xFF5F else c for c in text ) # 去掉多余空白 text re.sub(r\s, , text).strip() return text df pd.read_csv(raw_reviews.csv) df[text] df[text].apply(clean_text) # 去除清洗后为空的行 df df[df[text].str.len() 0] # 按label分层打乱保证正负样本在训练测试中的比例一致 df df.groupby(label, group_keysFalse).apply(lambda x: x.sample(frac1, random_state42)) df.to_csv(dataset.csv, indexFalse, encodingutf-8-sig)逻辑说明re.sub(r[^], , text)是正则去HTML标签的通用写法全角转半角的循环逻辑利用了Unicode码位偏移规律只处理0xFF00到0xFF5F范围内的字符df.groupby(label, group_keysFalse).apply(...)是按类别分组后再随机打乱目的是防止原始数据里正负样本顺序集中导致训练时类别分布失衡。参数random_state42是为了复现打乱顺序答辩时如果你被要求重新跑一遍结果一致会让实验更有说服力。清洗之后另一个关键步骤是检查重复样本和矛盾标注。文本情感数据里常见的情况是同一条评论出现多次或者内容接近但标注相反的样本。重复样本不处理的话训练集和测试集之间可能出现数据泄漏得到的准确率虚高。处理方式很简单对text列做去重保留每条文本第一次出现的记录。去重后如果样本不足8000条优先补充数据而不是靠复制已有样本来凑数。复制样本会让模型学到的分布失真答辩时一旦被追问训练集和测试集的构造方式这个问题很难解释。2.3 数据预处理分词、构建词表、生成训练样本数据清洗完只是第一步模型不能直接吃字符串需要把文本转成数值。中文文本预处理的常见做法是先分词再基于分词结果构建词表把每个词映射成一个整数ID最后把一条文本转成一个整数序列。分词工具有很多选择jieb、pkuseg、THULAC都能用。考虑到毕业设计场景下的复现稳定性和安装便利性我推荐使用jieba它安装简单、纯Python实现、无需额外模型文件。下面给出一段完整的分词加词表构建代码import jieba import json from collections import Counter def tokenize(text: str) - list: # 精确模式分词适合情感分析场景 return list(jieba.cut(text.strip())) # 读取已清洗的数据 df pd.read_csv(dataset.csv) # 全部分词统计词频 all_tokens [] for text in df[text].tolist(): all_tokens.extend(tokenize(text)) counter Counter(all_tokens) # 保留出现次数2的词过滤低频噪音 min_freq 2 vocab {word for word, freq in counter.items() if freq min_freq} # 构建词表映射PAD0, UNK1, 其余从2开始 word2idx {PAD: 0, UNK: 1} for word in vocab: word2idx[word] len(word2idx) with open(word2idx.json, w, encodingutf-8) as f: json.dump(word2idx, f, ensure_asciiFalse) # 文本转ID序列统一截断或填充到固定长度 MAX_LEN 100 def encode_text(text: str) - list: tokens tokenize(text) ids [word2idx.get(w, word2idx[UNK]) for w in tokens] if len(ids) MAX_LEN: ids ids[:MAX_LEN] else: ids ids [word2idx[PAD]] * (MAX_LEN - len(ids)) return ids df[input_ids] df[text].apply(encode_text) df.to_pickle(encoded_data.pkl)参数说明min_freq2的意思是只保留在全部语料中出现过至少2次的词出现1次的词统一映射为UNK这样做的理由是低频词往往是错别字、专有名词或噪音保留它们会让模型去记忆不具泛化性的模式。MAX_LEN100是序列长度上限文本超过100个词就截断不足就补PAD。这里100是一个经验值你需要先统计一下数据集中文本长度的分布如果90%的文本都少于80个词那100是安全的如果你的数据源里有大量长评可能需要调到200或更高。词表构建好之后检查一下word2idx的大小。常见情况是词表规模在1万到3万之间。如果你发现词表超过5万说明min_freq设得太低如果词表不到5000说明min_freq应该调低或者数据集本身就偏小。词表规模直接影响Embedding层的参数数量词表越大模型越大训练越慢。编码完成后的数据我是用to_pickle保存的原因是df.to_csv会把列表形式的input_ids序列化成字符串下次读取还要再解析而pickle能完整保留DataFrame里的Python对象结构省去格式转换的麻烦。缺点是文件不可直接打开查看但这在毕设场景里不算问题。3. 用PyTorch实现LSTM情感分类模型搭建、训练、评估的全流程3.1 模型结构设计Embedding LSTM 全连接的标准组合数据准备好之后进入模型实现环节。我采用PyTorch实现理由一是它在学术和工程场景都是主流答辩时不容易被质疑二是代码结构清晰forward过程很好解释。模型结构选用经典的Embedding层加单层LSTM加全连接输出层。Embedding层把词ID映射成稠密向量LSTM层负责按顺序编码序列信息全连接层把LSTM最后一步的隐状态压缩成两个类别的得分。用PyTorch的nn.Module定义模型完整代码如下import torch import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_layers, num_classes2, dropout0.5): super(LSTMClassifier, self).__init__() self.embedding nn.Embedding(vocab_size, embedding_dim, padding_idx0) self.lstm nn.LSTM( embedding_dim, hidden_dim, num_layersnum_layers, batch_firstTrue, bidirectionalFalse, dropoutdropout if num_layers 1 else 0.0 ) self.fc nn.Linear(hidden_dim, num_classes) self.dropout nn.Dropout(dropout) def forward(self, input_ids): emb self.embedding(input_ids) # [batch, seq_len, embedding_dim] lstm_out, (h_n, c_n) self.lstm(emb) # lstm_out: [batch, seq_len, hidden_dim] # 取最后一层的最终隐状态 h_n[-1]形状 [batch, hidden_dim] last_hidden h_n[-1] out self.dropout(last_hidden) logits self.fc(out) # [batch, num_classes] return logits逻辑说明nn.Embedding的第一个参数是词表大小第二个参数是嵌入维度padding_idx0告诉Embedding层ID为0的位置不更新梯度始终为全零向量避免填充位引入无意义的信息。LSTM层设置batch_firstTrue后输入形状为[batch_size, seq_len, embedding_dim]h_n的形状是[num_layers, batch_size, hidden_dim]取h_n[-1]得到最后一层的隐状态。dropout只应用在全连接层前这是LSTM加全连接结构里常见的做法。参数设置需要重点解释embedding_dim一般取100或200太小表示能力不足太大在小数据集上容易过拟合hidden_dim取128是一个折中值计算量适中且表达力足够num_layers取1单层LSTM在16000条样本的规模上已经足够捕获序列依赖层数加深不一定提升效果还会显著增加训练时间。如果你坚持用两层LSTMdropout参数必须大于0否则两层之间没有正则化训练集准确率会虚高而测试集表现很差。3.2 数据加载用DataLoader批量喂给模型定义好模型之后需要把之前保存的编码数据加载成PyTorch的Dataset和DataLoader。注意encoded_data.pkl里存的input_ids是Python列表需要转成torch.LongTensor。代码如下from torch.utils.data import Dataset, DataLoader from sklearn.model_selection import train_test_split class SentimentDataset(Dataset): def __init__(self, df): self.input_ids df[input_ids].tolist() self.labels df[label].tolist() def __len__(self): return len(self.labels) def __getitem__(self, idx): input_ids torch.LongTensor(self.input_ids[idx]) label torch.LongTensor([self.labels[idx]]) return input_ids, label # 数据划分训练集 80%测试集 20% df pd.read_pickle(encoded_data.pkl) train_df, test_df train_test_split(df, test_size0.2, stratifydf[label], random_state42) train_dataset SentimentDataset(train_df) test_dataset SentimentDataset(test_df) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse)参数说明stratifydf[label]是分层抽样保证训练集和测试集中正负样本比例都和原始数据一致这个参数在分类任务里必须设置否则随机划分可能导致某一类别在测试集里占比异常评估指标失真。batch_size64是一个偏保守的设置如果你的显存或内存充足调到128能加快训练如果训练时频繁内存溢出降到32。shuffleTrue只用在训练集让每个epoch里样本顺序不同避免模型学到样本顺序带来的虚假模式测试集不需要打乱保持顺序方便后续分析错误样本。3.3 训练循环损失函数、优化器与评估指标模型和数据都准备好之后进入训练环节。情感二分类任务默认使用交叉熵损失函数优化器选择Adam学习率从1e-3开始试。训练循环的完整代码如下import torch.optim as optim from sklearn.metrics import accuracy_score, f1_score device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMClassifier( vocab_sizelen(word2idx), embedding_dim100, hidden_dim128, num_layers1, num_classes2, dropout0.5 ).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) num_epochs 20 for epoch in range(num_epochs): model.train() total_loss 0.0 for input_ids, labels in train_loader: input_ids input_ids.to(device) labels labels.squeeze(1).to(device) optimizer.zero_grad() logits model(input_ids) loss criterion(logits, labels) loss.backward() optimizer.step() total_loss loss.item() # 每个epoch结束在测试集上评估方便观察是否过拟合 model.eval() all_preds [] all_labels [] with torch.no_grad(): for input_ids, labels in test_loader: input_ids input_ids.to(device) logits model(input_ids) preds torch.argmax(logits, dim1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.squeeze(1).cpu().numpy()) acc accuracy_score(all_labels, all_preds) f1 f1_score(all_labels, all_preds) print(fEpoch {epoch1}/{num_epochs}, Loss: {total_loss/len(train_loader):.4f}, fTest Acc: {acc:.4f}, F1: {f1:.4f})这段代码有几点需要说明。第一labels.squeeze(1)是因为__getitem__返回的label形状是[1]不压缩的话CrossEntropyLoss会报维度不匹配。第二每个epoch结束后都在测试集上做一次完整评估这个习惯非常重要它能让你看到训练过程中测试准确率的变化曲线判断模型是欠拟合还是过拟合。第三评估时要写torch.no_grad()否则PyTorch会为推理过程构建计算图白白浪费内存和时间。3.4 训练效果的判断损失下降不代表模型好训练跑完之后不要只看最后一个epoch的准确率。把每个epoch的损失和准确率打印出来观察趋势如果训练损失持续下降但测试准确率在第8个epoch开始不升反降说明模型开始过拟合如果训练损失在头几个epoch就降得很慢大概率是学习率偏大或数据预处理环节有bug。一个很常见的现象是第一个epoch结束测试准确率就已经到了90%以上这时候不要高兴太早。你需要检查一下是不是数据泄漏了训练集和测试集里是否有重复文本、是否用了同一批样本做过数据增强、词汇表的构建是否在划分数据之前用了全量数据。如果是后者词表本身并不算泄漏但如果你的统计特征来自全量数据模型会从测试集的分布里获取信息。训练好的模型要保存下来后续预测和答辩演示都需要用到torch.save(model.state_dict(), lstm_sentiment_model.pth)同时建议把词表word2idx.json放在同一个目录里。模型文件只存了参数权重不包含词表恢复模型时必须先加载词表才能正确编码输入文本。这个问题在答辩演示时经常出问题换个机器跑起来报错十有八九是忘了拷贝词表文件。4. 避坑指南LSTM情感分析项目的6个典型翻车现场4.1 样本不均衡引起的“虚假高分”现象训练完成后测试集准确率高达97%但你看一下分类报告发现正样本的召回率和F1都很低模型几乎把所有样本都预测成负面。原因正负面各8000条虽然在总量上均衡但如果你在2.2节清洗数据时没有按类别检查清洗后的数量可能出现负面评论更容易清洗、正面评论被误删的情况。更常见的原因是训练时没有分层抽样训练集里正面样本远少于负面样本。解决在2.3节划分数据集时强制使用stratifydf[label]并打印train_df[label].value_counts()确认分布。如果实际数据本身就不均衡可以考虑在CrossEntropyLoss里设置class_weight参数给少数类更高的权重。用混淆矩阵替代准确率作为主要评估指标。4.2 文本长度截断导致关键信息丢失现象测试集上某些短文本预测正确但一旦评论长度超过MAX_LEN预测结果明显变差。检查被截断的样本发现情感关键词正好出现在截断位置之后。原因MAX_LEN100是从整体分布统计出来的但你没有看被截断样本的比例。如果截断样本占了总样本的20%那说明100这个值偏小。更隐蔽的问题是截断策略是直接从头截100个词尾部的信息全部丢弃。解决先统计文本长度分布df[text].str.len().describe()把MAX_LEN设为90分位数对应的长度而不是拍脑袋定100。如果某些样本实在太长也可以采用头尾各保留一部分的策略但这会引入更多代码复杂度我建议优先调大MAX_LEN。4.3 训练损失不下降梯度爆炸或学习率过大现象训练损失在几个epoch后变成NaN或者Loss值忽大忽小完全没规律模型完全学不到东西。原因LSTM虽然比原生RNN抗梯度消失但输入数据没有归一化或者学习率过大依然可能触发梯度爆炸。更常见的原因是Embedding层初始化产生的梯度过大在训练初期直接让权重崩掉。解决把学习率从1e-3降到1e-4重新试在模型训练循环里加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)限制梯度的最大范数这是LSTM训练的标准操作。如果你用了预训练词向量检查词向量加载后是否需要缩放默认的均匀分布初始化在数据量小时反而是最安全的。4.4 过拟合训练集准确率接近100%测试集只有75%现象训练集的损失随着epoch增加一路下降直到接近0但测试集准确率在第5个epoch之后就开始波动甚至下降。原因16000条数据对单层LSTM来说规模不大模型的参数数量明显多于数据量很容易记住训练数据里的噪音。dropout0.5在全连接层前加了正则化但LSTM层本身没有dropout单层结构也不支持nn.LSTM内部启用dropout。解决把dropout从0.5提到0.7全连接层前和输出层之间多一层nn.Linear(hidden_dim, hidden_dim)再接ReLU激活函数让模型容量适度增加但正则化更强。或者改用早停法保存每个epoch在测试集上准确率最高的模型状态训练结束后加载那个最优权重而不是用最后一个epoch的权重。早停的实现代码很简洁用best_acc记录历史最优达到最优时保存权重即可。4.5 复现结果对不上随机种子没固定现象同一个代码在答辩演示时跑出来的准确率和实验记录里的差了一大截损失曲线也完全对不上。原因PyTorch、Python的random库、NumPy各自有独立的随机状态我只设置了一个random_state但没固定所有随机源。DataLoader的shuffle、模型初始化、分词顺序都会引入随机性。解决在训练脚本最前面固定所有随机种子import random import numpy as np def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True注意cudnn.deterministic True会让CuDNN使用确定性算法速度稍慢但结果可复现。这个设置是答辩前必须做的否则实验结果无法复现会被视为学术不严谨。4.6 中文文本分词引入的噪声现象模型在训练集上表现正常但把训练好的模型拿去做实测时对口语化文本、表情符号夹杂的评论预测效果很差。原因模型学到的词表是基于训练数据分词的测试时遇到词表里没出现过的词全部映射为UNK。如果训练数据是规范的电商评论而实测数据里有大量“哈哈哈”“yyds”这类网络热词分词后这些词全部落入UNK模型看到的信息量大幅减少。解决在构建词表时把min_freq1纳入所有出现过的词虽然会让词表变大、过拟合风险增加但能缓解UNK过多的问题。另一个更推荐的做法是在预测前对输入文本做一次emoji和网络用语映射把“yyds”替换成“很好”“绝绝子”替换成“喜欢”这种简单规则能显著提升实际场景的预测效果且不需要改动模型。5. 让项目从“能跑”到“能答辩”的进阶验证与部署技巧5.1 用训练好的模型预测单条新文本训练和评估跑通之后你的项目已经具备基本完整性。但答辩现场最常遇到的提问是“你现场预测几条数据给我看”。如果只靠训练代码里的测试循环你无法方便地输入任意文本并得到结果。所以封装一个预测函数是必需的import json import jieba import torch def predict_sentiment(text: str, model, word2idx, max_len100, devicecpu): model.eval() # 与训练时相同的预处理流程 text clean_text(text) tokens list(jieba.cut(text.strip())) ids [word2idx.get(w, word2idx[UNK]) for w in tokens] if len(ids) max_len: ids ids[:max_len] else: ids ids [word2idx[PAD]] * (max_len - len(ids)) input_tensor torch.LongTensor([ids]).to(device) with torch.no_grad(): logits model(input_tensor) prob torch.softmax(logits, dim1) pred torch.argmax(logits, dim1).item() return { label: 正面 if pred 1 else 负面, positive_prob: round(prob[0][1].item(), 4), negative_prob: round(prob[0][0].item(), 4) } with open(word2idx.json, r, encodingutf-8) as f: word2idx json.load(f) model LSTMClassifier(...) model.load_state_dict(torch.load(lstm_sentiment_model.pth, map_locationcpu)) print(predict_sentiment(这家餐厅的菜非常好吃服务也很周到, model, word2idx))这里输出正负概率而不只是类别是一个答辩加分点。你可以解释模型先输出两个类别的得分用softmax转换成概率分布不仅告诉用户结果还能给出置信度。比如一条样本预测为正面但positive_prob只有0.55说明模型不太确定如果positive_prob达到0.98说明特征非常明确。这样的细节能体现你对模型输出做过深入思考。5.2 用注意力可视化解释模型为什么预测这个结果LSTM最大的短板是黑匣子特性——输入一段文本输出一个标签中间发生了什么很难解释。答辩老师非常喜欢追问“模型凭什么认为这句话是负面的”。如果你答不上来分数会打折扣。常见的做法是加一个基于LSTM隐状态的注意力机制在forward过程中计算每个时间步的隐状态对最终分类的贡献权重。把权重最大的几个词打印出来就能看到模型重点关注的词是什么。这一节我强烈建议实现代码只需要在原有模型上做小幅修改class AttentionLSTMClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_classes2, dropout0.5): super(AttentionLSTMClassifier, self).__init__() self.embedding nn.Embedding(vocab_size, embedding_dim, padding_idx0) self.lstm nn.LSTM(embedding_dim, hidden_dim, batch_firstTrue) self.attention nn.Linear(hidden_dim, 1, biasFalse) self.fc nn.Linear(hidden_dim, num_classes) self.dropout nn.Dropout(dropout) def forward(self, input_ids): emb self.embedding(input_ids) lstm_out, _ self.lstm(emb) # [batch, seq_len, hidden_dim] attn_scores self.attention(lstm_out) # [batch, seq_len, 1] attn_weights torch.softmax(attn_scores, dim1) # 对序列维度归一化 # 加权求和得到句子向量 context torch.sum(lstm_out * attn_weights, dim1) # [batch, hidden_dim] out self.dropout(context) logits self.fc(out) return logits注意力机制在这里的作用是替代“只取最后一步隐状态”的做法。最后一步隐状态要求模型把整个句子的信息压缩到一个向量里长句子容易丢失开头信息注意力机制则是把所有时间步的隐状态按权重相加权重由模型自己学习可解释性更强。训练完成后对一条新文本同时输出预测结果和attn_weights最高的前5个词这段展示在答辩时会让老师直接看到模型的判断依据。5.3 把模型封装成可演示的推理接口如果毕设要求里包含系统展示可以做一个最简单的命令行交互或Flask接口。不用做前端一个input()循环足够演示while True: text input(请输入评论) if text exit: break result predict_sentiment(text, model, word2idx) print(f预测结果{result[label]}正面概率{result[positive_prob]}负面概率{result[negative_prob]})用这种方式演示的好处是交互直观老师可以随意输入测试文本你能现场看到预测效果。我建议准备几条测试文本备用一条包含明显情感词的正面评论一条用反问或反讽表达的负面评论一条夹杂网络热词的口语化评论。第一条用来展示模型正常能力第二条用来展示模型在语义反转上的不足第三条用来展示UNK带来的影响。提前展示模型的不足反而比只展示完美结果更可信也能顺势说出未来的改进方向比如引入BERT、加入更多训练语料、优化分词策略。做项目的过程中我的个人习惯是每跑通一个阶段就做一次记录数据清洗后各类样本数量、词表大小、每个epoch的准确率和损失。这些随手记录在写毕业论文的实验章节时能节省大量时间不用回头重新跑实验。情感分析这个方向本身不新LSTM也是一个成熟的模型但能把数据、模型、训练、评估、演示每个环节都讲清楚就已经是一份及格的毕业设计。希望这些踩坑经验能让你的项目推进得更顺利也希望你在这个项目里不仅拿到代码更拿到一套可以迁移到其他文本分类任务的方法论。本文还有配套的精品资源点击获取