朴素贝叶斯实现情感文本分类:源码解析与实战避坑指南
发布时间:2026/9/25 6:10:44来源:尧图网络
简介面向计算机相关专业学生与从业者基于朴素贝叶斯算法的情感文本分析与分类项目源码及数据集是期末大作业的完整方案。项目以微博短文本情感分类为核心利用预训练词向量完成文本向量化配合朴素贝叶斯分类器实现训练与预测覆盖数据清洗、特征构建、模型评估等环节代码结构清晰可作为自然语言处理课程设计的参考模板。压缩包共12个文件约173MB包含两个Python脚本、两个CSV情感数据集、微博预训练词向量压缩包以及PyCharm工程配置文件导入开发环境即可直接调试。项目评审分达95分以上所有模块经严格调试可运行能帮助掌握朴素贝叶斯在真实文本数据上的建模流程理解从数据处理到分类结果输出的完整链路。已有1025人学习下载适合需完成情感分析类课程设计或系统提升机器学习实践能力的学生。1. 基于朴素贝叶斯机器学习算法实现情感文本分析与分类源码数据集这份 zip 到底该怎么用如果刚下载了这份《基于朴素贝叶斯机器学习算法实现情感文本分析与分类源码数据集高分大作业.zip》别急着把里面所有文件读一遍。它做的事情其实很集中给一段中文文本判断它是正面还是负面情绪数据清洗、分词、特征提取、朴素贝叶斯训练和评估全用 Python 串起来了数据集也打包在 zip 里。对机器学习入门阶段的同学来说它是理解监督学习闭环的现成样本对要交期末大作业的人来说它最省事的地方是省掉了到处找数据集这一步——毕竟免费 python 源码很多能直接跑通的不多。下面我会从原理、跑通步骤、换数据、避坑到验证把这个项目真正吃透。2. 朴素贝叶斯凭什么适合做情感分类从公式到词袋再到参数选型情感文本分析是文本分类里最好落地的一个方向电商评论、微博短评、影评都是“一句话判好坏”的问题。很多黑马朴素贝叶斯案例拿它当入门模型不是因为它在所有数据集上准确率最高而是因为它训练快、可解释性强而且在短文本情感分类这种高维稀疏场景下效果并不比复杂模型差太多。我自己的体会是用 CountVectorizer 把几万条评论转成词频矩阵再训练一个多项式朴素贝叶斯普通笔记本十几秒就完事。2.1 贝叶斯公式在情感分析里的实际含义先验、似然和后验要判断一句话是正面还是负面模型实际算的是 P(正面|这句话) 和 P(负面|这句话) 哪个更大。由贝叶斯公式P(类别|文本)P(文本|类别)P(类别)/P(文本)。对同一个句子分母 P(文本) 是常数所以只需要比较分子的相对大小。P(类别) 是先验概率也就是训练集里正面/负面的占比P(文本|类别) 是似然表示在已知类别时这段文本出现的概率。朴素贝叶斯的“朴素”在于假设文本里的词在给定类别下是相互独立的。于是似然可以拆成每个词概率的连乘P(文本|类别)Π P(w_i|类别)。现实中这个假设显然不成立比如“不”和“好吃”之间有强依赖但文本分类任务并不需要精确估计概率只要类别间的相对大小别错就行。短文本每个句子只有几十个字词与词之间的共现关系被大量训练样本平均掉以后误差往往互相抵消。这正是它几十年来在垃圾邮件识别和情感分析里还没被淘汰的原因。训练朴素贝叶斯的过程很简单遍历训练集统计每个类别下每个词出现的次数除以该类别总词数得到条件概率再统计每个类别的样本占比得到先验概率。预测时把 log 先验概率加上每个词的 log 条件概率取最大者作为预测类别。取 log 是为了防止连乘几千个小数后下溢成 0。sklearn 的 predict_proba 内部也是这么计算的所以输出很稳定。有一个值得说的点从机器学习处理任务的角度看情感分类属于监督学习里的分类任务朴素贝叶斯是生成式模型它学的是联合分布 P(文本,类别)而不是像逻辑回归那样直接拟合 P(类别|文本)。生成式模型的优点是小样本时收敛快缺点是特征相关性完全没建模。周志华《机器学习》里对朴素贝叶斯的评价是“简单、高效、可解释”用在情感分析这种词特征稀疏的任务上恰好扬长避短。2.2 中文文本怎么变成机器能读的数字分词、停用词与词频矩阵模型不认识汉字需要把每句话变成固定长度的特征向量。最常见做法是先对中文分词再去掉停用词然后用 CountVectorizer 或 TfidfVectorizer 统计词频。下面的代码演示用 jieba 分词后再用 CountVectorizer 构造词频矩阵import jieba from sklearn.feature_extraction.text import CountVectorizer sentences [ 这家店的东西很划算服务态度也好, 太难吃了再也不来了, ] # 先分词再空格连接方便调试时查看中间结果 tokenized [ .join(jieba.lcut(s)) for s in sentences] print(tokenized) # 输出类似[这家 店 的 东西 很 划算 服务态度 也 好, 太 难吃 了 再也 不 来 了] # 直接指定 tokenizer让向量化器内部调用 jieba 分词 vec CountVectorizer(tokenizerjieba.lcut, min_df1, max_features5000) X vec.fit_transform(sentences) print(X.shape) # (2, 特征数) print(vec.get_feature_names_out()[:10])这里要解释两个参数。min_df1 表示某个词至少在一条样本里出现过才保留写 1 其实就是全保留写成 5 则可以去掉只在极少数评论里出现的词减小噪声。max_features5000 是只取词频最高的 5000 个词这是控制维度和内存最直接的旋钮10 万条评论的原始词表可能到十几万全留下不仅慢很多生僻词对分类也没帮助。停用词一般单独维护一个文本文件加载后传给 stop_words 参数比如“的”“了”“吗”。注意情感分析里不要把所有标点都无脑删掉感叹号“”在某些场景下是有情感倾向的。常见做法是先分词再用停用词表过滤最后再做向量化。顺序反了会导致词表里有大量“的”这类无意义特征模型会被稀释。2.3 多项式朴素贝叶斯 vs 伯努利朴素贝叶斯选型理由同样是贝叶斯分类器sklearn 里有两个常用变体。MultinomialNB 适合特征取值为非负计数的场景比如词频或 TF-IDFBernoulliNB 则把特征看成 0/1 二值只关心词出现没有。对情感文本分析我一般优先选 MultinomialNB因为“好吃”出现 3 次和出现 1 次情感强度显然不同频次是有信息量的。Bernoulli 更适合朴素贝叶斯垃圾邮件分类那种“提到某个词就大概率是垃圾”的场景或者文本特别短、频次不稳定时。模型特征含义适合场景MultinomialNB词频 / TF-IDF词出现次数有区分度电商评论、影评、长短不一的文本BernoulliNB0/1 出现与否不关心次数短文本、垃圾邮件检测、字典特征选型可以拍脑袋也可以量化用同样的词频矩阵分别跑一遍 MultinomialNB 和 BernoulliNB在验证集上对比 F1。文本情感分析数据集通常不大交叉验证成本很低不要怕麻烦。还有一个关键参数——拉普拉斯平滑 alpha。它的作用是给从未出现的词一个非零概率否则某个测试词在训练集的某类里没出现过连乘结果直接变 0。alpha 默认是 1.0也就是分子加 1、分母加词汇表大小。调大 alpha 会让概率估计更均匀对噪声鲁棒调小 alpha 则更依赖原始统计但过拟合风险更高。我一般先在 alpha1.0 跑一遍如果验证集方差大再把 alpha 往 0.1 或 5 方向试。此外fit_prior 默认 True表示让模型从数据里学先验 P(类别)如果 False则认为两类先验各 0.5。class_prior 则允许手动设置先验。当训练集类别很不平衡时这两个参数会直接影响预测偏向后面避坑章节再细说。3. 把 zip 里的项目跑起来目录结构、依赖与第一次训练拿到这样一个 zip不建议先完整读源码。正确流程是先看目录结构了解哪些是入口、哪些是数据再安装依赖跑通训练脚本最后根据报错和输出逐行理解。不然很容易陷入“每个文件都像在读天书”的状态。3.1 先看目录识别核心文件这类大作业包解压后通常包含几个固定角色一个或两个入口脚本、一个放算法实现的模块、一个数据加载模块、一个数据集文件夹可能还有 requirements.txt。用命令展开目录会更直观unzip 基于朴素贝叶斯机器学习算法实现情感文本分析与分类源码数据集高分大作业.zip -d sentiment_project cd sentiment_project tree -L 2如果系统没有 tree用find . -maxdepth 2 -type f也能看到平铺结构。文件名里带中文和括号shell 里记得加上引号否则会被解释成通配符或命令分隔符。看到结构后按优先级读这样几个文件首先是记录依赖的 requirements.txt 或 import 列表其次是数据加载文件确认数据集列名然后是训练入口确认模型用什么特征最后才是算法实现。很多大作业源码里的朴素贝叶斯是手写的比如用字典统计词频不依赖 sklearn。手写实现的训练循环反而更好懂但边界处理容易出 bug后面避坑章节会提到。3.2 安装依赖与加载数据依赖一般不会超过这几个包jieba、scikit-learn、pandas、joblib。有 requirements.txt 就直接pip install -r requirements.txt没有的话自己装一份版本不用死磕sklearn 1.x 都兼容。然后打开数据文件确认标签是什么形式import pandas as pd df pd.read_csv(data/comments.csv, encodingutf-8-sig) print(df.head()) print(df[label].value_counts())常见的大作业数据是一个 csvdf 里至少两列一列是文本原文列名可能是 text、review、comment一列是标签列名可能是 label、sentiment、tag。标签有 0/1 数字也有“好评/差评”字符串。读进来第一件事就是 print 前几行和 value_counts确认没有空值和类别全部跑偏。3.3 最小训练脚本从词频矩阵到朴素贝叶斯模型如果源码里已经封装好了训练函数直接调入口就行。但为了让你能独立复现我给你一个最小可运行脚本它等价于这类大作业的核心训练流程import pandas as pd import jieba from sklearn.feature_extraction.text import CountVectorizer from sklearn.model_selection import train_test_split from sklearn.naive_bayes import MultinomialNB df pd.read_csv(data/comments.csv, encodingutf-8-sig) df df[[text, label]].dropna() df[label] df[label].astype(int) X_train, X_test, y_train, y_test train_test_split( df[text], df[label], test_size0.2, random_state42, stratifydf[label] ) vec CountVectorizer(tokenizerjieba.lcut, max_features5000) X_train_vec vec.fit_transform(X_train) X_test_vec vec.transform(X_test) model MultinomialNB(alpha1.0) model.fit(X_train_vec, y_train) print(准确率: %.4f % model.score(X_test_vec, y_test))这里有三个要点。第一必须先切分训练集/测试集再对训练集 fit_transform测试集只 transform否则向量化的词表会用到测试集信息属于数据泄漏。第二vec.transform(X_test) 用的是训练集学会的词典所以测试集里出现训练集没见过的新词会被直接忽略这是正常现象。第三stratifydf[label] 会让切分后的训练集和测试集保持和原数据集相同的类别比例避免切完以后测试集里只有一个类别。跑通后可以把 CountVectorizer 换成 TfidfVectorizer 试试只用改 import 和构造函数本质上对 MultinomialNB 也适用因为 TF-IDF 值是非负的。大作业里常常用这个作为“改进点”。3.4 保存模型与向量化器避免每次跑都重新训练训练结束后只把模型参数打印出来是不够的占分点在于能不能直接加载新样本预测。用 joblib 把两个对象一起存下来import joblib joblib.dump(vec, models/vectorizer.joblib) joblib.dump(model, models/naive_bayes.joblib)预测新样本时loaded_vec joblib.load(models/vectorizer.joblib) loaded_model joblib.load(models/naive_bayes.joblib) new_texts [物流很快客服态度也特别热情, 等了三天才发货差评] new_vec loaded_vec.transform(new_texts) probs loaded_model.predict_proba(new_vec) for text, prob in zip(new_texts, probs): print(text, prob)保存时一定要把 vec 和 model 同时保存因为加载新样本时 transform 要依赖同一个词典只保存模型不保存 vec预测时就得重新拟合词典词表对不上结果完全不可用。这是我在实际调试里被坑过好几次的地方。另外要确保 models 目录存在joblib 不会自动创建目录。4. 把数据集换成自己的清洗、打标签与训练集划分大作业答辩时老师大概率会问“你这个模型如果换成微博评论或者商品评价还能用吗”所以别只守着 zip 里自带的数据集动手换一套数据整个项目的完成度会立刻上一个台阶。甚至很多课程的优秀作业就是基于 Python 的短文本舆情情感倾向分析系统出现的无外乎是数据换成了自己采集的评论微调了标签体系。4.1 标签体系决定后处理二分类还是三分类情感文本分析的常见标签体系有两种二分类正面/负面和三分类正面/负面/中性。大作业一般建议先做二分类因为中性样本的主观性很强不同人标注结果可能完全相反会让朴素贝叶斯的条件概率统计变得混乱。如果老师的数据源里自带“中性”可以先把中性样本去掉或者单独作为第三类训练但不强求准确率。标签用什么符号呢sklearn 的 MultinomialNB 支持字符串标签所以即使 label 列是“正/负”也可以直接 fit。但后面画混淆矩阵、算 recall 时字符串标签容易排序不稳定我一般统一映射成 0 和 10 表示负面1 表示正面。这样 predict_proba 输出的第 1 列是负面概率第 2 列是正面概率。4.2 用 pandas 做文本清洗去掉 URL、数字和无意义符号自己爬的数据往往脏得离谱夹杂 URL、表情符号、 用户名、数字、乱码。清洗规则不需要很复杂以下代码覆盖大多数场景import pandas as pd import re df pd.read_csv(raw_comments.csv) print(df.columns) def clean_text(text): text str(text) text re.sub(rhttps?://\S, , text) # 去 URL text re.sub(r\w, , text) # 去 用户名 text re.sub(r\d, , text) # 去数字 text re.sub(r[^\u4e00-\u9fa5a-zA-Z], , text) # 保留中文和英文字母 return text.strip() df[text] df[content].apply(clean_text) df[label] df[sentiment].map({正面: 1, 负面: 0}) df df.dropna(subset[text, label]) df.to_csv(data/custom_comments.csv, indexFalse, encodingutf-8-sig)正则最后一行把所有不是中文、不是英文字母的字符替换成空格手机号、表情、emoji 都会被剔除。注意表情符号在微博评论里往往有情感信息比如“太开心了”如果你发现表情对结果影响明显可以把 emoji 单独转成特征词例如用 emot 库把映射为 happy_face再把其余字符清洗。大作业做到这个程度已经是加分项。保存 csv 时用 encodingutf-8-sig是因为 Windows 上的 Excel 只认带 BOM 的 utf-8pandas 再读这个文件时用 utf-8-sig 也能正常解析。4.3 训练集/测试集划分分层抽样与随机种子清洗完的数据直接喂给训练脚本还不够一定要先划分。划分的代码在上一章已经出现过但换数据集后有两个细节要特别留意。第一个细节是 stratify。如果不带 stratify切分后可能出现测试集中 90% 都是负面正面只剩几十条准确率显示 90%其实模型没学会正面判断。带上 stratify 可以保证训练和测试集里的类别比例与原数据一致from sklearn.model_selection import train_test_split train_df, test_df train_test_split( df, test_size0.2, random_state42, stratifydf[label] ) print(train_df[label].value_counts(normalizeTrue)) print(test_df[label].value_counts(normalizeTrue))第二个细节是 random_state。random_state42 不是玄学它让每次运行切分结果完全相同保证你调参前后的准确率可比。自己实验时想换一种随机切分就改成其他整数。如果类别不平衡到少数类只占 10%最好先做重采样再切分。对文本特征不要用 SMOTE 这类连续值插值方法朴素贝叶斯用的是离散词频SMOTE 生成的不是整数特征没有意义。常见做法是直接复制少数类样本from sklearn.utils import resample df_major df[df[label] 0] df_minor df[df[label] 1] df_minor_upsampled resample( df_minor, replaceTrue, n_sampleslen(df_major), random_state42 ) df_balanced pd.concat([df_major, df_minor_upsampled])这样处理后先验概率 P(正面)≈P(负面)模型不会天生偏向多数类。但要记住复制样本会让训练集变大并且模型对少数类的过拟合风险上升训练完成后再用原始分布测试集评估不要用平衡后的测试集。我自己常用的顺序是先选样本量最小的一类按它的数量做欠采样或者不做平衡直接在分类报告里看 F1。5. 避坑指南朴素贝叶斯情感文本分析常见的 5 个翻车现场前面几章把流程走通了但这套代码在真实数据上会遇到的坑远多于模型本身。我把这些翻车点集中整理成 5 条每一条都是实际运行时报错过、花过时间排查过的。5.1 编码和分词两个最常见的第一道坎现象pandas 读 csv 直接抛 UnicodeDecodeError或者训练出来的模型预测结果全是同一个标签。原因数据文件不是 UTF-8而是 GBK 或者带 BOM 的 UTF-8。解决读文件时不要省 encoding直接用 utf-8-sig 或 gbk 试。如果文件很大可以先用二进制读取前两个字节探测with open(data.csv, rb) as f: head f.read(2) print(head) # b\xff\xfe UTF-16 # b\xef\xbb\xbf UTF-8 with BOM现象CountVectorizer 出来的特征不是词而是一整个句子串比如特征名是“这家店的东西很划算”。原因CountVectorizer 默认按空白字符把字符串切成 token中文句子如果没先用空格连接会被当成一个连续 token。解决要么构造时传 tokenizerjieba.lcut要么在送入向量化器之前用空格把分词结果 join 起来。现象分词后特征里全是单字比如“店”“了”准确率惨不忍睹。原因有些源码里用 jieba.cut 但没装词表或者把 jieba 分词结果过滤得太狠把双字词全部拆开。解决先打印 jieba.lcut 对典型句子的切分结果再确认停用词表没有把“好吃”这类有情感倾向的词误删。5.2 数据划分与评估准确率骗人问题在后面现象训练集准确率 99%测试集只有 80%自己写两句预测还经常翻车。原因最常见的不是模型过拟合而是数据泄漏。很多人先对全量文本做了 vec.fit_transform再 train_test_split测试集信息已经参与了词表构建和词频统计模型的“好成绩”是夹带私货的结果。解决永远先 split再对训练集 fit_transform测试集只 transform。如果是从网上抄的代码看到 .fit_transform(X) 之后再切分基本就是这个坑。现象准确率挺高但混淆矩阵里有一整类几乎全错。原因类别不平衡模型先验概率过度偏向大类别。解决打印 value_counts 看分布用上一章的 resample 做上采样评估不要只报准确率报 classification_report 里的 F1。注意训练时不要给 MultinomialNB 传 class_weightsklearn 的这个类没有该参数传了会直接报错。现象设置 alpha0 后模型报错或预测结果出现 NaN 概率。原因测试样本里出现了训练集中某个类别从未见过的词条件概率为 0再往下传就变成 NaN。解决alpha 不要设 0拉普拉斯平滑的意义就是兜底。alpha0.01 可以在低频词上保留一点概率但对短文本来说alpha1.0 是默认安全选择。现象标签列是字符串 0/1训练没问题画混淆矩阵时报 labels 参数错误。原因y 的类型不统一或者类别编码不一致。解决在训练前统一 astype(int)并检查 df[label].unique()。也可以用 LabelEncoder但大作业里没必要0/1 映射最简单。6. 进阶验证用混淆矩阵和错误样本回看让大作业从“能跑”变成“能答”准确率只是一个开始。很多同学训练完看到 0.87 就当作完工老师一问模型缺点就答不上来。我自己写情感分析大作业时养成的习惯是训练完立刻做两件事打印分类报告回看预测错误样本。这两步能让你对模型的边界看得非常清楚。from sklearn.metrics import classification_report, confusion_matrix import numpy as np pred model.predict(X_test_vec) print(classification_report(y_test, pred, target_names[负面, 正面])) print(confusion_matrix(y_test, pred)) wrong_idx np.where(y_test ! pred)[0][:10] for idx in wrong_idx: print(原文:, X_test.iloc[idx]) print(真实:, y_test.iloc[idx], 预测:, pred[idx]) print(---)看错误样本通常会发现两类典型问题一是反讽比如“真是谢谢您嘞”字面没有负面词但人是负面态度二是否定词与情感词搭配比如“不大好吃”“好吃”作为正面特征贡献了很大的似然朴素贝叶斯无法感知“不”对它的否定。答辩时能主动说出这些例子比把准确率从 88% 调到 89% 更能得分。还有一个具体技巧调整分类阈值。MultinomialNB 的 predict 默认取后验概率最大的一类但如果你更关心某类召回率可以直接操作预测概率prob_pos model.predict_proba(X_test_vec)[:, 1] y_new (prob_pos 0.6).astype(int)这相当于把正面类别的判定门槛抬高只有非常像正面的才判为正负面召回率会下降但精度可能上升反过来把阈值降到 0.4 则更激进。这段代码写入大作业可以体现你对“分类不是非黑即白”的理解。我的习惯是先跑一版默认参数存底再调一版带阈值调整的最后把两次的 precision/recall 对比写进实验结论。这种做法不需要额外依赖又显得项目有迭代过程。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网