ARTICLE DETAIL

资讯详情

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

情感分类三方法对比:从情感词典到深度学习的一站式实验指南

情感分类三方法对比:从情感词典到深度学习的一站式实验指南 简介这是一份基于情感词典法、传统机器学习和深度学习的情感分类系统课程大作业面向数据挖掘、机器学习与深度学习初学者及课程设计或毕业设计参考人群。资源共16个文件压缩包约11.84MB内部按代码、数据、图像和文档划分包含3个Python脚本、6个文本词典/停用词表、2个微博CSV数据集、4张模型效果图及1份Markdown说明覆盖数据清洗、特征提取、模型构建、训练评估的完整流程。已有58人学习。代码分别实现情感词典匹配、传统机器学习分类如支持向量机、朴素贝叶斯以及CNN、BiLSTM、TextCNN等深度模型并提供可直接运行的微博情感数据集与配套词典课设报告和数据集说明则帮助理解不同方法在情感分类任务中的性能差异与适用场景。整体结构清晰适合用于课设参考、实践复现和情感分析入门。1. 情感分类三种方法同台对比一份代码就能跑通的情感分析实验教科书爱把情感分析按“情感词典法、传统机器学习、深度学习”三个阶段讲但真正能拿到手、跑得动、还能看明白三个方法怎么对比的课程项目其实不多。这份课程大作业基于 Python 实现了一个完整的情感分类系统用 weibo_senti_10k 和 weibo_senti_100k 两份微博语料把情感词典打分、传统机器学习朴素贝叶斯与 SVM以及深度学习CNN、BiLSTM、TextCNN三条路线全部跑通输出准确率、精确率、召回率、F1 四维指标并在 image 目录下保留了损失曲线和模型对比图。对刚接触 NLP、正在准备课程设计或者需要快速搭建一个中文情感分类对比实验的学习者来说这套代码是一个可以直接照着复现、改参数、写进课设报告的实验框架。2. 项目文件与数据预处理先从文件结构找到三个实验的统一入口2.1 代码与文件结构dict.py、ml.py、nn.py 各管一段解压 zip 之后项目根目录是code1128所有代码、数据、词典和结果图都放在这个目录下。文件虽然不算多但分工非常清楚三个核心 Python 脚本分别对应三类方法词典资源单独放一个文件夹数据单独放一个文件夹训练结果的截图放在 image 目录。第一次打开项目先花两分钟把文件结构和对应关系捋顺后面跑实验、改代码、写报告就不会一头雾水。文件/目录作用dict.py情感词典法实现负责加载词典并计算文本情感得分ml.py传统机器学习实现包含特征向量化、模型训练与评估nn.py深度学习实现包含 CNN、BiLSTM、TextCNN 三个模型data/weibo_senti_10k.csv1 万条标注微博数据适合快速调试data/weibo_senti_100k.csv10 万条标注微博数据适合最终实验dictionary/六个词典文件包括正负情感词、程度副词、否定词、停用词等image/训练过程的四张结果图包括总体对比和单个模型损失曲线README.md项目的运行说明和课设报告入口先看这个准没错我在拿到这类项目时有个习惯先读 README再跑一个最小的脚本验证数据能读入。README.md里通常写了每一步的运行顺序和环境要求课设报告的主要内容也被浓缩在里面。如果直接跳到nn.py去调参很容易因为数据格式没对齐而浪费时间。建议你也在自己的机器上先保持这份目录结构不变至少在第一次跑通之前不要重命名文件因为dict.py和ml.py里读取词典和数据集的路径都是相对于根目录写的。2.2 读取微博 CSV 并清洗文本编码和字段名是第一道坑无论你最后用哪种方法做情感分类第一步都是把weibo_senti_10k.csv读进内存。这个数据集的字段结构比较简单一列是微博文本一列是情感标签通常用 1 表示积极、0 表示消极。读取时最常见的坑是编码问题因为微博数据里包含各种表情符号和特殊字符用默认的编码方式读大概率会报错。我一般会优先试utf-8-sig编码如果还不行再试gbk。import pandas as pd df pd.read_csv(data/weibo_senti_10k.csv, encodingutf-8-sig) print(df.columns.tolist()) # 查看字段名 print(df[label].value_counts()) # 查看标签分布 print(df.head())这段代码先确认字段名是不是text和label。不同版本的数据集可能把文本列命名为review或content标签列也可能叫sentiment如果列名对不上后面df[label]会直接抛 KeyError。确认标签分布也很关键如果 1 和 0 的比例严重失衡后面计算的准确率会有很强的误导性。第一轮调试建议只加载 10k 那份小数据因为它只有不到 1 万条几秒就能完成读取和预处理适合反复试错。等所有代码都跑通了再切换到 100k 那份做正式实验。2.3 文本清洗与分词三种方法必须共享同一份干净输入情感分类的效果很大程度上取决于输入质量。微博文本里常见的内容包括 用户、超链接、话题标签、多余空格和表情符号这些对情感判断的贡献有限通常会先清洗掉。清洗之后情感词典法和传统机器学习方法都需要分词。这里要注意一个原则三种对比方法必须用同一套清洗和分词逻辑否则对比出来的性能差异就分不清是模型带来的还是数据预处理带来的。import re import jieba stopwords set() with open(dictionary/stopwords.txt, r, encodingutf-8) as f: for line in f: w line.strip() if w: stopwords.add(w) def clean_text(text): text re.sub(r[\w\u4e00-\u9fa5], , text) # 去掉 用户名 text re.sub(rhttp\S, , text) # 去掉网页链接 text re.sub(r#.?#, , text) # 去掉 #话题# text re.sub(r\s, , text).strip() return text def tokenize(text): words jieba.lcut(clean_text(text)) return [w for w in words if w not in stopwords and w.strip()]上面这段代码里clean_text函数用正则表达式依次处理了几类噪声。tokenize函数调用了 jieba 分词并在分词后过滤掉停用词和空字符。停用词表直接在dictionary/stopwords.txt里读取没有额外下载资源这也是这个项目方便的地方——词典文件已经全部打包好不需要考虑外部依赖。分词之后的结果会作为后续所有方法的输入。深度学习模型虽然可以靠字符级输入绕过分词但这个项目里的nn.py仍然复用了同样的分词结果保持一致对后期性能对比很重要。3. 情感词典法从打分规则入手用 Python 实现一个可解释的强基线3.1 词典文件解读情感得分、程度副词和否定词各有什么用情感词典法的核心是先有一套带情绪极性的词表然后通过匹配文本中的词来计算整句的情感倾向。dictionary目录下的几个 txt 文件不只是简单的正负词列表它们分别承担了不同的职责positive_simplified.txt和negative_simplified.txt是基础情感词表一个负责识别积极词汇一个负责识别消极词汇sentiment_score.txt里可能存储了每个词的情感得分权重degree.txt是程度副词比如“很”“非常”“有点”它们不会直接表达情感但会放大或缩小后面情感词的强度inverse_words.txt是否定词表比如“不”“没”“无”它们能把句子的情感倾向反着转过来。读取词典的代码也不复杂就是把每一行文本读成一个集合或字典。需要注意的一点是txt 文件里可能有空行或者首尾空格读进内存前必须做 strip 处理否则匹配的时候会漏掉很多词情感得分就会偏低。我一般会用集合推导式来加载纯词表用字典推导式来加载带权重的词表保证查询效率。用集合而不是列表还有一个好处情感判断时匹配速度更快微博文本经过分词后通常只有十几个词每个词都要查一遍词典用集合能让整体耗时保持在毫秒级。3.2 情感得分计算逻辑否定翻转、程度加权与阈值判定情感词典法的核心逻辑就是遍历句子中的每个词遇到情感词就累计得分遇到程度副词就修改权重遇到否定词就翻转极性。这里有一个容易理解错的点否定词的作用范围。比如“不是很喜欢”否定的是“很”这个程度而不是把“喜欢”整个翻转成“不喜欢”。我在写这类代码时会采用只作用于最近一个情感词的方案也就是遇到否定词先置一个标志位等遇到下一个情感词时再应用翻转并复位。def sentiment_score(text, pos_words, neg_words, degree_dict, inverse_words): words tokenize(text) score 0.0 degree 1.0 negate False for w in words: if w in degree_dict: # 程度副词出现时更新当前情感词的加权系数 degree float(degree_dict[w]) elif w in inverse_words: # 否定词出现时翻转标记位 negate not negate elif w in pos_words or w in neg_words: # 遇到情感词把前面的加权和翻转一次性结算 base 1.0 if w in pos_words else -1.0 if negate: base -base negate False score degree * base degree 1.0 return score这段代码里degree的默认值是 1.0表示不加权negate的默认值是 False表示不翻转。每遇到一个情感词就把当前的权重和翻转标志结算进去然后重置状态。这个逻辑能处理“非常喜欢”“不太喜欢”“完全不怎么样”这类常见句式但注意它没有处理连用否定词的情况比如“不得不喜欢”如果要处理双重否定还需要额外维护一个否定计数。最后一个关键参数是阈值通常大于 0 判为积极小于 0 判为消极等于 0 的时候可以归为中性或者按多数类处理。3.3 词典法的边界为什么它只配当对比实验的基线跑通词典法之后你会发现它在测试集上的表现可能比预期低。这不是代码写错了而是方法本身的局限。词典法最大的问题是无视上下文它假设每个词的极性是固定的但“这电影绝了”里的“绝”在网络语境里其实是极高的正面评价被词典硬生生打成负面“这家店真黑”里的“黑”如果不在词典里就会被忽略。更麻烦的是微博上的网络新词和反讽表达词典永远跟不上语料的变化。不过词典法的优势也很明显训练耗时为零、每条预测都有明确的解释路径、结果完全可复现因此非常适合作为对比实验中的基线。后面跑完机器学习和深度学习你会发现一个经典现象词典法虽然指标低但在某些类别上的精确率反而比机器学习更高因为它不会像数据驱动模型那样被标注噪声带偏。4. 传统机器学习词向量、TF-IDF 与分类器调参的完整链路4.1 特征工程词袋模型与 TF-IDF 的取舍传统机器学习无法直接处理中文文本需要先把文本转换成数值向量。这个项目里的ml.py主要使用 TF-IDF 进行特征提取。TF-IDF 和词袋模型的核心区别在于词袋只统计词频高频词会占据主导地位TF-IDF 在词频的基础上乘以逆文档频率如果一个词在大量文档里都出现说明它区分能力弱权重会被削弱。对情感分类任务来说TF-IDF 明显更合理因为像“今天”“我们”这类高频但无情感含义的词会被自动压低。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, f1_score df[clean] df[text].apply(clean_text) X_train, X_test, y_train, y_test train_test_split( df[clean], df[label], test_size0.2, random_state42, stratifydf[label] ) vectorizer TfidfVectorizer(ngram_range(1, 2), max_features5000, min_df2) X_train_vec vectorizer.fit_transform(X_train) X_test_vec vectorizer.transform(X_test)这里几个参数值得细看。ngram_range(1, 2)表示同时保留单个词和相邻两个词的组合比如“不”“好吃”会保留“不好吃”也会作为一个特征出现这对情感分析很重要因为“不好吃”的语义和“好”“吃”单独出现完全不同。max_features5000限制了特征总数避免维度爆炸min_df2表示至少出现在 2 条样本里才保留这个特征这样能过滤掉只出现过一次的噪声词。这两项组合起来能把 10k 数据集的特征维度控制在 5000 以内内存占用很小。4.2 分类器训练朴素贝叶斯与 SVM 的对比思路特征做完之后接下来就是训练分类器。这个项目在ml.py里选用了朴素贝叶斯和线性 SVM 两种模型它们的原理差别很大正好适合放在一起对比。朴素贝叶斯基于条件独立性假设用词在类别下的出现概率来做判断在小样本、稀疏特征场景下表现稳定收敛也快线性 SVM 则是去找一个最大间隔分类超平面对特征间的线性组合更敏感通常在有较多有效特征时表现更好。from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC for name, clf in [(MultinomialNB, MultinomialNB()), (LinearSVC, LinearSVC(C1.0))]: clf.fit(X_train_vec, y_train) y_pred clf.predict(X_test_vec) acc accuracy_score(y_test, y_pred) f1 f1_score(y_test, y_pred, pos_label1) print(f{name}: accuracy{acc:.4f}, f1{f1:.4f})这里要注意MultinomialNB要求输入特征为非负值所以配合 TF-IDF 时必须使用默认的 L2 归一化不能手动加负值特征。LinearSVC的C参数是正则化强度的倒数C越大对训练集的拟合越强但容易过拟合C太小则可能欠拟合。第一次跑的时候可以先保持C1.0如果发现训练集准确率很高但测试集明显下降就说明过拟合了把C往下调。另外打印结果时最好同时输出准确率和 F1因为微博数据的标签往往不太平衡只看准确率会被多数类带偏。4.3 从 10k 到 100k数据规模对机器学习的实际影响项目里同时提供了 10k 和 100k 两份数据这会诱发一个很自然的实验思路分别在两份数据上训练同样的模型看性能提升的幅度。常见规律是从 10k 换到 100k朴素贝叶斯的 F1 可能会有几个百分点的提升但提升幅度会逐渐变小线性 SVM 在数据量增大后训练时间明显变长但准确率未必能超过朴素贝叶斯太多。这个现象本身就可以作为课设报告里的讨论点它反映了机器学习模型在数据规模到一定程度后会遇到收益递减。把ml.py里读取数据的路径从weibo_senti_10k.csv改成weibo_senti_100k.csv时建议同时把max_features调大一些因为数据量越大词汇量越丰富5000 个特征可能会成为瓶颈。我一般会调到 10000 左右保持min_df2不变。如果机器的内存比较紧张可以先看下训练时X_train_vec.shape输出的维度确认是不是真的需要那么多特征。100k 数据的 TF-IDF 矩阵训练时间在普通笔记本上通常只要几十秒不会太夸张。跑完之后对比两个规模的指标你会发现一个对实验报告很有价值的结论模型性能的提升不单靠方法数据规模同样是一个显著变量。5. 避坑指南从运行脚本到性能对比五个反复出现的现实问题5.1 CSV 文件读取乱码或报错怎么排查都找不到原因现象pd.read_csv直接抛UnicodeDecodeError或者数据读出来了但文本全是乱码。原因微博数据包含大量特殊字符和表情文件实际编码和读取时指定的编码不一致很多教材默认用utf-8但实际文件可能是utf-8-sig或其他编码。解决先试着用utf-8-sig读取不行就换成gbk。更稳的办法是先用记事本打开 CSV另存为时直接确认当前文件编码再回到代码里指定对应的编码格式。读回来之后打印前五行确认中文显示正常再做后续处理。5.2 情感词典法在否定句上频繁判反现象测试集里准确率勉强过半细看结果发现凡是带“不”的句子几乎都被判反了“这部电影不好看”被识别成积极。原因很多人写词典法逻辑时把否定词当成一次性翻转开关遇到一个否定词就把整个句子的极性反转遇到“不好看”时这个词翻转后变成正面自然判反。解决回到 3.2 节的逻辑把否定翻转的范围限定到下一个情感词之前遇到情感词结算后就复位。还要考虑“太不”“不是很”这种程度副词与否定词交错的场景先处理程度副词再处理否定翻转顺序不能反。5.3 训练集准确率很高测试集 F1 却低得离谱现象用ml.py跑 10k 数据时训练集准确率超过 95%测试集却只有不到 70%换到 100k 数据也一样。原因模型过拟合了特征工程里max_features设置得太大很多只在训练集出现一次的稀疏特征被当成有效信号模型记住了噪声。解决把min_df从 2 调成 5增大max_features之前先确认有效特征密度同时把LinearSVC的C往小了调降低对训练集噪声的拟合强度。交叉验证是验证过拟合的可靠手段把train_test_split换成cross_val_score重新跑一遍看各折的得分方差大不大。5.4 深度学习模型在 CPU 上训练时间长到怀疑人生现象nn.py跑 100k 数据的时候一个 epoch 要跑好几分钟总共 10 个 epoch 可能需要半个多小时跑完一组对比实验下来半天就没了。原因embedding 层尺寸设置过大词典又全部加载进内存加上 CPU 本身不适合训练大规模深度模型导致每一步计算都非常慢。解决先检查词典截断逻辑把训练语料里出现次数低于 2 次的词过滤掉词典最多保留 50000 个常用词同时把 embedding 维度降到 128 或者 100。如果机器有 NVIDIA 显卡确认nn.py里有没有把模型和数据切到 CUDA 上很多 AI 学习者环境配置齐全但代码里没写.to(device)结果一直用 CPU 跑这个坑在体验中极其常见。5.5 image 目录下的结果图和自己训练的输出对不上现象按照 README 跑完三个脚本生成的图和image目录里的 png 不一样连数值都对不上。原因理解了就好说了——仓库里 image 目录保存的是作者在特定硬件和随机种子下跑出来的结果你本地的 jieba 版本、sklearn 版本、PyTorch 版本和随机种子都会影响最终指标。解决不要追求完全复制作者的数值把重点放在复现趋势上。重新运行的时候在代码里显式固定random_seed42所有涉及随机初始化的地方都传入这个 seed。只要三种方法的相对排序关系和说明书里一致就说明代码跑通了具体数值有波动是正常的。6. 深度学习模型与性能对比验证跑通 nn.py 并看懂四张图6.1 nn.py 里的模型套路与推理逻辑和大部分课设代码一样nn.py里的三个模型都遵循“Embedding 向量化 特征提取 全连接分类”的套路。TextCNN 用多个卷积核提取短文本的 n-gram 局部特征BiLSTM 通过前向和后向两个 LSTM 隐层拼接来捕捉上下文依赖CNN 则用一维卷积做局部窗口的特征映射。三个模型共享同一个预训练或随机初始化的 embedding 层词向量维度一般设置在 100 到 200 之间。训练时先加载data/weibo_senti_100k.csv按 8:2 切分训练集和验证集batch_size 通常设为 64 或 128epoch 设 5 到 10。训练结束后nn.py会输出每个 epoch 的 loss 和验证集 F1并保存训练过程曲线。6.2 对照 image 目录验证实验结论并固定随机种子image目录下有四张图100k.png是三种方法在 100k 数据上的总体性能对比cnn.png、BiLSTM.png、textcnn.png分别是三个模型的 loss 下降曲线。跑完自己的实验后把生成的结果和这几张图对照一下如果三个模型的 loss 都在前几个 epoch 快速下降然后趋于平缓说明训练过程正常如果 loss 曲线尾部明显上扬说明学习率设置过大后期在震荡。100k.png里的对比结论通常是深度学习 F1 略高于传统机器学习词典法最低但差距没有想象中那么大。验证这个结论不需要额外下载依赖只要把ml.py和nn.py在相同测试集上的输出指标放到一张表里就能直接得出自己的对比结论。6.3 性能对比的正确姿势与一个值得注意的习惯做性能对比时我会强制自己在同一个测试集、同一样本切分方式、同一套分词逻辑下跑三个方法。第一次课设时我图省事先单独跑完机器学习再单独预处理一次数据跑深度学习最后发现两个模型的输入文本里分词都不一样对比结果毫无意义只能全部重跑。从那以后我每次拿到情感分析项目都会先写一个公共的tokenize函数数据预处理只做一遍三个方法共享同一份处理结果强制走一遍完整流程再讨论指标。这个习惯帮我在后面的比赛和工作中省下了大量重复调参的时间。希望这篇拆解能帮你少走一点弯路把课设跑通的同时真正搞明白三种方法从工程角度到底差在哪里。本文还有配套的精品资源点击获取
返回列表