尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

中文影评情感分析实战:从jieba预处理到LSTM与TextCNN模型对比

中文影评情感分析实战:从jieba预处理到LSTM与TextCNN模型对比 简介中文情感分析影评数据资源包提供了一套面向零基础学习者的完整入门方案覆盖从中文文本预处理、特征提取到模型训练与评估的完整流程尤其适合初涉NLP的大学生、开发者及对影评情感分类有需求的从业者。包内共14个文件其中3个Python脚本分别涉及深度学习与机器学习模型3个txt文本数据文件可直接用于训练6张图片示意和1个Markdown说明文档则帮助梳理思路压缩包整体大小仅3.57MB结构清晰便于快速定位。这套轻量级资源已吸引50人学习可用于快速启动影评情感分析实验。借助打包好的代码库与已清洗的数据用户可以免去大量准备环节文档和图片还能帮助理解CNN、RNN等方法在影评情感分析中的实际运用同时预处理脚本和requirements文件进一步降低了本地环境搭建门槛使零基础用户能顺畅地从理论走向本地可运行的项目实践。1. 影评数据与中文情感分析从问题定义到资源包拆解拿一份上万条的中文影评数据能做什么最直接的需求是判断一条评论是好评还是差评甚至区分出三星、四星这种细粒度情绪。我最早做类似项目时踩过最大的坑不在模型选型而在中文预处理颗粒度分词词典没配好、停用词过滤太狠这两项就能让准确率差出三五个百分点。这个资源包把 preprocessing.py、sentiment_analysis_ml.py、sentiment_analysis_dl.py 三个脚本和一份整理过的影评数据集放在一起覆盖从原始文本清洗到模型训练评估的完整链路。适合两类人刚入门 NLP、想拿中文真实数据练手的学生以及想快速搭情感分析原型却缺干净数据的工程师。下面按实际开发顺序拆解先讲预处理再对比机器学习和深度学习两条路线最后落到评估、调参与接口封装。2. 预处理流水线jieba 分词、停用词过滤与特征矩阵构建2.1 从原始影评到干净语料preprocessing.py 做了什么中文文本预处理的核心任务是把一条条非结构化的评论转成模型能读的数字矩阵。这个资源包的 preprocessing.py 主要处理四件事去除 HTML 标签和 URL、jieba 分词、过滤停用词、做情感标签映射。很多人直接跳过这一步把原文丢给模型结果特征空间里全是高频但无意义的词模型训完也说不清到底学到了什么。常见做法是先把数据目录下的原始 CSV 读进来按行处理成 token 序列。这里给出一段和资源包思路同构的实现import jieba import pandas as pd import re STOPWORDS set() with open(data/stopwords.txt, r, encodingutf-8) as f: for line in f: STOPWORDS.add(line.strip()) def clean_text(text: str) - str: # 去掉 HTML 标签和 URL避免噪声进入分词器 text re.sub(r[^], , text) text re.sub(rhttps?://\S, , text) # 只保留中文、英文和数字标点全删 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) return text def tokenize(text: str) - list: text clean_text(text) words jieba.lcut(text) # 过滤单字、停用词和空白长度大于 1 才保留 return [w for w in words if w.strip() and w not in STOPWORDS and len(w) 1] df pd.read_csv(data/movie_comments.csv) df[tokens] df[comment].apply(tokenize) df[label] df[rating].apply(lambda r: 1 if int(r) 4 else (0 if int(r) 2 else -1))逻辑说明clean_text 先清掉噪声再统一字符集把标点、表情符号全部剔除避免它们被 jieba 当成独立 token。tokenize 里用jieba.lcut做精确模式分词过滤条件有三个维度空字符串、停用词、单字。单字过滤要谨慎「烂」「差」这类单字在情感判断里往往是强特征资源包的通用做法是保留一个情感词典来兜底不在通用过滤阶段一刀切。label 映射规则采用三分类4 星及以上为正面12 星及以下为负面03 星为中性-1。参数说明正则[^\u4e00-\u9fa5a-zA-Z0-9]匹配所有非中文、非英文、非数字字符替换为空串即可一次性清掉标点。len(w) 1是过滤单字词的阈值实际操作时建议先打印词频分布再决定——如果发现「烂」「爽」这类强情感单字被过滤后模型效果下降就把这个判断去掉或改为按停用词表控制。2.2 特征矩阵构建TF-IDF 与词向量两条路径的分岔点预处理产出的是 token 列表下一步要把它变成矩阵。这里分两个方向资源包里的两个训练脚本分别对应这两条路路径特征表示适用模型特征维度硬件要求传统机器学习TF-IDF 向量SVM、朴素贝叶斯、逻辑回归5 万到 20 万维稀疏向量CPU 即可深度学习Embedding 词向量BiLSTM、TextCNN、BERTmax_len × embed_size 稠密矩阵有 GPU 更好CPU 也可跑小模型传统路线用 TF-IDF 直接把整句拼成向量from sklearn.feature_extraction.text import TfidfVectorizer df[text_joined] df[tokens].apply(lambda x: .join(x)) vectorizer TfidfVectorizer(max_features50000, ngram_range(1, 2)) X vectorizer.fit_transform(df[text_joined]) y df[label] print(特征矩阵形状:, X.shape)逻辑说明把 tokens 列表重新拼成空格分隔的字符串是因为 TfidfVectorizer 默认按空格切分。max_features50000限制特征维度只保留词频最高的 5 万个词和词组超出的直接丢弃这一步能把稀疏矩阵控制在一个合理的规模内。ngram_range(1, 2)同时考虑单词和二元词组对「不怎么样」「特别差」这类否定结构很重要。在影评数据上从 (1, 1) 调到 (1, 2) 通常能涨 2 个点左右再调到 (1, 3) 维度爆炸收益反而有限。深度学习路径需要把文本编码成整数序列from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences MAX_VOCAB_SIZE 30000 MAX_SEQ_LEN 128 tokenizer Tokenizer(num_wordsMAX_VOCAB_SIZE, oov_tokenUNK) tokenizer.fit_on_texts(df[text_joined]) sequences tokenizer.texts_to_sequences(df[text_joined]) X_pad pad_sequences(sequences, maxlenMAX_SEQ_LEN, paddingpost, truncatingpost)参数说明num_words30000表示词汇表保留出现频率最高的 3 万个 token出现次数低于阈值的词统一映射为UNK。MAX_SEQ_LEN128是经验值——影评平均长度在 50 到 90 字之间128 能覆盖 95% 以上的样本取太长会拖慢训练、增加内存占用。paddingpost在序列尾部补零truncatingpost从尾部截断超长部分二者保持一致是为了让模型看到的位置信息不偏移。提示预处理阶段最容易翻车的不是分词而是标签分布。影评数据通常是长尾分布1 星和 5 星占大头2、3、4 星数量稀少。如果直接用原始比例训练模型会把所有样本都倾向学成极端情感。切分数据集之前先打印y.value_counts()检查分布再决定是否做重采样或改用加权损失。3. 机器学习基线TF-IDF 加 SVM 与朴素贝叶斯的中文影评分类3.1 为什么先跑传统基线而不是直接上深度学习很多同学拿到这类资源包第一反应是跳过 ML 脚本直接跑 sentiment_analysis_dl.py。但资源包里放着两个脚本是有原因的。用 TF-IDF 加线性核 SVM 在中文短文本分类上依然非常有竞争力准确率往往只比未调优的 LSTM 低两三个百分点训练时间却从几十分钟缩短到几十秒。先跑传统基线还有一个实际的好处它能快速验证预处理流程是否正确、标签映射是否合理为后面对比深度模型提供一个可靠的基准线。3.2 三模型并行对比LinearSVC、朴素贝叶斯与逻辑回归sentiment_analysis_ml.py 里通常会把三个主流分类器放在一起对比。我惯用的实现方式如下from sklearn.svm import LinearSVC from sklearn.naive_bayes import MultinomialNB from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) models { LinearSVC: LinearSVC(C1.0, class_weightbalanced), MultinomialNB: MultinomialNB(alpha0.1), LogisticRegression: LogisticRegression(max_iter2000, C1.0, class_weightbalanced), } for name, model in models.items(): model.fit(X_train, y_train) y_pred model.predict(X_test) print(f\n {name} ) print(classification_report(y_test, y_pred, target_names[负面, 中性, 正面]))逻辑说明class_weightbalanced会按类别样本量的倒数自动调整权重对长尾分布的影评数据至关重要——否则模型为了降低整体损失会牺牲少数类别的召回率。stratifyy保证切分后训练集和测试集的类别比例一致避免随机切分导致某一类在测试集中样本过少、评估结果失真。LinearSVC 用 hinge loss对稀疏高维特征分类边界清晰MultinomialNB 的alpha0.1是拉普拉斯平滑参数调小一些可以避免对低频词过度平滑。参数说明SVC 的C是正则化强度的倒数C 越大越容易过拟合。影评数据上我一般从 0.5 到 2.0 做网格搜索步长 0.25。max_iter2000是逻辑回归的迭代上限TF-IDF 特征维度高sklearn 默认的 100 次经常不收敛运行时如果看到ConvergenceWarning优先调大这个值或调小 C。3.3 网格搜索与关键参数速查表下面这组参数是中文短文本情感分类常用起点可以直接复用模型关键参数推荐起始值调节方向LinearSVCC1.0过拟合调小欠拟合调大MultinomialNBalpha0.1类别不平衡严重时调小LogisticRegressionC1.0同 LinearSVCTfidfVectorizermax_features30000~50000内存不足或过拟合时调小TfidfVectorizerngram_range(1, 2)效果不足时试 (1, 3)TfidfVectorizermin_df2过滤只在一条评论出现过的词from sklearn.model_selection import GridSearchCV param_grid {C: [0.5, 0.75, 1.0, 1.5, 2.0]} grid GridSearchCV( LinearSVC(class_weightbalanced), param_grid, cv5, scoringf1_macro, n_jobs-1, ) grid.fit(X_train, y_train) print(最优 C:, grid.best_params_) print(最优 F1:, grid.best_score_)逻辑说明scoringf1_macro对三个类别分别计算 F1 再取宏平均比 accuracy 更能反映模型在少数类别上的表现。cv5表示 5 折交叉验证在 1 万条以上的数据量下结果足够稳定。n_jobs-1利用所有 CPU 核并行搜索LR 和 SVC 的候选参数少几秒钟就能跑完。这里有个容易被忽略的点网格搜索的评估指标要和业务目标对齐。如果后续场景更看重「别把差评漏掉」应该用recall_macro或针对负面类别的f1_score来筛选参数而不是只看整体 F1。影评数据里负面评论往往是电影方最关心的漏掉一条差评的代价远高于误伤一条好评。4. 深度学习模型进阶LSTM 与 TextCNN 在中文影评情感分类中的实现4.1 从稀疏向量到语义表达Embedding 层带来的本质变化传统模型靠 TF-IDF 把词变成稀疏向量每个词独立存在丢失了语义关系。「很棒」和「超棒」在 TF-IDF 空间里距离很远但在词向量空间里紧挨着。深度模型的核心优势在于 Embedding 层——模型在训练过程中自动学习词的分布式表示让语义相近的词拥有相近的向量。这对影评这类口语化文本尤其重要口语化的特点就是同一个意思有大量变体表达稀疏表示根本抓不住。4.2 从预处理输出到模型训练BiLSTM 完整流程sentiment_analysis_dl.py 直接把预处理结果送入深度模型。下面用 BiLSTM 做示范import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, SpatialDropout1D, Bidirectional, LSTM, Dense from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint VOCAB_SIZE 30000 EMBED_DIM 128 MAX_LEN 128 NUM_CLASSES 3 model Sequential([ Embedding(input_dimVOCAB_SIZE, output_dimEMBED_DIM, input_lengthMAX_LEN), SpatialDropout1D(0.2), Bidirectional(LSTM(units64, dropout0.2, recurrent_dropout0.2)), Dense(NUM_CLASSES, activationsoftmax), ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losssparse_categorical_crossentropy, metrics[accuracy], ) callbacks [ EarlyStopping(monitorval_loss, patience3, restore_best_weightsTrue), ModelCheckpoint(models/bilstm_best.h5, monitorval_accuracy, save_best_onlyTrue), ] history model.fit( X_pad, y, validation_split0.2, batch_size64, epochs20, callbackscallbacks, )逻辑说明Embedding 层把每个词的整数索引映射成 128 维稠密向量向量随训练更新等价于为影评语料专门训练一份词向量表。SpatialDropout1D 按通道随机置零比普通 Dropout 更适合 Embedding 的输出结构。Bidirectional 把正向和反向两个 LSTM 的输出拼接对「虽然剧情拖沓但演技在线」这种转折结构很有效——两个方向分别捕捉句子的前向和后向语义。参数说明dropout0.2是 LSTM 输出层的随机失活比例recurrent_dropout0.2是对循环状态做 dropout两者同时调大容易欠拟合。learning_rate 从 1e-3 起步如果 loss 震荡不降就降到 3e-4。patience3表示验证集 loss 连续 3 个 epoch 不下降就提前停止防止 20 个 epoch 跑过头。注意 y 里包含 -1 标签时要先做y y 1映射到 0、1、2sparse_categorical_crossentropy要求标签是从 0 开始的整数。4.3 TextCNN 与 BiLSTM 的取舍光靠一个模型不够资源包里通常还会提供 TextCNN 结构用不同尺寸的卷积核在序列上提取局部 n-gram 特征from tensorflow.keras.layers import Conv1D, GlobalMaxPooling1D, Dropout def build_textcnn(num_filters128, kernel_sizes[2, 3, 4]): inputs tf.keras.Input(shape(MAX_LEN,)) x Embedding(VOCAB_SIZE, EMBED_DIM, input_lengthMAX_LEN)(inputs) x SpatialDropout1D(0.2)(x) convs [] for size in kernel_sizes: conv Conv1D(filtersnum_filters, kernel_sizesize, activationrelu)(x) pool GlobalMaxPooling1D()(conv) convs.append(pool) x tf.keras.layers.Concatenate()(convs) x Dropout(0.3)(x) outputs Dense(NUM_CLASSES, activationsoftmax)(x) return tf.keras.Model(inputs, outputs)逻辑说明kernel_sizes[2, 3, 4]分别对应二元、三元和四元词组窗口卷积核在 Embedding 序列上滑动提取「不/好看」「太/差/了」这类局部组合特征。GlobalMaxPooling1D对每个特征图取最大值保留最能激活某个特征的局部位置从而忽略该特征出现在句中哪个位置。对比维度BiLSTMTextCNN特征捕捉全局序列依赖、转折结构局部 n-gram 模式训练速度CPU约 5-10 分钟/epoch约 1-3 分钟/epoch影评数据 F1通常高 1~2 个点基线水平参数量较大较小适用场景长文本、复杂态度短文本、快速验证在影评数据上两者最终效果差距一般不超过 2 个点。工程实践上先用 TextCNN 快速验证特征和预处理是否合理再换 BiLSTM 追求上限是更高效的做法。如果文本长度普遍超过 200 字BiLSTM 的优势会更明显如果做实时接口TextCNN 的推理速度则更有吸引力。提示深度模型的随机性比传统模型大很多。同一个模型同一份数据不同随机种子跑出来的 F1 可能差 1.5 个点。比较模型优劣时每个结构至少跑 3 个种子取平均别拿单次结果下结论。资源包里的脚本默认固定random_seed42正式实验时把这个值改成可传入参数。5. 模型评估、类别不平衡调参与影评预测接口封装5.1 用混淆矩阵定位模型短板光看 accuracy 会骗人。影评数据如果 5 星和 1 星占比过高模型只要全预测成极端情感就能拿到不错准确率但 3 星全部判错。正确做法是打印混淆矩阵from sklearn.metrics import confusion_matrix, classification_report import numpy as np y_pred model.predict(X_pad_test).argmax(axis1) y_true np.array(y_test) print(classification_report(y_true, y_pred, target_names[负面, 中性, 正面])) cm confusion_matrix(y_true, y_pred) print(cm)重点关注「中性」类别的召回率。如果低于 0.5说明模型几乎把中性评论推给了正负两极。这时优先检查两处一是预处理阶段的三分类标签映射规则是否合理3 星评论里混入了大量 4 星和 2 星的边缘样本二是训练时的类别权重是否失衡。5.2 类别不平衡的两类修正方法修正方法一给少数类别加损失权重。class_weights {0: 1.0, 1: 2.5, 2: 1.0} # 中性类权重调高 model.fit(X_pad_train, y_train, epochs10, batch_size64, class_weightclass_weights)权重的设置可以按样本量反比估算。比如中性样本占 20%正负样本各占 40%中性类权重设为 2.0 左右起步再根据验证集 F1 微调。修正方法二阈值移动适合传统模型。用predict_proba拿到每个类别的概率后手动调整判定边界让中性类概率达到 0.3 时就触发而不是默认的 0.33 最大概率。这个方法不改模型结构调起来最快。5.3 把模型封装成可直接调用的预测函数最后一步是把训练好的模型和向量器打包。传统模型用 pickle深度学习模型用model.save再写一层外层函数对接import pickle import jieba with open(models/tfidf_svc.pkl, rb) as f: saved pickle.load(f) vectorizer saved[vectorizer] model saved[model] def predict_sentiment(text: str): cleaned clean_text(text) tokens [w for w in jieba.lcut(cleaned) if w not in STOPWORDS] vec vectorizer.transform([ .join(tokens)]) label model.predict(vec)[0] return {0: 负面, 1: 中性, 2: 正面}[label] print(predict_sentiment(特效炸裂但剧情拖沓))封装时注意 pickle 要同时保存 vectorizer 和模型本身否则下次加载时特征维度不一致会直接报错。predict_sentiment内部完整复刻了训练时的预处理流程清洗、分词、停用词过滤的顺序不能变任何一步缺失都会导致线上结果和离线评估对不上。本文还有配套的精品资源点击获取
返回列表