☰
Django+深度学习构建酒店评论情感分析系统:从数据到部署全攻略
2026/10/13 2:17:35 网站建设 项目流程

每年一到课程设计或者毕业设计的节点,后台就会有一拨人拿着同一类问题来问:有没有一个题目,既不至于难到做不出来,又能把Web开发、数据库、算法这几个考核点全部占住?预算时间大概一个月,最好是网上能找到源码、数据库和文档参考,改一改就能跑。

遇到这种情况,我十有八九会推荐“基于Django+深度学习的酒店评论文本情感分析”。这个选题不算冷门,但正因为成熟,才有大量现成的路线可以参照。它把传统机器学习语境下的“情感分析”升级成了深度学习驱动的完整系统:Django负责Web端、SQLite负责数据存储、深度学习模型负责核心情感预测,三者在架构上界限清晰,评分老师能从任何一个维度找到技术亮点,做的人也能在有限时间内真正跑通全链路。

这篇文章,我把从选题、数据清洗、模型选型,到Django集成、数据库表设计,再到最后文档撰写和答辩演示的完整链路一次性复盘出来。如果你是第一次接触类似项目,照着这条路走,会比从零开始自己摸索省下大半个月时间。

1. 选题不盲目:酒店评论、Django与深度学习为什么是绝配

1.1 酒店评论这个场景,恰好卡在“简单”和“有深度”之间

情感分析本身是一个很宽泛的方向:电商评论、微博文本、新闻评论、影视评论都能做。为什么专门选酒店评论?我自己的经验是,酒店评论有三个天然优势。

  • 文本短、意图集中。“房间很干净”“前台服务态度特别差”这种评论,情感指向非常明确,不需要跨句子推理。模型容易训练,也适合课程设计的时间预算。
  • 数据量大且容易获取。酒店OTA平台上的评论文本大多是公开可见的,网上也有整理好的公开中文酒店评论数据集,不需要自己从零标注上万条。
  • 业务场景清晰。酒店评论背后连着口碑管理、服务质量优化、用户决策辅助,做出来的系统天然有“应用价值”,写摘要、写文档、答辩讲背景时都特别好讲。

相比之下,微博文本噪声大,网络用语多,情感表达经常反讽和隐晦;电商评论商品属性复杂,一条评论可能同时夸质量又骂物流,标注标准很难定。酒店评论是我见过的,最适合作为“文本情感分析从入门到系统落地”练手对象的语料类型。

1.2 深度学习在这个项目里不是“为用而用”

这里一定要回答一个问题:用TF-IDF加朴素贝叶斯,或者TF-IDF加逻辑回归,跑出来的准确率往往也能到0.85左右,为什么非要用深度学习?

三个理由。

第一,评论文本的情感表达依赖词与词之间的组合关系,而不是单个词的频次。传统机器学习基本靠词频和TF-IDF权重,对“虽然设施旧,但是服务特别好”这种转折句式基本抓不住重点。深度学习通过词向量把语义抽象成稠密向量,再通过卷积或循环结构捕捉局部和上下文信息,对这种转折句有一定的理解能力。

第二,课程设计和毕业设计的评分标准里,一般都会有一条“应用主流人工智能技术”。你做一个朴素贝叶斯情感分类,哪怕功能再完善,在“技术先进性”这个维度上也天然吃亏。深度学习模型不用多复杂,哪怕只是TextCNN,写进文档都比“传统机器学习方案”有说服力。

第三,深度学习的工程可控性其实被很多人低估了。数据量一万条左右,单卡GPU训练一个TextCNN,十分钟到半小时就能收敛。即使只用CPU,小模型也能在可接受的时间内出结果,完全不会出现实验中后期天天蹲训练日志等loss下降的情况。

1.3 提前预判评分老师的阅卷视角

做课程设计之前,我建议先摸清楚评审到底看什么,然后倒推选题。

通常就三块:系统功能完整度、技术栈合理性、文档与答辩表达。这个项目恰好把三个评分点全部拉满。

  • 功能完整度:不是只有一个训练脚本,而是有一个能在线输入评论、实时返回情感标签和置信度的Web系统。
  • 技术栈合理性:Django做后端服务,SQLite做记录存储,深度学习模型做预测核心,每层都有明确分工,而不是缝在一起的一坨代码。
  • 文档答辩:核心算法是深度学习,天然有东西可以讲,有实验数据可以放,有模型对比可以做。

想清楚这三点,选题基本就稳了。

2. 跑通模型之前,先搞定数据:采集、清洗与标注全流程

2.1 数据集从哪来,怎么判断够不够用

数据是这个项目里最容易被忽视、又最容易决定成败的一环。我第一次带人做的时候,对方兴致勃勃要自己写爬虫去抓点评数据,结果花了一周时间反爬、调试,数据质量还是一团糟。后来换了公开数据集,当天就把模型跑起来了。

首选方案是直接使用公开的中文酒店评论数据集。比如谭松波老师的ChnSentiCorp就包含酒店评论语料,网上很多开源的情感分析项目也整理了携程、美团、飞猪等平台的评论文本,字段通常是“评论内容+情感标签”两列。直接下载CSV就能开始。

如果一定要自己爬,建议用requests加BeautifulSoup做静态页面抓取,注意遵守robots协议,控制请求频率。采集的重点是文本质量而不是数量,一万条有效评论对于一个课设模型来说已经足够。

怎么判断数据集是否够用?三个指标。

  • 情感标签分布:正负样本不能差太远,偏差太大会导致模型偏向多数类,准确率虚高。
  • 平均评论长度:如果平均长度小于10个字,说明纯噪声评论太多,建议过滤。
  • 重复率:爬虫数据里经常出现大量重复评论,重复样本会严重干扰模型泛化。先跑一下去重逻辑,看剩余有效数据量再做判断。

2.2 清洗与预处理:脏数据比模型大小更重要

清洗流程是一套固定动作,按顺序执行就能搞定大部分脏数据问题:

  1. 用BeautifulSoup的get_text()去掉HTML标签;
  2. 统一全角半角字符,把连续空格和换行替换成单个空格;
  3. 去掉URL、乱码字符和奇怪的Unicode符号;
  4. 过滤掉长度小于5个字的评论;
  5. 对完全重复的评论做去重,保留第一条。

这一步很多人图省事跳过,结果模型准确率反而提不上去,最后只能归咎于“数据太难”。其实清洗花半天时间,后面训练、推理都会省心很多。特别注意,清洗规则要和后面Django预测时的预处理保持一致,否则训练时和部署时走的是两套逻辑,预测效果会明显变差。

2.3 分词、去停用词和序列化

中文文本和英文不同,词与词之间没有天然空格,必须先分词。业界常用的是jieba,安装方便,分词速度也快,不需要自己造轮子。

import jieba text = "房间很宽敞,床也很舒服,但隔音效果一般。" tokens = jieba.lcut(text) print(tokens) # 输出:['房间', '很', '宽敞', ',', '床', '也', '很', '舒服', ',', '但', '隔音', '效果', '一般', '。']

分词之后可以去掉“的、了、是、吗、很”这类停用词,减少序列长度和噪声。也可以选择保留,效果差别不算太大,我建议去掉,能让序列更紧凑。标点符号也一并过滤。

然后就要做序列化了,分三步:

  1. 基于训练集构建词表,给每个词分配一个数字ID;
  2. 把分词后的评论映射成数字ID列表;
  3. 统一序列长度,超出部分截断,不足部分用0填充。

这里藏着一个特别容易踩的坑:词表只能基于训练集构建。测试集和后续预测时如果出现训练集没见过的词,统一映射为UNK,不要往词表里加。否则训练和预测的词汇表不一致,模型加载后预测结果会莫名其妙。

2.4 标注策略:正负二分类最有性价比

酒店评论平台一般都有评分字段,但“好评/差评”不完全等于“正面/负面”。比如“房间不大但够用,服务很贴心”,整体是正面;比如“位置很好,但隔音差到崩溃”,整体是负面。直接拿评分做标签,会引入大量标注噪声。

所以课程设计里最稳的方案是二分类:正面或者负面。三分类要引入中性,中性样本往往极其模糊,“房间还行,早餐一般”到底算中性还是负面,标注的人自己都可能犹豫。二分类就不一样,情感倾向只要整体偏正向就是正面,整体偏负向就是负面,标注标准清晰,模型训练难度也低。

二分类模型在公开酒店评论数据集上,准确率做到0.88到0.91是很正常的区间,演示效果已经非常有说服力。

3. 模型选型与训练实测:从TextCNN到BiLSTM

3.1 TextCNN:短文本分类里的“性价比之王”

TextCNN是2014年Kim提出的文本分类模型,思路非常直观:把一句话看成一个词嵌入矩阵,用多个不同宽度的卷积核去扫描这个矩阵,分别提取3-gram、4-gram、5-gram的局部特征,再经过全局最大池化保留最显著的特征,最后接全连接层做分类。

酒店评论文本是典型的短文本,TextCNN在短文本上的效果不输大模型,训练速度还快。我自己实测下来,一万条数据在单卡GPU上训练一轮不到一分钟,CPU上也就几分钟,完全在课设可接受范围内。

核心模型定义长这样,可以直接参考:

import torch.nn as nn import torch.nn.functional as F class TextCNN(nn.Module): def __init__(self, vocab_size, embedding_dim=200, num_filters=128, filter_sizes=(3, 4, 5), num_classes=2): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0) self.convs = nn.ModuleList([ nn.Conv1d(embedding_dim, num_filters, kernel_size) for kernel_size in filter_sizes ]) self.fc = nn.Linear(num_filters * len(filter_sizes), num_classes) self.dropout = nn.Dropout(0.5) def forward(self, x): x = self.embedding(x).transpose(1, 2) conv_out = [F.relu(conv(x)) for conv in self.convs] pooled = [F.max_pool1d(item, item.size(2)).squeeze(2) for item in conv_out] x = torch.cat(pooled, dim=1) return self.fc(self.dropout(x))

3.2 BiLSTM与注意力机制:毕业设计可以往上再加一层

如果说TextCNN是短文本分类的稳妥选择,那BiLSTM就是毕业设计里让文档更有深度的加分项。

双向LSTM由前向和后向两个LSTM组成,能同时捕捉上下文信息。普通LSTM只能从左往右读,情感词经常出现在句尾,比如“服务态度非常糟糕”,如果只看前半段可能判断错误;双向LSTM可以结合两个方向的信息,对这种依赖后文的情况更友好。

更常见的进阶做法是BiLSTM加注意力层。注意力机制的本质是按权重聚合每个时间步的隐藏状态,让模型把注意力放到“干净”“差”“舒服”这类情感关键词上,而不是把整句话平均处理。

我的建议是:课程设计用TextCNN做主力模型,BiLSTM作为对比实验写进文档;毕业设计用BiLSTM加注意力作为主力模型,TextCNN作为基准。两个模型共享同一套数据预处理流程,切换成本很低,但写出来的实验对比内容就丰富多了。

3.3 训练参数与评价指标,直接照着跑

训练阶段的参数确实是有所谓的“安全区间”的,在合理区间内调参,大部分模型都能收敛。我记录一下自己实测比较好用的一套参数:

参数取值
最大序列长度60
Embedding维度200
卷积核尺寸[3, 4, 5]
卷积核数量128
BiLSTM隐藏层维度128
Batch Size64
学习率1e-3
OptimizerAdam
Dropout0.5
Epoch20,带早停

评价指标一定不能只看准确率。正负样本不平衡时,模型全预测成多数类,准确率也会很好看,但这没有任何意义。要看精确率、召回率和F1值。

  • 精确率:预测为“正面”的评论里,真正是正面的比例。
  • 召回率:所有真正的正面评论里,被模型成功找出来的比例。
  • F1值:精确率和召回率的调和平均。

再画一个混淆矩阵,把真实的正面/负面和预测的正面/负面四个格子列出来,文档里放上这张图,说服力直接上一个档次。

在公开酒店评论数据集上,不做任何复杂调参,TextCNN基本能到0.90左右,BiLSTM在0.89到0.92之间。BERT微调能到0.94以上,但部署体积大、推理速度慢,不建议作为课设主力模型,可以放在对比实验里提一句,展示你对前沿方案做过了解。

3.4 模型保存与加载:别让训练白跑

PyTorch保存模型时,一定要连同词表、配置一起保存。我见过太多人只保存了model.state_dict(),结果换环境加载时找不到词表,整个训练白跑。

torch.save({ 'model_state_dict': model.state_dict(), 'word2id': word2id, 'config': config, }, 'models/textcnn.pkl')

加载的时候也要注意:模型定义文件必须和加载路径一致。我踩过一次坑,训练脚本里定义的TextCNN放在项目根目录,后来Django项目里新建了ml目录,模型文件也移过去了,但TextCNN类的导入路径变了,加载直接报错。解决办法很简单:模型定义文件跟着权重文件走,保持目录结构一致,或者用相对路径导入。

4. Django集成深度学习模型:把PyTorch模型变成一个在线预测接口

4.1 项目结构与App拆分

模型训练完成,接下来就是把它变成能在线访问的Web系统。Django部分的核心不是写很多代码,而是把结构理清楚。

创建项目的命令很常规:

django-admin startproject hotel_sentiment cd hotel_sentiment python manage.py startapp review

这里要特别提醒一个新手陷阱:不要把业务代码全塞进views.py。一个完整的课设项目,我建议的目录结构是:

hotel_sentiment/ ├── manage.py ├── hotel_sentiment/ # 项目配置目录 ├── ml/ # 模型定义与训练相关脚本 │ ├── textcnn.py │ ├── bilstm.py │ └── train.py ├── review/ # Django应用 │ ├── models.py │ ├── views.py │ ├── ml_service.py # 模型加载与预测服务 │ └── urls.py ├── templates/ # 前端模板 │ ├── index.html │ └── statistics.html ├── data/ # 数据集 ├── models/ # 训练好的权重文件 └── db.sqlite3

把模型训练脚本、Web服务逻辑、前端模板分开存放,评审老师一眼就能看出工程能力,后期自己维护也省脑子。

4.2 模型加载的正确姿势:一次加载,多请求复用

深度学习模型文件动辄几十上百兆,如果每个请求都重新加载一次,系统会卡到没法演示。正确做法是在Django进程启动时加载一次模型到内存,后续所有请求复用同一个实例。

用AppConfig.ready()来做全局加载是最规范的做法:

# review/apps.py from django.apps import AppConfig class ReviewConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'review' def ready(self): from .ml_service import ModelService ModelService.load()

然后在ml_service.py里写一个模型服务类:

class ModelService: model = None word2id = None @classmethod def load(cls): import pickle import torch from ml.textcnn import TextCNN with open('models/word2id.pkl', 'rb') as f: cls.word2id = pickle.load(f) checkpoint = torch.load('models/textcnn.pkl', map_location='cpu') cls.model = TextCNN(**checkpoint['config']) cls.model.load_state_dict(checkpoint['model_state_dict']) cls.model.eval()

启动时加载一次,预测时模型已经在内存里了。实测下来,单个评论的预测响应时间可以控制在百毫秒级别,完全够用。

4.3 预测接口与前端异步刷新

核心视图逻辑并不复杂,接收请求里的评论文本,走一遍和训练时完全一致的预处理流程,调用模型,返回JSON结果。

import jieba import numpy as np import torch from django.http import JsonResponse from django.shortcuts import render from .ml_service import ModelService from .models import ReviewRecord def predict_view(request): if request.method == 'POST': text = request.POST.get('comment', '').strip() if not text: return JsonResponse({'error': '评论内容不能为空'}) result = ModelService.predict(text) ReviewRecord.objects.create( content=text, label=result['label'], confidence=result['confidence'], ) return JsonResponse(result) return render(request, 'index.html')

前端用fetch做异步提交,页面不刷新就能看到结果:

const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value; fetch('/predict/', { method: 'POST', headers: { 'X-CSRFToken': csrftoken, 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ comment: textarea.value }) }) .then(response => response.json()) .then(data => { document.getElementById('result').textContent = data.label === 1 ? '正面情感' : '负面情感'; document.getElementById('conf').textContent = '置信度:' + data.confidence.toFixed(4); });

这里说一个和热词相关的题外话:有人问Django后台有数据时能不能像WebSocket一样实时推送到前端。可以做,用Django Channels就行,但课程设计阶段我强烈建议用普通fetch加刷新就够了。WebSocket引入异步、长连接、跨域一堆问题,演示时网络环境稍有波动反而容易翻车。

4.4 预测结果写库,让每一次分析都可追溯

预测结果不能只在前端显示一下就完了,还要写进数据库,作为历史记录和统计报表的数据来源。

这部分直接复用Django的ORM,一行ReviewRecord.objects.create()就完成落库。后面的历史记录页面用ReviewRecord.objects.all()查询,统计页面用聚合函数按标签分组计算。整个闭环就完整了。

5. 数据库设计与统计报表:SQLite撑起一个完整系统

5.1 为什么课设阶段不需要MySQL

很多同学一上来就纠结SQLite还是MySQL,有的甚至为了“显得专业”硬上了MySQL,结果光配置就在不同电脑上折腾了两天。

我的观点很明确:课程设计的数据量、并发量根本没有到需要独立数据库服务的程度。SQLite是Django默认支持的数据库,零配置文件,数据全部存在一个db.sqlite3文件里,提交作业时连文件一起打包,老师拿到手双击就能跑,这是最大的优势。

MySQL通常要单独安装服务、配置账号密码、处理字符集,换一台电脑运行环境稍有差异就白屏。除非毕业设计明确要求必须使用MySQL,否则默认SQLite就是性价比最高的选择。至于数据库连接池、主从同步这类话题,等真到了高并发场景再去研究也不迟,课设阶段完全用不上。

5.2 数据表设计:围绕“评论分析”做一张主表

这个项目的核心数据表就是评论分析记录表,字段不复杂,但设计要合理:

from django.db import models class ReviewRecord(models.Model): POSITIVE = 1 NEGATIVE = 0 LABEL_CHOICES = [(POSITIVE, '正面'), (NEGATIVE, '负面')] content = models.TextField(verbose_name='评论内容') label = models.IntegerField(choices=LABEL_CHOICES, verbose_name='情感标签') confidence = models.FloatField(verbose_name='置信度') created_at = models.DateTimeField(auto_now_add=True, verbose_name='分析时间') class Meta: ordering = ['-created_at'] def __str__(self): return f'{self.content[:20]} - {"正面" if self.label == 1 else "负面"}'

如果需要用户管理,可以再单独建一张用户表,用外键关联到评论记录上。但纯课程设计不要求多用户体系,匿名提交已经可以把完整业务流程跑通。表结构简单,反而方便答辩时讲解。

5.3 ORM增删改查与迁移经验

Django模型定义好后,两条命令同步数据库,这个步骤不能漏:

python manage.py makemigrations review python manage.py migrate

ORM的增删改查也是答辩老师特别喜欢问的点,提前把这些语法吃透:

  • 查询所有记录:ReviewRecord.objects.all()
  • 条件过滤:ReviewRecord.objects.filter(label=0)
  • 查询单条:ReviewRecord.objects.get(id=1)
  • 删除记录:ReviewRecord.objects.filter(label=1).delete()
  • 聚合统计:ReviewRecord.objects.values('label').annotate(count=Count('id'))

删除对象时,如果数据表有外键关联,默认的级联删除行为要额外注意。课程设计里一般不会有级联场景,但答辩时如果被问到“删除用户时他的评论会不会一起消失”,能答出来就说明你真的理解了外键约束。

5.4 基于ECharts的情感分布可视化

为了让系统看起来像一个完整产品,建议加一个统计页面。后端返回聚合数据,前端用ECharts画图,视觉冲击力完全不一样。

后端统计接口可以用ORM聚合一行搞定:

from django.db.models import Count from django.http import JsonResponse def statistics_view(request): counts = ReviewRecord.objects.values('label').annotate(count=Count('id')) result = {'0': 0, '1': 0} for item in counts: result[str(item['label'])] = item['count'] return JsonResponse(result)

页面里可以画三个图:一个饼图显示正负面比例,一个折线图按日期显示分析数量趋势,一个柱状图展示置信度分布。ECharts直接引用CDN资源就行,不需要额外引入Node环境。前端模板代码量不大,但把这三个图放上去,整个项目的完成度观感会提升一大截。

6. 万字文档与答辩演示:最后一公里决定项目上限

6.1 文档结构:按毕业论文标准写

代码写得再好,文档只有两千字,老师也会觉得敷衍。这个项目的优秀样板文档,一般按照下面这个骨架来写:

  • 第一章 绪论:研究背景、研究意义、国内外研究现状
  • 第二章 相关技术介绍:情感分析概念、Django框架、深度学习理论
  • 第三章 需求分析:功能性需求在线预测、历史记录、统计报表,非功能性需求性能、易用性
  • 第四章 系统设计:总体架构、模块划分、数据库表设计
  • 第五章 系统实现:数据预处理、模型构建与训练、Web功能实现
  • 第六章 系统测试:功能测试用例、模型对比实验、结果分析
  • 第七章 总结与展望

每一章对应一个评分点,写完加上图表和代码片段,一万字以上的文档很容易出来。这里有个小技巧:在系统设计一章画好架构图,在系统实现一章放核心代码而不是全部代码,在系统测试一章放数据表格,这样的文档看起来专业且干净。

6.2 答辩高频问题与回答思路

答辩时老师通常会从三个方向问,提前准备就不会慌。

  • 技术理解类:“为什么用TextCNN?”“LSTM里的遗忘门、输入门、输出门分别有什么用?”回答时要落到具体参数和代码逻辑上,不要背定义。比如解释TextCNN时,直接说“我用了3、4、5三种卷积核尺寸来提取局部特征,全局最大池化取每个特征图最强的信号”,比背教材有效得多。
  • 方案对比类:“为什么不用BERT?”回答思路是:BERT效果确实更好,但模型体积大、推理速度慢,在课设这种短文本二分类场景下性价比不高,所以选用了训练快、易部署的TextCNN作为主力模型,把BERT作为后续优化方向写进展望。
  • 工程实现类:“模型是怎么嵌入Django的?”回答就一条链路:AppConfig.ready()启动时加载一次模型,ml_service里做预处理和预测,视图拿到预测结果后通过ORM写进数据库,前端异步展示。

6.3 十分钟演示脚本设计

演示顺序建议是“由浅入深”,让评审先看到效果,再看到数据积累,最后看到统计分析。

先输入一条“房间很干净,床很舒服,早餐也很丰富”,让模型给出正面结果和置信度;再输入一条“隔音太差了,一晚上没睡着,前台态度还不好”,让模型给出负面结果。两条评论对比展示,情感区分一目了然。

然后打开历史记录页面,连续分析几条之后刷新,让数据在表格里不断累积。这个动作是为了证明数据库是真的在写入,而不是写死的静态页面。

最后进入统计页面,展示饼图和折线图,说明系统支持从历史数据里提取情感分布趋势。到这一步,一个“从算法到系统再到数据可视化”的完整项目闭环就全部展示完了。

我在实际带项目的过程中最深的体会是:深度学习模型在这个项目里只占三分之一的工作量,剩下三分之二是数据处理、Web集成和数据展示。很多同学把精力全砸在调模型上,最后Web端做得稀烂,得不偿失。这个题目的正确打开方式是均衡用力,让每一个模块都能在答辩时拿出来说。按照上面的路线走一遍,你会发现自己拿到的不只是一个能跑的项目,而是一套完整的工程思维。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询