ARTICLE DETAIL

资讯详情

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

酒店评论情感分析Python实战:从数据清洗到模型调优全流程

酒店评论情感分析Python实战:从数据清洗到模型调优全流程 简介面向Python课程期末大作业与情感分析入门的一项酒店评论情感分析完整项目源码本地编译可运行评审分达95分以上难度适中且经助教审定可作为课程设计参考或结课作业模板。压缩包共23个文件、约4.36MB代码部分含2个Python程序分别承担情感打分与主流程执行词典和停用词部分包含14个TXT文件覆盖正面、负面、程度、否定等情感词典以及哈工大、四川大学等多套停用词表供文本预处理和情感评分调用另附正负向语料压缩包、实验报告、答辩PPT、展示图片和说明文档。整套内容覆盖数据准备、文本预处理、情感词典匹配、评分计算到结果可视化的闭环流程结构清晰下载后可直接运行并结合报告理解模块实现也可将报告和PPT作为课程答辩的参考模板。轻量易得已有157人学习适合需要快速复现项目、借鉴完整工程结构并争取高分的Python学习者。1. 酒店评论情感分析这个题目为什么年年有人做年年翻车打开任何一个期末大作业列表基于Python的酒店评论情感分析都是常客。原因很简单题目自带完整链路——数据、文本处理、模型、可视化、文档一个不少既不像爬虫那样游走灰色地带也不像神经网络图像识别那样需要显卡一台普通笔记本跑完绰绰有余。但正因为做的人多评分老师眼里这个题目的「标准答案」早就成型了能用、能出图、能解释三者缺一不可。我见过太多人代码跑得通最后文档里写不清「为什么用这个模型、参数怎么调的、失败了几次」照样拿不到高分。这篇笔记不会给你一个神乎其神的模型而是把整个项目拆成六个你能直接抄作业的部分数据怎么整、预处理怎么做、模型怎么选、可视化怎么出彩、坑在哪里、文档怎么配合演示拿高分。目标只有一个——照着一路做下来你的大作业能跑、能讲、能扛住答辩追问。先说结论这类项目拿高分的核心不在模型精度而在「每步都能自圆其说」。你用朴素贝叶斯拿 85% 准确率只要讲清楚原理和局限比闷头跑一个 BERT 拿 90% 但说不出所以然要稳得多。下面直接开工。2. 数据准备与任务定义先把「分析」这两个字框死2.1 数据从哪来三种来源的取舍做情感分析第一步不是写代码是搞清楚你要分析什么。酒店评论情感分析这个题目看似明确实际有个隐藏问题评论从哪来我见过三种做法各有各的坑。第一种是直接用公开数据集。网上能搜到不少中文酒店评论 CSV通常包含评论文本和评分1 到 5 分。这种数据最省事但要注意两个问题一是来源不明可能已经被人预处理过特征被洗掉一半二是评分分布往往不平衡5 分好评占半数以上直接拿来做分类会虚高。拿到数据先跑一句df[label].value_counts()看清楚分布再说。第二种是自己爬。携程、去哪儿、美团这些平台的酒店评论区反爬策略都不弱期末考试时间有限我不推荐你在爬虫上死磕。而且爬虫涉及平台条款问题为一个大作业去冒这个险不值。热词里「美团情感分析」经常出现你可以参考这个方向但别真去爬美团。第三种是自己造或者混合。找一份公开的中文评论数据做底再手动标注几百条补充进去既保证数据量又能对质量有掌控。大作业嘛数据来源写得越清楚越加分。提示无论用哪种来源最终你的数据格式必须是两列——text评论文本和label情感标签。这是后面所有步骤的基础别在数据格式上搞创新。2.2 标签怎么定二分类还是三分类标签定义直接决定模型复杂度。最常见的做法是基于评分映射4 分以上算正向2 分以下算负向3 分算中性。但这里有个行业默认的坑——酒店评论里的 3 分特别少因为用户习惯要么满意要么不满很少打中评。你映射完会发现中性样本少得可怜模型根本学不好。我的建议是如果你的数据量在 2000 条以下就做二分类正向 / 负向把 3 分并入负向或直接丢弃简单可靠数据量到 5000 条以上再考虑三分类。二分类的准确率目标应该在 85% 以上三分类能到 75% 就说明特征工程做得到位了。标签定完之后顺手把数据集按 8:2 切分成训练集和测试集固定随机种子保证每次跑的结果可复现。这一步写在代码注释里答辩时能体现工程素养。2.3 数据清洗这步做不好模型再好也白搭数据清洗是情感分析里最容易被低估的环节。很多人拿到原始评论直接丢给模型结果准确率奇低然后开始怀疑算法——其实问题出在文本质量上。酒店评论里的噪音主要有几类HTML 标签、表情符号、连续重复标点、URL、用户引用、多余空白。下面这段清洗函数是我反复调过的版本直接抄没问题import re import html def clean_text(raw): # 去掉HTML实体和标签 text html.unescape(raw) text re.sub(r[^], , text) # 去掉URL和用户 text re.sub(rhttp\S|www\.\S, , text) text re.sub(r\w, , text) # 连续重复标点压缩为单个 text re.sub(r([!?。])\1, r\1, text) # 去除非中文字符和常见标点保留字母数字 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9\s。、], , text) # 合并多余空白 text re.sub(r\s, , text).strip() return text df[clean_text] df[text].apply(clean_text)这段代码里html.unescape处理amp;这类实体[^]正则抓 HTML 标签[!?。]\1把「太好了」压成「太好了」。最后一行保留中英文、数字和几个常见标点其他符号全部剔除。为什么保留字母数字因为酒店名可能是英文房间型号如 A 座、B 栋需要保留。清洗完一定要抽样打印几条看看效果。比如原始文本是「房间太小了而且隔音效果差张三 你说呢 http://xxx」清洗后应该是「房间太小了而且隔音效果差你说呢」。如果看到清洗后文本断句错乱多半是正则范围没写对回头检查[^...]里有没有漏掉中文标点。3. 特征工程与模型选型从词袋到词向量的三条路线3.1 分词与停用词jieba 的参数细节中文文本不能像英文那样按空格切词分词是绕不过去的一步。主流选 jieba纯 Python 实现安装零负担pip install jieba就行。但 jieba 默认模式在评论这种短文本上效果一般我建议切成「精准模式」并且把自定义词典和停用词表一起接上。import jieba # 加载自定义词典每行一个词格式词 词频 词性后两项可省 # 酒店场景常见专有词行政酒廊、江景房、前台、隔音、入住、退房 jieba.load_userdict(hotel_dict.txt) def tokenize(text): return [w for w in jieba.lcut(text) if w.strip()] # 停用词表哈工大停用词表 自增酒店场景无意义词 stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) # 自增词 stopwords.update([酒店, 房间, 感觉, 觉得, 一个, 我们]) def tokenize_with_stopwords(text): return [w for w in jieba.lcut(text) if w.strip() and w not in stopwords] sample 酒店位置很好但是隔音效果差晚上能听到走廊声音 print(tokenize_with_stopwords(sample)) # 输出[位置, 很好, 隔音, 效果, 差, 晚上, 听到, 走廊, 声音]这里有个细节load_userdict对分词结果影响巨大。「行政酒廊」你不告诉 jieba 它是一个词会被切成「行政 / 酒廊」语义就散了。酒店领域的自定义词典内容我一般会从训练数据里抽高频二字以上片段人工筛选大概 200 到 500 词就够了。停用词表要注意别过度过滤——「差」不能进停用词「但是」一旦被过滤转折关系就丢了。注意jieba.lcut 返回的是列表比jieba.cut返回生成器更适合初学者调试。打印输出检查分词粒度如果发现「酒店」这种词被切得很碎把它加进load_userdict而不是加进停用词。3.2 三条特征路线TF-IDF、Word2Vec、预训练向量分词之后文本要变成模型能吃的数值。这里有三条路难度和效果递增但大作业场景下我强烈建议走第一条。路线 ATF-IDF 矩阵 传统分类器推荐TF-IDF 是统计特征里的标杆做法sklearn 一行搞定解释起来也容易一个词在文档里出现越多越重要但它在所有文档里都出现就不重要了。「房间」这种词 TF 高但 IDF 低权重被压低「臭虫」这种词 TF 不低且 IDF 高权重突出——这正合情感分析的直觉。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline # 把清洗分词之后的文本列表传入 train_texts df[clean_text].apply(lambda x: .join(tokenize_with_stopwords(x))) vectorizer TfidfVectorizer( max_features5000, # 只保留最常用的5000个词控制维度 ngram_range(1, 2), # 考虑相邻两个词的组合 sublinear_tfTrue # 用1log(tf)替代原始tf降频次差异 ) model Pipeline([ (tfidf, vectorizer), (clf, LogisticRegression(C1.0, max_iter1000)) ]) model.fit(train_texts, y_train) acc model.score(test_texts, y_test) print(fTF-IDF LR 准确率: {acc:.4f})三个参数值得细说。max_features5000是维度上限酒店评论语料常用词也就几千个设 5000 够了设太大会引入长尾噪音训练也慢。ngram_range(1, 2)是关键让「不 干净」这种两词组合能被当成一个特征捕捉到单用 unigram 的话「不」和「干净」会被拆开。sublinear_tf则是弱化高频词的绝对优势——一个词出现 100 次和 10 次差异不应是 10 倍做对数压缩更合理。分类器用逻辑回归而不是朴素贝叶斯原因在于 LR 给出概率输出方便做置信度筛选而且它对稀疏矩阵的兼容性好训练快答辩时还能讲「我用 L2 正则防过拟合」。别小看这种基础模型词搜热词里反复出现的「情感分析」项目八成都是这个套路。路线 BWord2Vec 词向量平均 分类器如果老师要求「用深度学习或词向量」你就自己训练一个 Word2Vec 或者用现成的预训练中文词向量。做法是把一条评论的所有词向量求平均作为这条评论的向量表示再接逻辑回归或 SVM。from gensim.models import Word2Vec # 训练一个简单的word2vec尺寸和窗口根据语料规模定 sentences [tokenize_with_stopwords(t) for t in df[clean_text]] w2v Word2Vec(sentences, vector_size128, window5, min_count2, workers4) def sentence_embedding(tokens): vecs [w2v.wv[w] for w in tokens if w in w2v.wv] if not vecs: return [0.0] * 128 return sum(vecs) / len(vecs) X_w2v df[clean_text].apply(lambda x: sentence_embedding(tokenize_with_stopwords(x)))路线 B 的好处是能捕捉词义相似性「干净」和「整洁」的向量距离近坏处是短文本取平均后大量信息被稀释效果未必比 TF-IDF 好。如果你只求稳别碰这条线——它是论文里讲「模型对比」时用来当背景板的内容。路线 C预训练模型BERT 类能做但要慎重。BERT 在小样本上微调很容易过拟合而且推理速度慢CPU 上跑一条评论要几秒。大作业里除非你有两三百条人工标注数据起步否则别上 BERT。真要用我建议只拿它做最后的对比实验证明「传统方法在效率上更优」反而能成加分项。3.3 模型对比别只报一个准确率高分大作业和低分大作业的分水岭就在这里。多数人跑一个模型输出一个准确率就交差。聪明一点的做法是跑两个以上模型做对比——拿朴素贝叶斯当基线逻辑回归当主模型再用一个随机森林当对照。理由很简单老师要看你「为什么选它」对比实验是最有力的支撑。from sklearn.naive_bayes import MultinomialNB from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix models { NaiveBayes: MultinomialNB(), LogisticRegression: LogisticRegression(C1.0, max_iter1000), RandomForest: RandomForestClassifier(n_estimators200, random_state42) } for name, clf in models.items(): pipe Pipeline([ (tfidf, TfidfVectorizer(max_features5000, ngram_range(1, 2), sublinear_tfTrue)), (clf, clf) ]) pipe.fit(train_texts, y_train) y_pred pipe.predict(test_texts) acc (y_pred y_test).mean() print(f{name}: {acc:.4f}) # 输出混淆矩阵和F1 print(confusion_matrix(y_test, y_pred))注意看MultinomialNB和RandomForestClassifier的结果。通常逻辑回归和朴素贝叶斯准确率接近85% 上下随机森林反而差一点——因为稀疏高维特征对树模型不友好。这个结果本身就是写文档的好素材你可以分析为什么树模型不适合 TF-IDF 特征逻辑回归为什么适合线性可分的文本分类问题。有对比、有分析比单模型跑个 90% 更有说服力。4. 结果可视化与前台展示把「分析」变成「系统」4.1 词云最容易出效果但最容易做错做词云几乎是这类大作业的标配操作。但大多数人做出来就是一张没有重点的彩色词堆——那是你没设置好参数。情感分析项目里的词云应该分两张正向词云和负向词云分开才能看出门道。import matplotlib.pyplot as plt from wordcloud import WordCloud plt.rcParams[font.sans-serif] [SimHei] # 确保中文显示 plt.rcParams[axes.unicode_minus] False def plot_wordcloud(texts, title, filename): text .join([ .join(tokenize_with_stopwords(t)) for t in texts]) wc WordCloud( font_pathsimhei.ttf, # 缺字体时词云全是方块 width800, height400, max_words100, background_colorwhite ).generate(text) plt.figure(figsize(12, 6)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.title(title) plt.savefig(filename, dpi150, bbox_inchestight) plt.show() positive_texts df[df[label] 1][clean_text].tolist() negative_texts df[df[label] 0][clean_text].tolist() plot_wordcloud(positive_texts, 正向评论词云, pos_wordcloud.png) plot_wordcloud(negative_texts, 负向评论词云, neg_wordcloud.png)关键词在font_path上。Windows 下一般用simhei.ttfMac 下换成PingFang.ttcLinux 服务器就得先装中文字体。词云图里如果全是方块字九成是字体问题不是数据问题。如果你做的是正向和负向词云会发现一个有趣的现象正向词云高频词里大概率有「方便、干净、服务、热情」负向词云里有「隔音、卫生、设施、陈旧」。这几组词本身就是做文档「结论分析」章节的素材——高频情感词直接支撑你的改进建议。4.2 情感分布与时间趋势两个必做的统计图词云是「面」统计图才是「点」。两个图必做情感占比饼图和评分分布直方图。前者一眼看出正负比例后者反映原始数据的偏斜程度答辩时老师基本都会盯着这两个图问。import matplotlib.pyplot as plt # 图1情感占比饼图 plt.figure(figsize(6, 6)) labels [正向, 负向] sizes [df[label].value_counts()[1], df[label].value_counts()[0]] colors [#4CAF50, #F44336] plt.pie(sizes, labelslabels, colorscolors, autopct%.1f%%, startangle90) plt.title(酒店评论情感分布) plt.axis(equal) plt.tight_layout() plt.savefig(sentiment_pie.png, dpi150) plt.show()如果你的数据里有评论时间字段再加一张「每月平均情感得分变化」折线图——按月分组算平均分按月绘图。这个图一出来项目就从「情感分类」升级成了「情感趋势分析」档次立刻不一样。数据里没有时间戳的话跳过这张图即可别伪造。4.3 简单交互给大作业加一个预测入口如果你想让项目在演示环节更抓眼可以用tkinter或gradio做一个输入框用户输入一句「房间很干净但隔音太差了」程序输出判断结果和置信度。gradio最简单三五行代码出网页界面答辩时用浏览器一开就能演示比在终端里敲命令直观太多。import gradio as gr # 使用上一节训练好的pipeline def predict_sentiment(text): cleaned clean_text(text) proba model.predict_proba([cleaned])[0] label 正向 if model.predict([cleaned])[0] 1 else 负向 confidence max(proba) * 100 return f{label}置信度 {confidence:.1f}% gr.Interface( fnpredict_sentiment, inputsgr.Textbox(lines3, placeholder请输入酒店评论...), outputstext, title酒店评论情感分析 ).launch()predict_proba返回每个类别的概率我一般取最大值当置信度展示给用户这比只给一个「正向/负向」结果专业得多——用户对「置信度 92%」的信任感远高于裸奔的「正向」两个字。提示用gradio时本地会起一个网页服务演示前先跑通答辩时别现场装包。另外gradio和notebook的端口可能冲突launch()里加server_port7860指定端口能避开偶发问题。5. 避坑指南酒店评论情感分析最常见的 6 个坑5.1 坑一TF-IDF LR 对「否定词」失效现象模型把「不干净」判为正向。原因ngram_range(1, 2)虽然能捕捉「不 干净」但分词后的组合是「不/干净」TF-IDF 对「不干净」这个组合的权重不够突出。更麻烦的是「不是很干净」这种带程度副词的表达组合特征是「不是/很/干净」信号被稀释。解决针对否定词做简单规则融合。在模型判断为正向时检测句中是否出现「不、没、无、莫、别、不用、难以、毫无」等否定词及其作用范围一旦命中就把结果翻转。这种「规则 统计」的混合方法在开题答辩里讲出来老师会觉得你确实思考了问题。neg_words [不, 没, 无, 别, 难, 毫无, 不太] def rule_correction(text, pred): if any(w in text for w in neg_words): return 1 - pred return pred5.2 坑二数据集标签严重不均衡准确率虚高现象测试集里 90% 是正向评论模型全猜正向就有 90% 准确率但负向评论一条都识别不出来。原因酒店的评分分布天然偏斜——满意的客人才有动力去写好评不满的很多人直接差评但不写细节评论数据里好评占比 70% 以上很常见。解决训练时用class_weightbalanced给少数类加权评估时不只看准确率重点看混淆矩阵里的召回率负向评论被找出来的比例。如果负向召回率低于 60%说明模型实际上是个「正向复读机」必须调整。5.3 坑三jieba 分词对行业词切错「江景房」被切成「江景/房」现象词云里「江景」和「房」分开出现模型特征里这两个词无法组合。原因jieba 的默认词典不包含酒店行业专有名词它按通用语料概率切分。解决维护一份酒店领域词典把「江景房、行政酒廊、落地窗、前台接待、早餐自助」这类词全部加进去。做法是从训练数据里抽高频词人工筛选后存成hotel_dict.txt每行一个词。5.4 坑四评论中的长短句混合让特征维度爆炸现象有的评论只有两个字「很好」有的写了一大段 500 字。特征矩阵极其稀疏训练速度慢而且短评论被长评论的特征淹没了。原因TF-IDF 按词频统计长评论包含的词多会摊薄每个词的 IDF 计算短评论词少向量里大部分维度是零。解决加入文本长度筛选删掉小于 3 个字的评论多半是「很好」「不错」这种无法提取有效特征的短句对超过 200 字的评论做截断只保留前 200 字。虽然会损失部分信息但特征质量会明显提升。5.5 坑五wordcloud 显示方块字现象词云图生成后所有中文词都呈现为方块或乱码。原因matplotlib 和 wordcloud 默认字体不支持中文必须指定中文字体路径。解决代码里加font_path参数Windows 用simhei.ttfMac 用/System/Library/Fonts/PingFang.ttcLinux 先apt install fonts-wqy-zenhei再指定路径。别用plt.rcParams[font.sans-serif]那个只对 matplotlib 生效对 wordcloud 无效。5.6 坑六文档和代码对不上现象文档里写的准确率是 88%老师跑出来只有 75%文档里画的情感分布图和代码输出的完全不是一回事。原因文档是经过多次调参后的最终版代码是早期版本或者数据集被换过但没同步更新。这是大作业评分里最致命的错误——直接暴露「不看代码、编数据」的嫌疑。解决把文档里的每一个数字都对应到代码里的一个输出。准确率、图表、词云、样本示例全部用同一份代码、同一份数据跑出来。我习惯在文档末尾附上「实验环境」一节写上 Python 版本、依赖库版本和机器 CPU 型号进一步佐证所有结果都是真实复现的。6. 从「能跑」到「拿高分」文档结构与答辩演示的四个关键动作代码跑通了图都画出来了但大作业评分的大头其实在文档和演示。一个常见误区是把文档写成「跟着代码走一遍」——从导入 pandas 开始写到最后 print 准确率结束老师翻完根本不知道你的方法论是什么。文档的推荐结构是这样摘要 → 数据获取与清洗 → 情感分析方案设计 → 实验与结果分析 → 总结与展望。其中「实验与结果分析」占的篇幅应该是最多的——放对比表格列每个模型在准确率、精确率、召回率上的数据放混淆矩阵截图讨论为什么树模型不如线性模型。把这些分析写透了即便准确率只有 82%评分背后「工作量充足」的结论也跑不掉。答辩演示时我会坚持一个原则现场跑一遍测试集预测不只看图片。选几条典型的正向和负向评论当场输入、当场输出让老师看到模型真实响应。如果模型预测错了别慌张——大方承认并解释「这条评论包含了反讽表达训练数据里这种情况很少这也是模型未来的改进方向。」这个临场反应本身就是加分项。最后一个建议关于「文档里怎么放代码」。大作业文档不是技术博客正文字号正文里别贴大段代码核心算法截取关键片段就行源码完整地放附录。页数控制在 25 到 35 页之间图表占比三分之一——太短显得单薄太长老师看不过来。这套流程我自己反复用过多次从数据清洗到可视化呈现每一步的权重我都替你踩过。照着做拿高分不难真正难的是你在做完之后能指着混淆矩阵自信地说出「我知道这个模型在哪里失效以及为什么失效」——这句话比任何准确率数字都值钱。希望帮到你。本文还有配套的精品资源点击获取
返回列表