资讯详情

情感分类三路方案:词典法、TF-IDF+SVM与深度学习实战对比

📅 2026/10/30 1:09:33 | 华诺云谱 👁 阅读
情感分类三路方案:词典法、TF-IDF+SVM与深度学习实战对比
简介这份资源是面向高校数据挖掘、机器学习课程学习者与期末大作业需求者的完整情感分类项目源码围绕情感词典法、传统机器学习与深度学习三条技术路线展开适合作为课程设计参考或二次开发基础。压缩包共16个文件约11.84MB包含3个Python源码文件、2个CSV微博情感数据集、6个情感词典与停用词文本、4张模型结构示意图及1份README说明文档覆盖从词典构建、数据预处理到模型训练的全流程。项目已积累324人学习下载代码注释较为完整便于初学者理解。读者可据此掌握基于情感词典的打分规则、传统机器学习分类流程以及CNN、TextCNN、BiLSTM等深度学习模型的实现方式并借助附带的词典文件与微博语料快速复现实验也可在现有结构上替换数据集或调整模型进行扩展研究。1. 从课程大作业到真实项目情感分类三路方案怎么选做数据挖掘课程大作业时情感分类几乎是出现频率最高的题目。原因很直接数据好找、标签明确、评价指标清晰还能把数据挖掘课里学的预处理、特征工程、模型评估串成一条完整链路。但真正动手时多数人会卡在同一个地方——情感词典法、传统机器学习、深度学习这三条路到底选哪条还是全做我见过太多大作业翻车的案例有人直接上 BERT结果环境配了三天最后跑出来准确率还不如词典法也有人只用词典法被老师一句“工作量不够”打回。这篇笔记就按我实际带过几届学生做这个题目的经验把三条路线的原理、代码、参数和踩坑点讲清楚。适合正在做数据挖掘课程大作业的本科生也适合想快速跑通一个中文情感分类 baseline 的工程师。读完你至少能拿到一套可复现的 Python 源码结构知道每条路线的边界在哪。2. 情感词典法不训练模型也能跑出 70% 准确率的基线2.1 词典法的核心逻辑与适用边界情感词典法的思路非常朴素一句话的情感极性等于句中所有情感词的情感得分之和再考虑否定词和程度副词的修饰。比如“这个产品非常好用”里“好”得 1“非常”作为程度副词把权重放大到 1.5最终得分 1.5判为正面。它的优势在于零训练成本、可解释性强、冷启动快。你不需要标注数据不需要 GPU甚至不需要 sklearn。但边界也很明显对反讽、隐喻、网络新词几乎无能为力。“这波操作真是绝了”到底算褒还是贬词典法给不出稳定答案。所以它适合做 baseline或者作为集成模型的一个特征来源不适合作为最终方案单独交差。常见的中文情感词典有知网 HowNet 情感词典、大连理工情感词汇本体库、BosonNLP 情感词典。课程大作业里我一般推荐用大连理工的覆盖度高且格式规整。2.2 用 Python 实现一个可用的词典法分类器下面这段代码是我常用的最小实现依赖 jieba 分词和一份情感词典文件。词典格式假设为每行“词语 极性强度”正面为正数负面为负数。import jieba import re # 加载情感词典格式词语 强度值 def load_sentiment_dict(path): sentiment_dict {} with open(path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 2: word, score parts[0], float(parts[1]) sentiment_dict[word] score return sentiment_dict # 否定词和程度副词表 negation_words {不, 没, 无, 非, 莫, 弗, 毋, 未, 否, 别, 休} degree_words { 非常: 1.8, 极其: 2.0, 十分: 1.6, 特别: 1.7, 很: 1.5, 太: 1.6, 最: 2.0, 更: 1.3, 较: 1.2, 稍微: 0.8, 有点: 0.7, 有些: 0.7 } def score_sentence(text, sentiment_dict): words list(jieba.cut(text)) total 0.0 i 0 while i len(words): word words[i] if word in sentiment_dict: score sentiment_dict[word] # 向前看两个词处理否定和程度修饰 for j in range(max(0, i-2), i): if words[j] in negation_words: score * -1 if words[j] in degree_words: score * degree_words[words[j]] total score i 1 return total def predict(text, sentiment_dict): score score_sentence(text, sentiment_dict) if score 0: return 1 # 正面 elif score 0: return 0 # 负面 else: return 2 # 中性 if __name__ __main__: sd load_sentiment_dict(sentiment_dict.txt) test_cases [ 这个产品质量非常好物流也很快, 完全不推荐用了两天就坏了, 还行吧没什么特别的感觉 ] for t in test_cases: print(t, -, predict(t, sd))逻辑说明score_sentence遍历分词结果遇到情感词时向前回溯两个位置检查是否有否定词或程度副词。否定词翻转符号程度副词按倍数放大。这个窗口设为 2 是经验值设太大容易误判设太小会漏掉“不是特别好”这类结构。参数说明degree_words里的倍数是可调的不同词典和语料下最优值不同。我一般先用默认值跑一遍看混淆矩阵里哪类错得多再针对性调整。如果负面样本被大量误判为正面说明否定词处理不够可以把回溯窗口扩到 3。2.3 词典法的评估与常见误判分析跑完上面代码在一个 5000 条的中文评论数据集上词典法通常能拿到 65% 到 75% 的准确率。别小看这个数字很多学生第一次跑深度学习模型也就 80% 出头但花的时间是词典法的几十倍。误判主要集中在三类一是网络新词比如“绝绝子”“yyds”词典里没有二是反讽句“这服务真是好得让我想投诉”三是长句中的情感转移“虽然外观不错但性能太差”词典法会把“不错”和“差”简单相加可能得出中性。针对第一类可以在预处理阶段加一个新词发现模块第二类目前没有低成本解法只能靠模型第三类可以通过分句后加权来缓解。3. 传统机器学习TF-IDF 加 SVM 的稳定组合3.1 为什么 TF-IDF 加 SVM 仍然值得做深度学习火了好几年但在情感分类这个任务上TF-IDF 加线性 SVM 依然是一个很难被大幅超越的强基线。尤其在数据量小于一万条时SVM 的表现往往和 BERT 微调差不多训练时间却只要几秒。课程大作业里老师看的是你完整的实验对比不是单纯刷高准确率。把 SVM 作为传统机器学习代表和词典法、深度学习形成三档对比逻辑上非常完整。TF-IDF 的核心是给每个词算一个权重这个词在当前文档里出现越多、在整个语料里出现越少权重越高。SVM 则是在高维空间里找一个超平面把正面和负面样本分开。两者结合对中文情感分类这种中等维度、线性可分性较好的任务非常合适。3.2 从原始文本到 TF-IDF 特征矩阵的完整流程下面这段代码覆盖了从读取数据、分词、去停用词到生成 TF-IDF 矩阵的全过程。假设数据是 CSV 格式两列text和labellabel 为 0 或 1。import pandas as pd import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split from sklearn.svm import LinearSVC from sklearn.metrics import classification_report, accuracy_score # 读取数据 df pd.read_csv(data.csv, encodingutf-8) print(样本数:, len(df), 正负比例:, df[label].value_counts().to_dict()) # 加载停用词 def load_stopwords(path): with open(path, r, encodingutf-8) as f: return set([line.strip() for line in f if line.strip()]) stopwords load_stopwords(stopwords.txt) # 分词并去停用词 def preprocess(text): words jieba.cut(str(text)) return .join([w for w in words if w not in stopwords and len(w) 1]) df[seg] df[text].apply(preprocess) # 划分训练集和测试集stratify 保证标签比例一致 X_train, X_test, y_train, y_test train_test_split( df[seg], df[label], test_size0.2, random_state42, stratifydf[label] ) # TF-IDF 向量化 vectorizer TfidfVectorizer( max_features5000, # 保留权重最高的 5000 个词 ngram_range(1, 2), # 同时考虑单字和双字组合 min_df3, # 至少在 3 个文档中出现才保留 max_df0.9 # 出现在超过 90% 文档中的词丢弃 ) X_train_vec vectorizer.fit_transform(X_train) X_test_vec vectorizer.transform(X_test) # 训练 LinearSVC clf LinearSVC(C1.0, max_iter2000, class_weightbalanced) clf.fit(X_train_vec, y_train) # 评估 y_pred clf.predict(X_test_vec) print(准确率:, accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names[负面, 正面]))逻辑说明preprocess里过滤掉长度小于等于 1 的词是因为中文单字很多是噪声比如“的”“了”“是”。TfidfVectorizer的ngram_range(1,2)让模型能捕捉“不 好”这种双词组合对否定句有帮助。class_weightbalanced在正负样本不均衡时自动调整权重避免模型偏向多数类。参数说明max_features5000是经验值数据量小可以降到 3000数据量大可以升到 10000。C是 SVM 的正则化参数C 越大越容易过拟合C 越小越容易欠拟合。我一般从 1.0 开始在验证集上按 0.1、0.5、1.0、2.0 网格搜一下。min_df3能过滤掉只出现一两次的稀有词减少噪声。3.3 参数调优与交叉验证的实操细节上面代码跑完在一个 10000 条左右的电商评论数据集上准确率通常在 82% 到 88% 之间。想再往上提重点调三个地方。第一是分词粒度。jieba 默认模式会把“性价比高”切成“性价比”和“高”但有些领域词需要自定义词典。比如“续航”在手机评论里是一个关键情感载体如果被切碎TF-IDF 就抓不到。可以在 jieba 加载时用jieba.load_userdict(userdict.txt)补充。第二是 TF-IDF 的sublinear_tf参数。默认是 False设为 True 后词频取对数能抑制高频词的权重。在评论数据上我一般会打开准确率通常有 0.5 到 1 个点的提升。第三是交叉验证。不要只跑一次 train_test_split用cross_val_score跑 5 折看均值和方差。如果方差超过 2 个点说明数据划分影响太大需要检查是否有重复样本或标签泄漏。4. 深度学习用 BiLSTM 和 CNN 把准确率推到 90% 以上4.1 深度学习方案选型CNN、LSTM 还是预训练模型到了深度学习这一档选型就多了。课程大作业里常见的有三种TextCNN、BiLSTM、BERT 微调。我的建议是如果 GPU 资源有限或者时间紧优先做 TextCNN 加 BiLSTM 的对比如果追求最高准确率且能联网下载预训练权重再上 BERT。TextCNN 用不同尺寸的卷积核捕捉局部 n-gram 特征训练快参数少。BiLSTM 通过双向序列建模捕捉长距离依赖对否定句和转折句更友好。BERT 微调效果最好但需要 8G 以上显存训练时间也长。课程大作业里把 TextCNN 和 BiLSTM 都实现一遍再和 SVM 对比工作量已经足够。4.2 用 PyTorch 搭建 TextCNN 和 BiLSTM 的代码模板下面是一个统一的训练框架通过model_type参数切换 TextCNN 和 BiLSTM。依赖 torch、numpy 和 sklearn。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader import numpy as np from collections import Counter # 构建词表 def build_vocab(texts, max_size20000, min_freq2): counter Counter() for text in texts: counter.update(text.split()) vocab {pad: 0, unk: 1} for word, freq in counter.most_common(max_size): if freq min_freq: vocab[word] len(vocab) return vocab # 数据集类 class SentimentDataset(Dataset): def __init__(self, texts, labels, vocab, max_len128): self.texts texts self.labels labels self.vocab vocab self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): words self.texts[idx].split()[:self.max_len] ids [self.vocab.get(w, 1) for w in words] ids ids [0] * (self.max_len - len(ids)) return torch.tensor(ids), torch.tensor(self.labels[idx]) # TextCNN 模型 class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim128, num_filters100, filter_sizes[2,3,4], num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.convs nn.ModuleList([ nn.Conv2d(1, num_filters, (fs, embed_dim)) for fs in filter_sizes ]) self.dropout nn.Dropout(0.5) self.fc nn.Linear(num_filters * len(filter_sizes), num_classes) def forward(self, x): x self.embedding(x) # [B, L, D] x x.unsqueeze(1) # [B, 1, L, D] x [torch.relu(conv(x)).squeeze(3) for conv in self.convs] x [torch.max_pool1d(c, c.size(2)).squeeze(2) for c in x] x torch.cat(x, dim1) x self.dropout(x) return self.fc(x) # BiLSTM 模型 class BiLSTM(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim128, num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm nn.LSTM(embed_dim, hidden_dim, batch_firstTrue, bidirectionalTrue) self.dropout nn.Dropout(0.5) self.fc nn.Linear(hidden_dim * 2, num_classes) def forward(self, x): x self.embedding(x) out, _ self.lstm(x) out torch.max_pool1d(out.transpose(1, 2), out.size(1)).squeeze(2) out self.dropout(out) return self.fc(out) # 训练函数 def train_model(model, train_loader, val_loader, epochs10, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) optimizer optim.Adam(model.parameters(), lrlr) criterion nn.CrossEntropyLoss() best_acc 0.0 for epoch in range(epochs): model.train() for ids, labels in train_loader: ids, labels ids.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(ids), labels) loss.backward() optimizer.step() # 验证 model.eval() correct, total 0, 0 with torch.no_grad(): for ids, labels in val_loader: ids, labels ids.to(device), labels.to(device) preds model(ids).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) acc correct / total print(fEpoch {epoch1}, Val Acc: {acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_model.pt) return best_acc逻辑说明TextCNN 用三种尺寸的卷积核分别提取 2 元、3 元、4 元词组特征再拼接后分类。BiLSTM 用双向 LSTM 编码序列取每个时间步的最大值作为句子表示。两个模型都加了 dropout 防止过拟合。参数说明embed_dim128是常见起点数据量大可以加到 256。num_filters100控制卷积核数量太多容易过拟合。max_len128覆盖大多数评论长度超过的截断不足的补零。学习率1e-3配合 Adam 是稳妥选择如果 loss 震荡可以降到5e-4。4.3 训练过程中的显存、过拟合与早停策略深度学习部分最容易翻车的地方不是模型结构而是训练过程。显存不够是最常见的报错CUDA out of memory时先把 batch_size 从 64 降到 32 或 16再考虑缩短 max_len。如果还不行检查是不是在验证阶段没有加torch.no_grad()导致计算图一直累积。过拟合的判断标准是训练集准确率持续上升但验证集准确率下降。这时候加 dropout 比例、加 L2 正则、或者直接早停。我一般设 patience3连续 3 个 epoch 验证集不提升就停。另外BiLSTM 比 TextCNN 更容易过拟合因为参数量更大在小数据集上尤其明显。还有一个玄学问题同样的代码不同随机种子跑出来的结果可能差 2 到 3 个点。解决办法是固定种子并且用 3 个种子各跑一次取平均。虽然费时间但报告里写出来的数字更可信。5. 避坑与排查三条路线各自最容易翻车的地方5.1 词典法否定词窗口和词典覆盖度现象句子“我不是很喜欢这个”被判为正面。原因否定词“不”和情感词“喜欢”之间隔了“是”和“很”如果回溯窗口只有 1就抓不到否定关系。解决把回溯窗口扩到 2 或 3同时把“不是”作为一个整体否定词加入词表。现象大量网络评论被判为中性。原因情感词典没有收录“绝绝子”“yyds”“踩雷”这类新词。解决从训练集里统计情感词人工补充进词典或者用 TF-IDF 权重最高的词做候选人工筛选后加入。5.2 传统机器学习标签泄漏和特征维度爆炸现象SVM 准确率异常高达到 98% 以上。原因预处理时把标签信息泄漏到了特征里比如数据里混入了“正面”“负面”这样的词。解决检查原始数据把标签相关词加入停用词表并且在划分训练测试集之后再 fit TF-IDF。现象训练时报错ValueError: X has N features, but LinearSVC is expecting M features。原因对训练集和测试集分别 fit 了 TF-IDF导致特征维度不一致。解决只在训练集上 fit测试集用 transform。5.3 深度学习分词器与预训练权重不匹配现象加载 BERT 时报错OSError: Cant load weights。原因模型名称写错或者本地缓存路径不对。解决确认模型名称和 transformers 版本匹配国内环境可以先下载到本地再指定路径。现象BiLSTM 训练 loss 不下降。原因学习率太大导致梯度爆炸或者词表太小导致大量词被映射为 unk。解决把学习率降到 1e-4检查词表大小如果小于 5000 说明分词或过滤太激进。5.4 数据层面样本不均衡与重复样本现象模型把所有样本都预测为多数类准确率看着不低但 F1 很差。原因正负样本比例超过 5:1。解决用class_weightbalanced或者在深度学习里用 WeightedRandomSampler 重采样。现象验证集准确率波动极大。原因数据里有重复样本训练集和验证集出现了同样的文本。解决在划分数据集之前先 drop_duplicates并且用去重后的数据重新划分。6. 把三路结果串成一张对比表大作业报告怎么写才站得住三条路线都跑通之后最后一步是把结果整理成可对比的表格。这张表是你大作业报告的核心也是老师判断你工作量和技术深度的直接依据。我一般会按下面这个结构来组织。方案准确率F1 值训练时间依赖资源适用场景情感词典法68%-75%0.65-0.72无需训练词典文件冷启动、baselineTF-IDF SVM82%-88%0.80-0.86秒级CPU中小数据集、快速迭代TextCNN86%-90%0.85-0.89分钟级GPU 可选中等数据集、局部特征BiLSTM88%-92%0.87-0.91分钟级GPU 推荐长文本、否定转折BERT 微调92%-96%0.91-0.95小时级GPU 必需追求最高精度表格里的数字是区间不是固定值因为不同数据集上差异很大。写报告时不要只写一个数字要写“在 XX 数据集上经过 5 折交叉验证准确率为 XX±XX”。这样才显得专业。还有一个技巧把词典法的得分作为特征拼接到 TF-IDF 向量后面再喂给 SVM。这个混合方案我试过多次通常能在纯 SVM 基础上再提 1 到 2 个点。代码改动很小在TfidfVectorizer输出后加一列词典得分即可。这个点写进报告里能体现你对特征工程的理解比单纯堆模型更有说服力。最后说一个我自己的习惯每次跑完实验不管结果好坏先把随机种子、参数配置、数据集版本记在一个 markdown 文件里。大作业周期通常两三周中间隔几天再回来跑没有记录根本复现不了当时的结果。这个习惯帮我省了无数次后悔药。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑