ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于朴素贝叶斯与SVM的微博评论情感分析实战

基于朴素贝叶斯与SVM的微博评论情感分析实战 简介一套基于机器学习朴素贝叶斯与支持向量机算法的微博评论情感分析可视化项目源码面向计算机相关专业正在准备期末大作业、课程设计或需要项目实战练习的学习者。项目经导师指导并获评审99分代码完整、可运行覆盖从微博评论数据获取、中文停用词过滤、情感得分计算到模型训练与可视化展示的完整流程可作为高分大作业模板参考。压缩包共51个文件以Python源码5个py文件、csv数据集、txt词库/停用词表、png/jpg截图与结构图、html可视化页面及训练好的model模型文件为主总大小约17.79MB目录清晰便于按模块对照学习。当前已有303人学习下载。随包提供README说明、操作命令txt、项目流程图及完整模型文件适合快速理解情感分析项目从数据到应用的实现思路能有效降低独立完成类似大作业的上手门槛。1. 这门期末大作业选它的人最后都拿到了什么先给结论用朴素贝叶斯和支持向量机做微博评论情感分析是文本分类方向里性价比最高的一套组合拳。它不挑显卡、不依赖深度学习框架、训练时间以秒计却能把「分词 → 特征提取 → 模型训练 → 评估 → 可视化」这条完整链路走通。对期末大作业来说这意味着你不需要花两周调超参数只需要把每个模块做扎实就能拿到一份结构完整、答辩有东西可讲的源码和文档说明。很多学生卡在同一个地方数据有了模型也跑起来了但不知道中间每一步在做什么。这篇笔记直接按一套可复现的方案来写——从微博评论怎么清洗、中文分词怎么选词到朴素贝叶斯和 SVM 两个模型的差异分析再到混淆矩阵和情感分布图怎么画最后把最常见的五个翻车现场列出来。全程 Python 实现依赖只用 sklearn、jieba、pandas 和 matplotlib环境配好就能跑。这套方案对做文本分类、舆情分析、电商评论分类的人同样适用期末作业只是它最典型的一个落地场景。2. 从微博评论到特征向量文本预处理与中文分词落地2.1 为什么中文情感分析不能直接喂原始文本机器学习模型吃的是数值不是字符串。微博评论是短文本通常不到 50 个字噪声却很大有用户、URL、表情符号、重复标点、繁体字和网络用语。如果你直接把原始文本传给朴素贝叶斯模型看到的是一堆无法计算的字符结果必然是报错或者全部分到同一类。常见做法是把文本转成向量这一步决定了模型的天花板。对短文本情感分析来说最常用的特征是 TF-IDF 权重它比单纯的词频更好用因为 TF-IDF 能降低「的」「了」「就」这类高频无意义词的干扰同时保留「难喝」「客服」「差评」这类判别性强的词。选 TF-IDF 而不是 Word2Vec 或 BERT 的原因很现实期末项目时间有限TF-IDF 在几千条样本上几分钟就能跑完而且 sklearn 里TfidfVectorizer直接封装好了不需要额外下载预训练词向量。2.2 清洗规则去噪比选模型更重要我先给出一份通用的清洗函数它处理的是微博评论最常见的几类噪声。这份代码直接复制就能用但你要理解每一行在干什么答辩时老师大概率会问。import re import jieba def clean_weibo_text(text: str) - str: # 去URL text re.sub(rhttp\S|www\.\S, , text) # 去用户名 text re.sub(r\w[:\s]?, , text) # 去话题标签如 #苹果发布会# text re.sub(r#.*?#, , text) # 去表情符号占位微博评论里常见[哈哈]、[泪]这种 text re.sub(r\[.*?\], , text) # 去重复标点!!! 和 。。。 统一成单个 text re.sub(r[!]{2,}, !, text) text re.sub(r[。.]{2,}, 。, text) # 去空格和换行 text re.sub(r\s, , text) return text.strip()这段代码的关键在于处理顺序先去掉 URL 和 用户再处理话题标签和表情占位符。顺序不能乱比如如果先去空格再匹配 URLURL 里的空格会导致匹配失败。re.sub的正则模式里\S匹配非空白字符.*?是非贪婪匹配保证[哈哈]这种短表情能被完整去掉。清洗之后做分词和去停用词。停用词表建议自己维护一份网上有现成的中文停用词库但你要检查它是否包含「不」「没」「别」这类否定词——这三个词如果被当成停用词删掉「不好看」会变成「好看」情感极性直接反转。这是一个非常隐蔽的坑第五章节会展开说。STOPWORDS set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: STOPWORDS.add(line.strip()) def tokenize_for_ml(text: str) - list: words jieba.lcut(text) # 过滤停用词、单字词和纯数字 return [w for w in words if w not in STOPWORDS and len(w) 1 and not w.isdigit()]jieba.lcut返回的是列表比jieba.cut生成器更直观。过滤条件里len(w) 1会把大多数单字词去掉但也会误删「好」「差」「烂」这类单字情感词这一步要看你的数据分布。如果你的语料里单字情感词出现频率高就把这个条件去掉或者单独保留一个情感词白名单。2.3 TF-IDF 向量化参数怎么设才不亏向量化是整个预处理链路里最值得调参的一步。TfidfVectorizer有四个参数直接决定特征质量max_features、ngram_range、min_df和max_df。我常用的配置和理由如下。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features5000, ngram_range(1, 2), min_df2, max_df0.8, token_patternr\S # jieba 已经分好词不需要默认的字母切分 ) # corpus 是清洗并分好词后的评论列表每条评论的分词结果用空格拼接 corpus [ .join(tokenize_for_ml(text)) for text in df[clean_comment]] X vectorizer.fit_transform(corpus)max_features5000限制特征维度减少过拟合并加快训练。ngram_range(1, 2)同时保留单个词和双词组合双词组合能捕捉「不好」「服务差」这类短语但三元以上在短文本上提升有限还容易造成维度爆炸。min_df2表示词至少出现在 2 条评论里才保留可以去掉只出现一次的噪声词。max_df0.8表示词在 80% 以上的评论中都出现则忽略这类词基本是「的」「了」等高频无意义词。token_patternr\S很重要因为 jieba 已经用空格分隔好了词默认的正则会把中文标点也切进去。提示fit_transform是在训练集上调用测试集只调transform不要对测试集再 fit 一次否则会引入数据泄露模型评估分数会虚高。3. 朴素贝叶斯与 SVM选型依据和 Python 实现3.1 两个模型各自擅长什么朴素贝叶斯和 SVM 在文本分类里都有一席之地但原理完全不同。朴素贝叶斯基于贝叶斯定理假设特征之间条件独立——这个假设在文本上显然不成立「好吃」和「环境」有关联但朴素贝叶斯假装它们无关。这个「错误」的假设反而让它在短文本上表现稳健尤其在训练数据少的时候它的先验概率平滑能力能压住过拟合。SVM 的思路是找一个最大间隔超平面把不同类别的样本分开。对线性可分的数据SVM 找到的分隔线不仅要分类正确还要距离最近样本点最远这让它的泛化能力比感知机强一个档次。文本经过 TF-IDF 向量化后往往维度很高但稀疏这种场景下线性 SVM 非常擅长因为它只在支持向量上计算不受样本整体分布影响。淘系和阿里的很多早期文本分类系统里SVM 都是默认基线模型今天用深度学习的人多了但 SVM 在几千条标注样本上的表现依然能打。选两个模型做对比还有一个好处期末答辩时你能从「特征独立性假设」和「最大间隔」两个角度解释为什么它们在不同数据规模下各有优势这是加分项。3.2 朴素贝叶斯代码MultinomialNB 与参数细节sklearn 里朴素贝叶斯有三种GaussianNB、MultinomialNB 和 BernoulliNB。文本 TF-IDF 特征是离散计数或者权重满足多项分布所以这里选 MultinomialNB。from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split # labels 是情感标签0 表示负向1 表示正向 X_train, X_test, y_train, y_test train_test_split( X, labels, test_size0.2, random_state42, stratifylabels ) nb_model MultinomialNB(alpha1.0, fit_priorTrue) nb_model.fit(X_train, y_train) nb_acc nb_model.score(X_test, y_test) print(fMultinomialNB 准确率: {nb_acc:.4f})alpha是拉普拉斯平滑参数默认 1.0。它的作用是防止某个特征在训练集中没出现导致概率为 0调大 alpha 会让概率分布更平滑对噪声数据的容忍度更高。fit_prior表示是否学习类别先验概率如果数据类别不平衡建议保持 True 让模型自动调整。stratifylabels是train_test_split里容易被忽略的参数它保证划分后训练集和测试集的正负样本比例和原始数据一致这个不设置如果原始数据恰好顺序排列很可能测试集全是同一类评估分数会严重失真。3.3 线性 SVM 代码LinearSVC 为什么比 SVC 更快SVM 在 sklearn 里有SVC和LinearSVC两个实现。对文本这种高维稀疏数据LinearSVC用的是线性核的优化版本训练速度比SVC(kernellinear)快一个数量级这是因为LinearSVC基于 liblinear 库而SVC基于 libsvmlibsvm 在处理大规模稀疏数据时需要计算核矩阵代价高很多。期末项目几千条数据感受不明显但如果你后面扩到几万条差别就出来了。from sklearn.svm import LinearSVC svm_model LinearSVC(C1.0, losssquared_hinge, max_iter2000, random_state42) svm_model.fit(X_train, y_train) svm_acc svm_model.score(X_test, y_test) print(fLinearSVC 准确率: {svm_acc:.4f})C是正则化强度的倒数C 越大越强调训练集分类正确越容易过拟合C 越小则容忍更多误分类换取更大的间隔和更好的泛化。文本分类通常从 C1.0 开始试如果训练集分数远高于测试集说明过拟合调小 C 到 0.1 或 0.5。losssquared_hinge比默认的hinge收敛更快在有约束优化问题上更稳定。max_iter2000是防止默认 1000 次迭代没收敛时直接报警告加上之后哪怕不收敛也能看到提示而不是黑匣子一样跑完。这两个模型训练完之后建议把它们的预测结果都保存下来后面的混淆矩阵和可视化两套都要用。3.4 模型对比不要只看准确率还要看差异样本准确率相近时不代表两个模型真的等价。常见做法是把两个模型的预测结果做差集找到 NB 预测对但 SVM 预测错的样本以及反过来的情况。这些样本往往是情感倾向模糊或者包含反讽的评论它们能直观反映两个模型的性格差异。import numpy as np nb_pred nb_model.predict(X_test) svm_pred svm_model.predict(X_test) # 找两个模型预测不一致的样本 diff_idx np.where(nb_pred ! svm_pred)[0] print(f两个模型预测不一致的样本数: {len(diff_idx)}) # 把不一致样本里的原始评论和预测结果打印出来人工观察规律 for i in diff_idx[:10]: print(f原文: {df_clean.iloc[X_test.indices[i]]}) print(f真实: {y_test.iloc[i]} | NB: {nb_pred[i]} | SVM: {svm_pred[i]})这里的X_test.indices能回溯原始样本位置这依赖于train_test_split返回的索引对应关系。如果你的 X_test 是一个 numpy 数组而没有保留索引可以直接把原始 DataFrame 切成X_test同顺序的版本再取。这步帮你判断如果 SVM 在带否定词的句子上更强说明它的决策边界对特征组合更敏感如果 NB 在短评上更强说明它的概率平滑更适合稀疏表示。把差异样本的分析写进文档说明答辩时是很有分量的内容。4. 模型训练与评估准确率之外还要看混淆矩阵和可视化输出4.1 评估指标怎么选才不骗自己准确率在类别不平衡时是最不能信的指标。假设你的数据里 90% 是正向评论模型全预测正向也能拿到 90% 准确率但这个模型毫无价值。情感分析至少要看三个指标精确率、召回率和 F1。精确率回答的是「预测为正的样本里真的正的比例」召回率回答的是「真实为正的样本里被找出来的比例」F1 是两者的调和平均。from sklearn.metrics import classification_report, confusion_matrix # 用 SVM 结果做示例NB 同理 print(classification_report(y_test, svm_pred, target_names[负向, 正向])) cm confusion_matrix(y_test, svm_pred) print(混淆矩阵:) print(cm)classification_report输出的每一行对应一个类别的精确率、召回率和 F1最后一行还有宏平均和加权平均。宏平均对类别不平衡敏感加权平均按类别样本数加权两者一起看能帮你判断模型是不是只在多数类上表现好。混淆矩阵是 2×2 结构行是真实标签列是预测标签。cm[0][1]是真实负向被预测成正向的数量这个值如果偏高说明模型有「乐观偏置」即倾向于输出正向情感。4.2 用 matplotlib 输出情感分布和 ROC 曲线可视化的意义不是好看而是让老师一眼看懂你的数据分布和模型性能。项目里至少要有三张图情感类别分布柱状图、混淆矩阵热力图、SVM 的 ROC 曲线。这三张图分别回答「数据长什么样」「模型错在哪」「模型的判别能力有多强」。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import roc_curve, auc from sklearn.preprocessing import label_binarize plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 图1情感分布 fig, axes plt.subplots(1, 3, figsize(15, 4)) df[label_name] df[label].map({0: 负向, 1: 正向}) df[label_name].value_counts().plot(kindbar, axaxes[0], color[#d9534f, #5cb85c]) axes[0].set_title(微博评论情感类别分布) # 图2混淆矩阵热力图用SVM的 sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[负向, 正向], yticklabels[负向, 正向], axaxes[1]) axes[1].set_title(SVM 混淆矩阵) axes[1].set_xlabel(预测标签) axes[1].set_ylabel(真实标签) # 图3ROC 曲线 y_score svm_model.decision_function(X_test) fpr, tpr, _ roc_curve(y_test, y_score) roc_auc auc(fpr, tpr) axes[2].plot(fpr, tpr, labelfSVM (AUC {roc_auc:.3f})) axes[2].plot([0, 1], [0, 1], k--, label随机猜测) axes[2].set_xlabel(假正率) axes[2].set_ylabel(真正率) axes[2].set_title(SVM ROC 曲线) axes[2].legend() plt.tight_layout() plt.savefig(sentiment_analysis_result.png, dpi150, bbox_inchestight) plt.show()plt.rcParams设置中文字体是关键不设置的话图里所有中文都会变成方框这是每次画图都有人翻车的点。ROC 曲线用decision_function拿的是样本到超平面的距离这个距离表示模型对分类结果的置信度距离越大说明越确信是正向。AUC 面积越接近 1 越好0.5 等于随机猜。如果你的 NB 模型也想画 ROCMultinomialNB 没有decision_function只能用predict_proba取正类概率替代这是两个模型在可视化上的一个差异点。4.3 词云把特征词变成可视化证据词云不是必须的但如果你的文档说明里需要展示「模型学到了什么」词云比特征重要性排序更直观。按情感类别分别生成正向词云和负向词云能直接看出模型依据哪些词做判断。from wordcloud import WordCloud def generate_wordcloud(texts, save_path, color): # 把所有评论拼成一个大字符串 all_text .join(texts) wc WordCloud( font_pathsimhei.ttf, width800, height400, background_colorwhite, max_words100, colormapcolor ) wc.generate(all_text) wc.to_file(save_path) # 正向评论词云 pos_texts df[df[label] 1][clean_comment].tolist() generate_wordcloud(pos_texts, pos_wordcloud.png, Greens) # 负向评论词云 neg_texts df[df[label] 0][clean_comment].tolist() generate_wordcloud(neg_texts, neg_wordcloud.png, Reds)font_path必须指定中文字体文件路径Windows 下用C:/Windows/Fonts/simhei.ttfmacOS 下用系统自带的 PingFang 字体路径。不指定字体直接生成词云会出现中文全部变成方块的问题。词云生成依赖jieba已经把词语用空格隔开如果你传入的是原始句子WordCloud 内部会按空格分词得到的结果就是整个短语而不是词语。把词云放进文档说明时记住它的作用是辅助验证而不是证明模型效果好——模型效果还是以 F1 和 AUC 为准。5. 微博情感分析五大常见坑现象、原因与解决方案5.1 否定词被停用词表误删情感极性全反现象模型对「不好吃」「不满意」这类评论全部判为正向准确率惨不忍睹。原因网上找的开源停用词表里收录了「不」「没」「别」等否定词预处理时把这些词删了「不好吃」变成「好吃」情感极性反转。这是情感分析里最典型的预处理事故。解决在加载停用词表后强制把否定词白名单加回来NEGATION_WORDS {不, 没, 别, 无, 莫, 勿, 未, 难} STOPWORDS STOPWORDS - NEGATION_WORDS # 从停用词表里剔除否定词如果你用的是带neg前缀的否定词特征比如把「不好」整体变成neg_好那是在特征工程层面处理当前项目里只要保证否定词不进停用词表就够了。5.2 类别不平衡导致模型只会输出多数类现象训练结束后分类报告显示正向类的 F1 有 0.95负向类的 F1 只有 0.5负向类大量被判成正向。原因标注数据时负向评论占比不足 20%模型学到的最优策略是全部输出正向。MultinomialNB 的fit_priorTrue会学习这种先验分布SVM 的决策边界也会偏向多数类。解决优先用class_weight参数修正而不是先做数据增强。对 SVM 加class_weightbalanced对朴素贝叶斯需要手工调整class_prior或者直接用compute_class_weight计算权重。from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight(balanced, classesnp.array([0, 1]), ylabels) print(f类别权重: 负向{class_weights[0]:.3f}, 正向{class_weights[1]:.3f}) # 传入 LinearSVC svm_balanced LinearSVC(C1.0, class_weightbalanced, max_iter2000)5.3 分词结果太碎情感词全被拆散现象jieba 分词后「不太行」被切成「不太」和「行」「难吃」被切成「难」和「吃」。原因jieba 默认词典面向通用场景微博网络用语和口语化表达覆盖不足情感短语经常被拆分。解决加载自定义词典把高频情感短语整体加入。# custom_dict.txt 内容格式词 词频 词性 # 不太行 100 adj # 难吃 100 adj # 绝绝子 100 adj jieba.load_userdict(custom_dict.txt)自定义词典的格式是三列词、词频建议填一个大于默认分词频率的数比如 100、词性。加载词典后再次分词你会看到「不太行」和「绝绝子」被保留为整体词。这一步不增加代码量但对模型效果的影响有时候比调 C 参数还明显因为特征质量直接决定分类上限。5.4 SVM 训练报错或不收敛日志里全是警告现象LinearSVC训练时控制台输出大量ConvergenceWarning或者直接抛出ValueError说需要至少两个类。原因两种情况最常见。第一max_iter默认 1000 在某些稀疏高维数据集上不够收敛第二数据划分后训练集里全是同一类通常是因为原始数据按标签顺序排列切分时没有洗牌。解决train_test_split时加shuffleTrue默认就是 True但要确认你没手动关掉并且设置random_state固定随机种子确保结果可复现。如果已经用了stratify就不会出现训练集缺类的问题。max_iter从 2000 起步要是还警告就把tol放宽到 1e-4svm_model LinearSVC(C1.0, max_iter5000, tol1e-4)5.5 可视化中文全部变成方框现象柱状图、热力图的横纵坐标中文标签全是方框或者乱码。原因matplotlib 默认字体不支持中文系统找不到对应的中文字形就渲染成方框。解决不要只用plt.rcParams[font.sans-serif] [SimHei]SimHei在 Linux 服务器上没有直接指定字体路径或者用font_manager加载本地字体文件最稳。import matplotlib.font_manager as fm font_path /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc fm.fontManager.addfont(font_path) plt.rcParams[font.family] fm.FontProperties(fnamefont_path).get_name()加上plt.rcParams[axes.unicode_minus] False处理负号显示问题这个也是老坑忘记写的话 ROC 曲线 X 轴的负号会显示成方块。6. 从「跑通」到「能讲」最后一步把项目变成模块化代码做到这里你的代码大概率还是一个顺序执行的脚本。期末答辩时如果老师问「如果新增一类样本你怎么改」顺序脚本的劣势就暴露了。我把项目按模块拆成五个文件每个文件职责单一文档说明也能顺着模块讲清楚。weibo_sentiment/ ├── data/ │ ├── raw_weibo_comments.csv # 原始评论 标签 │ ├── stopwords.txt # 停用词表已剔除否定词 │ └── custom_dict.txt # 自定义情感词典 ├── src/ │ ├── preprocess.py # 清洗 分词 自定义词典加载 │ ├── features.py # TF-IDF 向量化 │ ├── models.py # 朴素贝叶斯 SVM 训练与评估 │ └── visualization.py # 分布图、混淆矩阵、ROC、词云 └── run.py # 主流程按顺序调用上面四个模块run.py里只需要写一个main()函数按「读取数据 → 清洗分词 → 向量化 → 训练评估 → 可视化」的顺序调用。这样的结构让文档说明变得很好写每个模块对应一个小节配一段关键代码和一句设计理由。额外再准备一个批量验证脚本模拟用户输入一句新评论输出预测情感——这个在答辩现场演示效果非常好老师会看到你的模型不是只在历史测试集上有效而是真的能对新文本做预测。演示验证的时候注意一个细节新输入的文本要走和训练数据完全一样的预处理流程先清洗再分词再 transform不能直接调model.predict([这家店真好吃])因为模型没见过原始字符串TfidfVectorizer的transform要求输入已经是空格分隔的分词结果。把「训练和预测走同一套预处理」写进代码注释和文档说明是证明你真正理解整个链路的标志。我自己的习惯是每次运行完把分类报告、混淆矩阵、AUC 这三组数字记在一个 markdown 文件里。改一次预处理方案对比一次数字谁变好谁变坏一目了然。这个习惯做三个版本迭代后你会对哪些操作对情感分析真正有效有直觉而不是靠猜。希望你也能从这套代码里得到你自己的结论祝顺利。本文还有配套的精品资源点击获取
返回列表