资讯详情 微博评论情感分析毕设:SVM、朴素贝叶斯与AdaBoost同口径对比
📅 2026/10/30 7:27:26
简介面向计算机与数据科学专业本科毕业设计的微博评论文本情感分析项目围绕正面、负面、中性三分类任务用支持向量机、朴素贝叶斯与AdaBoost三种算法开展对比实验覆盖从选题、实现到结论整理的全流程。压缩包共70个文件、约6.05MB以31个Python脚本为主体涵盖数据清洗分词、停用词过滤、词袋与TF-IDF特征构建、模型训练及交叉验证评估15个npy与4个model文件保存训练参数13个txt提供语料、情感词典和停用词表另附docx研究文档、md说明、中文字体与词云配图可直接复现或二次修改。已有382人学习下载。读者可获得三模型在准确率、召回率、F1值上的对比结果及ROC曲线、词云产物并借助AdaBoost迭代加权理解不平衡数据下的性能提升目录按训练、测试、绘图分模块组织便于按模块检索。1. 一条带货微博底下情感分析毕设最容易翻车的地方做毕业设计挑微博评论文本情感分析最常见的结果是交上去一个分词脚本、一个朴素贝叶斯、一张 0.83 的准确率截图。导师随口问一句“你凭什么说 SVM 不比它差AdaBoost 你又试过没有”答不上来整套东西的分量立刻掉一半。这份毕设方案把 SVM、朴素贝叶斯、AdaBoost 三个分类器压在同一份语料、同一套特征、同一套评估口径下跑并配了完整项目文档要解决的就是“我为什么这么选”这个说不清的问题。读完至少能拿得出一张三模型对比表而不是一句“我感觉 SVM 效果好”。2. 总体方案设计四个层次与一条统一评估口径2.1 需求边界这条流水线只做四件事不少人在开题阶段就把题目做大了既要爬数据又要做三分类还想搭个网页实时预测。结果是每块都做了半截答辩时哪块都经不起追问。微博评论情感分析这个题目看着轻实际上把“数据从哪来、标签怎么定、模型怎么比、结论怎么下”四件事全压在一个人身上范围不收窄后期一定返工。我一向建议从二分类起步正面和负面语料控制在八千到两万条之间。原因很实在微博评论里大量所谓“中性”其实是“转发微博”“哈哈哈哈”这类噪声标成中性既难标也难复现换成三分类之后标注一致性撑不住答辩现场被问“中性你怎么判定”很容易卡住。二分类的规则一句话能讲清有明确的喜爱、支持、期待倾向算正面有明确的厌恶、反对、嘲讽倾向算负面其余样本直接丢弃。丢掉的部分不是浪费是给标签质量让路。模块划分上按层次切每一层只对下一层负责出问题时能快速定位是哪一层坏了。下面的表是我一般会写进项目文档的模块说明控制在四行以内答辩时直接投出来讲。层次核心模块交付物数据层语料采集、去重、标签清洗干净的「文本-标签」对文件特征层中文分词、停用词处理、TF-IDF 向量化稀疏特征矩阵与向量化器模型层SVM、朴素贝叶斯、AdaBoost 训练与调参三个持久化模型文件评估层指标计算、混淆矩阵、对比可视化对比表与图表四层之间的接口要固定下来数据层输出统一是两列text、label的 CSV特征层输出统一是 scipy 稀疏矩阵加标签数组模型层只接受这两样东西。接口固定了换模型、换特征都不会牵一发动全身这也是后面能公平对比三个分类器的前提。2.2 技术栈与目录结构够用就行的最小组合技术栈上不需要堆东西。Python 加 jieba 做分词scikit-learn 做向量化和三个分类器pandas 读数据matplotlib 出图这套组合能覆盖从清洗到评估的全流程而且每个环节的中间结果都能落到文件里出了问题能回看。有人会想上深度学习训个 TextCNN 或者 BERT 微调理论上限确实更高但毕设周期内调不出稳定结果反而把“为什么这么选”这件事讲歪了——模型越黑越难解释。SVM 和朴素贝叶斯在文本分类上本来就是经过验证的强基线AdaBoost 在 scikit-learn 里几行就能搭起来三者共享同一份 TF-IDF 输出横向对比才有意义。这也是我推荐这套选的核心理由不是为了炫技是为了让对比结论站得住。目录结构按层次划分一个模块一个文件主脚本只负责调度。下面这套结构是我用了几个项目之后稳定下来的改起来最省心。weibo_sentiment/ ├── data/ │ ├── raw/ # 原始抓取或整理好的评论保持不动 │ ├── cleaned.csv # 清洗去重后的「text,label」 │ └── split/ # 固定下来的训练集/测试集只生成一次 ├── src/ │ ├── clean.py # 文本清洗规则 │ ├── features.py # 分词 TF-IDF 向量化 │ ├── models.py # 三个分类器的构建与调参 │ ├── evaluate.py # 指标、混淆矩阵、对比表 │ └── train_all.py # 主入口一次跑完三个模型 ├── output/ │ ├── models/ # 持久化的 .pkl 模型与向量化器 │ └── reports/ # 对比表、混淆矩阵图 └── docs/ # 项目文档需求、设计、实验记录data/raw永远不写任何清洗动作都产出新文件这样规则改错了还能退回去重跑。data/split里的划分文件一旦生成就冻结后面所有实验都读同一份这是三个模型能对比的前提。output/models里向量化器要和分类器一起存推理时必须用同一个向量化器否则特征空间对不上预测结果会变成玄学。2.3 评估口径统一三个模型凭什么叫同一场比赛这一步最容易被跳过也最容易出问题。三个模型如果各自调train_test_split、各自设随机种子跑出来的数字根本没法放一起比导师一眼就能看出来这是在凑结果。正确做法是先切一次、存下来、所有模型都读它。import pandas as pd from sklearn.model_selection import train_test_split df pd.read_csv(data/cleaned.csv) # stratify 保证训练集和测试集的类别比例与全量一致 # random_state 固定划分结果可复现换机器也是同一份 train_df, test_df train_test_split( df, test_size0.2, random_state42, stratifydf[label], ) train_df.to_csv(data/split/train.csv, indexFalse) test_df.to_csv(data/split/test.csv, indexFalse) print(train_df[label].value_counts(normalizeTrue)) print(test_df[label].value_counts(normalizeTrue))stratify这个参数必须加。微博语料里正面评论通常远多于负面随机切分时有可能出现测试集负面样本只剩几十条的情况算出来的召回率抖动极大。分层抽样能让两边比例对齐结论才稳。random_state固定成同一个值是为了让对比实验可复现——复现性在答辩里是加分项被问到“这数字能重跑出来吗”你能当场跑一遍给他看。指标上不要只报准确率。类别不平衡时准确率会骗人全猜正面也有七成多这个数字毫无意义。我一般同时报宏平均的精确率、召回率和 F1再加一个混淆矩阵。宏平均对每个类别一视同仁少数类被压垮时它会立刻掉下来比准确率诚实得多。from sklearn.metrics import classification_report, confusion_matrix, f1_score def report(y_true, y_pred, name): print(f {name} ) # averagemacro不分样本多少每类权重相同能暴露少数类被忽略的问题 print(classification_report(y_true, y_pred, digits4)) print(macro-F1:, round(f1_score(y_true, y_pred, averagemacro), 4)) print(confusion_matrix(y_true, y_pred))三个模型全部走这一个report函数输出的格式完全一致最后拼成一张表。格式统一这件事看着琐碎实际省掉的是手工抄数字抄错、以及不同模型用不同指标导致结论打架的麻烦。3. 数据与特征工程中文微博语料从脏文本到特征矩阵3.1 语料清洗先分清哪些是噪声哪些是情感信号微博文本脏得很有规律某人、#话题#、短链接、[微笑]这类表情占位符、全角半角混排、同一句话被转发几十遍。清洗时不能一律删掉得先分清性质。和链接属于纯噪声删掉不影响语义#话题#里往往藏着关键信息比如“#某品牌翻车#”本身就是态度把话题词抽出来保留比整段删掉更合适表情占位符是明确的情感信号[泪]、[怒]、[心]这类应当映射成对应的情感词而不是当垃圾扔掉。重复文本必须去重而且要按“清洗后文本”去重不是按原始文本。转发链里同一句话前面挂不同的 原始文本不一样清洗完就一样了这时候再去重才有效。否则同一个句子在训练集和测试集各出现一次模型等于提前看过答案准确率虚高这种翻车在毕设里非常典型。import re # 表情占位符到情感词的映射只收高频且情感倾向明确的 EMOJI_MAP { [泪]: 难过, [怒]: 生气, [心]: 喜欢, [good]: 赞, [弱]: 失望, [笑]: 开心, } def clean_text(text: str) - str: if not isinstance(text, str): return # 1. 表情占位符先替换避免后续正则把方括号内容当噪声删掉 for k, v in EMOJI_MAP.items(): text text.replace(k, v) # 2. 去掉 用户 与 http(s) 链接 text re.sub(r[\w\u4e00-\u9fa5\-], , text) text re.sub(rhttps?://\S, , text) # 3. 保留话题内部文字去掉井号本身 text re.sub(r#(.?)#, r\1, text) # 4. 只保留中文、英文、数字和基本标点 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) # 5. 全角转半角并压缩空白 text text.replace(\u3000, ) return re.sub(r\s, , text).strip() def build_clean_corpus(raw_df): raw_df[text_clean] raw_df[text].map(clean_text) # 清洗后长度过短的样本信息量太低直接丢弃 raw_df raw_df[raw_df[text_clean].str.len() 4] # 按清洗后文本去重保留第一条避免同一句在训练/测试集中重复出现 raw_df raw_df.drop_duplicates(subset[text_clean]) return raw_df[[text_clean, label]].rename(columns{text_clean: text})清洗顺序有讲究表情替换必须排在删除特殊符号之前否则方括号内容先被规则 4 干掉映射表就成了摆设。长度过滤的阈值定在 4 个字是权衡后的结果——再短基本是“哈哈”“好的”这类无信息样本留下来只会让模型学到噪声。3.2 分词与停用词别把否定词当垃圾删了jieba 有三种模式精确模式、全模式和搜索引擎模式。微博评论短、口语化、新词多精确模式配合自定义词典是最稳的选择。自定义词典要自己维护把语料里高频的品牌名、网络用语、梗词加进去否则“绝绝子”“破防”“退退退”会被切得七零八落特征直接失真。停用词表是另一个高频出错点。网上随手下载的停用词表往往把“不”“没”“无”“别”这类否定词也收进去了删完之后“不好用”变成“好用”“不支持”变成“支持”情感极性直接反转模型学到的全是反的。我的做法是否定词一律不删而且把常见的“否定词形容词”组合用 jieba 的add_word合成一个词让它在特征空间里独立成一维。import jieba # 领域词典网络用语和品牌词按语料实际情况补 for w in [破防, 绝绝子, 退退退, 不好用, 不支持, 不喜欢, 没意思]: jieba.add_word(w, freq1000) # 停用词表手动剔除否定词避免极性反转 NEGATIONS {不, 没, 无, 别, 未, 非, 莫} def load_stopwords(pathdata/stopwords.txt): with open(path, encodingutf-8) as f: words {line.strip() for line in f if line.strip()} return words - NEGATIONS # 关键一步把否定词从停用词里捞回来 def tokenize(text: str, stopwords: set) - str: # 精确模式切分短文本下比全模式更干净 tokens jieba.lcut(text, cut_allFalse) return .join(t for t in tokens if t not in stopwords and len(t.strip()) 0)注意jieba.add_word要在任何分词动作之前调用且只调一次。有人在循环里反复 add词典越滚越大跑久了内存飙升还查不出原因。分词结果用空格拼回字符串是为了直接喂给 scikit-learn 的向量化器它默认按空格切词。3.3 TF-IDF 向量化维度控制和 n-gram 的取舍向量化这一步决定特征空间长什么样。词袋模型只统计词频高频虚词会淹没真正有区分度的词TF-IDF 用逆文档频率做加权把“的”“了”这类到处都有的词压下去把只在某类评论里密集出现的词抬起来在情感分类上通常比纯词频稳。参数上有几个必须想清楚。max_features控制维度上限微博语料两万条左右取 10000 到 30000 是常见区间太小丢信息太大训练变慢还容易过拟合ngram_range(1, 2)表示同时保留单词和相邻两词组合这一步对情感分析很关键因为“不”加“开心”被切成两维时模型得自己学组合关系而 bigram 直接给它一维“不 开心”学习成本低得多min_df2过滤只出现一次的词这类词多半是错别字或噪声sublinear_tfTrue用对数压缩词频避免某个词重复几十次就把权重拉爆。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features20000, # 维度上限按语料规模调整 ngram_range(1, 2), # 保留 bigram捕捉「不 开心」这类否定组合 min_df2, # 丢掉只出现一次的词降噪 max_df0.9, # 丢掉 90% 以上文档都有的词近似停用词效果 sublinear_tfTrue, # 对数词频抑制高频词主导 ) X_train vectorizer.fit_transform(train_df[tokens]) X_test vectorizer.transform(test_df[tokens]) # 只 transform绝不 fit最后一行是原则问题。测试集只能transform不能fit_transform。一旦在测试集上 fit词表和 IDF 权重里就混进了测试集信息评估结果全部失效这是数据泄漏里最常见的一种而且它不报错、不报警只是让你的准确率好看得不真实。4. 三模型落地实现SVM、朴素贝叶斯、AdaBoost 的代码与调参4.1 支持向量机线性核在小样本高维文本上的性价比文本特征动辄上万维且极度稀疏这种数据形态下线性核 SVM 往往就是最优解之一。高维空间里数据本身就容易线性可分映射到更高维的 RBF 核收益有限反而多出 gamma 一个要调的超参调不好还不如线性。所以我的默认选择是线性核先跑出基线再决定要不要试 RBF。实现上LinearSVC比SVC(kernellinear)快一个量级代价是没有predict_proba。如果项目需要输出概率做置信度展示就用SVC(kernellinear, probabilityTrue)或者对LinearSVC的输出做 Platt 校准。毕设里如果需要“这句话 87% 是正面”这种展示建议直接用后者省事。from sklearn.svm import LinearSVC from sklearn.model_selection import GridSearchCV # C 是正则强度的倒数越大越不容忍错分文本任务常用区间 0.1 ~ 10 svm_param {C: [0.1, 0.5, 1.0, 2.0, 5.0]} svm GridSearchCV( LinearSVC(random_state42, max_iter5000), svm_param, cv5, scoringf1_macro, # 用宏 F1 选参不用 accuracy n_jobs-1, ) svm.fit(X_train, y_train) print(best C:, svm.best_params_, cv macro-F1:, svm.best_score_)max_iter要给够。稀疏高维数据下LinearSVC偶发不收敛默认 1000 次往往不够会抛ConvergenceWarning很多人直接忽略这条警告结果拿到的是半成品模型。设到 5000 基本能收敛还报就说明 C 太大或者特征有问题得回去查。4.2 朴素贝叶斯平滑系数比模型本身更值得调MultinomialNB在文本分类上是经典基线训练快、解释性强作为对照组非常合适。它的核心超参只有一个alpha即拉普拉斯平滑系数。默认 1.0 偏保守实际在微博这种短文本上0.1 到 0.5 往往效果更好因为平滑太强会把有区分度的低频词权重压平。还有一个容易被忽略的点类别不平衡时ComplementNB通常比MultinomialNB表现更稳。它专门针对不平衡数据做了权重修正如果你的语料正面样本是负面的三倍值得把它加进对比列表多一行代码的事。from sklearn.naive_bayes import MultinomialNB, ComplementNB # alpha 越小平滑越弱越依赖训练数据本身 nb GridSearchCV( MultinomialNB(), {alpha: [0.01, 0.05, 0.1, 0.3, 0.5, 1.0]}, cv5, scoringf1_macro, n_jobs-1, ) nb.fit(X_train, y_train) print(best alpha:, nb.best_params_) # 类别不平衡时的备选作为额外对照组 cnb ComplementNB(alpha0.3).fit(X_train, y_train)4.3 AdaBoost基学习器深度决定它是帮忙还是添乱AdaBoost 的思路是串行训练一串弱分类器每轮把上一轮分错的样本权重调高最后加权投票。放在文本任务上它的表现高度依赖基学习器的复杂度基学习器太强几轮就把训练集拟合完后面的轮次全在拟合噪声太弱又提不起来。实践中的稳妥配置是决策树深度取 1 到 2也就是决策树桩n_estimators在 50 到 200 之间learning_rate配合着调——学习率调小、轮数调大是更稳的组合反之容易过拟合。这套配置在稀疏高维特征上不一定打得过线性 SVM但它作为对照组很有价值如果三模型对比里 AdaBoost 明显落后恰好能说明“集成方法在超高维稀疏文本上不一定占优”这个结论答辩时这是个能讲出道理的点。from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier # 基学习器用深度 1 的决策树桩避免单棵树过强导致前几轮就拟合完 base DecisionTreeClassifier(max_depth1, random_state42) ada GridSearchCV( AdaBoostClassifier(estimatorbase, random_state42), { n_estimators: [50, 100, 200], learning_rate: [0.05, 0.1, 0.5], }, cv5, scoringf1_macro, n_jobs-1, ) ada.fit(X_train, y_train) print(best params:, ada.best_params_)注意 scikit-learn 新版本把base_estimator改成了estimator装的是旧版本会报参数不存在这类版本差异在答辩现场演示时最尴尬环境一定要提前锁死并写进文档。4.4 统一训练脚本一次跑完三模型并输出对比表三个模型分散在三个脚本里跑最后手工抄数字拼表是出错的重灾区。写一个统一入口把模型、评估、落盘串起来跑一次就出结果表重复实验的成本降到最低。import json import pandas as pd from sklearn.metrics import f1_score, precision_score, recall_score, accuracy_score def run_and_score(name, model, X_tr, y_tr, X_te, y_te): model.fit(X_tr, y_tr) pred model.predict(X_te) return { model: name, accuracy: round(accuracy_score(y_te, pred), 4), precision_macro: round(precision_score(y_te, pred, averagemacro), 4), recall_macro: round(recall_score(y_te, pred, averagemacro), 4), f1_macro: round(f1_score(y_te, pred, averagemacro), 4), } results [ run_and_score(LinearSVC, svm.best_estimator_, X_train, y_train, X_test, y_test), run_and_score(MultinomialNB, nb.best_estimator_, X_train, y_train, X_test, y_test), run_and_score(AdaBoost, ada.best_estimator_, X_train, y_train, X_test, y_test), ] df pd.DataFrame(results).sort_values(f1_macro, ascendingFalse) df.to_csv(output/reports/model_compare.csv, indexFalse, encodingutf-8-sig) print(df.to_string(indexFalse))encodingutf-8-sig这个细节别省。用 Excel 打开不带 BOM 的 CSV中文列名会变成乱码答辩投屏时很扎眼。四个指标统一保留四位小数是为了让差距可见——三个模型的准确率可能都在 0.85 上下差的是宏 F1 那零点零几保留两位小数根本看不出区别。5. 避坑清单情感分析毕设里最容易翻车的五处细节5.1 准确率虚高测试集里混进了训练集的原句现象是模型在测试集上跑到 0.95 以上换一批新评论立刻掉到 0.6。原因基本只有一个去重没做干净同一句话因为前面挂着不同的 而在原始数据里出现多次清洗后变成同一条却分别落进了训练集和测试集。解决办法是在清洗之后、切分之前按清洗后文本去重并且把去重这一步写进流水线而不是手工做一次。切分完再抽查一次两边文本的交集交集必须为空。5.2 否定词被停用词表误删情感极性整体反转现象是模型对“不好用”“不支持”这类句子的预测稳定出错且错误方向一致。原因是停用词表里收了否定词分词阶段就把“不”删掉了剩下的“好用”“支持”全是正面词。解决办法是把否定词从停用词表中显式剔除并把常见否定组合用自定义词典合成单词。改完要重新跑一遍特征旧的特征矩阵作废——用旧矩阵继续训练等于没改。5.3 三个模型用了不同的数据划分对比结论不成立现象是对比表里某个模型明显领先但复现不出来。原因是三个脚本各自调train_test_split随机种子不同训练集和测试集根本不是同一份。解决办法是划分只做一次、落盘冻结所有模型读同一份文件。这件事要在项目文档里写清楚答辩被问到实验设置时直接翻到那一页。5.4 类别不平衡被准确率掩盖少数类基本没学会现象是准确率 0.85但负面评论的召回率只有 0.4。原因是语料里正面样本占七成以上模型全猜正面就能拿到不错的准确率。解决办法是把评估主指标换成宏平均 F1选参时也用scoringf1_macro如果差距过大再考虑class_weightbalanced或对少数类做欠采样。欠采样要在训练集上做绝不能动测试集否则评估又失真了。5.5 模型存了但向量化器没存推理时特征空间对不上现象是训练时指标正常加载模型做单句预测时报维度不匹配或者预测结果毫无规律。原因是只 pickle 了分类器没存TfidfVectorizer推理时重新 fit 了一个新的向量化器词表和 IDF 权重全变了。解决办法是用joblib把向量化器和分类器打包成一个字典一起存加载时一起取出推理流程严格走“清洗 → 分词 → transform → predict”四步中间不许重新 fit。6. 验收与答辩用一张对比表和三次复现实验把方案钉死验收这件事我一般按“能不能被别人重跑一遍”来定标准而不是“我这边跑通了”。具体拆成四项逐项打勾任何一项不过都算没做完。验收项判定标准检查方式数据可复现从 raw 到 cleaned 再到 split一条命令跑完删掉中间文件重跑一次实验可复现固定种子下三次运行指标完全一致连续跑三次对比 CSV结论可支撑三模型在同一测试集上的对比表完整检查划分文件是否唯一推理可用输入一句新评论能返回标签和置信度现场手输三条不同极性文本第二项最容易被忽略但答辩时最有用。评委问“你这个 0.86 稳定吗”你可以当场把脚本跑三遍三份 CSV 的宏 F1 一模一样这个问题就彻底结束了。跑不一致说明流水线里还有未固定的随机源常见的是GridSearchCV的cv没设random_state、或者train_test_split漏了参数回去逐个排查。答辩陈述的顺序我建议反过来讲先放对比表再说为什么这三个模型这么选最后讲数据和特征。评委的注意力在前三分钟把结论摆在最前面他才有耐心听你讲清洗规则。讲对比表时用排除法——线性 SVM 在万维稀疏特征上表现最好朴素贝叶斯训练最快但宏 F1 低几个点AdaBoost 在这个特征形态下没有优势——这套说法比“我的模型准确率 0.86”有说服力得多因为它回答的是“为什么”不是“是多少”。还有一个小技巧提前准备三条边界样本一条带强烈否定、一条带反讽、一条是纯表情。现场输入把预测结果和置信度一起展示然后主动说明反讽那条为什么可能出错。主动暴露局限比等评委挑出来强而且这正好引出“后续可以引入预训练语言模型”的延伸方向把答辩的主动权拿回自己手里。从那以后我每次带这类毕设都强制自己先跑三遍复现实验再写文档数字对不上就不往下写。这个习惯救过我好几回——有一次三遍结果不一致查下来是向量化器在测试集上被误 fit 了改完指标掉了六个点但那份结果是能拿去答辩的。希望帮到你。本文还有配套的精品资源点击获取