简介这是一份面向计算机相关专业学生与项目实战学习者的酒店评论情感分析完整项目包由导师指导并通过评审评审分98分适合用作毕业设计、课程设计或期末大作业参考。资源共23个文件压缩包约4.36MB以14个txt词典与停用词表、2个py源码脚本、2个rar语料压缩包为主另含docx设计文档、pptx汇报演示、md说明及jpg词云图覆盖从数据预处理到情感打分的完整链路。项目内置情感词典、否定词与程度副词词典、哈工大及四川大学等多套停用词库配合emotion_score.py与run.py实现评论极性计算并附正负样本语料便于直接运行与二次调参。目前已有304人学习下载读者可借此掌握中文文本情感分析的基本流程、词典匹配策略与结果可视化方法快速形成可展示、可答辩的完整方案。1. 酒店评论情感分析一份 Python 源码能帮你省掉多少重复劳动打开任何一个酒店预订平台翻到评论区你会看到两种极端一种是“位置好、早餐棒、前台小姐姐超温柔”另一种是“隔音差到凌晨三点没睡着、空调漏水、投诉没人理”。对做运营或产品的人来说这两类评论背后是截然不同的行动信号——前者值得放大宣传后者需要立刻排查。但问题在于一家中型酒店动辄几千条评论靠人工一条条读读完黄花菜都凉了。基于 Python 的酒店评论情感分析要解决的就是这件事把非结构化的中文评论自动分成正面、负面甚至更细的维度服务、卫生、位置、设施让运营能按维度看板做决策。这份“源码文档”的组合适合两类人一是刚学完 Python 基础、想找一个完整项目练手的入门者二是手里有评论数据、想快速搭一套分析流水线的从业者。它不涉及多模态情感分析那种图文音混合的复杂场景就是纯文本分类但胜在链路完整、可复现、能直接换成自己的数据跑。2. 从原始评论到情感标签数据清洗与标注的四个关键决策2.1 为什么中文酒店评论不能直接丢进模型中文评论有几个绕不开的坑。第一是口语化严重“yyds”“绝绝子”“踩雷”这类词通用情感词典根本覆盖不到。第二是反讽“这隔音效果真是绝了隔壁打呼噜我都听得一清二楚”字面看“绝了”是褒义实际是强烈负面。第三是混合情感一条评论里可能同时夸早餐、骂前台整条标成正面或负面都会丢信息。常见做法是先把评论切成句子级再对每个句子做情感判断最后按维度聚合。如果只想做粗粒度二分类那就整条评论一个标签但要在文档里写清楚这个局限。我一般会建议如果评论平均长度超过 80 字优先做句子级如果大多是“很好”“差评”这种短评整条分类就够用。2.2 清洗脚本去噪、分句、去重下面这段代码做三件事去掉 HTML 标签和多余空白、按中文标点分句、对完全重复的评论去重。依赖只有re和pandas不需要额外装分词库。import re import pandas as pd def clean_text(text): # 去掉 HTML 标签 text re.sub(r[^], , text) # 去掉 URL text re.sub(rhttp[s]?://\S, , text) # 合并多余空白 text re.sub(r\s, , text).strip() return text def split_sentences(text): # 按中文句号、问号、感叹号、分号切分 parts re.split(r[。], text) return [p.strip() for p in parts if len(p.strip()) 2] # 假设 df 有 comment 列 df pd.read_csv(hotel_comments.csv) df[clean] df[comment].apply(clean_text) df[sentences] df[clean].apply(split_sentences) # 按清洗后文本去重 df df.drop_duplicates(subset[clean]) print(f清洗后剩余 {len(df)} 条评论)逻辑说明clean_text里的正则顺序不能反先去标签再去 URL否则标签里的链接可能被误删。split_sentences过滤掉长度小于等于 2 的片段是因为“好。”“差。”这种太短情感信号弱留着反而增加噪声。去重按clean列而不是原始列避免“很好”和“很好”被当成两条。参数方面len(p.strip()) 2这个阈值可以调。如果评论普遍很短改成 1如果长评多改成 5能进一步降噪。没有绝对标准看你的数据分布。2.3 标注策略规则打底 人工抽检如果没有现成标签别一上来就人工标几千条。先用规则打底维护一个正面词表和负面词表每条评论按命中词数算一个初始分正分标 1负分标 0零分标“待定”。然后从“待定”里随机抽 200 条人工标再从已标数据里抽 100 条校验规则准确率。pos_words [好, 棒, 干净, 舒适, 满意, 推荐, 贴心, 方便] neg_words [差, 脏, 吵, 慢, 失望, 投诉, 漏水, 异味] def rule_label(text): pos sum(1 for w in pos_words if w in text) neg sum(1 for w in neg_words if w in text) if pos neg: return 1 elif neg pos: return 0 else: return -1 # 待定 df[rule_label] df[clean].apply(rule_label) print(df[rule_label].value_counts())这个规则表很短但覆盖了酒店场景最高频的褒贬词。实际用的时候把pos_words和neg_words扩到各 50 个左右准确率能到 75% 上下。剩下的 25% 就是模型要啃的硬骨头。注意规则标签只用来做冷启动和抽检不要直接当最终标签训练模型否则模型学到的只是词表匹配。3. 用 Python 跑通情感分类从 TF-IDF 到 BERT 微调的最小命令3.1 基线模型TF-IDF 逻辑回归十分钟出结果任何文本分类项目我都建议先跑一个基线。不是为了上线是为了知道“不用深度学习能到多少分”。TF-IDF 加逻辑回归在酒店评论二分类上通常能到 85% 左右的准确率而且训练时间以秒计。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 classification_report # 只用有明确标签的数据 labeled df[df[rule_label] ! -1].copy() X_train, X_test, y_train, y_test train_test_split( labeled[clean], labeled[rule_label], test_size0.2, random_state42 ) vectorizer TfidfVectorizer( max_features5000, ngram_range(1, 2), min_df3 ) X_train_vec vectorizer.fit_transform(X_train) X_test_vec vectorizer.transform(X_test) clf LogisticRegression(max_iter1000, C1.0) clf.fit(X_train_vec, y_train) y_pred clf.predict(X_test_vec) print(classification_report(y_test, y_pred))max_features5000控制词表大小太大容易过拟合太小丢信息。ngram_range(1,2)让模型能捕捉“不干净”“不推荐”这种二元否定。min_df3过滤掉只出现一两次的生僻词。C1.0是正则强度如果发现过拟合训练集准确率远高于测试集降到 0.5 或 0.1。这个基线跑完你会得到 precision、recall、f1-score。如果 f1 已经超过 0.85而你的业务对精度要求没那么苛刻其实可以先用着。如果 f1 低于 0.8再考虑上 BERT。3.2 BERT 微调什么时候值得上怎么设参数BERT 中文预训练模型比如bert-base-chinese在酒店评论上通常能把 f1 拉到 0.92 以上但代价是训练时间和显存。我的判断标准是如果基线 f1 和业务要求差距超过 5 个百分点或者评论里反讽、混合情感占比超过 20%就值得上 BERT。from transformers import BertTokenizer, BertForSequenceClassification from torch.utils.data import DataLoader, Dataset import torch tokenizer BertTokenizer.from_pretrained(bert-base-chinese) class CommentDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_len128): self.texts texts self.labels labels self.tokenizer tokenizer self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): encoding self.tokenizer( self.texts.iloc[idx], max_lengthself.max_len, paddingmax_length, truncationTrue, return_tensorspt ) return { input_ids: encoding[input_ids].squeeze(), attention_mask: encoding[attention_mask].squeeze(), labels: torch.tensor(self.labels.iloc[idx], dtypetorch.long) } train_dataset CommentDataset(X_train, y_train, tokenizer) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue) model BertForSequenceClassification.from_pretrained(bert-base-chinese, num_labels2) optimizer torch.optim.AdamW(model.parameters(), lr2e-5) model.train() for epoch in range(3): for batch in train_loader: optimizer.zero_grad() outputs model( input_idsbatch[input_ids], attention_maskbatch[attention_mask], labelsbatch[labels] ) loss outputs.loss loss.backward() optimizer.step() print(fEpoch {epoch1} done)max_len128覆盖绝大多数酒店评论超过 128 字的评论会被截断如果长评多就调到 256但显存占用翻倍。batch_size16是 8G 显存下的安全值显存够可以到 32。lr2e-5是 BERT 微调的经典学习率太大容易灾难性遗忘太小收敛慢。epoch3通常够用超过 5 轮基本过拟合。注意这里没有写验证集评估和模型保存实际项目里每轮结束要在验证集上算 f1保存最好的那个 checkpoint。另外bert-base-chinese只是举例你可以换成任何你手头有的中文预训练模型参数逻辑一样。3.3 维度拆分把“服务”“卫生”“位置”分开预测粗粒度二分类只能告诉你“这条评论是正面还是负面”但运营想知道“到底是哪里出了问题”。做法是先定义维度关键词表把评论按维度切分每个维度单独训一个二分类模型。dimension_keywords { service: [前台, 服务, 态度, 办理, 入住], cleanliness: [卫生, 干净, 打扫, 床单, 浴室], location: [位置, 交通, 地铁, 附近, 距离], facility: [设施, 空调, 热水, 电梯, 隔音] } def split_by_dimension(text): result {} for dim, keywords in dimension_keywords.items(): if any(kw in text for kw in keywords): result[dim] text return result # 对每条评论生成维度子集 df[dimensions] df[clean].apply(split_by_dimension) # 展开后每个维度单独训练 dim_data [] for _, row in df.iterrows(): for dim, text in row[dimensions].items(): dim_data.append({dimension: dim, text: text, label: row[rule_label]}) dim_df pd.DataFrame(dim_data) print(dim_df[dimension].value_counts())这段代码的关键是dimension_keywords的设计。关键词不能太宽比如“服务”这个词本身出现在“服务很好”和“服务很差”里所以它只用来定位维度不用来判断情感。情感判断还是靠该维度下的文本单独过模型。如果一条评论同时提到多个维度就复制成多条每个维度一条。这样训练出来的模型预测时也是先分维度再判情感。4. 避坑与排查酒店评论情感分析里最容易翻车的五件事4.1 现象模型在测试集上 f1 很高上线后准确率暴跌原因训练集和测试集来自同一批数据分布一致但上线后遇到的是新评论用词、句式、话题都变了。更隐蔽的原因是标注阶段用规则打底规则词表里的词在测试集里也大量出现模型其实在“背词表”。解决划分数据时按时间切分用前 80% 时间的数据训练后 20% 验证。如果数据没有时间戳至少按酒店 ID 分组切分保证同一家酒店的评论不会同时出现在训练和测试集里。另外规则打底的标签只用来冷启动最终训练集要经过人工抽检修正。4.2 现象负面评论被大量预测成正面原因酒店评论天然偏正面很多平台上好评率超过 80%。模型在类别不平衡数据上训练会倾向于预测多数类。另外反讽句“这隔音真是绝了”里的“绝了”在正面词表里规则标签就错了模型跟着错。解决训练时给少数类加权重LogisticRegression(class_weightbalanced)或 BERT 的WeightedRandomSampler。反讽问题靠规则解决不了只能靠人工标注一批反讽样本让模型学。如果反讽占比高考虑用句子级标注替代整条标注因为反讽通常只出现在某个句子里。4.3 现象分词后“不干净”变成“不”“干净”情感反转原因中文分词工具默认会把否定词和形容词切开导致“不干净”被拆成两个词模型看到“干净”就判正面。这是中文情感分析最经典的坑。解决用ngram_range(1,2)让 TF-IDF 捕捉二元组合或者自定义分词词典把“不干净”“不推荐”“没热水”这类否定短语作为一个整体加入词典。BERT 类模型因为有自己的子词切分这个问题会轻很多但也不是完全没有。4.4 现象训练 loss 正常下降但验证集 f1 一直不涨原因学习率太大模型在最优解附近震荡或者max_len设得太小关键信息被截断或者标签噪声太大模型在拟合错误标签。解决先把学习率降一个数量级试试BERT 从2e-5降到5e-6。然后检查被截断的样本比例如果超过 10%调大max_len。最后从验证集里抽 50 条预测错误的样本人工看一遍如果发现标签本身就有问题先修标签再训模型。4.5 现象模型预测结果无法解释运营不信任原因深度学习模型是黑匣子运营看到“这条评论负面概率 0.87”不知道依据是什么自然不敢用。解决对 TF-IDF 模型可以直接输出权重最高的词对 BERT用shap或lime做局部解释标出哪些词对预测贡献最大。更实用的做法是在输出情感标签的同时把评论里命中情感词表的词高亮出来运营一眼就能看到“吵”“脏”“慢”这些信号词。解释性不一定要多精确但要让运营觉得“有道理”。5. 把分析结果变成运营动作维度看板与预警阈值的一个具体技巧模型跑通只是第一步真正产生价值的是把预测结果接到运营流程里。我一般会做一个简单的维度看板按酒店、按月份、按维度聚合情感得分然后设一个预警阈值。比如某个酒店“卫生”维度的负面占比连续三天超过 15%就自动发通知给店长。# 假设 pred_df 有 hotel_id, dimension, pred_label, date pivot pred_df.groupby([hotel_id, dimension, date]).agg( neg_ratio(pred_label, lambda x: (x 0).mean()) ).reset_index() # 连续三天负面占比超过 0.15 的酒店-维度组合 alert pivot[pivot[neg_ratio] 0.15].groupby([hotel_id, dimension]).filter( lambda g: len(g) 3 ) print(alert[[hotel_id, dimension, date, neg_ratio]])这个逻辑里neg_ratio 0.15是阈值不是拍脑袋定的。我的习惯是先跑一个月的历史数据看每个维度负面占比的分布取 80 分位数作为初始阈值然后根据实际告警量调整。告警太多就提高阈值告警太少就降低。len(g) 3是要求连续三天避免单日波动误报。还有一个技巧把情感得分和评分星级做交叉验证。如果一条评论情感预测是负面但用户打了 5 星大概率是反讽或者模型错了这类样本单独捞出来人工复核既能修模型又能发现运营盲区。最后说个我自己的教训。早期做这个项目时我花了两周调 BERT 参数f1 从 0.91 提到 0.93特别得意。结果运营看了一眼说“你这些负面评论里有一半是抱怨隔壁工地施工跟酒店没关系。”那一刻我才意识到情感分析只解决了“用户情绪是什么”没解决“情绪指向谁”。后来我在维度拆分里加了一个“外部因素”类别把施工、天气、交通管制这些和酒店无关的负面评论单独拎出来运营看板才真正可用。做文本分析模型精度是下限业务对齐才是上限。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?