简介:这份资源是面向计算机、人工智能、通信、自动化等专业学生与从业者的Python毕业设计项目,主题为基于机器学习朴素贝叶斯的微博评论情感分析,适合作为课程大作业、期末设计或毕设参考,也可供初学者进阶学习。压缩包共27个文件,约133.26MB,以20个txt数据与标签文件、2个py源码脚本、2个npz模型文件、2个md说明文档及1份pdf实验报告为主,覆盖数据预处理、模型训练与结果输出等环节。项目代码经过调试测试,可正常运行,答辩评审分达98分,已有222人学习下载。读者可获取完整源码、数据集、实验报告与模型文件,理解朴素贝叶斯在中文文本情感分类中的特征提取、训练与评估流程,并在此基础上修改调整,实现不同功能。
1. 微博评论情感分析:一条评论进来,模型到底怎么判它正负
刷微博时看到一条评论「这手机续航真顶,一天两充变一天一充」,你一眼知道是好评;但让程序自动判断,它得先学会「顶」在这里是褒义、「两充变一充」是改善。这就是微博评论情感分析要解决的问题:把非结构化的短文本,映射成正面、负面(有时加中性)三类标签。它常被用作 Python 毕业设计,因为链路完整——爬数据、清洗、分词、特征、训练、评估、可视化,每一环都能单独讲清楚,又不会像深度学习那样一上来就卡在显卡和调参上。
朴素贝叶斯是这条链路里性价比最高的基线模型。它假设特征之间相互独立,虽然微博评论里「不」和「好」明显不独立,但工程上这个「错误假设」反而让它在短文本分类里又快又稳,几千条数据几分钟出结果,特别适合毕业设计这种要讲清原理、又要跑出可复现结果的场景。这篇笔记就按「数据怎么来 → 特征怎么建 → 模型怎么训 → 坑在哪」的顺序,把一套能直接抄作业的方案讲透,适合正在做机器学习入门项目、需要一份完整情感分析代码和数据流程的人。
2. 数据与标签:微博评论从哪来、怎么洗、标签怎么定
2.1 微博评论数据的获取与合规边界
毕业设计里数据来源通常两条路:一是用公开的情感分析数据集(如带标注的微博语料),二是自己采集。自己采集常见做法是用 requests 配合页面接口拿评论,但这里必须提醒:采集前看清目标站点的 robots 协议和使用条款,控制频率、只取公开可见内容、不涉及用户隐私字段,这是底线,不是可选项。很多同学一上来就写多线程猛拉,结果 IP 被封、数据拿一半,血泪经验就是——先小批量跑通,再谈量。
采集下来的原始评论是脏的:带表情符号、@某人、话题标签 #、链接、重复转发。清洗的目标不是「干净得像教科书」,而是「保留情感信号」。比如「转发微博」这种要删,「哈哈哈」这种要留(它是正向信号),「[微笑]」这种表情要转成文字或统一标记,否则分词器直接丢掉。
import re import pandas as pd def clean_weibo_comment(text): if not isinstance(text, str): return "" # 去掉 @用户 和话题标签,它们通常不携带情感 text = re.sub(r"@[\w\u4e00-\u9fa5\-]+", "", text) text = re.sub(r"#.*?#", "", text) # 去掉链接 text = re.sub(r"http[s]?://\S+", "", text) # 常见表情转文字,保留情感信号 text = text.replace("[微笑]", "开心").replace("[泪]", "难过") # 去掉多余空白 text = re.sub(r"\s+", "", text) return text.strip() df = pd.read_csv("weibo_comments.csv") df["clean"] = df["content"].apply(clean_weibo_comment) # 去掉清洗后为空的、以及明显的广告转发 df = df[df["clean"].str.len() >= 2] df = df[~df["clean"].str.contains("转发微博")] print(df.shape)这段代码的逻辑是「先删噪声、再保信号」。re.sub的三条正则分别处理 @、话题、链接,顺序无所谓但都要做;表情替换是可选增强,如果你的数据集里表情少可以跳过。str.len() >= 2这个阈值是经验值,太短的评论(如「嗯」「哦」)情感极弱,留着反而干扰训练。参数上唯一需要你按数据调的是长度阈值和表情映射表,其余可以照搬。
2.2 标签体系:三分类还是二分类,标注一致性怎么保
标签设计直接决定模型上限。微博评论常见做法是三分类:正面(1)、负面(0)、中性(2)。但中性类最难标,「还行吧」「一般」到底算中性还是弱正面,不同人标出来不一致,模型学到的就是噪声。我的建议是:毕业设计优先做二分类(正/负),把中性样本剔除或归入较弱的一侧,等基线跑通再考虑加中性类。这样准确率好看,答辩时也好解释。
如果一定要三分类,标注阶段至少两人独立标,算一下 Kappa 一致性,低于 0.6 就说明标准没对齐,得回去改标注规则。别小看这一步,很多项目最后模型效果差,根因不在算法,在标签本身就是乱的。数据量上,二分类每类 1000~2000 条就能跑出像样的结果,三分类建议每类不少于 1500 条,否则中性类样本太少,模型直接偏向多数类。
提示:划分训练集和测试集时用
stratify=y保持类别比例,否则小样本类别可能全被分到一边,评估结果失真。
3. 中文分词与特征工程:jieba 切词 + 停用词 + 向量化怎么配
3.1 jieba 分词与停用词表的最小可用配置
中文不像英文有空格,必须先分词。jieba 是最常用的选择,jieba.lcut返回列表,cut返回生成器。微博评论里有很多网络新词(如「绝绝子」「yyds」),默认词典切不出来,可以用jieba.add_word手动加,或者加载自定义词典。停用词表要包含「的、了、是、我、你」这类高频无情感词,但注意别把「不、没、别」删了——它们是否定词,删了会把「不好」变成「好」,这是新手最常翻的车。
import jieba # 加载停用词,注意保留否定词 stopwords = set() with open("stopwords.txt", encoding="utf-8") as f: for line in f: w = line.strip() if w and w not in {"不", "没", "别", "无", "非"}: stopwords.add(w) # 补充微博常见新词 for w in ["绝绝子", "yyds", "破防", "栓Q"]: jieba.add_word(w) def tokenize(text): words = jieba.lcut(text) return [w for w in words if w not in stopwords and len(w) > 1] df["tokens"] = df["clean"].apply(tokenize) df["seg"] = df["tokens"].apply(lambda x: " ".join(x)) print(df[["clean", "seg"]].head())逻辑说明:停用词过滤时用len(w) > 1去掉单字,是因为单字噪声大且情感弱,但「不」这类单字否定词已经在停用词阶段被保护,不会被这条规则误伤——因为它压根没进停用词集合。jieba.add_word要在分词前调用,否则不生效。参数上,停用词表可以网上找通用版再按你的数据补,新词表按你爬到的实际热词加,不用贪多。
3.2 从词袋到 TF-IDF:朴素贝叶斯到底吃什么特征
朴素贝叶斯(这里用多项式模型 MultinomialNB)吃的是词频类特征。最朴素的是 CountVectorizer 词袋,统计每个词出现次数;更好的是 TF-IDF,它压低「哈哈」这种到处都有的词、抬高「垃圾」「惊艳」这种区分度高的词。对情感分析,TF-IDF 通常比纯词袋高 1~3 个百分点,代价是稍微慢一点,但完全可接受。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split tfidf = TfidfVectorizer( max_features=5000, # 只保留区分度最高的5000个词 ngram_range=(1, 2), # 同时看单字词和双字词,捕捉"不好"这类组合 min_df=2 # 出现少于2次的词丢掉,降噪 ) X = tfidf.fit_transform(df["seg"]) y = df["label"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) print(X_train.shape, X_test.shape)三个参数是重点。max_features=5000控制维度,太大容易过拟合、太小学不到东西,5000 是短文本常用起点。ngram_range=(1,2)让模型看到「不 好」这种二元组合,对否定句很关键,但会显著增加特征数,所以配合 max_features 一起用。min_df=2过滤只出现一次的词,这些词多半是噪声或错别字。注意fit_transform只能在训练集上做,测试集要用transform,否则就是数据泄漏,评估分数虚高,答辩时被问到就露馅。
4. 朴素贝叶斯建模与评估:MultinomialNB 参数怎么调、结果怎么看
4.1 训练一个可复现的 MultinomialNB 基线
多项式朴素贝叶斯的核心参数只有一个平滑项alpha,默认 1.0(拉普拉斯平滑)。它的作用是防止某个词在训练集没出现导致概率为 0,从而整个预测崩掉。alpha 越小,模型越相信训练数据,容易过拟合;alpha 越大,平滑越强,可能欠拟合。对微博短文本,alpha 在 0.1~1.0 之间通常表现最好,可以用交叉验证选。
from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import cross_val_score import numpy as np # 用交叉验证挑 alpha for a in [0.01, 0.1, 0.5, 1.0, 2.0]: clf = MultinomialNB(alpha=a) scores = cross_val_score(clf, X_train, y_train, cv=5, scoring="f1") print(f"alpha={a}, f1={np.mean(scores):.4f}") # 用最优 alpha 训练最终模型 model = MultinomialNB(alpha=0.1) model.fit(X_train, y_train)逻辑说明:cross_val_score做 5 折交叉验证,比单次划分更稳,避免「这次运气好」的假象。评分用f1而不是 accuracy,因为如果正负样本不均衡,accuracy 会被多数类带偏。选完 alpha 再用全量训练集重训一次,这才是最终模型。参数上,cv 折数 5 是通用选择,数据量小于 500 时可以用 3 折。
4.2 评估指标:准确率之外必须看的混淆矩阵和 F1
只看 accuracy 是毕业设计里最常见的偷懒。假设 90% 评论是正面,模型全猜正面也有 90% 准确率,但它毫无用处。必须看混淆矩阵和每类的 precision、recall、f1。情感分析里,负面类的 recall 往往偏低——因为负面表达更隐晦(如反讽),模型容易漏判。如果你的应用场景是舆情预警,漏判负面比误判正面代价大得多,这时要优先提负面类的 recall。
from sklearn.metrics import classification_report, confusion_matrix y_pred = model.predict(X_test) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names=["负面", "正面"]))classification_report会给出每类的 precision(查准)、recall(查全)、f1(调和平均)和支持度。重点看负面类的 recall:如果低于 0.7,说明大量负面评论被漏掉,可以考虑调低判定负面的概率阈值,或者补充负面样本。混淆矩阵则直观告诉你错在哪——是负面被判成正面多,还是反过来。这两个输出建议直接放进论文的评估章节,比单写一个准确率有说服力得多。
5. 避坑与排查:情感分析项目里最容易翻车的 5 个地方
5.1 数据泄漏:测试集混进训练集,分数虚高
现象:交叉验证 F1 0.95,换一批新数据掉到 0.6。原因:在划分训练测试之前就做了 TF-IDF 的fit,或者去重没做好,同一条评论同时出现在训练和测试集。解决:严格按「先划分、再 fit 训练集、transform 测试集」的顺序;划分前对评论文本做去重(df.drop_duplicates(subset="clean"))。这是最隐蔽也最致命的坑,答辩老师一问「你的测试集有没有参与特征拟合」就能试出来。
5.2 否定词被停用词表误删
现象:模型把「不好」「不满意」都判成正面。原因:停用词表里包含了「不」「没」,分词后否定词消失,「好」单独留下。解决:停用词表里显式排除否定词,或者用 ngram 让「不 好」作为整体特征进入模型。检查方法很简单:打印几条含否定词的评论分词结果,看「不」还在不在。
5.3 类别不均衡导致模型偏向多数类
现象:正面样本 3000 条、负面 500 条,模型几乎全预测正面,accuracy 还挺高。原因:朴素贝叶斯先验概率被多数类主导。解决:训练时给fit传sample_weight,或用class_prior手动设先验,或对少数类过采样。评估时一定看每类 F1,别被总 accuracy 骗了。
5.4 新词和网络用语切不出来
现象:「绝绝子」「栓Q」被切成单字或错误组合,特征里全是噪声。原因:jieba 默认词典没收录这些新词。解决:维护一个自定义词典,用jieba.load_userdict加载,或在代码里add_word。词典要按你实际数据里的高频新词来加,加太多无关词反而增加噪声。
5.5 用 accuracy 当唯一指标
现象:论文里只报了一个准确率,被问「负面类召回多少」答不上来。原因:没看分类报告。解决:固定输出混淆矩阵 + 每类 precision/recall/f1,并在论文里解释为什么选 F1 作为主指标。这一步花不了十分钟,但能让你的评估章节从「及格」变「扎实」。
6. 进阶技巧:把基线做扎实后再谈提升
基线跑通后,想再往上提一两个点,有几个性价比高的方向。第一是特征层面:把 TF-IDF 的ngram_range从 (1,2) 试到 (1,3),或者把max_features从 5000 调到 10000,看验证集 F1 有没有变化,但注意别过拟合。第二是模型融合:朴素贝叶斯和逻辑回归各训一个,预测概率取平均,短文本上经常能涨点,代码也就十几行。
from sklearn.linear_model import LogisticRegression from sklearn.ensemble import VotingClassifier nb = MultinomialNB(alpha=0.1) lr = LogisticRegression(max_iter=1000, C=1.0) ensemble = VotingClassifier( estimators=[("nb", nb), ("lr", lr)], voting="soft" # 按概率平均,比硬投票稳 ) ensemble.fit(X_train, y_train) print(classification_report(y_test, ensemble.predict(X_test)))voting="soft"要求两个模型都能输出概率,MultinomialNB 和 LogisticRegression 都满足。软投票比硬投票(按类别票数)更平滑,尤其在两个模型分歧大时。参数上,逻辑回归的C控制正则强度,C 越小正则越强,可以从 1.0 开始试。
另一个容易被忽略的点是错误分析。把测试集里预测错的样本导出来,人工看 50 条,你会发现问题往往集中在反讽(「这服务真是太好了,好到我再也不想来了」)、双重否定、以及带表情的复杂句。针对反讽,纯词袋模型基本无解,这时候可以老实承认基线局限,把它写成论文的「不足与展望」,比硬吹模型多强要诚实得多。
最后说个验证习惯:每次改完参数或特征,别只看一次划分的结果,跑 5 折交叉验证取均值,波动小于 0.01 才算真的提升,否则可能只是随机种子的玄学。我自己做这类项目时,习惯把每次实验的配置和 F1 记在一个 csv 里,跑十几轮后回头看,哪些改动真有用一目了然。这套流程不复杂,但能让你少走很多「改了参数不知道有没有用」的弯路。希望帮到你。
本文还有配套的精品资源,点击获取