Python 实战:基于朴素贝叶斯的中文评价情感分析(好评 / 差评自动识别)| 附完整可运行代码
前言
在电商、本地生活、内容平台等场景中,每天都会产生海量的用户评价文本。人工逐条区分好评 / 差评、挖掘用户痛点效率极低,而通过 NLP + 机器学习技术实现评价情感倾向自动分类,可以帮助商家快速定位产品问题、优化服务体验,也能为平台运营提供数据支撑。
本文将从 0 到 1 完整实现一套中文评价情感分析系统:从原始文本数据预处理、中文分词、停用词过滤,到词向量转换、朴素贝叶斯模型训练,再到解决数据不平衡问题、最终实现用户输入任意评价自动判断好评 / 差评,全程附带代码解析、踩坑记录和真实运行效果,新手也能直接复现。数据集的准备请移步博客文章《开箱即用!商品评价爬虫实战,好评差评数据直接拿》。
一、项目整体效果与技术选型
1. 最终实现效果
- 支持本地优质评价 / 差评文本批量训练模型
- 自动完成中文文本分词、停用词过滤、词向量转换全流程
- 针对评价数据常见的类别不平衡问题,通过 SMOTE 过采样优化模型效果
- 提供交互式输入功能:输入任意评价文本,实时输出「优质评价」/「差评」的判断结果
- 完整的模型评估报告,精准查看模型在训练集 / 测试集的分类效果
2. 核心技术栈
| 工具 / 库 | 核心用途 |
|---|---|
| jieba | 中文文本分词,解决中文无天然分词符的核心问题 |
| pandas | 文本数据读取、结构化处理与数据集整合 |
| scikit-learn | 数据集切分、词向量转换、朴素贝叶斯模型构建、分类效果评估 |
| imblearn(SMOTE) | 过采样处理,解决好评 / 差评样本数量严重不平衡的问题 |
| MultinomialNB | 多项式朴素贝叶斯模型,专为文本分类这类离散特征场景优化 |
3. 环境准备
执行以下命令一键安装所有依赖:
pip install pandas jieba scikit-learn imbalanced-learn二、数据准备与文本预处理
1. 数据源说明
本文使用 3 个核心数据文件,和代码放在同一目录下即可:
优质评价.txt:每行一条用户好评文本,作为正样本差评.txt:每行一条用户差评文本,作为负样本stopword.txt:中文停用词表,包含「的、了、啊、吧」等无情感意义的语气词、连接词,用于过滤噪声
2. 文本读取与中文分词
中文和英文最大的区别是没有天然的空格分隔词,因此第一步必须用分词工具将句子拆分为独立的词语。这里选用最主流的jieba分词库,使用精确模式lcut完成分词。
import pandas as pd import jieba # 读取评价文件 cp_content = open(r'差评.txt', encoding='utf-8') yz_content = open(r'优质评价.txt', encoding='utf-8') # 对差评文本逐行分词 cp_sum = [] for line in cp_content: result = jieba.lcut(line) # 过滤空行和分词后仅1个词的无意义内容 if len(result) > 1: cp_sum.append(result) # 对优质评价文本逐行分词 yz_sum = [] for line in yz_content: yo = jieba.lcut(line) if len(yo) > 1: yz_sum.append(yo) # 关闭文件,避免资源泄露(新手易踩坑点) cp_content.close() yz_content.close()3. 停用词过滤
分词后的结果里包含大量无情感意义的停用词,会干扰模型的特征学习,因此需要过滤掉;同时补充过滤长度≤1 的单字,进一步降低噪声。
# 读取停用词表,转为列表格式(关键修正:直接用DataFrame会导致in判断失效) stopwords = pd.read_csv(r'stopword.txt', encoding='utf-8', engine='python') stopwords_list = stopwords['stopword'].values.tolist() # 定义停用词过滤函数 def stop(contests, stopwords): seg_clear = [] for contest in contests: line_clear = [] for word in contest: # 核心过滤规则:停用词 + 长度≤1的无意义词 if word in stopwords or len(word) <= 1: continue line_clear.append(word) seg_clear.append(line_clear) return seg_clear # 对分词结果执行停用词过滤 cp_fc_results_clear = stop(cp_sum, stopwords_list) yz_fc_results_clear = stop(yz_sum, stopwords_list)4. 标签标注与数据集合并
监督学习需要为样本标注标签,这里我们定义:优质评价(好评):标签为 0,差评:标签为 1,将两类样本合并为完整的训练数据集,方便后续切分和训练。
# 为样本标注标签 cp_train = pd.DataFrame({'pj':cp_fc_results_clear,'lable':1}) yp_train = pd.DataFrame({'pj':yz_fc_results_clear,'lable':0}) # 纵向合并两个数据集,得到完整的标注数据集 pj_train = pd.concat([cp_train,yp_train])三、数据集切分与词向量转换
1. 训练集 / 测试集切分
将数据集按 8:2 的比例切分为训练集和测试集。训练集:用于模型学习特征和拟合。测试集:用于验证模型的泛化能力,避免过拟合
固定random_state保证每次运行的切分结果一致,方便复现效果。
from sklearn.model_selection import train_test_split x_train, x_test, y_train, y_test = \ train_test_split( pj_train['pj'].values, pj_train['lable'].values, train_size=0.8, random_state=0 )2. 文本转词向量(核心步骤)
计算机无法直接处理文本字符串,必须将其转为数值型的向量,这一步就是文本向量化。本文使用CountVectorizer词频向量化,核心逻辑是:基于训练集构建高频词库,将每条文本转为「词频向量」,向量的每个维度对应词库中的一个词,值为该词在文本中出现的次数。
# 格式化文本:将分词列表转为空格分隔的字符串,适配CountVectorizer的输入格式 words = [] for line_index in range(len(x_train)): words.append(' '.join(x_train[line_index])) # 导入词向量转换库 from sklearn.feature_extraction.text import CountVectorizer # 初始化词向量转换器 vec = CountVectorizer( max_features=1700, # 只保留词频最高的1700个词作为词库,降低维度避免过拟合 lowercase=False, # 中文无需转为小写,关闭该功能 ngram_range=(1,3) # 同时考虑1-3个词的组合,捕捉「物流慢、质量差」这类短语语义 ) # 核心步骤:fit基于训练集构建词库,transform将文本转为词频向量 vec.fit(words) # 仅用训练集构建词库,避免数据泄露 x_train_vec = vec.transform(words) # 训练集转为词向量注!!!
新手必看踩坑点:测试集只能用
vec.transform(),绝对不能用fit_transform()!否则会用测试集重新构建词库,导致数据泄露,模型评估结果失真。
四、解决核心痛点:类别不平衡问题
从我的运行结果可以看到,电商评价数据存在严重的类别不平衡:
- 训练集中,优质评价(标签 0)有 2911 条,差评(标签 1)仅 15 条
- 测试集中,优质评价 725 条,差评仅 7 条
如果直接用不平衡的数据集训练,模型会严重偏向多数类(好评),哪怕全部预测为好评,准确率也能达到 99%,但完全无法识别差评,失去了业务价值。
这里我们用SMOTE 过采样解决该问题:通过算法合成少数类(差评)的样本,让训练集中两类样本的数量达到平衡,提升模型对差评的识别能力。
# 导入SMOTE过采样器 from imblearn.over_sampling import SMOTE # 初始化SMOTE,固定随机种子保证结果可复现 smote = SMOTE(random_state=42) # 仅对训练集执行过采样,测试集保持原始分布 x_train_smote, y_train_smote = smote.fit_resample(x_train_vec, y_train)五、朴素贝叶斯模型训练与效果评估
1. 模型训练
文本分类场景中,多项式朴素贝叶斯MultinomialNB是最经典、高效的算法,对离散的词频特征适配性极强,训练速度快,泛化效果好。
# 导入朴素贝叶斯模型 from sklearn.naive_bayes import MultinomialNB # 初始化模型,alpha=1为拉普拉斯平滑,避免出现零概率问题 classifier = MultinomialNB(alpha=1) # 用平衡后的训练集训练模型 classifier.fit(x_train_smote, y_train_smote)2. 模型效果评估
使用classification_report输出完整的分类报告,重点关注精准率、召回率、F1-score,不要只看整体准确率(不平衡数据中准确率无参考意义)。
from sklearn import metrics # 训练集效果验证 train_pr = classifier.predict(x_train_vec) print("训练集分类报告(原始数据预测):") print(metrics.classification_report(y_train,train_pr)) # 测试集效果验证(核心看泛化能力) test_words = [] for line_index in range(len(x_test)): test_words.append(' '.join(x_test[line_index])) test_pr = classifier.predict(vec.transform(test_words)) print("测试集分类报告:") print(metrics.classification_report(y_test,test_pr))真实运行结果展示
从运行结果可以看到:
- 模型整体准确率达到 99%,对优质评价的识别效果极佳
- 经过 SMOTE 过采样后,模型对差评的召回率达到 71%,解决了不平衡数据下少数类完全无法识别的问题
- 测试集泛化效果稳定
- 由于差评数据较少,即使通过过拟合,结果仍然不是很理想。可以通过继续爬取差评数据来增加差评的真实数据。增强差评的训练能力
六、交互式预测:输入评价自动判断好评 / 差评
封装预测函数,实现和训练集完全一致的预处理流程,保证预测结果的准确性,同时增加边界处理,避免无效输入导致报错。
def predict_sentiment(text): # 1. 对输入文本执行jieba分词 seg = jieba.lcut(text) # 2. 执行停用词过滤,和训练集预处理规则保持一致 clear_seg = stop([seg], stopwords_list)[0] # 边界处理:过滤后无有效词汇,返回提示信息 if not clear_seg: return "无法判断(输入文本过短或无有效词汇)" # 3. 转为词向量,使用训练集构建的词库 text_vec = vec.transform([' '.join(clear_seg)]) # 4. 模型预测 pred = classifier.predict(text_vec)[0] # 5. 返回可读性强的结果 return "优质评价(好)" if pred == 0 else "差评(坏)" # 程序入口:用户交互式输入 if __name__ == "__main__": user_input = input("请输入要判断的评价:") result = predict_sentiment(user_input) print(f"判断结果:{result}")七、新手必看踩坑记录
- 停用词判断失效:读取停用词后直接用 DataFrame,没有转为列表,导致
word in stopwords判断完全失效,必须用.values.tolist()转为列表格式 - 数据泄露问题:测试集使用
fit_transform()重新构建词库、在全数据集执行过采样,都会导致模型评估结果虚高,正确做法是仅用训练集 fit / 过采样 - 文件资源泄露:用 open 打开文件后没有 close,长时间运行会导致句柄泄露,推荐用
with open()上下文管理器自动管理文件 - 预处理流程不一致:预测单条文本时,没有执行和训练集完全一致的分词、停用词过滤规则,导致预测结果出错
- 只看准确率:不平衡数据中,哪怕全部预测为多数类,准确率也能达到 99%,必须重点关注少数类的 F1-score 和召回率
八、完整可运行代码
import pandas as pd import jieba # 读取评价文件 cp_content = open(r'差评.txt', encoding='utf-8') yz_content = open(r'优质评价.txt', encoding='utf-8') # 对差评分词 cp_sum = [] for line in cp_content: result = jieba.lcut(line) if len(result) > 1: cp_sum.append(result) cp_fc_results = pd.DataFrame({'chaping': cp_sum}) # 对优质评价分词 yz_sum = [] for line in yz_content: yo = jieba.lcut(line) if len(yo) > 1: yz_sum.append(yo) yz_fc_results = pd.DataFrame({'content': yz_sum}) # 导入停用词(关键修正:读取后转为列表) stopwords = pd.read_csv(r'stopword.txt', encoding='utf-8', engine='python') stopwords_list = stopwords['stopword'].values.tolist() # 提取停用词列表 # 定义去除停用词(关键修正:判断词是否在停用词列表中) def stop(contests, stopwords): seg_clear = [] for contest in contests: line_clear = [] for word in contest: # 过滤停用词 + 过滤长度为1的无意义词(可选优化,不影响核心功能) if word in stopwords or len(word) <= 1: continue line_clear.append(word) seg_clear.append(line_clear) return seg_clear # 去除停用词(传入停用词列表) cp_fc_results_clear = stop(cp_sum, stopwords_list) yz_fc_results_clear = stop(yz_sum, stopwords_list) # 关闭文件(原代码遗漏,补充关闭避免资源泄露) cp_content.close() yz_content.close() '''朴素贝叶斯分类''' '''标签分类''' cp_train = pd.DataFrame({'pj':cp_fc_results_clear,'lable':1})#表格数据 yp_train = pd.DataFrame({'pj':yz_fc_results_clear,'lable':0}) pj_train = pd.concat([cp_train,yp_train]) '''数据切分''' from sklearn.model_selection import train_test_split x_train, x_test, y_train, y_test = \ train_test_split(pj_train['pj'].values,pj_train['lable'].values,train_size=0.8,random_state=0) '''将所有词转化为词向量''' words = [] #将转化的词向量为标准格式 for line_index in range(len(x_train)): words.append(' '.join(x_train[line_index])) print(words) #导入词向量转化库 from sklearn.feature_extraction.text import CountVectorizer vec = CountVectorizer(max_features=1700,lowercase=False,ngram_range=(1,3)) #max_features表示提取词频最高的1700个作为词库 #lowercase表示把所有词转化为小写,False不需要 vec.fit(words) #传入训练集的所有词 ##fit_transform()2个功能 1:训练出词库 2:transform根据词库转化为词向量 x_train_vec = vec.transform(words) # 添加过采样处理(SMOTE) from imblearn.over_sampling import SMOTE # 初始化SMOTE过采样器 smote = SMOTE(random_state=42) # 对训练集进行过采样 x_train_smote, y_train_smote = smote.fit_resample(x_train_vec, y_train) """使用贝叶斯""" from sklearn.naive_bayes import MultinomialNB,ComplementNB classifier = MultinomialNB(alpha=1) # 使用过采样后的数据集进行训练 classifier.fit(x_train_smote, y_train_smote) train_pr = classifier.predict(x_train_vec) # 用原始训练集向量预测(对比用) from sklearn import metrics print("训练集分类报告(原始数据预测):") print(metrics.classification_report(y_train,train_pr)) # 测试集数据分析测试 test_words = [] for line_index in range(len(x_test)): test_words.append(' '.join(x_test[line_index])) test_pr = classifier.predict(vec.transform(test_words)) print("测试集分类报告:") print(metrics.classification_report(y_test,test_pr)) '''用户输入预测部分''' def predict_sentiment(text): # 对输入文本分词 seg = jieba.lcut(text) # 去除停用词和长度为1的词 clear_seg = stop([seg], stopwords_list)[0] # 传入列表的列表,取第一个结果 if not clear_seg: # 若处理后无有效词汇 return "无法判断(输入文本过短或无有效词汇)" # 转换为词向量格式 text_vec = vec.transform([' '.join(clear_seg)]) # 预测 pred = classifier.predict(text_vec)[0] return "优质评价(好)" if pred == 0 else "差评(坏)" # 用户交互 if __name__ == "__main__": user_input = input("请输入要判断的评价:") result = predict_sentiment(user_input) print(f"判断结果:{result}")九、总结与拓展优化方向
本文完整实现了从原始评价文本到自动情感分类的全流程,解决了中文分词、文本向量化、数据不平衡等核心问题,最终实现了用户输入任意评价自动判断好评 / 差评的功能,可直接落地到电商评价分析、客服消息情感识别等场景。
后续可拓展优化的方向:
- 数据优化:继续爬取差评,增加真实差评数量。进而增加差评训练能力
- 词向量优化:用
TfidfVectorizer替代CountVectorizer,给有区分度的词更高权重,进一步提升分类效果 - 模型优化:使用专为不平衡文本分类设计的
ComplementNB模型,或尝试 SVM、随机森林、XGBoost 等算法 - 功能拓展:批量读取 Excel/CSV 中的评价数据,批量完成分类并输出结果文件,生成词云可视化
- 效果升级:使用预训练语言模型(如 BERT、RoBERTa)实现更精准的情感分析,适配更复杂的语义场景
