首页 / 资讯中心 / 文章详情

情感分类三方法对比:词典法、机器学习与深度学习实战拆解

情感分类三方法对比:词典法、机器学习与深度学习实战拆解 ★ FEATURED ARTICLE
简介这是一份面向数据挖掘、机器学习与深度学习课程作业的情感分类实践项目特别适合零基础学习者完成课设或毕业设计。项目用Python实现了情感词典法、传统机器学习SVM、朴素贝叶斯等和深度学习CNN、BiLSTM、TextCNN等三套可运行方案并通过对比实验展示不同方法在情感判别任务中的准确率与适用场景。压缩包共16个文件以py源码、csv数据集、txt词典与停用词、png结构图、md说明文档为主要构成整体11.84MB内容贯穿数据预处理、特征提取、模型构建、训练与评估等完整流程。目前已有58人学习。配套还附有详细的课设报告和数据集说明情感词典法部分可帮助理解如何构建与使用情感词典机器学习部分涵盖TF-IDF等特征工程深度学习部分则演示神经网络模型的搭建与调参。三套方案的性能对比结果以图表形式保存在png文件中便于直观理解模型差异适合复现实验、撰写课程总结是兼顾学习与提交作业的实用资源。1. 课程大作业里的情感分类三件套词典法、ML、DL到底怎么对比性能这份资源我拆完之后的第一反应是它把情感分类这条路上最典型的三个技术代际一次性打包了——情感词典法、传统机器学习、深度学习而且不是各写各的 demo是同一份微博数据集、同一个分类目标、同样完整的预处理和评估流程最后用准确率、F1 这些指标横向对比。对于正在做课程设计或者毕业设计的同学来说最值钱的地方在于你不需要自己从零去凑代码三套方案的完整 pipeline 都是能直接跑的README 和课设报告把复现路径写得比较清楚。它适合两类人一是机器学习或深度学习课程作业不知道从哪里下手、想拿一份可以复现的完整工程做参考的学生二是已经跑通单一模型、但想看看三种方法在同一份数据上到底差多少的研究型学习者。数据用的是微博情感标注语料10k 和 100k 两个规模都有分别适合快速调试和完整实验。接下来我会按代码文件逐个拆重点讲每个脚本的核心逻辑、参数含义和运行顺序最后把我在复现时遇到的几个坑一并列出来。2. 数据与词表先行先搞懂 weibo_senti 数据集和四份词典文件2.1 weibo_senti_10k 和 100k 的格式与分布进入任何分类项目之前我习惯先把数据集摊开看一遍。这个包里的数据是 CSV 格式核心字段就两列一列是评论文本一列是情感标签。标签是二分类正面和负面各占一半没有中性类别这意味着后面所有模型面对的都是二分类问题评估指标直接用准确率、精确率、召回率、F1 就够用不需要做多分类的混淆矩阵扩展。10k 版本大概一万条样本适合在词典法和传统机器学习上做快速验证训练时间控制在分钟级100k 版本则是十万条主要给深度学习模型用——CNN、BiLSTM 这类模型在数据量小的时候很难体现出优势但到了十万条规模深度模型的文本表征能力就能明显和词典法拉开差距。我在实际使用时是先拿 10k 跑通全流程确认每个脚本都没有问题再切到 100k 做最终的性能对比实验。2.2 词典文件各管什么degree、inverse、stopwords、positive、negative拆开压缩包后词典部分一共有五份文件它们在情感词典法里各自承担不同的职责。我建议先单独读这几份文件理解每个文件的行格式后面看 dict.py 的逻辑会顺畅很多。positive_simplified.txt 和 negative_simplified.txt 是基础情感词表分别存放积极和消极词汇每行一个词。这是情感计算的第一层文档中文本匹配到这些词就对应加分或减分。degree.txt 存放程度副词比如“非常”“极其”“有点”这类词本身不表达情感但会放大或缩小后面情感词的权重通常取值在 0.5 到 2 之间。inverse_words.txt 是反转词表也叫否定词表存放“不”“没”“无”这类词它们会翻转情感极性——比如“不错”是正向但“不太好”就变成了负向。stopwords.txt 是停用词表在特征提取阶段过滤掉“的”“了”“是”这类无信息量的词。这里有个细节值得注意在情感词典法中否定词和程度副词的处理顺序是决定效果的关键。通常的做法是从左到右扫描句子遇到程度副词就把权重累乘遇到否定词就翻转极性。但中文里“不太好看”和“太不好看”的语义差别很大这种连续的否定程度组合就要靠代码里的窗口逻辑去处理不是简单翻转一次就能解决。2.3 数据清洗与分词jieba 在情感分类里的标准用法在进入三套方法之前还有一段公共的前置流程——数据清洗和分词。这个包的做法是先把文本里的 URL、用户、多余空格去掉然后对每条文本做 jieba 分词再用停用词表过滤。虽然代码分成三个脚本但预处理逻辑是一致的这样三套方法在输入数据上才是公平对比的。分词这一步对词典法的影响比对深度学习模型的影响大得多。词典法靠词匹配来累加情感分如果分词粒度不对比如“不高兴”被切成了“不高兴”整体而不是“不”“高兴”否定词反转逻辑就失效了。jieba 默认词典在这个场景下表现一般我一般会在初始化时加载用户自定义词典把情感词典里的词汇加进去强制 jieba 优先按情感词边界切分。代码里如果没做这一步你在复现时可以在 jieba 初始化后加一行jieba.load_userdict(positive_simplified.txt)效果会明显改善。3. 情感词典法实现拆解dict.py 的评分规则与阈值设定3.1 核心评分逻辑正向分加负向分如何叠加程度词和反转词dict.py 实现的是最经典的情感词典打分法整体思路不复杂对一条文本完成分词后逐个匹配情感词表累加情感分数最后用总分正负号判断情感极性。真正让代码有区分度的地方在于它如何处理程度副词和否定词。我拆代码时看到的典型处理方式是维护一个情感分变量初始为 0。遍历分词结果如果当前词是情感词就查看它前面是否有否定词和程度副词。如果前面有否定词情感词的极性反转加分变减分如果同时还有程度副词还要乘上程度权重。举例来说“电影非常不好看”这句话分词后“非常”是程度词权重约 1.8“不好看”由否定词“不”加上情感词“好看”组成那么计算时“好看”本身如果是 2遇到否定词变成 -2再乘上“非常”的 1.8最终约 -3.6。核心代码逻辑类似这样def score_sentence(words, sentiment_dict, degree_dict, inverse_words): score 0.0 i 0 while i len(words): word words[i] if word in sentiment_dict: base_score sentiment_dict[word] degree_weight 1.0 inverse_flag 1 # 向前看两个词判断否定和程度副词 if i 1: if words[i-1] in inverse_words: inverse_flag -1 if words[i-1] in degree_dict: degree_weight degree_dict[words[i-1]] if i 2: if words[i-2] in inverse_words and words[i-1] in degree_dict: inverse_flag -1 score base_score * inverse_flag * degree_weight i 1 return score窗口大小的设置很关键。这里向前看两个词是常见的取值因为中文里“不 程度词 情感词”是高频组合比如“不是很喜欢”。如果窗口开到 3 甚至更大会引入跨分句的误判如果只向前看 1 个词又处理不了“不是很喜欢”这类连续修饰。我在自己的项目里一般把窗口设为 2同时要求否定词和程度词必须紧邻情感词的左侧才生效。3.2 阈值从哪来没有标注数据时能不能用 0 作为判断边界词典法的最后一步是把情感总分映射到分类标签。很多初学者以为阈值设为 0 就万事大吉——总分大于 0 判正向小于 0 判负向。但实际跑下来你会发现大量中性文本或正负混杂的文本总分接近 0直接用 0 做边界会把很多微弱情感样本强行分成两类精度很难看。更稳的做法是在验证集上搜索最优阈值。把训练集的分数的 10 分位和 90 分位之间的区间等分 50 份逐个阈值计算 F1 分数选 F1 最大的那个点作为最终判决边界。这个思路在代码里不一定实现了但如果你要优化词典法性能这是一条值得自己动手改的路径。另外如果某些情感词在词表里不存在比如网络新词“绝绝子”“yyds”词典法的召回率会明显下降这是方法本身的局限不是代码 bug。3.3 词典法在微博语料上的效果预期使用微博 10k 数据集跑通后词典法的准确率通常在 60% 上下浮动好的情况下能到 65%。直观感受是对表达直接、词汇明显的句子判断准确对反讽和隐含情感几乎全部判错。这不是实现的问题而是词典法天然缺少上下文建模能力。所以看这份代码时不要纠结于准确率数字本身重点放在理解打分规则和阈值调优上。4. 传统机器学习进阶ml.py 的特征工程与三类模型对比4.1 文本向量化词袋模型和 TF-IDF 各自的适用边界ml.py 走的是标准传统机器学习流程分词 → 向量化 → 分类器 → 评估。向量化环节一般同时提供两种特征词袋模型CountVectorizer和 TF-IDF。词袋模型统计词频适合文本长度相对均匀、情感词出现频率能代表倾向的场景TF-IDF 则给高频但无区分度的词降权给低频但情感明确的词提权在情感分类任务里通常略优于纯词袋。在调参上我认为有一个参数值得优先关注——min_df。它控制词在所有文档中出现的最少次数默认值 1 意味着词只要出现过一次就进特征矩阵这会让特征维度膨胀到几万维训练变慢且容易过拟合。我一般会把它设为 5即至少在 5 条文档中出现过的词才保留同时配合max_df设置为 0.8过滤出现在超过 80% 文档里的词这些通常是停用词或高频无意义词。另外ngram_range可以试着从(1, 1)调到(1, 2)把相邻词组纳入特征对“不太好”这类否定形容词组合会有正向帮助。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.pipeline import make_pipeline def build_pipeline(model, use_tfidfTrue): if use_tfidf: vectorizer TfidfVectorizer(min_df5, max_df0.8, ngram_range(1, 2)) else: vectorizer CountVectorizer(min_df5, max_df0.8, ngram_range(1, 2)) pipeline make_pipeline(vectorizer, model) return pipeline4.2 三个经典分类器朴素贝叶斯、SVM、逻辑回归在情感分类中的表现差异传统机器学习的分类器选择上这份代码主要会涉及朴素贝叶斯、SVM 和逻辑回归三个模型。它们在不同特征空间下的表现有规律可循。朴素贝叶斯是情感分类的经典基线。它假设特征之间独立这个假设在文本上严格说不成立但因为实现简单、训练极快在小样本上表现稳定且对 TF-IDF 特征有很好的适应能力。我一般把它当作所有实验的第一个跑通模型验证数据链路没有问题。SVM 特别是在 RBF 核下表达能力更强对特征维度和样本非线性能拟合得更好但两个问题很现实一是训练复杂度随样本量上升明显10 万条样本配几万维特征在普通笔记本上可能要跑很久二是 RBF 核的两个参数 C 和 gamma 需要调网格搜索的耗时也是在传统模型里最高的。我的建议是10k 数据上用 RBF 核没问题100k 数据上建议换成线性核。逻辑回归是三者里最容易被低估的。线性分类器在文本这类高维稀疏特征上性能通常不输 SVM而且训练速度快得多还自带概率输出方便后面画 ROC 曲线或做阈值调整。实际对比中逻辑回归和线性 SVM 在微博情感数据上的准确率非常接近但训练时间是 SVM 的几分之一。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 ) # 逻辑回归 TF-IDF pipeline lr_pipeline build_pipeline(LogisticRegression(C1.0, max_iter1000), use_tfidfTrue) lr_pipeline.fit(X_train, y_train) y_pred lr_pipeline.predict(X_test) print(classification_report(y_test, y_pred))参数设置上重点说明两点test_size0.2是常规划分但必须配合stratifyy保证训练集和测试集里正负样本比例一致否则遇到原始数据本身分布不均衡时模型评估会失真max_iter要调大到 1000否则逻辑回归默认 100 次迭代用在 TF-IDF 高维特征上经常无法收敛控制台会直接报ConvergenceWarning。4.3 传统机器学习的性能预期与常见误区在 10k 微博数据上这三个模型的准确率普遍能到 70% 到 75% 区间明显超过词典法的 60% 左右。逻辑回归和线性 SVM 的表现接近朴素贝叶斯通常会略低两三个百分点但差距不会太大。这一阶段能获得的结论是标注数据带来的监督信号比人工规则更可靠特征工程的收益在传统方法中是决定性的。常犯的一个误区是认为 TF-IDF 一定比词袋好。实际上在情感分类短文本场景中很多情感词出现的绝对频率比它在全语料中的权重更有判别力词袋模型表现并不差。我自己的习惯是两个都跑一遍通过交叉验证决定选哪个特征而不是凭经验拍板。5. 深度学习模型落地nn.py 里的 TextCNN、BiLSTM 与训练避坑指南5.1 嵌入层选择与 embedding 矩阵初始化方式深度学习部分的核心脚本是 nn.py它包含三种网络结构CNN、BiLSTM、TextCNN。所有模型的第一步都是把文本转成 ID 序列再经过嵌入层映射成稠密向量。在嵌入层这个环节需要决定两件事一是嵌入维度常见取值是 100 或 200二是预训练词向量如果不使用预训练权重就随机初始化让模型自己学。这个小规模课程项目里没有附带预训练词向量文件所以用随机初始化是可以接受的。但有一个参数值得注意——嵌入层的padding_idx一定要设置为 0这样在训练时填充位置不会产生梯度更新可以稳定训练过程。import torch import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim100, num_filters128, num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.convs nn.ModuleList([ nn.Conv1d(embed_dim, num_filters, kernel_sizesize, paddingsize // 2) for size in [2, 3, 4] ]) self.fc nn.Linear(num_filters * 3, num_classes) def forward(self, x): x self.embedding(x) # (batch, seq_len, embed_dim) x x.transpose(1, 2) # (batch, embed_dim, seq_len) conv_outputs [] for conv in self.convs: conv_out torch.relu(conv(x)) pooled torch.max_pool1d(conv_out, conv_out.size(2)).squeeze(2) conv_outputs.append(pooled) x torch.cat(conv_outputs, dim1) return self.fc(x)TextCNN 的关键机制是三个不同宽度的卷积核并行提取 n-gram 特征。核宽度 2、3、4 分别对应二元、三元、四元词组这比在机器学习阶段手动设置ngram_range更灵活——卷积核能自动学习哪些词组组合最有情感判别力。num_filters128表示每个宽度用 128 个卷积核输出拼接后得到 384 维特征向量再接全连接层。滤波器数量太小时特征表达不足太大则训练变慢且容易过拟合128 在这个数据规模下是稳妥的选择。5.2 BiLSTM 的序列建模能力与训练细节BiLSTM 结构就是双向长短时记忆网络它从左到右和从右到左各扫一遍文本然后把两个方向的隐藏状态拼接。对情感分类来说右到左的信息流非常有用因为“太差了”这种否定情感的语义反向信息流能更清晰地捕捉情感词后面的修饰关系。训练 BiLSTM 有两个值得注意的细节。第一是隐藏层维度常见取hidden_dim128层数一般设 1 到 2 层2 层能捕获更抽象的语义但训练时间倍增且小数据下容易过拟合。第二是池化方式BiLSTM 每个时间步都会输出一个隐藏状态常用的做法是取最后一个时间步的输出但更好的做法是做一个全局最大池化——把每个时间步的隐藏状态在时间维度做 max-pool这样不会漏掉句子中间位置的关键信息。class BiLSTM(nn.Module): def __init__(self, vocab_size, embed_dim100, hidden_dim128, num_layers2, num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue, bidirectionalTrue) self.fc nn.Linear(hidden_dim * 2, num_classes) def forward(self, x): x self.embedding(x) # (batch, seq_len, embed_dim) lstm_out, _ self.lstm(x) # (batch, seq_len, hidden_dim * 2) pooled torch.max_pool1d(lstm_out.transpose(1, 2), lstm_out.size(1)).squeeze(2) return self.fc(pooled)注意全连接层的输入维度是hidden_dim * 2因为双向 LSTM 把正反两个方向的隐藏状态拼在了一起。如果把bidirectionalFalse就要把维度改成hidden_dim。这是改代码时最容易踩的维度不匹配错误。5.3 训练参数边界学习率、batch size、epoch 和早停机制怎么定nn.py 的训练函数一般使用 Adam 优化器和交叉熵损失函数。我在复现中建议这样设参数学习率 0.001batch size 64最大 epoch 数 20并搭配早停机制——连续 3 个 epoch 验证集损失没有下降就提前终止。深度学习模型在小数据集上跑太多 epoch 很容易过拟合微博情感数据本身噪声不小训练集准确率刷到 99% 但验证集只有 75% 的情况经常出现。def train_model(model, train_loader, val_loader, epochs20, lr0.001, patience3): optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.CrossEntropyLoss() best_f1 0 bad_counter 0 for epoch in range(epochs): model.train() for batch_x, batch_y in train_loader: optimizer.zero_grad() output model(batch_x) loss criterion(output, batch_y) loss.backward() optimizer.step() val_f1 evaluate(model, val_loader) if val_f1 best_f1: best_f1 val_f1 torch.save(model.state_dict(), best_model.pt) bad_counter 0 else: bad_counter 1 if bad_counter patience: print(fEarly stop at epoch {epoch 1}) break这里把patience设为 3 是我个人常用的折中。太大模型要继续跑好几个没提升的 epoch 浪费时间太小又容易在训练初期验证集波动时误停。保存验证集指标最优的权重而不是最后一个 epoch 的权重这条经验很关键——深度学习训练后期验证指标波动很大最后一个 epoch 的模型往往不是最优状态。5.4 避坑章节复现中最常踩的五类问题问题一torch 维度不匹配报错模型前向传播直接挂掉。现象是mat1 and mat2 shapes cannot be multiplied。原因绝大多数是 BiLSTM 双向拼接后的维度在后续全连接层没对齐或者 Embedding 层的padding_idx导致输出形状变化。解决方式是先在模型里print(x.shape)逐步打印传播过程中的形状逐层核对期望维度不要直接跑完整训练再排错。问题二训练 loss 下降缓慢几个 epoch 后准确率纹丝不动。原因大概率是文本序列没有做截断或填充同一个 batch 里的序列长度极度不一致导致大量 pad token 参与计算。解决方式是设定max_len为 128超过部分直接截断不足部分填充到统一长度同时确认padding_idx0。问题三GPU 显存不足日志报 CUDA out of memory。原因有两个一是 batch size 设得太大二是max_len太长导致序列矩阵过于庞大。解决方式是 batch size 从 64 降到 32max_len从 256 降到 128。如果还在报错就要检查数据加载时是否用了pin_memoryTrue这个参数在某些机器上的内存拷贝开销也很大。问题四验证集 F1 比训练集低 20 个百分点以上明显过拟合。原因通常是训练轮次过多或模型容量过大而数据量只有 10k。解决方式是缩短max_len减小参数规模、增大dropout到 0.5、提前终止如果还不行就切到 100k 数据集训练深度学习模型在 10k 规模下过拟合是正常现象。问题五分词和预处理不一致导致深度学习结果比机器学习还差。现象是深度学习模型的准确率反而比逻辑回归低。原因很多时候不是模型不行而是深度学习脚本里没有做同等的文本去噪比如 URL、表情符、符号直接保留进了模型输入。解决方式是把三份代码的预处理统一对齐——同样的去噪逻辑、同样的停用词表、同样的 max_len 截断。在做对比实验时保证唯一的变量是模型本身这比调参更重要。6. 性能对比与复现验证三套方法的精确评估技巧与词典法阈值优化6.1 对比实验的公平性控制这个课程大作业最有价值的部分是三套方法的性能对比结论。但对比实验的公平性如果不控制好结论就是废的。我在复现时要求自己做到以下几点三份脚本使用同一切分后的训练集和测试集而不是各自随机切分一次统一分词方式全部用 jieba 加自定义词典评估指标统一输出准确率、精确率、召回率、F1 四项而不仅仅看准确率。from sklearn.metrics import classification_report, f1_score results {} results[dict] evaluate_dict_model(test_texts, test_labels) results[ml_lr] evaluate_ml_model(lr_pipeline, test_texts, test_labels) results[dl_textcnn] evaluate_dl_model(model, test_loader) for name, metrics in results.items(): print(f{name}: {metrics})一个小技巧是用sklearn的classification_report输出完整指标矩阵它会同时给每个类别的精确率、召回率和 F1以及 macro 平均和 weighted 平均。对二分类情感任务我习惯看 macro F1因为它不偏向样本量多的那一类比单一准确率更能反映模型在小类别上的真实表现。6.2 阈值再优化词典法唯一能低成本提升的空间词典法的性能瓶颈不在代码实现而在阈值和词表覆盖度。如果你不想引入机器学习还想把词典法往上推一推可以在验证集上做阈值网格搜索。做法是把每条测试样本的总分算出来后遍历从 0 到 1 的阈值找出 F1 最大化的最优判定点。这个优化的收益通常能提升一到两个百分点的 F1属于词典法里少有的低成本高回报手段。在微博这种短文本领域还可以做一个针对性补充把语料中出现频率高的网络新词人工加入本来就有的情感词表比如“冲”“yyds”“绝绝子”。加入几个高频网络词后情感词典在微博数据上的覆盖率会立刻改观这是一个数据领域特有的 trick。6.3 结论如何写进课设报告不止看准确率还要关注训练成本和可解释性如果你正在写课程设计报告对比章节不要只放一张准确率表格评委想看到的是对三个方法本质差异的分析。站在资源使用者的角度我的建议是按下面几条逻辑组织词典法准确率最低但完全不依赖标注数据推理在毫秒级且每一步判断都有词表依据传统机器学习需要数千条标注数据但训练成本低特征结果能反向分析哪些词对情感判断贡献最大深度学习准确率最高尤其在 100k 数据上能领先传统方法五个百分点以上但需要 GPU、更多的调参工作且模型内部是黑匣子难以解释。课设报告最有说服力的做法是三张折线图第一张展示训练数据规模从 10k 到 100k 时三类方法的准确率变化曲线你会看到深度学习在 10k 时甚至可能不如逻辑回归但到 100k 会明显反超第二张展示训练时间对比词典法几乎瞬间完成机器学习分钟级深度学习小时级第三张展示不同阈值下的 F1 变化曲线用来佐证词典法对阈值敏感。这三张图一放整个课设的深度立刻和网上随便找的代码拉开了距离。对我来说从这次拆包最大的收获是把三份脚本的数据流理清楚之后再跑对比实验得到的结论才是真正可信的。从那以后我每次拿到所谓的多模型对比代码都会强制自己先做一次完整复现把每份脚本的预处理环节对齐、确认评估指标口径一致再开始谈性能差异。这个方法建议你也能用起来希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站