简介:毕业设计级微博评论文本情感分析项目,完整整合支持向量机(SVM)、朴素贝叶斯与自适应提升(AdaBoost)三种经典算法,并附带论文文档与可直接运行的源码,面向计算机、通信、人工智能、自动化等专业学生和从业者,适合课程设计、期末大作业或毕业设计参考。资源包共69个文件,包括31个Python脚本、15个npy模型参数、13个txt语料与停用词表、4个model文件及docx论文等,覆盖数据预处理、模型训练、ROC曲线绘制、情感分类与词云展示等完整模块,压缩后仅6.38MB,目录清晰便于检索。已有131人学习下载。代码均经过调试测试,答辩评审分达98分,可作为入门机器学习的实践范例;基础较强的读者还可替换数据集或调整算法,快速搭建自己的微博情感分析方案,整体具有较高的借鉴价值。
1. 微博评论文本情感分析选型:SVM + 朴素贝叶斯 + AdaBoost 为什么适合当毕设主线
很多人觉得情感分析必须上深度学习,但真到了毕业设计这个节点,几千条微博评论、一台普通笔记本、三个月时间这些约束摆在一起时,SVM、朴素贝叶斯、AdaBoost 这三个传统模型反而是性价比最高的组合。模型足够简单,调参空间又没大到失控,还能在论文里做出正经的横向对比,最终交付一套完整项目论文文档也不吃力。
这篇笔记就按我实际带毕设时验证过的路径来写:从微博评论数据怎么拿、怎么清洗分词,到 TF-IDF 特征工程,再到三个模型的参数设定和评估避坑,最后落回论文文档的组织方式。整个过程不需要 GPU,不需要租服务器,普通电脑就能跑完,适合想用传统机器学习打底、又怕在答辩环节被细节问住的同学。
2. 微博评论数据获取与预处理:从抓取到干净语料的完整 pipeline
2.1 评论数据从哪来:公开接口抓取与自建标注集的取舍
做微博评论文本情感分析,第一步不是训练模型,而是先把语料凑够。常见的做法是两种:一种是自己写脚本调微博移动端的公开评论接口,按微博 id 拉取评论;另一种是手工从网页上复制评论,自己标注正负向。我一般建议两条腿走路——脚本抓一部分,再手工补一部分,因为纯接口抓回来的评论文本里全是 HTML 片段和表情描述,清洗工作量不小,而纯手工做法又撑不起训练集规模。
抓取这一环,常规做法是用 requests 请求 m.weibo.cn 的评论接口,按页翻。Cookie 从自己浏览器登录后的开发者工具里复制即可,量小、只为毕设研究,不会有太大风控压力。
import requests def fetch_comments(weibo_id: str, cookie: str, max_page: int = 20): url = "https://m.weibo.cn/api/comments/show" headers = { "Cookie": cookie, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" } for page in range(1, max_page + 1): params = {"id": weibo_id, "page": page} resp = requests.get(url, params=params, headers=headers, timeout=10) data = resp.json() if data.get("ok") != 1: print(f"第 {page} 页拉取失败,响应: {data.get('msg')}") continue for item in data.get("data", {}).get("data", []): yield item.get("text", "")这段逻辑很直接:按页循环,拿到 JSON 后从data.data列表里逐个取text字段。weibo_id是单条微博的 id,在浏览器地址栏里能看到;max_page控制翻页深度,毕设建议最多 20 页,再多是给反爬送人头的。timeout=10是为了防止接口卡住导致脚本挂死。拿到原始评论后不要急着直接存,先看一眼前几条长什么样再进下一步清洗。
2.2 清洗与分词:URL、@用户、表情描述与 jieba 的配合
微博评论文本比新闻语料脏得多:正文里嵌着 URL、@用户名、话题标签,还有大量[微笑]、[泪]这类表情描述。如果不先把这些噪音洗掉,分词和向量化都会被干扰,后面模型再强也白搭。清洗的核心原则是:只保留对情感判别有用的汉字和英文单词,其余全部抹掉。
import re import html import jieba def clean_comment(text: str) -> str: text = html.unescape(text) # 还原 & 这类实体 text = re.sub(r"https?://\S+", "", text) # 去掉 URL text = re.sub(r"@[\u4e00-\u9fa5\w\-]+", "", text) # 去掉 @用户 text = re.sub(r"#.*?#", "", text) # 去掉 #话题# text = re.sub(r"\[[^\]]{1,10}\]", "", text) # 去掉 [微笑] 这类表情描述 text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z]", " ", text) # 非中文/字母转空格 return text.strip() def cut_comment(text: str, stopwords: set) -> str: words = jieba.lcut(text) words = [w for w in words if w.strip() and w not in stopwords] return " ".join(words)html.unescape是为了把&恢复成&,再交给后面的正则统一清除。注意清洗顺序不能乱:先还原实体,再去 URL 和标签,最后做字符过滤。如果先过滤再还原,实体里的分号会有残留。jieba.lcut返回词列表,然后用停用词表过滤空串和语义承载低的词。这里我把停用词作为参数传入,加载方式在 2.3 里写。
2.3 去停用词与类别标签:让模型拿到的是“标准答案”而不是原始噪声
停止词表的来源很自由,网上有哈工大停用词表、百度停用词表,我常用的是把几个表合并去重,再额外加上微博特有的口语噪声词,比如“哈哈哈”“emmm”“懂了”“确实”这类。加载方式很简单:
def load_stopwords(path: str) -> set: stopwords = set() with open(path, encoding="utf-8") as f: for line in f: stopwords.add(line.strip()) return stopwords加载后把停用词集合传给cut_comment,得到空格分词的长字符串,这一列就是后续 TF-IDF 的输入。这里有一个普遍犯的错:用 pandas 读停用词文件时,line.strip()没做空行判断,结果把空串也塞进集合,分词结果里出现大量“空格”特征,影响不大但会让特征维度虚高。加载后可以stopwords.discard("")顺手处理一下。
标签标注方面,毕设最稳妥的是正负二分类。标注逻辑是:从清洗后的评论里随机抽样,找两个人分别标“积极/消极”,不一致的丢掉或按少数服从多数处理。几百到两千条足够支撑这三个传统模型跑出区分度。中性评论最难标,建议直接不设中性类,否则类间界限模糊,SVM 和朴素贝叶斯都会明显变犹豫。
3. 特征工程:TF-IDF 向量化与三个模型共用的稀疏矩阵
3.1 TF-IDF 的四个必调参数:max_features、ngram_range、min_df、sublinear_tf
文本情感分析里,特征工程的核心就是把分词结果变成数值矩阵。TF-IDF 是三个模型都能直接吃的输入格式:SVM 喜欢稀疏高维向量,朴素贝叶斯要求特征非负,AdaBoost 可以在稀疏矩阵上训练树桩。实际项目中我不会上来就默认TfidfVectorizer(),而是固定四个参数,让特征空间没那么膨胀。
from sklearn.feature_extraction.text import TfidfVectorizer def tokenizer(text: str): return text.split() tfidf = TfidfVectorizer( tokenizer=tokenizer, max_features=10000, ngram_range=(1, 2), min_df=2, sublinear_tf=True )tokenizer=tokenizer是让向量器按空格切分已经分好的词,避免默认正则表达式对中文的意外切分;max_features=10000只保留词频最高的 1 万个特征,抑制维度爆炸;ngram_range=(1, 2)同时用单词和二元词组,能捕捉“不好”和“好”的差别,文本分类里二元组收益比较明显;min_df=2过滤掉只在一条评论里出现过的词,去掉那些纯属偶然的噪声;sublinear_tf=True用1 + log(tf)替代原始词频,弱化长评论里反复出现同一个词带来的放大效应。
这里需要注意,max_features不是越大越好。几千条数据的时候,取 1 万到 2 万比较合适;取 5 万以上的话,SVM 训练时间上去了,朴素贝叶斯的平滑系数反而更难调。最好先跑一个fit_transform,看tfidf.shape的实际维度再决定是否下调。
3.2 训练集/测试集切分与防数据泄漏:transform 不是 fit
特征工程里最容易踩的坑,是把整个数据集拿去fit_transform,然后才切训练测试集。这样测试集的信息已经参与了 IDF 统计,测试时模型看到的是“作弊”后的数据,指标虚高,答辩时一问就露馅。正确的顺序是先train_test_split,再在训练集上fit_transform,测试集上只做transform。
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( df["cut_comment"], df["label"], test_size=0.2, random_state=42, stratify=df["label"] ) X_train_vec = tfidf.fit_transform(X_train) X_test_vec = tfidf.transform(X_test)stratify=df["label"]是分层抽样,保证正负样本在训练集和测试集里的比例跟原始数据一致。类别不均衡的时候,这一行代码能直接决定你模型的 F1 分数上下浮动多少。random_state=42固定随机种子,所有实验可复现——这也是论文里必须写清楚的一项,否则你自己第二天都跑不出一样的结果。
4. 三种算法落地与调参:SVM、朴素贝叶斯、AdaBoost 的完整复现路径
4.1 线性 SVM:稀疏高维文本下 RBF 核为什么容易翻车
SVM 在文本情感分析里是公认的强基线。文本经过 TF-IDF 后通常是高维稀疏向量,样本之间在绝大多数维度上都是 0,这种数据天然适合线性核。很多同学看到 SVM 第一反应是 RBF 核,这在图像领域确实常见,比如手写数字分类里核函数与参数的影响研究,但在几千维的稀疏文本特征上,RBF 核要计算两两样本之间的径向距离,距离度量被大量零值稀释,gamma 稍微调不好就全部判成多数类,训练时间还长,翻车概率很高。
from sklearn.svm import LinearSVC svm_model = LinearSVC( C=1.0, class_weight="balanced", random_state=42 ) svm_model.fit(X_train_vec, y_train)LinearSVC用的是 liblinear,专门优化大规模稀疏线性分类,训练速度上明显优于SVC(kernel="linear")。C=1.0是误分类惩罚的倒数,C 越小模型越容忍训练集错误,C 越大越容易过拟合。文本分类里 C 在 0.1 到 10 之间网格搜索即可,不必过度纠结。class_weight="balanced"会根据类别频率自动加权,正负样本 7:3 这种场景下能有效拉回少数类的召回率。
如果你非要用核函数版,SVC(kernel="linear")也可以,但在数据量几千条的时候,两者精度差距很小,LinearSVC没有predict_proba,拿 AUC 的时候要用decision_function代替,这个差异在 5.1 节会讲到。
4.2 朴素贝叶斯 MultinomialNB:alpha 平滑与先验到底怎么影响结果
朴素贝叶斯是情感分析里的老熟人,网上能搜到的朴素贝叶斯案例一大半都拿新闻分类或垃圾邮件做演示,换成微博短文本之后,最值得调的不是词表,而是平滑系数alpha。MultinomialNB假设特征之间独立,这个假设在语言数据里显然不成立,但实际效果在小样本场景下依然能打,因为它对噪声和缺失特征不敏感。
from sklearn.naive_bayes import MultinomialNB nb_model = MultinomialNB( alpha=0.5, fit_prior=True ) nb_model.fit(X_train_vec, y_train)alpha是拉普拉斯平滑的平滑强度,默认是 1.0。对微博评论这种词频稀疏的数据,alpha 太大会把条件概率摊得过平,导致对强情感词不敏感;alpha 太小又会放大低频词的干扰,比如某个只在训练集出现一次的骂人词,测试时没出现就会反向惩罚。我一般从 0.1、0.5、1.0 三档开始试,用交叉验证选。fit_prior=True表示从训练集学习先验概率;如果你的标注本身已经被人工调整过类别比例,可以改成class_prior=[0.5, 0.5]固定先验,但这种场景在毕设里很少见。
还要注意MultinomialNB的输入必须是非负特征,TF-IDF 矩阵虽然多数值是 0,但值本身非负,直接喂给它没有问题。千万不要在向量化之前套一个StandardScaler,归一化后的负值会让 MultinomialNB 直接报错,这个坑在 5.3 里细说。
4.3 AdaBoost:决策树桩、学习率与 n_estimators 的搭配
AdaBoost 在文本情感分析里没有前两者常见,但作为集成学习代表放进毕设对比,论文的说服力会强不少。它的思路是串行训练一堆弱分类器,每个新分类器更关注前一个分错的样本。文本稀疏高维特征下,基分类器最好用max_depth=1的决策树桩,因为树桩每次只挑一个特征做切割,正好契合 TF-IDF 稀疏矩阵的特点,不容易在几个强特征上反复过拟合。
from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier ada_model = AdaBoostClassifier( estimator=DecisionTreeClassifier(max_depth=1), n_estimators=100, learning_rate=0.8, algorithm="SAMME", random_state=42 ) ada_model.fit(X_train_vec, y_train)n_estimators=100是弱分类器数量,太少欠拟合,太多后期只是在拟合离群点,训练时间线性增长。learning_rate=0.8是每棵树的贡献衰减系数,学习率越小,需要的树越多。我一般固定learning_rate=0.8~1.0去调n_estimators,不推荐两个参数一起网格搜索,组合空间太大,普通机器跑不起。algorithm="SAMME"是当前 sklearn 版本推荐的离散 AdaBoost 算法,旧版本默认的SAMME.R在新版本已经弃用,不显式指定的话代码里会挂一行警告,答辩时被看到容易显得代码不够干净。
三个模型都训练好之后,可以把它们放进一个字典循环执行,统一输出分类报告,这也方便后续做对比表。我一般会先把models字典写好,每个模型都固定好random_state,这样后面无论怎么调参,都能追溯到某一组参数是谁跑出来的。
5. 评估对比与避坑排查:准确率之外,这些坑最容易被答辩老师抓住
5.1 评估指标选择:类别不均衡时为什么不能只看 accuracy
微博评论的正负样本天然不均衡,现实中负面评论往往占比更高,如果你按默认随机切分,模型可能靠“全猜多数类”拿到 85% 的准确率,看起来很好看,实则对少数类完全无感。答辩老师最擅长拿这种虚高指标开刀,所以评估时不能只 report accuracy,要同时看 precision、recall、F1 和混淆矩阵。
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score def evaluate(model, X_test, y_test): y_pred = model.predict(X_test) print(classification_report(y_test, y_pred, target_names=["negative", "positive"])) print("confusion matrix:") print(confusion_matrix(y_test, y_pred)) evaluate(svm_model, X_test_vec, y_test)classification_report里重点看 macro avg 和 weighted avg 两行。类别不均衡时,我通常以macro-F1为第一排序指标,因为它把少数类和多数类一视同仁。混淆矩阵则能直接看出模型是在哪种类别上犯傻:如果 positive 那一列的召回率极低,说明模型把大量正向评论误判成了负向。
ROC/AUC 也是论文里常用的指标。注意LinearSVC没有predict_proba,要用decision_function的输出算 AUC;MultinomialNB有predict_proba,两者在 5.3 的对比表里要写清楚。
5.2 三模型横向对比与参数记录表
横向对比不是把三个模型跑完就完事,而是要记录每一组参数对应的结果,形成一张可追溯的对比表。我通常先在训练集上用cross_val_score做 5 折交叉验证,再在测试集上复现一遍,保证论文里写的数据不是“一次性巧合”。
| 模型 | 参数设置 | accuracy | macro-F1 | AUC | 训练耗时 |
|---|---|---|---|---|---|
| LinearSVC | C=1.0, class_weight=balanced | 待填 | 待填 | 待填 | 待填 |
| MultinomialNB | alpha=0.5, fit_prior=True | 待填 | 待填 | 待填 | 待填 |
| AdaBoost | n_estimators=100, lr=0.8, depth=1 | 待填 | 待填 | 待填 | 待填 |
这张表直接放进论文的实验章节,每行参数写清楚,能体现你到底懂不懂自己在干什么。训练耗时方面,传统模型在几千条数据上都是秒级到十几秒级,AdaBoost 因为是串行训练,通常比前两者慢一点,但这些耗时对比本身也是论文里可写的一笔。
5.3 高频踩坑记录:现象、原因与解决
坑一:准确率虚高,预测结果全是多数类。现象是 accuracy 到了 90% 以上,但 confusion matrix 里少数类那一行全是 0。原因是类别不均衡且没有做分层切分,模型直接躺平。解决方法是train_test_split里加stratify=y,同时给模型开class_weight="balanced",并以 macro-F1 作为主要评判指标。
坑二:训练集 F1 接近满分,测试集掉得厉害。现象是本地验证很漂亮,一换数据就原形毕露。原因多半是数据泄漏,向量化时把全量数据拿去fit_transform,测试集的信息提前进了 IDF。解决方法是严格走fit_transformon 训练集、transformon 测试集的流程,或者直接用 Pipeline 包起来避免人为失误。
坑三:MultinomialNB 报错Input X must be non-negative。现象是模型 fit 到一半抛出异常。原因是有人对 TF-IDF 矩阵做了标准化,或者用了带负值的 CountVectorizer 结果。解决方法是只保留非负特征矩阵,朴素贝叶斯家族的设计前提就是特征非负,别再画蛇添足做归一化。
坑四:AdaBoost 训练时弹出弃用警告。现象是代码能跑,但控制台出现algorithm="SAMME.R" has been deprecated之类的提示。原因是 sklearn 新版把默认算法改成了SAMME,SAMME.R废止。解决方法是显式传algorithm="SAMME",并把base_estimator换成新参数名estimator。
坑五:SVM 用 RBF 核训练时间过长,几小时不出结果。现象是代码卡在某个 epoch 不动。原因是高维稀疏矩阵在 RBF 核下要算两两距离,复杂度随样本量平方上升。解决方法是换LinearSVC或SVC(kernel="linear"),如果真的想用 RBF 做对比,先对特征做SelectKBest降维到几百维再训练。
6. 进阶技巧与论文文档落地:把实验结果变成能过查重的毕业设计
模型跑通只是第一步,毕设最终要交付的是“完整项目论文文档”。我习惯把整个实验流程包成两个东西:一个可复现的 Pipeline,一张实验记录表。Pipeline 的价值是防止自己在换参的过程中把数据弄脏,实验记录表的价值是答辩时经得起追问。
from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV pipe = Pipeline([ ("tfidf", TfidfVectorizer(tokenizer=tokenizer, max_features=10000, min_df=2, sublinear_tf=True)), ("clf", LinearSVC()) ]) param_grid = {"clf__C": [0.1, 1.0, 10.0]} search = GridSearchCV(pipe, param_grid, cv=5, scoring="f1_macro") search.fit(df["cut_comment"], df["label"]) print(search.best_params_)Pipeline 的优势是向量化参数和模型参数在一个对象里统一管理,交叉验证时每次 fold 都会在训练折内重新 fit 向量器,从机制上杜绝了数据泄漏。param_grid里的clf__C这种双下划线写法是 Pipeline 参数寻址的固定语法,必须在模型名后加两个下划线再写参数名。
论文文档的组织上,我一般按摘要、绪论、相关技术、数据与预处理、实验设置、结果对比、总结展望、参考文献、附录代码的顺序写。相关技术一章不用长篇抄书,重点写清楚 SVM 的线性核选择理由、朴素贝叶斯的独立性假设与 alpha 平滑、AdaBoost 的串行集成机制,这些内容跟你的实验结果直接呼应。实验设置部分必须包含数据集规模、正负样本比例、随机种子、TF-IDF 参数、每个模型的超参数。答辩老师最爱问的恰恰是这些容易被忽略的细节。
还有一个值得做进附录的进阶内容:学习曲线。在三个模型上分别画出训练/验证分数随样本量的变化曲线,能直观展示模型是欠拟合还是过拟合。这个图放进论文里能显著提高完整度,而且实现成本很低,十几行代码而已。我自己的一个习惯是每次实验结束,立刻把数据集大小、随机种子、参数、指标写进一个文本文件里,而不是先记在心里——以前吃过这个亏,调了一下午参数,晚上发现忘了记录,第二天全部推倒重来。这个习惯也希望能给你留个参考,毕设周期长,你的记忆真的没有实验日志可靠,希望帮到你。
本文还有配套的精品资源,点击获取