ARTICLE DETAIL

资讯详情

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

三分类情感分析实战:LSTM模型与Word2vec词向量的完整落地

三分类情感分析实战:LSTM模型与Word2vec词向量的完整落地 简介这份资源是Python基于LSTM三分类的文本情感分析完整项目面向计算机相关专业正在做课程设计、期末大作业的学生以及需要NLP项目实战练习的学习者。项目将文本情感划分为负面、中性、正面三类采用LSTM网络建模配套Word2vec词向量与训练好的模型权重可直接运行并复现结果。压缩包共13个文件约11.63MB包含3个csv情感语料、3个py训练与测试脚本、2个ipynb交互式笔记本以及yml模型配置、h5权重文件、pkl词向量模型、txt依赖清单和md说明文档覆盖数据处理、模型训练、评估测试的完整链路。目前已有307人学习下载。读者可据此掌握文本清洗、分词、词向量构建、LSTM三分类建模与结果评估的全流程并借助文档说明快速理解目录结构与关键代码适合作为课程设计参考或NLP入门实战模板。1. 三分类情感分析项目从数据到 LSTM 模型的完整落地路径做文本情感分析时很多人第一反应是二分类——好评还是差评。但真实业务里用户评论往往存在大量「中性」表达比如「东西收到了还没用」「物流一般包装还行」。这类文本既不是正向也不是负向硬塞进二分类模型只会让准确率虚高、线上效果翻车。这个项目用 LSTM 做三分类积极 / 中性 / 消极配套了完整的训练脚本、测试脚本、Word2vec 词向量模型和已训练好的 h5 权重文件拿到手就能跑推理也能从头复现训练过程。适合正在做课程设计、期末大作业的计算机相关专业学生也适合想快速搭一个情感分析 baseline 的从业者。项目结构清晰lstm_train.py负责训练lstm_test.py负责评估data deal.py做数据预处理lstm.yml存模型结构lstm.h5是训练好的权重Word2vec_model.pkl是预训练词向量。下面按「数据怎么处理 → 模型怎么搭 → 怎么训练和评估 → 坑在哪 → 怎么进阶」的顺序拆一遍。2. 数据预处理与 Word2vec 词向量三分类语料怎么喂给 LSTM2.1 三分类语料的结构与标签映射项目里data目录下有三个 CSV 文件pos.csv、neutral.csv、neg.csv。常见做法是每个文件一列文本没有标签列靠文件名区分类别。训练前需要把它们合并成一个 DataFrame并生成数值标签。我一般会按下面的方式做标签映射import pandas as pd # 读取三个类别的语料假设每个 CSV 只有一列文本列名统一为 text pos pd.read_csv(data/pos.csv, headerNone, names[text]) neu pd.read_csv(data/neutral.csv, headerNone, names[text]) neg pd.read_csv(data/neg.csv, headerNone, names[text]) # 打标签0-消极1-中性2-积极 pos[label] 2 neu[label] 1 neg[label] 0 # 合并并打乱顺序避免训练时类别集中 df pd.concat([pos, neu, neg], ignore_indexTrue) df df.sample(frac1, random_state42).reset_index(dropTrue) print(df[label].value_counts())这段代码的逻辑很直接先分别读取三个文件给每个类别赋一个整数标签然后合并、打乱。打乱这一步很关键如果按 pos → neutral → neg 的顺序直接训练模型在最后一个 batch 上会严重偏向 neg 类导致验证集准确率波动大。random_state42是为了保证每次运行结果可复现方便调试。标签映射顺序可以改但训练和推理必须保持一致否则预测结果会完全反掉。2.2 中文分词与停用词处理中文文本不能直接按空格切分常见做法是用 jieba 分词。项目里data deal.py应该包含了分词逻辑但如果你要自己改可以按下面的方式写import jieba import re def clean_text(text): # 去掉非中文、非字母数字的字符保留基本标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。], , str(text)) # 分词 words jieba.lcut(text) # 去掉长度为1的词和空白符 words [w.strip() for w in words if len(w.strip()) 1] return words df[tokens] df[text].apply(clean_text)这里有几个参数需要留意。len(w.strip()) 1会过滤掉「的」「了」「是」这类单字减少噪声但也会误杀「好」「差」这种单字情感词。如果你的语料里单字情感词很重要可以把阈值改成 1然后单独维护一个停用词表。正则里保留了中文逗号、句号、问号和感叹号因为这些标点对情感判断有辅助作用比如感叹号往往出现在强烈情感表达里。清洗完的tokens列就是后续训练用的输入。2.3 Word2vec 词向量加载与嵌入矩阵构建项目提供了Word2vec_model.pkl这是一个已经训练好的 Word2vec 模型。LSTM 的嵌入层需要把词映射成向量有两种做法一是直接用 Word2vec 的权重初始化嵌入矩阵二是把 Word2vec 当特征提取器先转成向量序列再喂给 LSTM。这个项目用的是第一种更常见也更省显存。import numpy as np import pickle from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences # 加载 Word2vec 模型 with open(Word2vec_model.pkl, rb) as f: w2v_model pickle.load(f) # 构建 Tokenizer只保留出现频率最高的 5000 个词 tokenizer Tokenizer(num_words5000, oov_tokenOOV) tokenizer.fit_on_texts(df[tokens].apply(lambda x: .join(x))) # 转成序列并填充到固定长度 sequences tokenizer.texts_to_sequences(df[tokens].apply(lambda x: .join(x))) max_len 100 # 常见做法是取 100 或 200根据语料长度分布调整 X pad_sequences(sequences, maxlenmax_len, paddingpost, truncatingpost) # 构建嵌入矩阵 embedding_dim w2v_model.vector_size word_index tokenizer.word_index embedding_matrix np.zeros((5000, embedding_dim)) for word, i in word_index.items(): if i 5000 and word in w2v_model.wv: embedding_matrix[i] w2v_model.wv[word]num_words5000表示只保留词频最高的 5000 个词超出部分用OOV代替。这个值可以调大但嵌入矩阵会线性增大显存不够时优先降这个。max_len100是序列截断长度短于 100 的补零长于 100 的截断。paddingpost表示在末尾补零truncatingpost表示从末尾截断这两个参数要一致否则序列对齐会乱。嵌入矩阵的维度来自 Word2vec 模型的vector_size常见是 100 或 200。如果某个词不在 Word2vec 词表里对应行保持全零训练时嵌入层会把它当未知词处理。注意tokenizer.word_index里索引从 1 开始0 是 padding 占位所以嵌入矩阵第 0 行保持全零不要往里填词向量。3. LSTM 模型搭建与训练从 lstm.yml 到 lstm.h53.1 模型结构解析与 Keras 实现项目里lstm.yml保存了模型结构lstm.h5保存了权重。如果你要自己重建模型可以按下面的方式搭一个典型的三分类 LSTMfrom tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout, SpatialDropout1D model Sequential() model.add(Embedding(input_dim5000, output_dimembedding_dim, weights[embedding_matrix], input_lengthmax_len, trainableFalse)) # 先冻结嵌入层 model.add(SpatialDropout1D(0.2)) model.add(LSTM(128, dropout0.2, recurrent_dropout0.2)) model.add(Dense(64, activationrelu)) model.add(Dropout(0.3)) model.add(Dense(3, activationsoftmax)) # 三分类输出 model.compile(losssparse_categorical_crossentropy, optimizeradam, metrics[accuracy]) model.summary()Embedding层的trainableFalse表示先冻结 Word2vec 权重不让它在训练初期被破坏。如果语料够大可以在训练几轮后解冻微调。SpatialDropout1D按通道丢弃比普通 Dropout 更适合嵌入层。LSTM(128)是隐藏单元数常见范围 64 到 256太大容易过拟合太小欠拟合。recurrent_dropout在 LSTM 内部循环连接上做 dropout能进一步抑制过拟合但会拖慢训练速度。最后一层Dense(3, activationsoftmax)输出三个类别的概率损失函数用sparse_categorical_crossentropy因为标签是整数而不是 one-hot。3.2 训练脚本 lstm_train.py 的关键参数lstm_train.py是训练入口核心是model.fit()。下面是一个可复现的训练片段from sklearn.model_selection import train_test_split from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint # 划分训练集和验证集 stratify 保证类别比例一致 X_train, X_val, y_train, y_val train_test_split( X, df[label].values, test_size0.2, random_state42, stratifydf[label].values ) # 回调早停和保存最佳权重 callbacks [ EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue), ModelCheckpoint(lstm.h5, monitorval_accuracy, save_best_onlyTrue) ] history model.fit(X_train, y_train, validation_data(X_val, y_val), epochs20, batch_size64, callbackscallbacks, verbose1)stratifydf[label].values很关键三分类里如果中性样本特别少不 stratify 可能导致验证集里某个类别一个样本都没有准确率直接失真。EarlyStopping的patience3表示验证损失连续 3 轮不下降就停restore_best_weightsTrue会把权重回滚到最佳轮次。ModelCheckpoint只保存验证准确率最高的权重到lstm.h5。batch_size64是常见起点显存不够就降到 32 或 16。epochs20配合早停实际训练轮次通常更少。3.3 评估脚本 lstm_test.py 与混淆矩阵训练完不能只看准确率三分类问题必须看每个类别的召回率和混淆矩阵。lstm_test.py里常见做法是加载lstm.h5然后跑测试集from tensorflow.keras.models import load_model from sklearn.metrics import classification_report, confusion_matrix import numpy as np model load_model(lstm.h5) y_pred_prob model.predict(X_val) y_pred np.argmax(y_pred_prob, axis1) print(classification_report(y_val, y_pred, target_names[消极, 中性, 积极])) print(confusion_matrix(y_val, y_pred))np.argmax把 softmax 输出的概率转成类别索引。classification_report会输出每个类别的 precision、recall、f1-score。如果中性类的 recall 明显低于其他两类说明模型没学好中性特征常见原因是中性语料太少或和积极/消极边界模糊。混淆矩阵能看出具体错分方向比如中性被大量预测成积极就要检查中性语料里是不是混入了正向表达。4. 避坑与排查三分类 LSTM 训练中最容易翻车的五个点4.1 现象验证集准确率很高但测试集一塌糊涂原因训练集和验证集划分时没有 stratify或者验证集本身太小导致验证集类别分布和真实分布偏差大。另一个常见原因是lstm.h5保存的是最后一轮权重而不是最佳权重。解决划分时强制stratify验证集比例不低于 0.15。ModelCheckpoint必须加save_best_onlyTrue并且监控指标用val_accuracy或val_loss。加载模型后先在验证集上跑一遍classification_report确认每个类别都有足够样本。4.2 现象模型只预测中性类积极和消极全被吞掉原因中性语料占比过高或者中性文本和积极/消极文本区分度太低。LSTM 在类别不平衡时会倾向于预测多数类因为这样损失下降最快。解决先看df[label].value_counts()如果中性样本是其他类的 3 倍以上要么降采样中性类要么在model.fit()里加class_weight参数。常见做法是用 sklearn 的compute_class_weight算权重from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight(balanced, classesnp.unique(y_train), yy_train) class_weight_dict dict(enumerate(class_weights)) model.fit(..., class_weightclass_weight_dict)4.3 现象训练时 loss 不下降准确率一直在 0.33 附近原因嵌入层冻结后Word2vec 词向量和当前语料的语义空间不匹配或者max_len设得太短关键情感词被截掉了。解决先把trainableTrue打开让嵌入层跟着训练几轮观察 loss 是否下降。如果下降明显说明 Word2vec 权重需要微调。同时检查max_len统计一下df[tokens].apply(len)的分布如果 90% 的文本长度超过 100就要把max_len调到 150 或 200。4.4 现象加载 lstm.h5 时报错提示维度不匹配原因lstm.yml里的模型结构和lstm.h5的权重维度不一致常见于手动改了Embedding的input_dim或LSTM的units后没有重新训练。解决要么用lstm.yml重建模型再加载权重要么直接用load_model(lstm.h5)让 Keras 自己解析结构。不要手动改结构后强行加载旧权重。如果必须改就重新训练并保存新的 h5 文件。4.5 现象推理时中文分词结果和训练时不一致原因lstm_test.py里用了不同的分词函数或停用词表导致同一个词在训练时映射到索引 A推理时映射到索引 B。解决把分词、清洗、Tokenizer 的构建逻辑封装成一个函数或类训练和推理共用同一份代码。tokenizer的word_index必须和训练时完全一致最好把tokenizer也保存成 pickle 文件推理时直接加载不要重新fit_on_texts。5. 进阶技巧用已训练模型做单条推理与批量预测5.1 单条文本推理的完整封装拿到lstm.h5和Word2vec_model.pkl后最常见的需求是给一条新评论判断情感。下面是一个可直接抄的推理函数import pickle import numpy as np import jieba import re from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing.sequence import pad_sequences # 加载模型和 tokenizer model load_model(lstm.h5) with open(tokenizer.pkl, rb) as f: tokenizer pickle.load(f) max_len 100 label_map {0: 消极, 1: 中性, 2: 积极} def predict_sentiment(text): # 清洗和分词必须和训练时一致 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。], , str(text)) words jieba.lcut(text) words [w.strip() for w in words if len(w.strip()) 1] seq tokenizer.texts_to_sequences([ .join(words)]) seq pad_sequences(seq, maxlenmax_len, paddingpost, truncatingpost) prob model.predict(seq, verbose0)[0] pred int(np.argmax(prob)) return label_map[pred], prob # 测试 print(predict_sentiment(物流很快东西也很好用下次还来)) print(predict_sentiment(收到货了还没拆开看)) print(predict_sentiment(质量太差了用了两天就坏了))这个函数的关键点在于分词逻辑必须和训练时完全一致tokenizer必须是从训练阶段保存下来的不能重新 fit。pad_sequences的参数也要和训练时一致否则序列长度对不上模型输出会乱。返回的prob是三个类别的概率可以用来做置信度过滤比如概率低于 0.6 的样本标记为「不确定」转人工处理。5.2 批量预测与结果落盘如果有一批 CSV 文件需要跑情感分析可以按下面的方式批量处理import pandas as pd def batch_predict(input_csv, output_csv): df pd.read_csv(input_csv) results [] for text in df[text]: label, prob predict_sentiment(text) results.append({ text: text, label: label, prob_neg: prob[0], prob_neu: prob[1], prob_pos: prob[2] }) result_df pd.DataFrame(results) result_df.to_csv(output_csv, indexFalse, encodingutf-8-sig) return result_df # 用法 batch_predict(new_data.csv, sentiment_result.csv)encodingutf-8-sig是为了 Excel 打开时不乱码。批量预测时如果数据量很大逐条model.predict会很慢常见做法是把文本攒成 batch 再一次性预测比如每 128 条调一次model.predict。另外如果输入文本里有大量空值或非中文字符清洗后可能变成空列表pad_sequences会生成全零序列模型会输出一个偏向多数类的预测这种结果没有意义建议在推理前加一层判断空文本直接返回「无效输入」。5.3 模型微调与领域适配如果拿这个模型去分析特定领域评论比如数码产品、餐饮评价准确率通常会下降因为 Word2vec 词向量和 LSTM 权重都是在通用语料上训练的。我一般会做两步微调先把嵌入层解冻用领域语料训练 2 到 3 轮学习率调到 1e-4然后冻结嵌入层只训练 LSTM 和全连接层再跑 3 到 5 轮。微调时batch_size可以降到 32避免梯度震荡。微调完记得用新的验证集跑一遍混淆矩阵确认每个类别的召回率都达标再上线。从那以后我每次拿到一个新的情感分析项目都会先把分词和 Tokenizer 的保存逻辑单独抽出来训练和推理共用同一份代码再也不敢在推理脚本里重新fit_on_texts。希望帮到你。本文还有配套的精品资源点击获取
返回列表