基于Flask与TF-IDF的酒店评论情感分析系统实现 简介面向酒店评论场景的情感分析项目以识别评论正面/负面情感为目标涵盖数据探索、特征工程、模型训练到Web交互部署的完整链路适合计算机、人工智能相关专业的毕业设计或课程实践。压缩包内共11个文件整体约6.78MB核心以pkl模型文件为主同时包含Flask应用脚本、TF-IDF向量器、Jupyter分析笔记、3个HTML页面模板及说明文档等其中Jupyter笔记完整记录了数据集探索、模型训练与性能评估过程各类型文件分工清楚便于按模块学习。项目目前已吸引179人学习浏览环境配置简单安装Python和相关依赖后即可运行在浏览器中输入评论文本便能即时获得情感预测结果。随包提供酒店评论数据集与两类训练好的模型逻辑回归、XGBoost无需重新训练即可启动演示既适合快速复现NLP处理与模型调优流程也能帮助理解向量化、模型对比和Web封装等实战细节可直接作为课程报告或毕设展示的基础项目。1. 酒店评论情感分析系统一条差评在几毫秒内变成预测结果假设你手上有一批酒店评论想做口碑监测但几百条文本逐条读太慢又不想为这点需求动用大模型接口。这个项目解决的正是这个场景一个 Python Flask 搭建的 Web 应用用户在页面上输入一段评论文本系统通过 TF-IDF 向量化后交给逻辑回归或 XGBoost 模型几毫秒内返回好评或差评的预测结果。它不是只有训练代码的半成品app.py、模型文件、向量器、HTML 模板、训练笔记全部打包在一起解压后装好依赖就能跑。想用 NLP 做毕业设计或课程作业的人以及第一次想把训练好的模型包装成 Web 服务来用的开发者都非常适合拿这份资源做起点。拆过不少情感分析项目大多数只有 Jupyter 训练过程缺少能让用户真正在浏览器里提交文本的落地页面这个项目把最后一公里补上了。2. 整体架构与技术选型从 Flask 路由到双模型文件的工作链路2.1 整体架构一次请求在 Flask 应用中的完整链路这个项目属于最典型的小型机器学习 Web 服务没有微服务、没有消息队列、没有数据库核心就是一条直线浏览器页面负责收集输入Flask 路由负责接收请求pkl 文件负责承载模型三个 HTML 模板负责展示不同状态。拆开看请求链路用户打开 index.html 页面填写评论文本并提交app.py 里注册的 /predict 路由拿到表单数据后先调用向量器的 transform 方法把原始文本转换成稀疏矩阵再调用模型的 predict 方法得到 0 或 1 的标签最后根据标签决定渲染哪个模板返回给浏览器。这个过程没有数据库参与也没有缓存层单机就能跑得很好。对初学者来说这条链路最大的价值在于用最少的组件演示了一件事训练好的模型文件到底是怎么对外提供服务的。很多人把模型训练出来之后就不知道下一步该怎么办这个项目直接给出了答案——把模型和向量器用 pickle 序列化保存再在 Flask 里加载就能让其他人通过网页来使用你的模型。这比把代码丢在 Jupyter 里自娱自乐要实用得多。项目里同时存在两个模型文件意味着作者有意提供了一条对比路径不只是给你一个能用的黑匣子而是让你能看到不同算法在同一批酒店评论数据上的差异。这种设计在课程作业里很讨喜写论文时也能多一个对比实验的数据点。2.2 文件清单压缩包里每个文件是干什么的从压缩包的目录结构来看这是一个结构非常清晰的项目每个文件的职责都相对单一。先列一个文件清单方便后续对照着看文件/目录类型职责app.pyPython 脚本Flask 应用入口负责路由、模型加载、预测逻辑logreg_model.pkl模型文件逻辑回归情感分类模型pickle 序列化保存xgboost_model.pkl模型文件XGBoost 情感分类模型作为备选方案vectorizer.pkl数据转换器配套逻辑回归模型的 TF-IDF 向量器tfidf_vectorizer.pkl数据转换器配套 XGBoost 模型的 TF-IDF 向量器sentimentAnalysisofHotelReviews.ipynbJupyter 笔记本数据集探索、模型训练、性能评估全过程记录templates/目录HTML 模板包含 index.html、recommendations.html、hotel_detail.htmlimage1.jpg图片可能是数据分布图或训练结果可视化图README.md文档项目说明与运行指引注意这里有两个向量器文件这说明逻辑回归和 XGBoost 各自搭配了一个 TF-IDF 向量器。常见的操作方式是训练阶段分别对两个模型做文本向量化然后把各自使用的向量器和模型一起序列化保存预测阶段必须用配套的向量器来做转换不能混用。这是一个容易踩坑的细节后面避坑部分会专门展开。2.3 技术选型为什么是 TF-IDF 加两个经典模型的组合先看文本表示方案。TF-IDF 是酒店评论这类短文本分类里最经典的向量化方式它统计每个词在文档中的词频再乘上逆文档频率从而降低的了是这类常见词的影响突出干净隔音服务这种有区分度的词。相比 Word2Vec、BERT 这类嵌入方法TF-IDF 的最大优势是训练成本低、可解释性强对几万条数据规模的项目完全够用。这个项目把 TF-IDF 向量器也保存成了 pkl 文件意味着预测阶段不需要依赖原始语料直接加载向量器即可完成转换。再看法模型。逻辑回归是高维稀疏文本分类的基线选择训练极快输入特征多几万个也不怕配合 TF-IDF 能达到不错的精确率而且权重系数可以反推出哪些词更偏向好评。XGBoost 则是集成学习路线的代表能够捕捉特征间的非线性关系对文本分类往往能比逻辑回归高几个百分点的准确率。项目同时给出两个模型本质上是让使用者能对比线性模型和树模型在情感分析任务上的表现差异。两种方案的预测接口几乎一致都是在 transform 之后的矩阵上调用 predict这也方便了后续在代码里做模型切换。这个技术组合放到今天来看不算新潮但胜在工程上非常可靠依赖少、跑得快、部署容易。总共需要安装的库也就 Flask、scikit-learn、xgboost、pandas 这几样不涉及深度学习框架连没有 GPU 的机器也能流畅运行。3. 核心代码拆解app.py 与情感预测全流程的实现方式3.1 app.py路由、模型加载与预测逻辑app.py 是整个项目的入口也是情感分析功能落到 Web 界面的关键。我拆这类项目时最常见的写法是把模型加载放在模块级别也就是 Flask 应用初始化时就读取 pkl 文件这样处理请求时就不用每次重新加载模型。核心代码大致长这样import pickle from flask import Flask, render_template, request app Flask(__name__) # 模块加载时一次性读取模型与向量器 with open(vectorizer.pkl, rb) as f: vectorizer pickle.load(f) # TF-IDF 向量器负责文本转矩阵 with open(logreg_model.pkl, rb) as f: logreg_model pickle.load(f) # 逻辑回归情感分类模型 app.route(/) def index(): return render_template(index.html) app.route(/predict, methods[POST]) def predict(): review request.form[review] # 从提交的表单中获取评论文本 X vectorizer.transform([review]) # 文本转换为 TF-IDF 稀疏矩阵 pred logreg_model.predict(X)[0] # 预测标签结果为 0 或 1 sentiment 好评 if pred 1 else 差评 return render_template(recommendations.html, sentimentsentiment, reviewreview) if __name__ __main__: app.run(debugTrue)这里有几个值得注意的设计点。第一模型加载的 with open 代码块放在路由函数之外意味着 app.py 启动一次就完成模型读取后续所有请求共用这份内存中的模型对象如果放在 predict 函数内部每来一个请求都要重新读磁盘响应会慢很多。第二request.form[review] 拿到的是表单字段对应 HTML 页面里 input 或 textarea 的 name 属性模板和 Python 代码必须约定同一个字段名。第三predict 返回的是一个一维数组取 [0] 拿到第一个样本的预测值。3.2 TF-IDF 向量器文本进入模型前发生了什么很多初学者会把向量器当成黑匣子以为文本丢进去就能出结果其实这里的关键是训练和转换的区别。向量器在训练阶段通过 fit_transform 学习出词表并计算出每个词的 IDF 值这个学习结果被 pickle 保存成 vectorizer.pkl预测阶段再用 transform 把新文本映射到同一套词表上。如果预测时对新数据重新 fit词表就会变化模型的输入特征就乱套了。# 预测阶段只允许 transform不能 fit_transform X vectorizer.transform([review])这一段代码看起来简单但它是整个流程里最容易翻车的位置。变量 review 来自网页表单是 Python 字符串transform 方法要求输入一个可迭代的字符串列表所以这里用 [review] 把它包成列表。转换之后得到的是一个稀疏矩阵形状是 1 x n_featuresn_features 是训练时词表的大小可能达到几千甚至上万普通展示里看不出来但调试时可以打印 X.shape 确认向量器是否正常工作。另一个值得留意的是vectorizer.pkl 和 logreg_model.pkl 必须配套使用。逻辑回归模型的权重维度是 n_features 维向量器输出的特征数如果对不上调用 predict 时就会报维度不匹配的错误。所以保存模型的逻辑里训练阶段的 TfidfVectorizer 实例也要一并保存这正是项目里单独提供 vectorizer.pkl 文件的原因。3.3 双模型切换XGBoost 的接入方式与预测接口第二个模型 xgboost_model.pkl 的接入方式几乎如出一辙只是加载时多了一个模型类的区别。XGBoost 的 sklearn 接口也实现了 predict 方法所以预测代码可以和逻辑回归保持同样的写法方便封装成公用的预测函数。import pickle with open(tfidf_vectorizer.pkl, rb) as f: tfidf_vec pickle.load(f) # 配套 XGBoost 的 TF-IDF 向量器 with open(xgboost_model.pkl, rb) as f: xgb_model pickle.load(f) # XGBoost 模型 def predict_sentiment(review, model, vectorizer): X vectorizer.transform([review]) # 文本转换为模型输入矩阵 pred model.predict(X)[0] # 统一调用 predict 接口 return int(pred) # 转成 int 方便模板渲染多了一个细节XGBoost 模型文件里的对象是通过 xgboost 库保存的 Booster 或者 XGBClassifier 包装器加载这类 pkl 时所在的 Python 环境必须安装 xgboost 库否则会报 ModuleNotFoundError。这个问题在项目部署到新机器时非常常见后面避坑部分也会提到。另外别忘了 tfidf_vectorizer.pkl 和 vectorizer.pkl 是两个不同的文件说明两个模型在训练时使用了不同的向量器配置或不同的词表预测阶段一定不要混搭否则结果完全没有参考价值。4. 训练笔记拆解从数据集探索到模型持久化的完整流程4.1 数据集探索与预处理先看清正负样本分布再动手sentimentAnalysisofHotelReviews.ipynb 这个笔记本记录了整个模型的诞生过程。训练的第一步通常是数据加载与探索酒店评论数据集一般包含评论文本和对应的情感标签项目中标签编码通常把 1 设为正向评论、0 设为负向评论。拿到数据后先要看的事情是类别分布如果好评和差评数量悬殊模型可能会偷懒地全部预测为多数类导致准确率虚高但实际没有区分能力。import pandas as pd import re df pd.read_csv(hotel_reviews.csv) print(df[label].value_counts()) # 查看正负样本数量分布 # 基础清洗统一小写、去标点、去缺失 df[review] df[review].astype(str).str.lower() df[review] df[review].apply(lambda x: re.sub(r[^\w\s], , x)) df df.dropna(subset[review, label])这里清洗逻辑很简单做两件事把英文评论统一转成小写再把标点符号去掉。如果中文评论清洗方式还要额外加一步分词但英文短文本场景这套规则就够了。dropna 则是把评论文本或标签为空的行删掉避免之后 fit 时报错。实际处理时还可以用 value_counts(normalizeTrue) 查看占比判断是否需要做类别重采样这是情感分析里一个常见的优化点。4.2 模型训练与评估逻辑回归与 XGBoost 的对照实验预处理完成后进入训练环节。项目采用了经典的 train_test_split 划分训练集和测试集把 TF-IDF 向量化、模型训练、预测和评估放到同一个流程里。逻辑回归的训练代码大致如下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, classification_report X_train, X_test, y_train, y_test train_test_split( df[review], df[label], test_size0.2, random_state42 ) # 只对训练集 fit测试集和预测阶段共用这套词表 vectorizer TfidfVectorizer(max_features5000, ngram_range(1, 2)) X_train_tf vectorizer.fit_transform(X_train) X_test_tf vectorizer.transform(X_test) model LogisticRegression(C1.0) model.fit(X_train_tf, y_train) y_pred model.predict(X_test_tf) print(accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred))这里有两个参数很值得调。max_features5000 限制词表只保留 IDF 值最高的 5000 个词防止特征矩阵过于庞大ngram_range(1, 2) 允许把相邻两个词的组合也当成特征比如空调噪音这种二元词组比单独的空调更有情感区分度。random_state42 固定随机种子保证每次重新运行切分结果一致这是训练评估里一个容易被忽略但非常重要的习惯。评估阶段除了 accuracy_scoreclassification_report 还会输出每个类别的精确率、召回率和 F1 值。如果数据类别不均衡单看准确率会骗人F1 值更可靠。XGBoost 的训练流程是把模型替换成 XGBClassifier向量化部分基本不变对比两者的准确率和 F1 值就可以得出一段漂亮的实验结论。4.3 模型持久化pkl 文件是怎么生成和使用的训练完成后模型和向量器都要保存成文件供 Flask 应用加载。常见保存方式是 pickle 序列化脚本里通常这样写import pickle with open(vectorizer.pkl, wb) as f: pickle.dump(vectorizer, f) # 保存训练好的 TF-IDF 向量器 with open(logreg_model.pkl, wb) as f: pickle.dump(model, f) # 保存训练好的模型这段代码的背后有一个很容易被忽略的点pickle 保存的是 Python 对象的完整状态但跨环境加载时会受依赖库版本影响。训练时用的 scikit-learn 版本和部署环境不一致常见的现象是加载 pkl 时提示某一个类找不到或者特征维度对不上。我的经验是把训练环境和运行环境尽量统一或者像我一样每次换环境后强制重新生成一份 pkl不拿旧文件去赌玄学兼容性。5. 启动运行与避坑让项目在自己的机器上跑通5.1 依赖安装与三步启动项目跑起来之前的准备工作很简单先把 Python 环境准备好再安装依赖。情感分析加 Flask 这条技术栈需要的库不多核心是以下几项pip install flask scikit-learn xgboost pandas然后进入项目根目录启动 Flask 应用cd Sentiment-Analysis-of-Hotel-Reviews-main python app.py默认情况下 Flask 会监听 127.0.0.1:5000浏览器访问这个地址就能看到 index.html 页面。如果 README.md 里有更具体的依赖或启动说明以那个文件为准。我拿到压缩包的第一反应也是先翻 README这个习惯能帮你省下不少猜文件的时间。5.2 高频踩坑记录模型加载、中文乱码与端口冲突这一部分内容是我在复现类似项目时真实踩过的坑每一条都按现象、原因、解决三个维度展开。现象一AttributeError: NoneType object has no attribute transform这条报错在 Flask 里出现时往往伴随 500 错误页面原因是 app.py 加载 pkl 文件失败但异常被捕获后变量仍然是 None。常见的原因是启动时工作目录不对pkl 文件用的是相对路径比如 open(vectorizer.pkl, rb)但你的终端在别的目录下执行 python app.py系统就找不到文件了。解决办法是先确认当前工作目录是项目根目录或者直接把代码里的路径改成基于file的绝对路径import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) vectorizer_path os.path.join(BASE_DIR, vectorizer.pkl)现象二ModuleNotFoundError: No module named xgboost这个报错非常直接运行 app.py 时系统找不到 xgboost 库。原因是项目里有两个模型文件如果代码里同时加载了 xgboost_model.pkl那么即使你只想用逻辑回归模型Python 读取 xgboost 模型文件的 pickle 时也会触发导入 xgboost 类的操作。解决方法就是显式安装依赖也就是前面提到的 pip install xgboost。这不算什么高深问题但它提醒我pkl 文件不是完全独立的数据文件它对生成它的库是有依赖的。现象三浏览器打开页面后中文显示为乱码项目模板里如果只有英文页面可能不会遇到这个问题但一旦涉及中文评论或中文模板渲染浏览器显示乱码基本上是编码问题。原因是 HTML 页面没有声明字符集或者 Flask 返回的响应中 Content-Type 没有带上 utf-8。解决办法是在 HTML 的 head 标签里加上 另外确认模板文件本身是以 UTF-8 编码保存的。现象四OSError: [Errno 98] Address already in use启动 app.py 时提示端口被占用原因是 5000 端口已经被上一个 Flask 实例或者其他程序占用了。解决办法有两个要么关掉占用端口的进程要么让 Flask 换一个端口启动if __name__ __main__: app.run(host0.0.0.0, port5001, debugTrue)这里顺带说明host 改成 0.0.0.0 可以让局域网内其他设备访问如果只是本机调试保持默认的 127.0.0.1 更安全。现象五UnicodeDecodeError 或 pickle 数据被截断的诡异报错Windows 系统上打开 pkl 文件如果用了文本模式或者文件本身不完整就会出现各种奇怪的解码错误。pkl 是二进制格式读取时必须使用 rb 模式写入。如果文件是在不同 Python 版本之间互相迁移也会遇到兼容性问题。这个问题的解决思路比较靠心态确认代码里的 open 模式是 rb确认压缩包解压完整如果还是报错就基于当前环境的训练脚本重新生成 pkl不要在一个坏文件上死磕。5.3 验证结果用两句话测试模型是否正常工作项目启动之后验证方式非常简单直接在页面上输入两段明显不同倾向的评论看预测结果是否符合预期。比如输入位置很好早餐丰富下次还会入住预期输出好评输入房间有异味床单不干净不会再来了预期输出差评。如果这两个例子都预测正确说明模型加载和向量化流程是通的。如果结果反了先检查训练笔记里的标签定义确认到底 1 是好还是 0 是好这种标签定义反了的情况在情感分析项目里并不少见。另外也可以打印一下预测概率值看看模型对某条评论的置信度置信度接近 0.5 时预测结果本身就没什么参考价值。6. 二次开发改造从单条预测到批量接口化6.1 把预测逻辑抽成可复用的独立函数原项目里 predict 函数和 Flask 路由耦合在一个函数里想换模型要改模板代码。我的第一个改造建议是把预测逻辑抽成一个独立的工具函数参数变成模型和向量器这样无论后续加接口还是做批量分析都能复用同一套逻辑。改造后的函数在 3.3 节已经展示过输入评论文本、模型、向量器输出 0 或 1 的预测标签这个函数可以放到单独的 predict_utils.py 里Flask 路由只负责从请求中取数据并调用它。6.2 增加 REST API 与批量预测如果想让别人通过接口调用而不用网页可以加一个 JSON 交互的路由。常见的实现是用 Flask 的 jsonify 返回结构化结果同时支持单个文本和文本列表两种输入。比如设计一个 /api/predict 路由接收 JSON 格式的 {review: 体验很好}返回 {sentiment: 1, label: 好评}。批量预测则用一个循环遍历列表把预测结果拼接成数组返回。这套改造的成本极低但能让项目从一个页面 demo 变成一个可以被外部程序调用的分析服务。6.3 模型替换流程与验证最后说模型更新的后悔药。训练好一个新模型后别急着删旧文件我一般的做法是保存成新文件名临时在代码里加载新模型跑一遍测试集确认性能没有下降再替换正式文件名。验证脚本要固定随机种子让新旧模型在完全相同的测试数据上比较。从那以后我每次换模型都会强制走一遍对比测试集指标、检查新 vectorizer 与模型匹配、替换后立即用两段正反例验证这个流程。这个习惯帮我避开了至少三次模型越换越差的尴尬。项目本身已经提供了双模型对比的基础在这个框架上扩展模型版本管理会非常顺手希望帮到你。本文还有配套的精品资源点击获取