当前位置: 首页 > news >正文

文本情感分类系统实战:用 jieba + TF-IDF + 逻辑回归,把中文评论自动分成好评和差评

大家好,这一篇我带你从零做一个能跑、能看、能答辩的毕业设计:文本情感分类系统。这有一些练手的项目,完整项目:https://pan.quark.cn/s/1e54aa2ae950

老规矩,先聊清楚这东西是干嘛的。你去淘宝买个手机,评论区一堆话;你去豆瓣看电影,短评一片。商家也好、平台也好,最想知道的就是这些评论里大家到底是夸还是骂。人工看太费劲,机器来分就很快。这个项目做的就是这件事:给模型一句话,它告诉你这句话是正面情感还是负面情感,顺便告诉你它有多大的把握,比如"服务态度很好"→ 正面 84%。

我用的是最经典、最好讲、答辩时也最不容易被问倒的一套组合:jieba 中文分词 + TF-IDF 特征 + 逻辑回归分类器,然后再加一个Tkinter 图形界面,把数据预览、预测结果、分布图都放进一个窗口里,看着就比纯命令行高级不少。整个项目所有图片都统一用 SimHei 黑体来显示中文,Windows 下一般自带了,不会出现方块乱码。


一、项目结构长什么样

先说目录,心里有数了再往下看代码:

15_文本情感分类/ ├── data/ │ └── reviews.csv # 训练数据,120 条中文评论 ├── tokenizer.py # jieba 分词函数(训练和预测共用) ├── build_model.py # 训练模型 + 输出评估结果 + 画图 ├── app.py # Tkinter 图形界面 ├── 模型/ │ ├── vectorizer.pkl # 训练好的词表 │ └── model.pkl # 训练好的分类器 ├── 图/ │ ├── 01_数据分布.png # 正负样本数量柱状图 │ └── 02_混淆矩阵.png # 测试结果混淆矩阵热力图 └── requirements.txt

运行顺序就两步:先build_model.py把模型训练出来,再app.py打开界面。下面一步步拆开讲。


二、数据是怎么准备的

网上有很多公开的情感数据集,但对毕设来说自己造一份小数据反而更可控:标签准、量不大、逻辑清晰,答辩时每一行你都能解释。我的reviews.csv一共120 条,正负各60 条,内容和淘宝/京东评论风格很像。

数据大概是这个样子的(节选):

文本标签
这个手机屏幕非常清晰,运行速度很快,一点都不卡positive
客服态度很差,问半天没人理,体验非常糟糕negative
价格实惠,性价比很高,推荐购买positive
东西质量太差了,用了一个星期就坏了,气人negative
外观设计很漂亮,手感舒适,非常满意positive
发货太慢了,等了半个月才到,太失望了negative

注意一个细节:CSV 存成utf-8-sig编码(带 BOM),这样既不会乱码,Excel 打开也正常。读数据时用 pandas 一行搞定:

df = pd.read_csv(DATA_FILE, encoding="utf-8-sig")

先说结论,这 120 条数据的分布是:

类别条数占比
正面 positive6050.0%
负面 negative6050.0%
合计120100%

正负完全平衡,这样训练出来的模型不会偏科——它不会因为"负面数据多"就偷偷全判负面。


三、核心算法为什么这么选

很多人一看"情感分析"就以为得上深度学习,其实这种短文本二分类,传统机器学习就够了,而且胜在能讲清楚、答辩稳。三条流水线,环环相扣:

1. 分词(jieba)

中文和英文不一样,英文天然有空格分词,中文是"糊"成一串的。"服务态度很好"这句话,得先切成"服务 / 态度 / 很好"这样的词,模型才知道看哪些单元。jieba 是最常用的中文分词库,速度也快。

2. 特征化(TF-IDF)

机器不认识汉字,只能认数字。TF-IDF 做的事就是把每句话变成一个向量,向量的每一维代表一个词或者一个词组合。它的核心思想很朴素:一个词在一句话里出现的次数越多越重要(TF),但如果它在所有评论里到处都在出现,那它的区分度就下降(IDF)。比如"质量差"这个词,就比"东西"这个词更能代表负面情绪。

3. 分类(逻辑回归)

逻辑回归输出的不是简单的"是或否",而是"属于正面的概率",比如 0.84。这点特别适合做界面展示,能直接给用户一个置信度。它本身就是一个线性模型加上 sigmoid 激活,好解释,答辩老师问起来不慌。


四、训练代码逐段讲解

完整的训练脚本是build_model.py,我把它拆成几块讲。

第一部分:基础配置和字体设置

import os import sys import joblib import numpy as np import pandas as pd sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from tokenizer import jieba_tokenizer import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report) plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False

两行rcParams很关键:第一行把 matplotlib 的默认字体换成SimHei,图里的中文标题、标签才不会变成小方块;第二行是让坐标轴的负号正常显示。下面那行sys.path.insert是为了让训练脚本能顺利引入同目录下的tokenizer.py,这样无论从哪个目录启动都不会找不到模块。

第二部分:加载数据

SEED = 42 def load_data(): df = pd.read_csv(DATA_FILE, encoding="utf-8-sig") df.columns = [c.strip() for c in df.columns] df = df.dropna().reset_index(drop=True) return df

df.columns一行是防坑用的,防止表头里带看不见的空格;dropna()把有空值的行扔掉,保证训练数据干干净净。SEED = 42是随机种子,固定下来,保证每次跑出来的结果一致,答辩演示时前后数据对得上。

第三部分:jieba 分词函数

def jieba_tokenizer(text): return [w for w in jieba.lcut(text) if w.strip()]

jieba.lcut是把一句话切成词列表,比如"客服态度很差"会切成"客服 / 态度 / 很差"。if w.strip()是过滤掉空白字符。为什么要单独放到tokenizer.py里而不是写在训练脚本里?因为训练和预测要共用同一个分词函数,而且这个函数会被存进模型文件,写到独立模块里才能保证保存、加载都不出错。

第四部分:训练模型

def build_model(X_train, y_train): vectorizer = TfidfVectorizer( tokenizer=jieba_tokenizer, ngram_range=(1, 2), sublinear_tf=True, token_pattern=None) clf = LogisticRegression(max_iter=1000, C=5.0) X_tr = vectorizer.fit_transform(X_train) clf.fit(X_tr, y_train) return vectorizer, clf

这里有两个值得一提的调参点:

  • ngram_range=(1, 2):除了单个词,还把相邻的两个词当一个特征。这样"质量好"和"质量差"这种带修饰的词组能被单独识别出来,单看"质量"一个词反而分不清好坏。
  • sublinear_tf=True:对词频做一次对数压缩,防止某个词出现次数太多把其他特征盖过去。

逻辑回归里max_iter=1000是给足迭代次数让它收敛,C=5.0是正则化强度的反比,调大一点让模型拟合得更充分。就这几行,模型已经能用了。

第五部分:训练集测试集划分

X_train, X_test, y_train, y_test = train_test_split( df["文本"], df["标签"], test_size=0.2, random_state=SEED, stratify=df["标签"])

test_size=0.2表示 120 条里抽 96 条训练、24 条测试。stratify是分层抽样,保证测试集里正负比例和全量一样,都是 50% 对 50%,不然测试结果会有偏差。

第六部分:评估模型

acc = accuracy_score(y_test, y_pred) p = precision_score(y_test, y_pred, pos_label="positive") r = recall_score(y_test, y_pred, pos_label="positive") f1 = f1_score(y_test, y_pred, pos_label="positive")

四个指标一起看,比单看准确率科学。准确率是整体预测对的占比;精确率是"预测为正面里真有几分是正面";召回率是"真正面里被找出几分";F1 是精确率和召回率的调和平均,两者偏科任何一头都会把它拉下来。测试集预测对错还会打印出来,方便答辩时随手指着说"你看这句预测对了,置信度 73%"。

第七部分:画图和保存模型

def draw_distribution(df): counts = df["标签"].value_counts() ... plt.title("训练数据中正面 / 负面评论数量分布") plt.ylabel("评论条数") plt.savefig(os.path.join(IMAGE_DIR, "01_数据分布.png"), dpi=150) def draw_confusion(cm): ... plt.title("测试集混淆矩阵") plt.savefig(os.path.join(IMAGE_DIR, "02_混淆矩阵.png"), dpi=150) joblib.dump(vectorizer, os.path.join(MODEL_DIR, "vectorizer.pkl")) joblib.dump(clf, os.path.join(MODEL_DIR, "model.pkl"))

图一是正负样本数量对比柱状图,图二是混淆矩阵热力图,都保存到"图"目录。最后用joblib.dump把词表和分类器存成两个 pkl 文件,这就是训练和预测解耦的关键:界面启动时不用重新训练,直接加载现成的模型,秒开。


五、真实运行结果(数据展示)

我跑了一遍,真实输出是这样的:

总样本数: 120 正面比例:50.0% 负面比例:50.0% 测试集数量: 24 准确率 :79.17% 精确率 :76.92% 召回率 :83.33% F1 值 :80.00%

分类报告:

类别精确率召回率F1样本数
负面0.820.750.7812
正面0.770.830.8012

测试样本对照(前 8 条,表示预测正确):

判定预测置信度原文
正面60%物流速度杠杠的,第二天就到了,包装也很严实
正面71%锅炉加热快,热水充足,冬天用很舒服
正面56%音质特别好,低音震撼,戴上就不想摘下来
正面54%卖家服务热情,有问必答,售后很及时
负面68%客服敷衍,退货一直被拖,态度恶劣
负面55%信号极差,经常断网,打电话都听不清
负面73%做工粗糙,缝隙很大,明显是次品
负面79%防水效果差,下雨天没一会就进水了

在 120 条小数据上做到79% 的准确率、80% 的 F1,对短文本二分类来说已经是不错的成绩了。混淆矩阵里可以看到,模型在"负面判成正面"和"正面判成负面"上大概各错两三例,这就是小样本数据的正常表现,答辩时如实说清楚就是加分项。


六、图形界面是怎么做的

命令行输出再漂亮,也抵不上一个窗口来得直观。我用 Python 自带的Tkinter写了个界面,不需要额外装 UI 库。窗口分成三块:

  1. 顶部输入区:一个文本框,贴一句评论,点"开始预测"。
  2. 中间结果区:大字显示"正面情感 / 负面情感",颜色红绿区分,旁边再标置信度。
  3. 下方标签页:一页是数据集预览表格,一页是本次会话所有预测结果的分布柱状图。

关键代码是这样:

root = tk.Tk() SentimentApp(root, vectorizer, clf) root.mainloop()

预测的核心就三行:

vec = self.vectorizer.transform([text]) label = self.clf.predict(vec)[0] conf = max(self.clf.predict_proba(vec)[0])

注意这里用的是transform而不是fit_transform——因为词表已经在训练时建好了,预测时只需要把新句子套进同一套词表,这就是保存模型的好处。predict_proba返回的是两个概率,取最大值就是置信度,比如"这个耳机音质太棒了"预测正面,置信度 84%,界面上就显示绿色的大字"正面情感 84%"。

柱状图是用 matplotlib 的FigureCanvasTkAgg嵌进 Tkinter 窗口的:

self.fig = plt.Figure(figsize=(5, 3), dpi=100) self.ax = self.fig.add_subplot(111) self.canvas = FigureCanvasTkAgg(self.fig, master=tab2) self.canvas.get_tk_widget().pack(fill="both", expand=True)

每点一次"开始预测",就会往self.records里追加一条结果,然后重画柱状图。你连续测十几句话,能看着柱子一点点长高,数据分布一目了然。界面的字体统一用了 SimHei,中文标题、按钮、提示文字都不会乱码。


七、怎么运行起来

环境要求:Python 3.8+,以及这几个库。

numpy pandas scikit-learn matplotlib joblib jieba

装完库之后两步走:

  1. 先训练模型:在项目目录下运行python build_model.py,运行完会看到评估结果,同时生成"模型"里的两个 pkl 文件和"图"里的两张图片。
  2. 再开界面:运行python app.py,会弹出图形窗口,直接输入中文评论点预测就行。

如果直接打开app.py提示找不到模型文件,那就是第一步还没跑,先回去把build_model.py跑一遍。


八、答辩时老师可能问什么

几个大概率被问到的问题,提前想好答案:

问:为什么不用深度学习?
答:短文本二分类用传统机器学习已经能到 80% 左右的 F1,逻辑回归训练快、好解释、还不用显卡,更适合教学和毕设场景。如果数据量到几万条再考虑 BERT 之类的深度模型。

问:TF-IDF 到底在算什么?
答:TF 是一个词在文本里出现的频率,IDF 是这个词在整个语料里的稀有程度。两者相乘,既看重词语在句子里是否重要,又削弱那些到处都有的常见词。

问:置信度是什么?
答:逻辑回归输出的本质是一个概率,属于正面的概率 0.84,就说明模型有 84% 的把握认为这句话是正面的。

问:还能怎么改进?
答:增大训练数据量、加一个情感词典、试试支持向量机或者朴素贝叶斯做对比实验,都能让准确率再上一截。

http://www.cnnetsun.cn/news/3868887.html

相关文章:

  • 揭秘江苏住房和城乡建设部网站:普通人如何利用它解决买房、装修与办事痛点?
  • JSP Tag File 实战:打造 Layui 后台管理布局模板
  • 揭秘最专业的网站建设价格背后逻辑:为什么有些报价让老板不敢问?
  • RTS5766DL固态硬盘量产开卡全攻略:从“砖头”到满血复活
  • 抖音批量下载终极指南:三步搞定无水印视频与作者主页全量备份
  • 如何5分钟内为离线音乐库批量下载同步歌词:LRCGET终极指南
  • 基于streamable-http协议的MCP服务设计与实现
  • Redis缓存进阶:从穿透、雪崩到多级缓存架构的实战解决方案
  • 电子商务网站建设与维护实训报告:从零基础小白到独立操盘手的实战进阶之路
  • PySpark UDF详解:从原理到性能优化实战
  • 硕博开题指南:全方位梳理硕博开题全流程要点 助力高效完成开题准备
  • 旅游网站规划建设方案:如何打造一个既美观又实用的在线旅游平台
  • 本地部署Qwen2.5大模型:使用llama-cpp-python实现流式对话
  • 配电网韧性优化:移动电源车预配置与鲁棒调度
  • 2026年8月,AI API 价格战进入“终局“:当智能变成白菜价,竞争逻辑彻底变了
  • GEO代理深度解析:老牌企业的AI转型底气
  • 职场沟通进阶:从对齐、赋能到闭环,高效协作必备术语解析
  • 网站建设不完整 审核 背后的真相:别让粗糙的上线毁了你品牌的信任基石
  • Mirror IL后处理技术:实现Unity零开销RPC调用的原理与实战
  • Report Builder 3.0连接Oracle数据库的完整指南
  • .NET多店进销存系统开发与部署指南
  • D3KeyHelper终极指南:5分钟掌握暗黑3技能自动化配置
  • 主题资源网站建设反思:从流量狂欢到价值回归的深度复盘与长期主义思考
  • 远控软件深度测评|用了连连控两个月,后悔没早点装!
  • 3分钟极速安装:Fast-GitHub加速插件完全指南,告别龟速下载烦恼
  • AI时代终极护城河:验证级网络效应的工程化构建与实践
  • 从零基础到精通,手把手教你掌握德州网站建设教程的每一步
  • 全球语言代码实战指南:从ISO标准到i18n配置的避坑手册
  • XOutput终极指南:3步解决老手柄不兼容问题,让所有游戏控制器重获新生 [特殊字符]
  • 2026 广东光伏四可改造避坑指南!五大主流服务商评测,选对少走弯路