
简介一份面向本科毕业设计与社会媒体文本情感分析学习者的完整代码与说明包。项目以Python 3.5实现综合运用情感字典与朴素贝叶斯、支持向量机、神经网络等机器学习模型对用户评论及帖子进行正面、负面、中立倾向判定覆盖数据预处理、特征提取、模型训练与评估全流程。压缩包共87个文件以46个py源码为主配以33个pyc编译文件与少量xml工程配置、txt说明文档整体仅52KB结构按sentimentdictionary、bayesian、neuralnetwork、utils等模块清晰组织便于对照学习和二次开发。已有61人学习下载。资料中特别记录了voca_dict.csv第43123行出现空值时的替换处理方案并包含readme与标签文件可帮助读者规避Pandas转换报错、更快跑通情感分析实验。1. 情感分析毕业设计不是刷榜词典与机器学习搭起来才是完整方案把情感分析作为本科毕业设计最怕的不是模型跑不起来而是答辩时被问一句你这个和网上的教程有什么区别社交媒体文本中的情感分析说难不难说简单也不简单——直接用预训练模型刷分很快但解释不清每一步为什么这样做只用情感字典打分又会被新词和反讽打得毫无还手之力。我建议把这个题目做成一条完整流水线情感字典先兜底机器学习再补盲区最后用错误分析把两条路的结果串起来。这套方案适合正在做毕设、课程设计或者想在项目里快速加一个文本分析模块的人数据量几千条也能跑成本主要在标注和调参。2. 先跑情感字典基线预处理、分词与情感词的坑2.1 数据准备抓取、清洗与人工标注的取舍社交文本和新闻语料完全是两种东西。评论、微博、短评里充斥着URL、用户、表情符号和重复字符直接喂给后面的词典或模型都会产生噪声。我当时用的是公开爬取的餐饮评论大约两万条先做了两层清洗第一层去掉HTML标签和超链接第二层压缩重复字符。压缩这一步容易被忽略但“好吃好吃好吃好好吃”如果不处理词典匹配时同一个词会重复计数情感分被无脑放大。我的处理方式是保留两个重复既不丢失叠字的情感强调又避免无意义的重复计数。import re def clean_text(raw: str) - str: raw re.sub(rhttps?://\S, , raw) # 去URL raw re.sub(r[\w\u4e00-\u9fa5], , raw) # 去用户 raw re.sub(r(.)\1{2,}, r\1\1, raw) # 压缩连续重复字符 raw re.sub(r\s, , raw).strip() return raw清洗逻辑里有一个参数值得你单独拎出来写进论文(.)\1{2,}压缩的是三个及以上相同字符保留两个。为什么不是全压成一个“哈哈哈”和“哈哈”的情感强度其实不一样全压会丢失语气保留两个是经验值。处理完之后还要决定标注策略。两万条没有标签不可能全人工标我的做法是抽500条让两位同学按“正向/负向/中性”三分类独立标注不一致的样本讨论后定夺。这里要注意中性类在后面对比词典法和机器学习时非常有用但初版可以先把中性并入负向或者直接去掉减少标注压力。2.2 情感字典的构建与打分逻辑情感字典法的核心不是“拿到一张词表”而是“把词表变成一套可解释的加减分规则”。常见做法是用现成的中文情感极性词表作为底表再补充一批社交口语词比如“绝绝子”“yyds”“下头”这些旧词表基本覆盖不到。词典法打分时最朴素的方式是正向词加一分、负向词减一分累加结果就是情感分。但社交文本里“不太好吃”“超级好吃”这种带有程度副词和否定词的结构朴素累加很容易判反。import jieba POS_LEX set() NEG_LEX set() # 程度词的倍率0.5弱化0.8略强1.5很2.0强烈 DEGREE_WORDS {有点: 0.5, 比较: 0.8, 很: 1.5, 太: 2.0, 超级: 2.0} NEG_WORDS {不, 没, 无, 别, 莫} def lexicon_score(text: str, window: int 3) - float: words [w for w in jieba.cut(text) if w.strip()] score 0.0 neg_remain 0 # 剩余多少词处于否定翻转状态 degree 1.0 # 当前累积程度倍率 for w in words: if w in NEG_WORDS: neg_remain window continue if w in DEGREE_WORDS: degree DEGREE_WORDS[w] continue if w in POS_LEX: score degree * (-1 if neg_remain 0 else 1) elif w in NEG_LEX: score - degree * (-1 if neg_remain 0 else 1) if neg_remain 0: neg_remain - 1 degree 1.0 # 程度词只作用于下一个情感词 return score这里两个参数是词典法的关键。window3表示否定词后面三个词之内出现的情感词都要翻转极性“不太好吃”里“不”影响“太”和“好吃”两个词就能覆盖设成3反而更稳。如果设得太大像“虽然不怎么样但是环境好”这种转折句后半句的“好”也会被翻转这是词典法最经典的翻车现场后面避坑章会专门说。DEGREE_WORDS的倍率是经验值你可以用 2.0 表示强烈程度但别设置成线性累乘——同一个词“太太好吃”在自然语言里并不存在词典法不需要处理这种极端输入。2.3 把词典法跑出可复现的基线分数词典法跑通之后别急着上机器学习先把它的分数记录下来。这个分数有两个用处一是证明你的情感字典对测试集有效二是给机器学习模型提供一个必须超越的底线。评估指标不要只看准确率社交媒体正负样本往往不平衡准确率会骗人我一般用F1和混淆矩阵一起看。from sklearn.metrics import classification_report, confusion_matrix def predict_by_lexicon(texts, th0.0): preds [] for t in texts: s lexicon_score(t) preds.append(1 if s th else 0) return preds preds predict_by_lexicon(val_texts, th0.0) print(classification_report(y_val, preds, target_names[负, 正])) print(confusion_matrix(y_val, preds))阈值th默认取0也就是情感分大于0判正向小于0判负向。调阈值是词典法唯一不需要重新训练就能改变召回率的旋钮如果你想少漏正向评论就把阈值调到 -0.5让更多弱正向文本被划入正向如果你想保证高精度就调到 0.5。我在自己的数据集上把词典法基线跑到了0.74左右的F1不高但它把“哪些样本词典法搞不定”这个问题暴露得很清楚——那些被判定为中性但人工标注为正向的样本往往含有新词、反讽或者复合句式。这就是下一步机器学习的切入点。3. 机器学习接手特征工程与模型调参的落地路径3.1 文本向量化TF-IDF与Word2Vec怎么选到了机器学习这一步首先要把文本变成向量。社交媒体情感分析里最常见的特征是TF-IDF理由不是因为它效果最好而是它稳定、可解释、和线性模型配合得好。Word2Vec能把词映射成稠密向量但需要足够大的同领域语料才有意义用别人在维基百科上预训练好的词向量处理“yyds”“绝绝子”这类口语词效果往往不如直接算TF-IDF。我当时选了TF-IDF做基线词向量作为备选方案写在论文的“后续工作”里。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline pipeline Pipeline([ (tfidf, TfidfVectorizer( ngram_range(1, 2), max_features20000, min_df2, sublinear_tfTrue )), (clf, LogisticRegression(C1.0, class_weightbalanced, max_iter1000)) ])四个参数里最值得调的是ngram_range。社交文本的词序信息比新闻语料更稀疏“不好吃”如果只按单个词“不好”“吃”来算情感极性很容易丢失把范围扩到(1, 2)就能抓到“不好吃”“太棒了”这样的二词短语信号。min_df2过滤掉只出现一次的词汇这些词多半是拼写错误或者人名留着只会增加维度。sublinear_tfTrue用 1log(tf) 平滑词频避免“哈哈”这种高频词把情感分全部吸走。class_weightbalanced是为了应对正负样本不平等让逻辑回归自动调高少数类的权重。3.2 模型选型从朴素贝叶斯到LightGBM的梯度模型选型上我的建议是按照“朴素贝叶斯 → 逻辑回归 → 可选LightGBM”这条路线走。朴素贝叶斯是情感分析的传统选手它假设词与词之间相互独立——这个假设在语言里显然不成立但它训练快、在小样本上表现稳很适合作为第一个跑通的模型。周志华《机器学习》那本书里对朴素贝叶斯的独立性假设讲得很清楚做毕设时可以直接引用这个观点来解释“为什么一个假设不成立的模型反而能用”。逻辑回归则是目前情感分析任务里性价比最高的模型它给出的是概率而非只给标签之后调阈值、做置信度过滤都要靠这个概率输出。from sklearn.model_selection import GridSearchCV, TimeSeriesSplit param_grid { tfidf__ngram_range: [(1, 1), (1, 2), (1, 3)], tfidf__max_features: [5000, 10000, 20000], clf__C: [0.1, 1.0, 10.0], } search GridSearchCV( pipeline, param_grid, cvTimeSeriesSplit(n_splits5), scoringf1, n_jobs-1 ) search.fit(train_texts, train_labels)这段网格搜索里有三个细节值得说明。第一cv用的是TimeSeriesSplit而不是普通的KFold因为社交话题随时间漂移随机划分会让同一条话题下的相似文本同时出现在训练集和测试集里F1虚高。第二scoringf1而不是accuracy理由和前面一样不平衡数据上F1才是真实能力。第三clf__C是逻辑回归正则强度的倒数C越小正则越强社交数据维度高噪音大我试下来 C1.0 附近比较稳。3.3 把F1推上去特征拼接与阈值微调机器学习模型跑通之后涨点最快的不是换更大模型而是往特征里补充文本之外的信息。社交文本的情感信号有相当大一部分藏在标点和表情里“这也太好吃了吧”和“这也太好吃了吧”情感强度完全不同但TF-IDF对这两个句子提取到的词特征是几乎一样的。我的做法是手工构造一维特征拼到TF-IDF矩阵右边。from scipy import sparse import numpy as np def extra_features(texts): rows [] for t in texts: rows.append([ len(t), t.count(), t.count(), t.count() t.count(哈哈), len([w for w in jieba.cut(t) if w in NEG_LEX]) ]) return np.array(rows) X_tfidf tfidf.fit_transform(train_texts) X_extra extra_features(train_texts) X_train sparse.hstack([X_tfidf, sparse.csr_matrix(X_extra)], formatcsr)注意这段代码里X_extra的计算不依赖训练集不会造成数据泄漏。这五个特征里t.count() t.count(哈哈)是社交文本特有的强信号表情符号本身就是情感标签这在后面的避坑章节还会详细展开。拼接之后模型从只看到“哪些词出现了”变成同时看到“这句话有多长、多激动、多少负面词”涨2到3个百分点的F1很常见。阈值微调则是一个完全独立于模型的操作。逻辑回归的predict_proba输出的是概率默认按0.5切分正负类。但我发现验证集上概率在0.4到0.6之间的样本错误率最高把阈值调成0.55让模型“宁可说不知道也不乱猜”F1反而涨了。这个调参过程建议画一条P-R曲线来选阈值比肉眼瞎猜靠谱得多。4. 四条血泪避坑社交文本情感分析的常见翻车点4.1 现象模型分数很高一上线就废我遇到过最困惑的事是在随机划分的测试集上F1跑到0.92模型看起来完美可拿新一天的数据去预测F1直接掉回0.80。后来发现原因是社交话题随时间漂移。餐饮评论里“网红店”这个词在某段时间大量出现随机划分数据时同一条话题下的帖子会被分到训练集和测试集两边模型相当于偷偷见过“答案”。原因不在模型在数据划分方式。解决方法是把数据按时间排序后用TimeSeriesSplit交叉验证并且在新数据集上留出最后一段做最终测试模拟真实上线后的场景。毕设答辩时能主动说出这个坑比报一个0.92的分数更有说服力。4.2 现象表情符号被分词器弄没了“哈哈哈”是社交文本里最典型的情感表达但我第一次跑词典法时这段文本被jieba切成了“哈哈哈”“”“”“”然后情感字典匹配不上任何词得分为零。问题出在预处理阶段清洗时没有保留全角表情而分词器对emoji的处理是当成普通符号直接丢弃。解决方法是把表情符号替换成占位标记让词典法能识别它并且这种标记本身可以当作一个特征词加入情感字典。EMOJI_MAP { : [LAUGH], : [CRY], : [ANGRY], : [LOVE], : [GOOD], } def replace_emoji(text: str) - str: for e, tag in EMOJI_MAP.items(): text text.replace(e, f {tag} ) return text替换之后把[LAUGH]、[LOVE]、[GOOD]加入正向情感词集合[CRY]、[ANGRY]加入负向情感词集合。这个操作不仅补上了词典法的盲区机器学习那边也更喜欢这种显式标记——它把原本会被拆碎的emoji变成了一个稳定的特征维度。注意一点replace_emoji必须在clean_text之后、jieba.cut之前调用顺序错了表情已经被分词器丢了再替换就晚了。4.3 现象否定句被词典法判反“这个味道不怎么样但环境挺好的”这句话人工标注是正向词典法给出的是负向。原因在于词典法的否定翻转是局部操作——neg_remain的作用范围只有3个词“不”管住了“怎么样”“环境挺好的”在后半句根本不受影响。但问题恰恰出在转折句式上前半句“不怎么样”是负向后半句“挺好的”是正向整句的情感由后半句决定。我的解决方法是给转折词单独建一个表遇到“但是”“不过”“可是”就把前面的情感得分乘一个衰减系数比如0.3让后半句主导全局。这个改动会让“虽然不好吃但是便宜”这类复杂的复合句从误判变成正确。TURN_WORDS {但是: 0.3, 不过: 0.3, 可是: 0.3} def lexicon_score_v2(text: str, window: int 3) - float: words [w for w in jieba.cut(text) if w.strip()] score 0.0 part_scores [] part 0.0 for w in words: if w in TURN_WORDS: part_scores.append(part * TURN_WORDS[w]) part 0.0 continue # 原有的否定和程度词逻辑不变但累加到 part 上 part 0.0 # 这里省略具体加分逻辑结构上按分句累加 part_scores.append(part) return sum(part_scores)这段代码展示的是结构思路真正实现时把之前lexicon_score的加分逻辑替换掉part 0.0那一行。核心变化是遇到转折词就把当前分句的得分衰减后存档最后取总和。经验值是0.3设成0会把前半句完全丢弃丢掉了“但是”前后的对比信息。4.4 现象数据泄漏藏在“预处理”里这是最隐蔽、也最容易在答辩时被拆穿的问题。很多教程会教你“先用TF-IDF把整个数据集向量化再做交叉验证”这个流程是错的。TfidfVectorizer的计算包含词频统计和IDF权重计算如果在划分数据集之前对整个数据集做fitTF-IDF的词表就已经看到了测试集的信息交叉验证得到的F1会虚高。我当时第一次跑网格搜索的时候也踩过这个坑换用Pipeline之后分数回落了4个百分点——回落之后的分数才是真实水平。# 错误做法全量fit后交叉验证 vec TfidfVectorizer() vec.fit(all_texts) # 泄漏词表和IDF看过测试集 X_all vec.transform(all_texts) cross_val_score(clf, X_all, y_all, cv5) # 分数虚高 # 正确做法把所有流程包进Pipeline pipeline Pipeline([ (vec, TfidfVectorizer()), (clf, LogisticRegression()) ]) cross_val_score(pipeline, all_texts, all_labels, cvTimeSeriesSplit(5))正确的做法是让交叉验证在每一折内部重新fit向量化器Pipeline 就是为此设计的。你在写毕设论文时数据划分部分一定要写清楚“训练集和测试集分别独立进行特征提取”这一句话就能证明你理解泛化的本质。5. 收尾验证与进阶用错误样本反推模型边界5.1 做一个带置信度的预测接口最后一件事把模型的可信度变成实际可用的输出。逻辑回归的predict_proba给出了情感概率但社交文本里总有模型搞不定的灰色地带。我在预测脚本里加了一个两段式判断概率大于0.8判正向小于0.2判负向中间部分输出“待人工复核”。prob pipeline.predict_proba(text)[:, 1] if prob 0.8: label 正 elif prob 0.2: label 负 else: label 待人工复核这个阈值不是拍脑袋定的而是根据验证集上的P-R曲线选的——精确率和召回率交叉点附近的区间就是错误率最高的区域。把低置信度样本抽出来逐条看错误原因比继续调参更有价值。我当时的错误样本里有一大批是反讽比如“这家店服务太好了等了一个小时呢”“好”字拿到高分但整句话是负向。模型不可能靠纯文本轻易解决反讽知道边界在哪里比假装全对更重要。5.2 多模态情感分析的切入点如果你还有精力往更深的方向做一步可以试试把情感分析和图片、视频信息结合起来也就是现在的多模态情感分析。社交文本往往配图图片里的表情、场景本身就有情感色彩。常见做法是先把图片用预训练分类器抽成情感标签再把标签像处理emoji那样转化成文本标记拼进特征里——这样“文本图像”两种模态就对齐了。视频人物情感分析也类似先把视频抽帧做人脸表情识别再把识别结果和时间轴上的文本拼接。这条路不需要推翻前面做的词典和机器学习只需要在前面流水线上加一个“多模态特征注入”的步骤作为毕设亮点足够了。我现在拿到一个新的文本分类任务第一件事是先写词典规则当基线再跑机器学习最后把两者的错误样本放在一起看。这个习惯救了我好几次希望帮到你。本文还有配套的精品资源点击获取