ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python基于LSTM的中文短文本情感分析源码:课程设计跑通与BiLSTM注意力改进

Python基于LSTM的中文短文本情感分析源码:课程设计跑通与BiLSTM注意力改进 简介这份资源是面向高校学生与课程设计需求者的Python情感分析实战项目基于LSTM网络实现中文短文本的正负向情感判别适合作为期末大作业、课程设计或NLP入门练手案例。压缩包共14个文件约1.96MB包含3个py源码文件、5个txt样本与说明文本、1个csv数据集、1个pt模型权重、1个md说明文档及pyc缓存等覆盖数据加载、文本分词、模型训练与测试预测的完整链路目录结构清晰下载后无需修改即可运行。目前已有245人学习下载。项目提供正负样本语料与训练数据读者可据此理解中文分词、词向量映射、LSTM建模与情感分类评估的完整流程并可直接复用代码框架替换自有数据快速完成一份结构完整、可演示、可答辩的高分作业方案。1. 一份能直接跑通的中文情感分析源码到底解决了什么课程设计周最怕的不是不会写代码而是环境配好了、数据找齐了、模型也搭起来了结果卡在中文分词和标签对齐上一卡就是两天。这份 Python 实现基于 LSTM 的中文短文本情感分析源码本质上就是把「数据加载 → 分词 → 词向量 → LSTM 建模 → 训练 → 测试」这条链路完整封在一个压缩包里下载解压后按说明跑sentiment_analysis.py就能出结果。它面向的是需要交课程设计或期末大作业的学生以及想快速验证 LSTM 文本分类流程的初学者。包里带了train_data、test_data、sample.positive.txt、sample.negative.txt和1170300418.csv说明数据是现成的不需要自己再去爬评论。适合谁适合已经装好 Python、知道 PyTorch 或 TensorFlow 基本用法、但没完整做过中文 NLP 项目的人。不适合谁不适合想直接拿它做生产级情感分析 API 的人因为短文本场景下 LSTM 的泛化能力有限后面会讲边界。2. 拆包先看结构哪些文件是入口哪些是缓存垃圾2.1 目录里真正要关心的文件拿到压缩包解压后第一眼会看到一堆.py、.txt、.csv和__pycache__。别急着全打开先按角色分类。核心入口是sentiment_analysis.py它负责模型定义和训练循环load_data.py负责读数据、分词、建词表、把文本转成索引序列test.py是推理脚本加载训练好的模型对test.txt做预测。数据侧train_data和test_data是目录里面放的是正负样本sample.positive.txt和sample.negative.txt是示例文件方便你确认格式1170300418.csv大概率是原始标注数据字段通常是「文本,标签」。label_data.pt是 PyTorch 保存的标签映射或处理后的数据张量cut.txt是分词结果缓存。readme-情感分类系统说明.txt和README.md是说明文档先读这两个能省很多时间。__pycache__里的.pyc文件是 Python 3.6 编译的字节码直接删掉不影响运行反而能避免版本不一致导致的玄学报错。.gitignore是给 Git 用的和运行无关。2.2 环境依赖与版本对齐这份源码的.pyc后缀是cpython-36说明作者当年用的是 Python 3.6。现在你本地可能是 3.8、3.9 甚至 3.11直接跑大概率会遇到两类问题一是torch版本不匹配导致load_data.cpython-36.pyc无法加载二是中文分词库的 API 变了。常见做法是新建一个虚拟环境装 Python 3.8 或 3.9然后按下面这样装依赖# 创建虚拟环境避免污染全局包 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS/Linux 激活 source venv/bin/activate # 安装核心依赖版本不要追新 pip install torch1.10.0 pip install jieba0.42.1 pip install numpy1.21.0 pip install pandas1.3.5逻辑说明torch是 LSTM 的底层框架1.10 版本对 Python 3.8/3.9 兼容性好且 API 和源码里的nn.LSTM写法一致。jieba是中文分词最常用的库0.42.1 是长期稳定版。numpy和pandas用于数据处理版本太新可能和旧代码里的np.float之类的写法冲突。参数说明如果你用 GPU把torch1.10.0换成对应 CUDA 版本的torch1.10.0cu113但这份短文本任务 CPU 也够跑不必折腾显卡驱动。提示不要直接pip install torch不指定版本最新版 PyTorch 可能移除了旧代码里用到的某些参数比如nn.LSTM的batch_first默认值没变但torch.load的map_location行为有调整。2.3 数据格式与标签对齐打开sample.positive.txt和sample.negative.txt你会看到每行一条短文本正负样本分开存放。load_data.py里的逻辑通常是先读正样本打标签 1再读负样本打标签 0然后合并打乱。1170300418.csv可能是另一种格式用逗号分隔文本和标签。这里最容易翻车的是编码问题——中文文本可能是 GBK 或 UTF-8读的时候要显式指定。# load_data.py 里常见的读取逻辑按你的实际文件调整 import pandas as pd def read_csv_data(path): # 尝试 UTF-8失败则用 GBK中文数据常见两种编码 try: df pd.read_csv(path, encodingutf-8) except UnicodeDecodeError: df pd.read_csv(path, encodinggbk) # 假设第一列是文本第二列是标签 texts df.iloc[:, 0].astype(str).tolist() labels df.iloc[:, 1].astype(int).tolist() return texts, labels逻辑说明try/except是为了兼容不同来源的 CSV。astype(str)防止文本列里混入数字被当成数值型。astype(int)确保标签是 0 和 1而不是字符串0、1。参数说明如果你的 CSV 没有表头加headerNone如果分隔符是制表符加sep\t。这一步做完打印前 5 条文本和标签确认没有错位。3. 从分词到 LSTM训练脚本里每个参数怎么调3.1 中文分词与词表构建load_data.py的核心任务是把中文句子切成词再映射成整数索引。常见做法是用jieba.lcut做精确模式分词然后过滤掉停用词和单字。词表一般保留频率最高的 N 个词比如 5000 或 10000剩下的用UNK代替。import jieba from collections import Counter def build_vocab(texts, max_vocab5000): # 分词并统计词频 all_words [] for text in texts: words jieba.lcut(text) # 过滤空白和单字短文本里单字噪声大 words [w for w in words if len(w) 1 and w.strip()] all_words.extend(words) # 按频率取前 max_vocab 个 counter Counter(all_words) vocab {word: idx 2 for idx, (word, _) in enumerate(counter.most_common(max_vocab))} # 0 给 padding1 给未知词 vocab[PAD] 0 vocab[UNK] 1 return vocab def text_to_ids(text, vocab, max_len50): words jieba.lcut(text) ids [vocab.get(w, vocab[UNK]) for w in words if len(w) 1] # 统一长度短补长截 if len(ids) max_len: ids ids [vocab[PAD]] * (max_len - len(ids)) else: ids ids[:max_len] return ids逻辑说明max_vocab5000是短文本任务的常见值太大容易过拟合太小则很多词变成UNK。max_len50覆盖大多数短评论超过 50 个词的句子很少。PAD的索引 0 在后续Embedding里要设置padding_idx0让模型忽略填充位。参数说明如果你的数据里长句多把max_len调到 100如果显存或内存紧张降到 30 也能跑但会损失信息。3.2 LSTM 模型定义与关键参数sentiment_analysis.py里定义了一个继承nn.Module的类结构通常是Embedding → LSTM → 全连接 → Sigmoid。这里有几个参数直接决定模型能不能收敛。import torch import torch.nn as nn class SentimentLSTM(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim128, num_layers1): super(SentimentLSTM, self).__init__() # padding_idx0 让填充位不参与梯度更新 self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_dim, 1) self.sigmoid nn.Sigmoid() def forward(self, x): # x: (batch, seq_len) embedded self.embedding(x) # out: (batch, seq_len, hidden)hn: (num_layers, batch, hidden) out, (hn, cn) self.lstm(embedded) # 取最后一个时间步的输出做分类 last_out out[:, -1, :] logit self.fc(last_out) return self.sigmoid(logit).squeeze()逻辑说明embed_dim128和hidden_dim128是短文本任务的甜点值再大容易过拟合再小欠拟合。num_layers1对短文本足够两层 LSTM 在几千条数据上反而难训练。batch_firstTrue让输入维度是(batch, seq_len, embed_dim)符合直觉。out[:, -1, :]取最后一个时间步因为 LSTM 的最后一个输出包含了整个序列的信息。参数说明如果数据量超过 5 万条可以把hidden_dim提到 256如果训练 loss 震荡把num_layers保持 1先调学习率。3.3 训练循环与超参数设置训练部分通常用BCELoss做二分类优化器用Adam学习率 1e-3。批次大小 32 或 64。这里要特别注意标签的形状——BCELoss要求输入和标签都是浮点型且形状一致。from torch.utils.data import DataLoader, TensorDataset # 假设 train_ids 和 train_labels 已经准备好 train_ids torch.tensor(train_ids, dtypetorch.long) train_labels torch.tensor(train_labels, dtypetorch.float32) dataset TensorDataset(train_ids, train_labels) loader DataLoader(dataset, batch_size32, shuffleTrue) model SentimentLSTM(vocab_sizelen(vocab)) criterion nn.BCELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(10): model.train() total_loss 0 for batch_x, batch_y in loader: optimizer.zero_grad() pred model(batch_x) loss criterion(pred, batch_y) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}, Loss: {total_loss/len(loader):.4f})逻辑说明shuffleTrue打乱顺序防止模型记住样本顺序。BCELoss配合Sigmoid是二分类标准组合。lr1e-3是 Adam 的常用起点如果 loss 不降降到 1e-4 再试。参数说明batch_size32适合几千条数据如果内存够可以到 64但不要超过 128短文本任务小批次反而泛化好。epoch10是观察用实际看验证集 loss 早停。注意如果训练时 loss 一直是 0.69 左右不降大概率是标签没对齐或者词表映射错了。先打印一个 batch 的batch_x和batch_y确认文本和标签一一对应。4. 跑通之后怎么验证测试脚本与指标解读4.1 加载模型做推理test.py的作用是加载训练好的模型权重对test.txt里的句子逐条预测。常见写法是先重建模型结构再load_state_dict然后对每条文本做同样的分词和索引转换。# 重建模型并加载权重 model SentimentLSTM(vocab_sizelen(vocab)) model.load_state_dict(torch.load(model.pt, map_locationcpu)) model.eval() def predict(text): ids text_to_ids(text, vocab, max_len50) tensor torch.tensor(ids, dtypetorch.long).unsqueeze(0) # 加 batch 维度 with torch.no_grad(): prob model(tensor).item() return 正面 if prob 0.5 else 负面, prob # 读取 test.txt 逐行预测 with open(test.txt, r, encodingutf-8) as f: for line in f: line line.strip() if line: label, prob predict(line) print(f{line} - {label} ({prob:.4f}))逻辑说明map_locationcpu保证在没有 GPU 的机器上也能加载。unsqueeze(0)把单条数据的形状从(seq_len,)变成(1, seq_len)因为模型要求 batch 维度。model.eval()关闭 dropout 和 batchnorm 的训练行为。参数说明如果test.txt是 GBK 编码把encodingutf-8改成gbk。prob 0.5是默认阈值如果正负样本不均衡可以调到 0.4 或 0.6。4.2 准确率、精确率、召回率怎么看如果test_data里带了标签可以算准确率。但短文本情感分析里准确率不是唯一指标。比如 100 条测试集里 80 条正面、20 条负面模型全猜正面也有 80% 准确率但召回率为 0。所以要看混淆矩阵。指标含义短文本场景下的参考值准确率预测对的占总数的比例70%85% 算正常精确率猜正面的里面真正面的比例看你对误报的容忍度召回率真正面的里面被猜出来的比例看你对漏报的容忍度F1精确率和召回率的调和平均75%85% 算合格常见做法是跑完test.py后把预测结果和真实标签对齐用sklearn.metrics.classification_report输出。如果 F1 低于 70%先检查分词是不是把情感词切碎了比如「不喜欢」被切成「不」和「喜欢」情感就反了。4.3 用cut.txt和label_data.pt做快速验证cut.txt是分词缓存label_data.pt是处理好的张量。如果你不想重新跑分词可以直接加载这两个文件。常见做法是import torch # 加载预处理好的数据 data torch.load(label_data.pt, map_locationcpu) # data 可能是 (ids, labels) 的元组或字典 print(type(data)) if isinstance(data, dict): print(data.keys())逻辑说明torch.load能直接还原 PyTorch 保存的对象。先打印类型和键确认里面是ids和labels还是别的结构。参数说明如果报UnpicklingError说明保存时的 PyTorch 版本和当前不一致换回 1.10 或更低版本再试。5. 避坑与排查五个让这份源码跑不起来的真实原因5.1 现象ModuleNotFoundError: No module named torch原因虚拟环境没激活或者装到了全局 Python 里。解决先which pythonWindows 用where python确认当前解释器路径在venv下再pip list看有没有torch。如果没有重新激活环境再装。5.2 现象UnicodeDecodeError: utf-8 codec cant decode byte原因数据文件是 GBK 编码代码里写死了encodingutf-8。解决把open或read_csv的encoding参数改成gbk或者用chardet检测编码。常见做法是先用记事本打开文件另存为 UTF-8一劳永逸。5.3 现象训练 loss 不降准确率一直在 50% 左右原因标签和文本错位或者词表里UNK太多。解决打印一个 batch 的原始文本和标签确认对应关系。如果UNK比例超过 30%把max_vocab从 5000 提到 10000或者检查分词是不是把整句切成了单字。5.4 现象RuntimeError: Expected hidden[0] size (1, 32, 128), got (1, 1, 128)原因LSTM 的batch_first和输入维度不匹配或者num_layers和hidden_dim在保存和加载时不一致。解决确认模型定义里的batch_firstTrue且load_state_dict前重建的模型参数和训练时完全一样。如果改过hidden_dim必须重新训练。5.5 现象预测结果全是正面或全是负面原因正负样本极度不均衡或者Sigmoid输出被阈值卡死。解决先统计训练集正负比例如果 9:1用WeightedRandomSampler或给BCELoss加pos_weight。另外把阈值从 0.5 调到 0.3 或 0.7 试试看预测分布有没有变化。提示__pycache__里的.pyc文件如果和当前 Python 版本不一致会直接报bad magic number。删掉整个__pycache__目录再跑让 Python 重新编译。6. 进阶技巧把 LSTM 换成 BiLSTM 并加注意力看 F1 能涨多少这份源码的 LSTM 是单向的对短文本来说单向已经能捕捉大部分上下文但「虽然…但是…」这种转折句单向 LSTM 在读到「但是」时已经忘了「虽然」后面的内容。一个低成本的改进是换成双向 LSTMBiLSTM再加一个简单的注意力池化。改动量不大但 F1 通常能涨 35 个百分点。class BiLSTMAttention(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim128): super(BiLSTMAttention, self).__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) # bidirectionalTrue 让输出维度变成 hidden_dim*2 self.lstm nn.LSTM(embed_dim, hidden_dim, batch_firstTrue, bidirectionalTrue) # 注意力权重层把每个时间步映射成一个分数 self.attn nn.Linear(hidden_dim * 2, 1) self.fc nn.Linear(hidden_dim * 2, 1) self.sigmoid nn.Sigmoid() def forward(self, x): embedded self.embedding(x) out, _ self.lstm(embedded) # out: (batch, seq_len, hidden*2) # 计算注意力分数并归一化 scores self.attn(out).squeeze(-1) # (batch, seq_len) weights torch.softmax(scores, dim1) # 加权求和得到句子表示 context torch.sum(out * weights.unsqueeze(-1), dim1) logit self.fc(context) return self.sigmoid(logit).squeeze()逻辑说明bidirectionalTrue让 LSTM 同时从前往后和从后往前读输出拼接后维度翻倍。self.attn是一个线性层把每个时间步的隐藏状态映射成一个标量分数softmax归一化成权重最后加权求和。这样模型能自动关注「但是」「非常」这些关键位置。参数说明hidden_dim保持 128双向后实际隐藏维度是 256。训练时学习率可以降到 5e-4因为双向模型参数更多容易震荡。改完模型后把sentiment_analysis.py里的SentimentLSTM替换成BiLSTMAttention重新跑训练。我一般会先跑 5 个 epoch 看验证集 F1如果比原来高再跑满 10 个 epoch。如果没涨检查注意力权重是不是均匀分布——如果每个时间步权重都差不多说明注意力没学到东西可能是数据量太小。从那以后我每次拿到这类课程设计源码都强制先跑一遍test.py确认基线再动模型结构。不然改了半天连原来能不能跑通都不知道血泪经验。希望帮到你。本文还有配套的精品资源点击获取
返回列表