Python中文情感分析实战:从语料到SVM的酒店评论分类

发布时间:2026/10/12 14:18:55
Python中文情感分析实战:从语料到SVM的酒店评论分类 简介面向计算机专业毕业生与课程设计学习者这套基于 Python 的酒店评论中文情感分析系统兼顾源码、设计文档与数据集可支撑毕业设计、课程设计或期末大作业。项目曾获导师认可并通过高分评审代码运行稳定整体思路完整能帮助理解中文文本预处理、特征降维与情感极性分类的典型流程。压缩包共收录 2000 个文件几乎全部为 txt 语料1999 个另有 1 个 Python 脚本包体仅 1.77MB。txt 包含停用词表、正负向酒店评论语料等多类文本Python 脚本中可看到 pca_svm 等关键实现便于对照学习。目录层级清晰打开后还可参考 README 说明快速上手。目前已有 360 人学习下载适合正在做相关方向毕设、课设或想实际操练情感分析任务的学生。下载后还可获得远程教学支持便于排查运行问题顺利复用或二次修改。1. 酒店评论中文情感分析一个 96.5 分的毕设骨架不是模型而是语料拿到这份「基于 python 的酒店评论中文情感分析系统源码设计文档数据集」时我原本以为重点会放在 SVM 调参或者特征工程上真正打开后才发现这套毕业设计最值钱的其实是那批整理好的正负向酒店评论文本和一套完整的预处理链路。评审平均分 96.5 说明它不只是能跑而是结构完整、答辩有东西可讲。它解决的核心问题很朴素把一段中文酒店评论自动判成好评还是差评从 txt 原始语料一直做到 PCASVM 出分类结果附带的说明文档恰好补上了课程设计和毕业设计最头疼的「写报告」环节。适合正在做毕设、课程设计或者刚入门 NLP 想找一份完整工程复现的从业者——你的诉求应该是快速跑通、看懂每一段代码在干什么、答辩时能讲清楚设计理由这份资源能覆盖这三件事。2. 从语料到 SVM把中文评论变成正负面标签的完整链路2.1 语料文件怎么读pos/neg 文件与 stopWord.txt压缩包里散着一批看起来很有规律的 txt 文件pos.719.txt、pos.709.txt、pos.554.txt、pos.545.txt、pos.318.txt负面一侧是neg.730.txt、neg.670.txt、neg.36.txt。从文件命名习惯看数字大概率是每个文件里的样本量或编号这类命名方式在课程设计里很常见——把不同来源的评论分批存放便于追溯数据来源。与其纠结数字到底代表什么不如打开文件看一眼首行内容一分钟就能确认格式。数据层面你需要知道三件事。第一这些 txt 是一行一条评论还是整块文本我习惯在读取时先按行切分并去空行避免把空字符串当成样本送进分类器。第二正负文件数量明显不对称pos侧有 5 个文件neg侧只有 3 个如果数字代表样本量那训练时就存在类不平衡的隐患。第三stopWord.txt是停用词表作用是在分词后把「的、了、很、也」这类无信息量的词过滤掉这份资源把停用词单独剥出来放在根目录说明原作者是把预处理当作独立环节来设计的这个习惯很值得学。常规做法是先把所有 pos 文件和所有 neg 文件分别合并再打上标签import os import random def load_corpus(pos_files, neg_files): texts, labels [], [] for path in pos_files: with open(path, encodingutf-8, errorsignore) as f: lines [line.strip() for line in f if line.strip()] texts.extend(lines) labels.extend([1] * len(lines)) for path in neg_files: with open(path, encodingutf-8, errorsignore) as f: lines [line.strip() for line in f if line.strip()] texts.extend(lines) labels.extend([0] * len(lines)) return texts, labels pos_files [pos.719.txt, pos.709.txt, pos.554.txt, pos.545.txt, pos.318.txt] neg_files [neg.730.txt, neg.670.txt, neg.36.txt] texts, labels load_corpus(pos_files, neg_files)这里encodingutf-8只对 UTF-8 编码的文件有效如果 txt 是 GBK 或 ANSI 编码读取会直接抛异常我在第 4 章的避坑部分会专门展开。errorsignore的作用是遇到无法解码的字符时跳过而不是中断在碰到半截乱码文本时能保住大部分样本。标签用 1 表示正面、0 表示负面这是分类任务最常见的编码方式。最后一定要把训练数据洗乱否则前 80% 都是正面、后 20% 都是负面交叉验证的结果会严重失真。2.2 中文分词与停用词过滤预处理链路的核心中文情感分析和英文最大的差异在于英文单词天然有空格分隔中文没有。所以中文 NLP 的第一步永远是分词这一步的质量直接决定后面的 TF-IDF 和 SVM 能学到什么。常见做法是使用 jieba 分词因为它对网络文本和日常口语的覆盖度在开源库里是数一数二的安装也简单。这份资源没有把 jieba 打进代码里说明运行时需要自己安装这是环境依赖层面的事。分词加停用词过滤的代码套路非常固定import jieba def load_stopwords(pathstopWord.txt): with open(path, encodingutf-8, errorsignore) as f: return set(line.strip() for line in f if line.strip()) stopwords load_stopwords(stopWord.txt) def preprocess(text): words jieba.cut(text.strip()) return .join(w for w in words if w not in stopwords and w.strip())jieba.cut返回的是生成器逐词产出分词结果过滤条件包含两个部分w not in stopwords是去掉停用词w.strip()是去掉空白字符和纯空格词。把分词结果用空格拼接成字符串是为了下一步喂给TfidfVectorizer——它默认按空格切分 token这个细节如果在答辩时被问到能解释得很清楚。我一般会在加载停用词时打印一下词表长度如果只有几十个词说明停用词表太薄对分类结果几乎没有实际帮助一份像样的中文停用词表至少应该有几百个词覆盖标点、语气词、助词和常见无意义动词。2.3 特征表示与 PCA 降维为什么 SVM 前面要加这一步把评论变成向量之后维度会非常夸张。假设语料里有 5000 个不同的词每条评论被表示成 5000 维的稀疏向量其中大部分是零。SVM 虽然在稀疏高维数据上表现不错但维度越高训练时间和内存开销越大而且 TF-IDF 向量里掺杂着大量低频词带来的噪声。主成分分析PCA在这里的作用不是降维可视化而是把 5000 维的高斯噪声压掉保留最具区分度的正交主成分让 SVM 在低维空间里找到更干净的分隔超平面。特征工程和降维的典型流程如下from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import PCA vectorizer TfidfVectorizer(max_features5000, ngram_range(1, 2)) X vectorizer.fit_transform([preprocess(t) for t in texts]) pca PCA(n_components200) X_pca pca.fit_transform(X.toarray())max_features5000的意思是只保留词频最高的前 5000 个词这既控制了维度也自动丢掉了只在极个别样本里出现的噪声词。ngram_range(1, 2)同时保留单个词和相邻两个词的组合像「服务周到」这种双词短语在情感分类里比单独的「服务」「周到」更有判别力。PCA 的n_components200是我在类似项目里比较常用的起点它把 5000 维压缩到 200 维通常能保留 80% 以上的方差贡献率。注意PCA.fit_transform接收的是稠密数组所以需要调用toarray()把稀疏矩阵转成稠密矩阵这也是全流程里最容易触发内存暴涨的地方——语料有几万条时这一步要格外小心。2.4 SVM 分类器线性核与 RBF 核怎么选SVM 是这个项目的分类核心文件名叫5_pca_svm.py说明原作者把 PCA 和 SVM 绑定成了一个完整阶段。SVM 的核心思想是找一个最大间隔超平面把正负样本分开在低维线性不可分时通过核函数把数据映射到高维空间再找超平面。对文本分类场景两个候选核函数是线性核和 RBF径向基核。线性核参数少、训练快、不容易过拟合适合特征维度较高、样本量中等的场景RBF 核能处理非线性边界但需要调C和gamma两个参数调不好反而比线性核更差。我的经验是先用线性核跑一版看准确率能不能到 85% 以上如果线性核效果已经不错就不要再碰 RBF答辩时也更容易自圆其说——「特征经过 PCA 降维后已经是低维稠密表示我优先选择线性核验证基线」。from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X_pca, labels, test_size0.2, stratifylabels, random_state42 ) model SVC(kernelrbf, C1.0, gammascale, class_weightbalanced, probabilityTrue) model.fit(X_train, y_train) y_pred model.predict(X_test) print(classification_report(y_test, y_pred, target_names[负面, 正面]))stratifylabels很关键它保证训练集和测试集里正负样本的比例和原始语料一致避免随机划分把某一类全部抽走这在正负样本不平衡的项目里是必须写的。class_weightbalanced是让 SVM 在计算损失时给样本少的那一类更高的权重正面负面数量差距大时这一行参数往往比调核函数更能直接提升召回率。probabilityTrue会启用 Platt 缩放让模型输出预测概率后面做置信度过滤或者画 ROC 曲线时用得上。输出报表里的 precision、recall、f1-score 三项是答辩时评委最常追问的数字你要能说清楚它们分别代表什么。3. 跑通 5_pca_svm.py环境、命令与结果解读3.1 环境准备Python 版本与依赖库这个项目是典型的机器学习三件套项目不涉及深度学习框架所以环境配置压力不大。Python 版本建议 3.8 到 3.10太新的 3.12 在某些旧版 scikit-learn 上会有编译问题如果你手头只有 3.12优先把 scikit-learn 升到 1.3 以上再尝试。必装的依赖库是numpy、scikit-learn、jieba如果之后要画混淆矩阵顺手装一个matplotlib。安装命令按顺序执行pip install numpy scikit-learn jieba matplotlib安装完成后打开 Python 解释器验证导入python -c import sklearn, jieba, numpy; print(sklearn.__version__, jieba.__version__)如果这一行不报错环境就是通的。常见问题是scikit-learn和numpy版本不匹配报ValueError: numpy.dtype size changed解决办法是把两个库一起升级到最新版而不是只装其中一个。资源里没有附带requirements.txt这也是课程设计项目常见的遗漏你可以在根目录自己补一个把环境一次固定住。3.2 目录结构与快速运行拿到压缩包之后建议先按下面这个结构整理目录再把5_pca_svm.py放到和 txt 文件同一级目录下运行减少路径问题hotel_sentiment/ ├── 5_pca_svm.py ├── stopWord.txt ├── pos.719.txt ├── pos.709.txt ├── pos.554.txt ├── pos.545.txt ├── pos.318.txt ├── neg.730.txt ├── neg.670.txt └── neg.36.txt直接运行脚本python 5_pca_svm.py如果脚本里用的是相对路径这个目录结构可以保证它直接找到所有语料和停用词表。运行成功后终端应该会输出模型在测试集上的准确率和分类报告。如果报FileNotFoundError优先检查当前终端所在目录是不是放文件的那一层可以用pwdLinux/macOS或cdWindows确认路径不要在代码里写死绝对路径换一台机器就废了。3.3 输出指标怎么解读accuracy、precision、recall、F1脚本跑完后会输出一串指标很多第一次接触的人只盯着准确率accuracy这是不够的。在正负样本不平衡时准确率会欺骗你——假如 80% 的样本都是正面你全预测成正面也能拿到 80% 准确率但这个模型没有任何使用价值。正确做法是看三组数字neg 类的 recall 代表「真实差评里有多少被正确找出来」pos 类的 precision 代表「模型说是好评的评论里有多少真的是好评」F1-score 是两者的调和平均综合衡量这一类别的表现。如果 pos 的 F1 是 0.9neg 的 F1 只有 0.6说明模型把大量差评误判成了好评——这在情感分析里是比误伤好评更糟糕的错误因为酒店更在意漏掉差评而不是错杀好评。评估代码一般是from sklearn.metrics import confusion_matrix, classification_report print(classification_report(y_test, y_pred, target_names[负面, 正面])) tn, fp, fn, tp confusion_matrix(y_test, y_pred).ravel() print(f真正例: {tp}, 假正例: {fp}, 真负例: {tn}, 假负例: {fn})confusion_matrix返回 2×2 矩阵ravel()把它展平后依次对应 TN、FP、FN、TP 四个值。答辩时你如果能指着这四格数字解释「假负例偏多说明模型对隐性差评识别不足下一步应该补充负面语料」这个回答质量就已经超过绝大部分本科毕设水平了。4. 避坑指南跑三遍这项目才总结出的五个实际问题4.1 编码报错UnicodeDecodeError 与 GBK/UTF-8 的坑现象运行脚本读取pos.554.txt时直接抛UnicodeDecodeError: utf-8 codec cant decode byte 0xc4 in position 0程序中断。原因下载的 txt 文件可能是 Windows 下用记事本默认保存的 ANSI/GBK 编码而 Python 的open()默认按 UTF-8 解码。这个项目的语料文件来源混杂不同文件编码可能不一致用统一编码读必然翻车。解决读取时不要写死编码先探测后读取或者直接用容错模式with open(path, encodinggbk, errorsignore) as f: lines f.readlines()如果 GBK 解码后中文显示正常就沿用如果仍有乱码改成encodingutf-8再试。更稳的方案是用chardet库自动探测pip install chardetimport chardet def read_text(path): with open(path, rb) as f: raw f.read() enc chardet.detect(raw)[encoding] or utf-8 return raw.decode(enc, errorsignore).splitlines()从那以后凡是处理来源不明的数据文件我都强制先过一遍编码探测这个习惯能省下大量排查时间。4.2 分词结果异常自定义词典与停用词表的边界现象jieba 分词后「酒店设施」被拆成「酒店」「设施」没有大问题但「华住会」「如家」这类专有名词被切成「华」「住」「会」特征完全失去语义同时停用词表把「不错」里的「不」过滤掉导致「不错」变成「错」。原因jieba 默认词典以通用词汇为主酒店评论里的品牌名、地名、服务术语覆盖不够停用词表过滤规则太粗单字「不」被粗暴去掉把否定词误伤掉。否定词在情感分析里是语义反转的关键信号「不」不见了「不好」就变成「好」分类结果直接反过来。解决加载停用词时排除否定词并手动给 jieba 补充酒店领域词jieba.add_word(华住会) jieba.add_word(如家) jieba.add_word(前台小姐) negation_words {不, 没, 没有, 不太, 不怎么} stopwords load_stopwords(stopWord.txt) stopwords stopwords - negation_words加完词后建议用一小段真实评论试分词效果确认「华住会」不再被拆开。停用词表的边界问题靠人工排查最有效跑一遍输出几条样例眼睛扫一遍比任何自动评估都直观。4.3 正负样本数量不对称类别不平衡的连锁反应现象训练出来的模型正向 F1 高达 0.92负向 F1 只有 0.55整体准确率看着还行但负面评论基本识别不出。原因从文件名看pos侧有 5 个文件neg侧只有 3 个合并后正面样本明显多于负面样本。SVM 在类别不平衡时会倾向于把边界推向样本量少的一侧导致少数类被大量误判。如果neg.36.txt里真的只有 36 条样本那这个不平衡程度已经非常严重。解决三个手段叠加使用。第一train_test_split里必须加stratifylabels保证切分后类别比例稳定。第二SVM 里设置class_weightbalanced。第三如果样本量实在太小手动构造平衡训练集from imblearn.over_sampling import SMOTE X_resampled, y_resampled SMOTE(random_state42).fit_resample(X_pca, labels)SMOTE 在 PCA 降维后的稠密特征上效果稳定它能生成少数类的合成样本把训练集补平衡。答辩时讲这段能体现出你考虑到了生产环境中真实数据分布不均的问题。4.4 PCA 降维后分类效果不升反降现象加 PCA 之后的准确率比直接用原始高维 TF-IDF 还低降维降了个寂寞。原因n_components200保留的方差占比不足时关键判别信息被压缩掉了或者 PCA 在降维前没有做标准化导致某些取值大的维度主导了主成分方向而情感判别真正依赖的中等取值维度被忽略。解决先查看累计方差贡献率选一个能解释 90% 以上方差的维度import numpy as np pca_temp PCA(n_components0.95) pca_temp.fit(X.toarray()) print(pca_temp.n_components_)把n_components设成 0.95 而不是固定数字让算法自动保留 95% 方差对应的维度数通常落在 200~500 之间。如果用了标准化后仍然下降那说明当前语料下 PCA 确实不适合退回全特征用线性 SVM 就好不要为了「用了算法」而硬用。4.5 模型保存与复用时特征维度对不上现象训练时一切正常保存模型后在新评论上预测时报ValueError: X has 300 features, but SVC is expecting 200 features as input。原因只保存了训练好的 SVM 模型没有保存对应的TfidfVectorizer和PCA对象。新评论进来时用新初始化的TfidfVectorizer得到的是完全不同的词表维度对不上即便维度碰巧一致词对应的位置也完全不同输出结果毫无意义。解决用sklearn.pipeline.Pipeline把向量化、降维、分类三步打包成一个对象整体保存整体加载from sklearn.pipeline import Pipeline import joblib pipe Pipeline([ (tfidf, TfidfVectorizer(max_features5000, ngram_range(1, 2))), (pca, PCA(n_components200)), (svm, SVC(kernelrbf, C1.0, class_weightbalanced)) ]) pipe.fit([preprocess(t) for t in texts], labels) joblib.dump(pipe, sentiment_model.pkl)只用一份sentiment_model.pkl就能完成「新文本 → 预测」的全过程不再需要手工组装三个对象。从那以后我每次保存模型都强制检查是不是整个 pipeline 一起 dump再也没有碰过维度对不上的问题。5. 进阶把训练好的模型接上真实预测与交叉验证5.1 用 joblib 加载模型预测一条新评论训练是手段预测才是目的。模型保存成sentiment_model.pkl后新写一个预测脚本来加载并跑真实评论import joblib pipe joblib.load(sentiment_model.pkl) def predict_sentiment(text): cleaned preprocess(text) proba pipe.predict_proba([cleaned])[0][1] label pipe.predict([cleaned])[0] return 正面 if label 1 else 负面, round(proba, 4) comment 酒店位置很好但隔音太差晚上吵得睡不着 label, proba predict_sentiment(comment) print(f预测结果: {label}, 正面概率: {proba})predict_proba要求模型在初始化时设置probabilityTrue输出的是一个二维数组[0][1]表示第一条样本属于正面的概率。这里有一个边界情况值得注意当概率接近 0.5 时模型处于置信度模糊区比如概率在 0.45~0.55 之间强行给一个二分类结果不如直接说「无法判断」。实际业务里我会加一道置信度门槛低于 0.6 就判为「中性」或「待人工审核」这在课程设计答辩里是一个很好的加分项。5.2 用 GridSearchCV 找最优参数直接跑默认参数能出结果但不够专业。给 SVM 做一次小规模网格搜索能同时展示你对参数的理解和调参方法from sklearn.model_selection import GridSearchCV tuned_pipe Pipeline([ (tfidf, TfidfVectorizer(max_features5000, ngram_range(1, 2))), (pca, PCA(n_components0.95)), (svm, SVC(class_weightbalanced)) ]) param_grid { svm__kernel: [linear, rbf], svm__C: [0.1, 1.0, 10], svm__gamma: [scale, 0.01] } grid GridSearchCV(tuned_pipe, param_grid, cv5, scoringf1_macro) grid.fit([preprocess(t) for t in texts], labels) print(grid.best_params_)cv5表示五折交叉验证数据被切成五份轮流做测试集比单次划分更稳定scoringf1_macro是在两类 F1 的均值上做优化而不是被数量多的那一类带走。搜索完成后打印best_params_结果里十有八九是linear核加上一个适中的 C 值——这个现象可以在答辩时解释PCA 降维后的特征已经接近线性可分RBF 的复杂度优势体现不出来。我记得有一次调完参数RBF 核反而比线性核低了三个百分点原因就是 gamma 没设好导致过拟合。从那以后我在低维特征场景下都默认先跑线性核把 RBF 当成备选而不是首选。这个项目真正做到这一步就不仅是一个能跑的毕设而是一个可以亲手改、亲手验证的情感分析小工具了。把这些步骤完整过一遍你对「分词 → 向量化 → 降维 → 分类 → 调参」的理解会比看十篇教程都扎实这份源码和数据集值得下载下来自己跑一遍。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询