ARTICLE DETAIL

资讯详情

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

情感分类大作业实战:词典法、机器学习与深度学习全流程对比

情感分类大作业实战:词典法、机器学习与深度学习全流程对比 简介这份资源是面向高校数据挖掘、机器学习课程学习者的完整课程大作业方案围绕情感分类任务系统整合了情感词典法、传统机器学习与深度学习三条技术路线适合作为期末大作业或课程设计的参考模板也便于有一定基础的同学在此基础上二次开发。压缩包共16个文件约11.84MB包含3个Python源码文件、2个CSV微博情感数据集、6个TXT情感词典与停用词文件、4张模型结构示意图及1份README说明文档覆盖从词典构建、数据预处理到模型训练与结果可视化的完整流程。代码注释较为详尽初学者也能读懂并直接运行。目前已有323人学习下载读者可获得一套可直接复用的情感分类实验框架理解词典法、传统机器学习与CNN、BiLSTM、TextCNN等深度学习模型的实现差异并借助结构图与说明文档快速梳理项目脉络为课程答辩与后续研究提供扎实参考。1. 情感分类大作业的三条路线词典、传统机器学习、深度学习到底怎么选做数据挖掘课程大作业情感分类几乎是出现频率最高的题目。原因很直接数据好找、任务定义清晰、评价指标明确而且能同时塞进数据预处理、特征工程、模型训练、结果分析这一整套流程。但真正动手时大多数人会卡在同一个地方——到底用情感词典法、传统机器学习还是上深度学习三条路线的代码量、调参成本、最终得分差距很大选错了方向后面全是返工。这篇笔记按我自己的实操顺序拆先用情感词典法快速跑通基线再用 sklearn 的传统机器学习把特征工程讲透最后用 PyTorch 搭一个能打的深度学习模型。每一段都有可直接复现的 Python 源码参数怎么设、坑在哪、结果怎么对比都会说清楚。适合正在赶大作业的本科生也适合想系统过一遍文本分类流程的从业者。2. 情感词典法不训练模型也能拿到及格线以上的基线2.1 情感词典法的打分逻辑与适用边界情感词典法的核心思路非常朴素一句话的情感极性等于句中所有情感词的情感得分之和再叠加否定词和程度副词的修正。它不需要标注数据不需要训练拿到词典就能跑。常见的中文情感词典有知网 HowNet 情感词典、大连理工情感词汇本体库、BosonNLP 情感词典等。课程大作业里用大连理工词典或自己整理一份小词典都够用。打分公式一般是这样的对每个情感词查词典得到基础分正面为正、负面为负然后往前看窗口内有没有否定词翻转极性和程度副词乘以权重系数。程度副词按强度分档比如“非常”乘 1.8“有点”乘 0.6“极其”乘 2.0。这个权重表可以自己定也可以参考知网的程度级别表。词典法的优势是快、可解释、零训练成本缺点是遇到反讽、网络新词、领域专有表达就翻车。比如“这手机贵得离谱”和“这手机贵得值”词典法只看“贵”可能判负面但后者其实是正面。所以词典法适合做基线不适合作为最终方案。2.2 用 Python 实现词典打分完整代码与参数说明下面是一份可以直接跑的中文情感词典打分代码。假设你已经有一份情感词典文件sentiment_dict.txt每行格式是“词语 得分”以及一份程度副词表degree_dict.txt每行是“词语 权重”还有一份否定词表negation.txt。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 # 加载程度副词词语 - 权重 def load_degree_dict(path): degree_dict {} with open(path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 2: word, weight parts[0], float(parts[1]) degree_dict[word] weight return degree_dict # 加载否定词 def load_negation_words(path): with open(path, r, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) sentiment_dict load_sentiment_dict(sentiment_dict.txt) degree_dict load_degree_dict(degree_dict.txt) negation_words load_negation_words(negation.txt) def score_sentence(text, window3): 对单句进行情感打分 window: 向前看的窗口大小用于找否定词和程度副词 words list(jieba.cut(text)) total_score 0.0 for i, word in enumerate(words): if word not in sentiment_dict: continue score sentiment_dict[word] # 向前看 window 个词找否定词和程度副词 for j in range(max(0, i - window), i): if words[j] in negation_words: score * -1 if words[j] in degree_dict: score * degree_dict[words[j]] total_score score return total_score def predict(text): score score_sentence(text) if score 0: return positive elif score 0: return negative else: return neutral # 测试 if __name__ __main__: samples [ 这个电影非常好看我很喜欢, 服务态度太差了不会再来了, 还行吧没什么特别的感觉 ] for s in samples: print(s, -, predict(s), score, score_sentence(s))这段代码的逻辑说明score_sentence先分词然后遍历每个词如果命中情感词典就取基础分再往前看window个词遇到否定词就翻转符号遇到程度副词就乘权重。window默认设 3是因为中文里修饰语通常紧挨着情感词窗口太大反而会误伤。如果句子长、修饰语离得远可以调到 4 或 5但准确率不一定升。参数方面程度副词的权重建议按三档设高1.5~2.0、中1.0~1.5、低0.5~1.0。否定词表要包含“不、没、无、非、莫、弗、毋、未、否”等常见词还要注意“不”和“不”开头的词比如“不错”本身是正面词如果被否定词表命中就会翻车。解决办法是把“不错”“不赖”这类词直接放进情感词典并在否定词匹配时排除它们。提示词典法跑完后一定要人工看 20~30 条错例把高频错词补进词典或否定词表这一步能快速把准确率从 60% 拉到 70% 以上。3. 传统机器学习特征工程决定上限分类器只是最后一脚3.1 从文本到向量TF-IDF 与 CountVectorizer 的选型对比传统机器学习做情感分类核心就两步把文本变成向量然后喂给分类器。文本向量化最常用的是词袋模型Bag of Words和 TF-IDF。词袋模型只统计词频TF-IDF 在此基础上乘以逆文档频率降低高频无意义词的权重。选哪个如果文本短、关键词重复多词袋模型够用如果文本长度差异大、有大量常见词TF-IDF 更稳。实际大作业里我一般直接用 TF-IDF因为 sklearn 的TfidfVectorizer支持 n-gram、停用词过滤、最大特征数限制调参空间大效果也普遍比纯词频好。关键参数有三个max_features控制特征维度通常设 5000~20000ngram_range设 (1,2) 能捕捉“不 好看”这种二元否定min_df和max_df过滤太罕见和太常见的词一般min_df2、max_df0.8。停用词表可以用哈工大停用词表也可以直接用 sklearn 的english停用词对中文没用所以中文场景要自己加载。3.2 用 sklearn 跑通朴素贝叶斯、SVM、逻辑回归的完整流程下面是一份完整的传统机器学习情感分类代码包含数据加载、TF-IDF 向量化、三个分类器训练和评估。假设数据是 CSV 格式两列text和label0 负面1 正面。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, accuracy_score import jieba # 加载数据 df pd.read_csv(data.csv) df[text] df[text].astype(str) # 中文分词 def cut_text(text): return .join(jieba.cut(text)) df[cut] df[text].apply(cut_text) # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( df[cut], df[label], test_size0.2, random_state42, stratifydf[label] ) # TF-IDF 向量化 vectorizer TfidfVectorizer( max_features10000, ngram_range(1, 2), min_df2, max_df0.8, sublinear_tfTrue ) X_train_vec vectorizer.fit_transform(X_train) X_test_vec vectorizer.transform(X_test) # 定义分类器 models { Naive Bayes: MultinomialNB(alpha0.1), Linear SVM: LinearSVC(C1.0, max_iter5000), Logistic Regression: LogisticRegression(C1.0, max_iter1000) } # 训练与评估 for name, model in models.items(): model.fit(X_train_vec, y_train) y_pred model.predict(X_test_vec) acc accuracy_score(y_test, y_pred) print(f {name} ) print(fAccuracy: {acc:.4f}) print(classification_report(y_test, y_pred, digits4))逻辑说明先分词再用 TF-IDF 把训练集和测试集分别向量化。注意fit_transform只在训练集上调用测试集用transform否则会数据泄露。三个分类器里朴素贝叶斯最快适合做基线LinearSVC 在文本分类上通常效果最好但输出的是距离不是概率逻辑回归介于两者之间可解释性好能输出概率。参数方面MultinomialNB的alpha是平滑系数设 0.1~1.0 都行数据量大就调小。LinearSVC的C控制正则强度C 越大越容易过拟合文本分类一般设 0.5~2.0。LogisticRegression的C同理另外max_iter要设大一点否则不收敛。注意TF-IDF 的ngram_range(1,2)会让特征数翻倍如果内存不够先降到 (1,1)或者把max_features降到 5000。3.3 交叉验证与网格搜索把准确率再往上推 3 个点单次划分测试集有随机性用 5 折交叉验证更稳。sklearn 的GridSearchCV可以同时搜参数和做交叉验证。下面这段代码在 LinearSVC 上搜C和 TF-IDF 的max_features。from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV pipeline Pipeline([ (tfidf, TfidfVectorizer(ngram_range(1, 2), min_df2, max_df0.8)), (clf, LinearSVC(max_iter5000)) ]) param_grid { tfidf__max_features: [5000, 10000, 20000], clf__C: [0.5, 1.0, 2.0] } grid GridSearchCV(pipeline, param_grid, cv5, scoringf1_macro, n_jobs-1, verbose1) grid.fit(df[cut], df[label]) print(Best params:, grid.best_params_) print(Best F1:, grid.best_score_)这里用f1_macro而不是准确率是因为如果正负样本不均衡准确率会虚高。n_jobs-1用满 CPUverbose1能看到进度。跑完后best_params_就是最优组合直接拿来用即可。我自己的经验是TF-IDF LinearSVC 在 1 万条左右的中文情感数据上F1 通常能到 0.85~0.90比词典法高 15 个点左右。4. 深度学习用 PyTorch 搭一个能打的情感分类模型4.1 为什么选 LSTM 而不是 BERT大作业场景下的取舍深度学习做情感分类可选模型很多TextCNN、LSTM、BiLSTMAttention、BERT。BERT 效果最好但需要预训练模型、GPU 显存、更长的训练时间大作业环境不一定撑得住。TextCNN 快但捕捉长距离依赖弱。BiLSTM 加 Attention 是折中方案效果比 TextCNN 好训练成本比 BERT 低代码量适中适合作为大作业的深度学习部分。如果课程明确要求用 BERT那就用transformers库加载bert-base-chinese微调 3~5 个 epoch效果通常能到 0.92 以上。但要注意BERT 的输入长度限制是 512超长文本要截断。下面重点讲 BiLSTMAttention 的实现因为它的每一部分都能讲清楚原理答辩时好解释。4.2 BiLSTMAttention 的 PyTorch 实现从词表到训练循环完整代码分四块构建词表、定义 Dataset、定义模型、训练循环。假设数据还是 CSV两列text和label。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader import pandas as pd import jieba from collections import Counter # 1. 构建词表 df pd.read_csv(data.csv) df[text] df[text].astype(str) def tokenize(text): return list(jieba.cut(text)) all_tokens [] for text in df[text]: all_tokens.extend(tokenize(text)) counter Counter(all_tokens) vocab {pad: 0, unk: 1} for word, freq in counter.most_common(20000): if freq 2: vocab[word] len(vocab) print(fVocab size: {len(vocab)}) # 2. Dataset 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): tokens tokenize(self.texts[idx]) ids [self.vocab.get(w, self.vocab[unk]) for w in tokens] if len(ids) self.max_len: ids ids[:self.max_len] else: ids ids [self.vocab[pad]] * (self.max_len - len(ids)) return torch.tensor(ids, dtypetorch.long), torch.tensor(self.labels[idx], dtypetorch.long) # 3. 模型 class BiLSTMAttention(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim128, num_classes2, dropout0.3): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.lstm nn.LSTM(embed_dim, hidden_dim, batch_firstTrue, bidirectionalTrue) self.attention nn.Linear(hidden_dim * 2, 1) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_dim * 2, num_classes) def forward(self, x): # x: (batch, seq_len) mask (x ! 0).float() # (batch, seq_len) emb self.embedding(x) # (batch, seq_len, embed_dim) lstm_out, _ self.lstm(emb) # (batch, seq_len, hidden_dim*2) # Attention attn_score self.attention(lstm_out).squeeze(-1) # (batch, seq_len) attn_score attn_score.masked_fill(mask 0, -1e9) attn_weight torch.softmax(attn_score, dim1) # (batch, seq_len) context torch.bmm(attn_weight.unsqueeze(1), lstm_out).squeeze(1) # (batch, hidden_dim*2) out self.dropout(context) return self.fc(out) # 4. 训练 device torch.device(cuda if torch.cuda.is_available() else cpu) print(fUsing device: {device}) from sklearn.model_selection import train_test_split train_texts, test_texts, train_labels, test_labels train_test_split( df[text].tolist(), df[label].tolist(), test_size0.2, random_state42, stratifydf[label] ) train_dataset SentimentDataset(train_texts, train_labels, vocab) test_dataset SentimentDataset(test_texts, test_labels, vocab) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse) model BiLSTMAttention(vocab_sizelen(vocab)).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(10): model.train() total_loss 0 for batch_x, batch_y in train_loader: batch_x, batch_y batch_x.to(device), batch_y.to(device) optimizer.zero_grad() logits model(batch_x) loss criterion(logits, batch_y) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}, Loss: {total_loss/len(train_loader):.4f}) # 评估 model.eval() correct, total 0, 0 with torch.no_grad(): for batch_x, batch_y in test_loader: batch_x, batch_y batch_x.to(device), batch_y.to(device) logits model(batch_x) preds torch.argmax(logits, dim1) correct (preds batch_y).sum().item() total batch_y.size(0) print(fTest Accuracy: {correct/total:.4f})逻辑说明词表只保留词频大于等于 2 的词其余归为unk这样能控制词表大小。SentimentDataset负责把文本转成 ID 序列统一长度到 128短的补 0长的截断。模型部分Embedding 层把 ID 转成向量BiLSTM 提取上下文特征Attention 层给每个时间步打分加权求和得到句子表示最后全连接分类。训练时用 Adam 优化器学习率 1e-3交叉熵损失。参数方面embed_dim和hidden_dim设 128 是常见起点数据量大可以加到 256。dropout设 0.3~0.5 防过拟合。batch_size设 64显存不够就降到 32。max_len设 128如果文本普遍更长调到 256但训练会变慢。学习率如果 loss 震荡降到 5e-4。提示如果训练集准确率一直涨但测试集不涨就是过拟合了优先加 dropout 或减小 hidden_dim而不是加 epoch。5. 避坑与排查情感分类大作业里最容易翻车的 5 个地方现象一词典法准确率只有 50% 多比随机猜好一点。原因情感词典覆盖不够或者否定词窗口设太大把不相干的词也翻转了。 解决先统计测试集里有多少情感词没被词典命中把高频未命中词补进词典窗口从 3 降到 2 试试检查否定词表里有没有“不”单独出现导致“不错”被误判把“不错”“不赖”加入情感词典并设置高优先级。现象二TF-IDF 训练时报内存错误。原因max_features设太大或者ngram_range(1,2)让特征数爆炸。 解决先把max_features降到 5000ngram_range改成 (1,1)跑通后再逐步加。也可以用HashingVectorizer替代它不需要存词表内存占用低。现象三深度学习模型 loss 不下降准确率一直在 0.5 附近。原因学习率太大导致震荡或者词表太小导致大部分词都是unk。 解决学习率从 1e-3 降到 1e-4 试试检查词表大小如果小于 5000说明分词或过滤太狠把min_freq从 2 降到 1另外确认标签是不是 0/1 编码如果是 -1/1 需要先映射。现象四训练集准确率 99%测试集只有 70%。原因过拟合。数据量小、模型参数多、训练轮数太多都会导致。 解决加 dropout0.3~0.5、加 L2 正则weight_decay1e-4、减少hidden_dim、早停验证集 loss 连续 3 轮不降就停。另外检查训练集和测试集有没有重复样本数据泄露也会导致虚高。现象五三个模型结果差不多不知道选哪个写进报告。原因没有统一评估标准或者数据本身区分度低。 解决统一用 5 折交叉验证的 F1-macro 对比画混淆矩阵看错分情况。如果词典法和深度学习只差 2 个点说明数据里情感表达很直接这时候选词典法传统机器学习组合更划算报告里也能体现你对不同方法的理解。6. 把三个模型串成一条流水线对比实验与结果分析的具体做法大作业最后要交报告光有代码不够得有对比实验和结果分析。我一般会做一张三行五列的表行是词典法、TF-IDFLinearSVC、BiLSTMAttention列是准确率、F1-macro、训练时间、推理速度、可解释性。准确率和 F1 用同一份测试集跑训练时间从开始 fit 到结束计时推理速度测 1000 条的平均耗时可解释性按高/中/低主观打分。具体操作上先把三个模型的最佳参数固定下来然后写一个统一的评估脚本输出每个模型的classification_report和混淆矩阵。混淆矩阵用sklearn.metrics.confusion_matrix加seaborn.heatmap画能直观看到哪类错得多。如果负面样本错分成正面多说明否定词处理有问题如果正面错分成负面多说明正面词典覆盖不够。还有一个技巧把三个模型都预测错的样本单独抽出来看。这些样本通常是反讽、隐喻、或者标注本身有争议。在报告里分析这些错例比单纯堆准确率更能体现思考深度。比如“这波操作我给满分”表面是正面实际可能是反讽词典法和传统机器学习都容易翻车深度学习如果训练数据里有类似表达可能能学到。最后说一个我自己的习惯不管多赶一定留半天时间把代码从头到尾在干净环境里跑一遍。我踩过最坑的一次是本地跑通了换到同学电脑上因为 jieba 版本不同分词结果变了准确率掉了 5 个点。后来我固定用requirements.txt锁版本jieba 用 0.42.1sklearn 用 1.3.0torch 用 2.0.1再也没出过这种玄学问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表