资讯动态

基于Flask的中文情感分析系统:从模型训练到Web部署全解析

发布时间:2026/10/18 10:25:16 来源:尧图企业网站定制
简介面向本科毕业设计的中文情感分析系统完整项目包基于Python、Flask与MySQL实现用深度学习模型对评论做正面/负面分类。资源覆盖登录注册、后台首页统计看板、文本分析及软件测试流程适合计算机专业学生快速搭建可演示环境也可作为Flask深度学习整合开发的参考范例。压缩包共378个文件大小97.54MB含Python源码、SQL数据库脚本、HTML/CSS/JS前端页面、模型权重文件pkl/npy、界面截图与演示动图png/jpg/gif另附文档、演示视频和PPT便于按模块检索目前已有220人学习浏览。通过这套资料可掌握数据预处理、模型训练到Web部署的完整链路理解注册登录安全、数据可视化和情感分析接口的集成方式尤其适合需要短时间完成系统演示与论文撰写的冲刺阶段资源包目录结构清晰可对照文档直接启动项目。1. 一个把中文评论自动分成正面和负面的 Web 系统我最近拆了一套基于 Python Flask 的中文情感分析系统把一段中文评论文本输进去后端跑一遍深度学习模型返回正面还是负面的结论整个系统还带登录注册、历史分析记录、正负面占比柱状图和饼图。这种「网页应用 深度学习推理」的组合恰好是很多毕业设计想要的样子——既要体现算法能力又要交付完整可演示的系统功能。它适合三类人正在选题的计算机专业学生、想快速把模型包装成 Web 服务的开发者、以及需要批量处理评论文本又不想从零搭系统的个人玩家。我按架构设计、核心模块、数据库、运行步骤和常见坑的顺序往下拆。2. 架构与工程结构Flask 业务和离线模型推理怎么配合2.1 技术栈选型为什么是 Flask MySQL LayUI 深度学习框架先看一眼整站的技术栈。后端用的是 Flask这是 Python 生态里最轻量的 Web 框架一个 app.py 就能把路由全写清楚数据库是 MySQL存用户、分析记录这类结构化数据。前端没引入大型框架用的是 LayUI 那套组件库——项目静态资源里的 layui.css、layer.css、laydate.css 都是它的组成部分表单、弹窗、日期选择器直接拿来用。样式文件里那批 index_2018051810.css、report.css 是后台模板的皮肤和报表样式index_pdf.css 主要用于导出或打印排版laydate.css 则管日期控件。图表部分结合 ECharts 绘制柱状图和饼图。深度学习部分是这套系统最值得拆的地方。训练一个文本分类模型需要准备数据、训练和调参如果每一次 Web 请求都现场训练显然不现实所以项目采用「离线训练 在线推理」的常见做法模型在训练脚本里跑完保存成文件Flask 启动时载入一次之后每个分析请求走预处理、模型推理、结果入库这条路。分类网络方面短文本常用 TextCNN长文本常用 BiLSTM这套项目里按短文本评论处理常规实现是 Embedding 双向 LSTM 全连接分类层词向量用中文预训练向量初始化网络结构我放到 3.2 节配合代码讲。为什么选 MySQL 而不是 SQLite毕业设计演示时MySQL 更能体现工程完整性网上部署环境基本都装了。用户表、分析记录表都放在 MySQL 里查询和统计都比文件型数据库顺手。为什么不是 DjangoDjango 是个全家桶框架自带 ORM、Admin、迁移工具很强但概念多Flask 路由和业务简单直接对演示型项目来说代码量小、每一行都能讲清楚而且深度学习代码大多是 Python 函数Flask 里直接 import 就能调用集成路径最短。数据库连接用 PyMySQL有人会再套一层 SQLAlchemy ORM这个项目为了降低上手门槛用原生 SQL 脚本建表加 PyMySQL 查询。演示阶段这种写法反而好排查问题。整体架构落地成一条数据流是这样的浏览器页面LayUI→ Flask 路由 → 视图函数 → MySQL验证用户、写分析记录→ 分类模型预处理 推理→ JSON 结果 → 前端渲染图表这条链路里有两个边界值得注意业务逻辑和数据都走 Flask MySQL深度学习模型被包在推理工具模块里不直接参与用户查询。带来的好处是后面想换模型Web 层几乎不用动。前后端也不是分离架构页面由 Flask 的 Jinja2 模板渲染图表初始化数据可以由后端模板变量注入也可以由前端再调接口拿 JSON。分析结果通常用 ajax 请求处理减少整页刷新二次开发想接 Vue保留 /analyze 和 /stats 接口返回 JSON 即可。2.2 目录结构源码包里每一块都是干什么的解压资源包之后核心目录结构大概是这个样子project/ ├── app.py # Flask 入口注册路由、启动服务 ├── config.py # 数据库配置、模型路径、secret_key ├── requirements.txt # 依赖清单 ├── models/ │ ├── ckpt/ # 训练好的模型权重文件 │ └── dict.pkl # 词表 / 索引映射字典 ├── utils/ │ ├── preprocess.py # jieba 分词、去停用词、序列化 │ └── predictor.py # 加载模型、执行推理 ├── service/ │ ├── user_service.py # 注册登录业务 │ └── analyze_service.py # 分析记录与统计业务 ├── static/ │ ├── css/ js/ layui/ # 前端静态资源 ├── templates/ │ ├── login.html register.html │ ├── index.html # 后台首页柱状图饼图 │ └── analyze.html # 文本分析页 └── sql/ └── sentiment.sql # 建库建表脚本templates 是 Flask 默认的模板目录页面都在这里static 放 LayUI、ECharts 这些静态资源。models 目录是整个系统的黑匣子所在dict.pkl 保存「词 → 索引」的映射字典ckpt 是训练好的权重。推理时文本要先转成索引序列才能喂给网络这两个文件丢了模型就跑不起来部署时要一起拷走。utils/predictor.py 是深度学习推理入口后续换模型主要改这个文件service 目录把业务从路由里抽出来是合格的 Flask 工程习惯。如果你的源码包结构更扁平所有路由写在 app.py 里也不影响阅读二次开发时再拆不迟。2.3 一个分析请求从进入到返回的完整旅行以「文本分析」请求为例。用户在 analyze.html 页面粘贴一段评论文本点击提交前端把文本 POST 到 /analyze。Flask 视图函数先取当前登录用户的 session 确认身份然后取出文本调用 preprocess 里封装的管线去特殊字符、jieba 分词、去停用词、按 dict.pkl 转成索引序列、统一 padding 到固定长度。这串索引交给 predictor.predict()模型跑一次前向传播得到两个类别的概率取最大值对应正面或负面。拿到结果后视图函数把原文、预测标签、概率一起写入 MySQL 的 sentiment_record 表同时把 JSON 返回给前端。前端展示正负面结果并刷新 /stats 统计接口后台首页就能看到最新占比。整条链路里最容易出问题的是预处理环节——词表里没有的词会被映射成 UNK 索引如果训练时没预留 UNK 位推理时就会直接越界报错padding 长度如果和训练时不统一模型输入维度也会对不上。这类问题的排查方法我放到第 5 章详细说。3. 核心模块拆解登录、推理、图表一个都别少这个系统的三个核心模块正好对应了项目简介里的几个界面登录和注册是进入系统的闸门文本分析承载深度学习推理统计图表负责把分析结果讲清楚。下面逐个拆代码。3.1 注册与登录密码不能明文落库先看注册接口的核心代码# service/user_service.py from flask import Blueprint, request, session, jsonify from werkzeug.security import generate_password_hash, check_password_hash from utils.db import get_db user_bp Blueprint(user, __name__) user_bp.route(/register, methods[POST]) def register(): data request.get_json() username data.get(username, ).strip() password data.get(password, ) phone data.get(phone, ) if not username or not password or not phone: return jsonify({code: 1, msg: 用户名、密码、手机号不能为空}) # 查询用户名是否已存在 conn get_db() cursor conn.cursor() cursor.execute(SELECT id FROM user WHERE username%s, (username,)) if cursor.fetchone(): return jsonify({code: 1, msg: 用户名已存在}) # 密码加盐哈希后再入库绝不存明文 hashed generate_password_hash(password) cursor.execute(INSERT INTO user (username, password, phone) VALUES (%s, %s, %s), (username, hashed, phone)) conn.commit() return jsonify({code: 0, msg: 注册成功})逻辑不复杂请求体是 JSON先做空值检查查重走 SELECT写入前用 generate_password_hash 加盐哈希数据库表里那一列存的是哈希串而不是原始密码。哈希函数每次生成的盐都不同同一密码两次注册得到的哈希值也不一样这是正常现象校验密码时不要拿哈希去比哈希而要专门用 check_password_hash。注册页面通常还有第二次密码确认输入框那是前端交互后端拿到的 password 已经是确认一致后的值。如果担心有人绕过页面直接调接口后端也应该再校验 password 和 confirm_password 是否相等。登录接口和注册是对称的user_bp.route(/login, methods[POST]) def login(): data request.get_json() username data.get(username, ).strip() password data.get(password, ) conn get_db() cursor conn.cursor(pymysql.cursors.DictCursor) cursor.execute(SELECT id, username, password FROM user WHERE username%s, (username,)) user cursor.fetchone() if not user or not check_password_hash(user[password], password): return jsonify({code: 1, msg: 用户名或密码错误}) session[user_id] user[id] session[username] user[username] return jsonify({code: 0, msg: 登录成功})登录时先按用户名把用户捞出来账号不存在直接拒绝存在则用 check_password_hash(哈希值, 明文密码) 比对。登录成功后把 user_id 和 username 写进 session后面的文本分析页、后台首页都靠它判断登录状态每条情感分析记录也带上 user_id 记录归属。这里有个很容易被忽略的点Flask 的 session 依赖 secret_keyconfig.py 里没配的话 session 根本写不进去表现为登录成功跳转后首页又立刻要求登录排查时第一眼看配置文件。3.2 文本预处理与模型推理黑匣子里面在做什么情感分析最核心的一段是预处理直接决定模型能不能正确读取输入。代码大致是这样# utils/preprocess.py import re import jieba def load_stopwords(pathdata/stopwords.txt): with open(path, encodingutf-8) as f: return set(line.strip() for line in f) def text_to_sequence(text, word2idx, max_len128, stopwordsNone): # 1. 清洗只保留中文字符数字、英文、标点替换成空格 text re.sub(r[^\u4e00-\u9fa5], , text) # 2. 分词 tokens list(jieba.cut(text)) # 3. 过滤停用词并转成词表索引 seq [] for token in tokens: if stopwords and token in stopwords: continue idx word2idx.get(token, word2idx.get(UNK, 0)) seq.append(idx) # 4. 截断和 padding 到固定长度 if len(seq) max_len: seq seq[:max_len] else: seq seq [word2idx.get(PAD, 0)] * (max_len - len(seq)) return seq参数说明word2idx 是 models/dict.pkl 里的词表映射来自训练阶段max_len 是训练时定死的序列长度推理阶段必须保持一致改了模型维度会报错stopwords 从停用词表按行读取常见停用词如“的”“了”“就”会被过滤清洗时只保留中文字符因为词表基本只覆盖中文词汇混入的符号会全部落到 UNK 位白占长度。预处理输出是一个固定长度的整型数组直接作为模型输入。推理部分封装一个 SentimentPredictor 类# utils/predictor.py import pickle import torch import numpy as np class SentimentPredictor: def __init__(self, model_path, dict_path): with open(dict_path, rb) as f: self.word2idx pickle.load(f) self.model torch.load(model_path, map_locationcpu) self.model.eval() def predict(self, text): seq text_to_sequence(text, self.word2idx) inputs torch.tensor([seq], dtypetorch.long) with torch.no_grad(): logits self.model(inputs) prob torch.softmax(logits, dim1).numpy()[0] label int(np.argmax(prob)) return { label: 正面 if label 1 else 负面, probability: float(max(prob)) }predict 方法接收原始文本返回标签和概率。label 为 1 表示正面0 表示负面概率是 softmax 后的置信度。这种封装的好处是 Web 层不关心底层是 PyTorch 还是 TensorFlow只要接口不变就能换实现。如果模型训练时保存的是 state_dict加载则要先创建网络对象再 load_state_dict两种保存方式别混用。整段代码里最容易翻车的是 max_len 和 dict.pkl 不一致我拆过好几个类似项目十有八九卡在这里日志报维度不匹配时先检查这两个文件。训练数据这块常规做法是整理几千到几万条已标注的中文评论正面负面各一半按 8:2 比例切分训练集和验证集训练验证集准确率到 0.85 以上对短评论文本已经可用。训练脚本一般和 Web 工程分开模型训练完输出 ckpt 和 dict.pklFlask 端只是消费它们这正是「离线训练 在线推理」的意义。3.3 统计图表接口SQL 聚合之后还要管格式后台首页要用柱状图和饼图展示统计结果数据源是 sentiment_record 表。常规设计是暴露一个统计接口analyze_bp.route(/stats) def stats(): user_id session.get(user_id) if not user_id: return jsonify({code: 1, msg: 未登录}) pie analyze_service.count_by_label(user_id) bar analyze_service.count_by_date(user_id, days7) return jsonify({code: 0, data: {pie: pie, bar: bar}})service 层两条 SQL 是这个模块的骨架-- 饼图正面/负面各多少条 SELECT label, COUNT(*) AS cnt FROM sentiment_record WHERE user_id %s GROUP BY label; -- 柱状图最近 7 天每天的分析量 SELECT DATE(create_time) AS d, COUNT(*) AS cnt FROM sentiment_record WHERE user_id %s AND create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(create_time);前端拿到 JSON 后再转成 ECharts 需要的格式饼图 data 是 [{name: 正面, value: n}, {name: 负面, value: m}]柱状图是 categories 加 values 两个数组。这里有个后端容易忽略的细节PyMySQL 默认查出来的 COUNT(*) 是 Python int但如果经过了 ORM 或数据类型转换可能变成 Decimal 或字符串ECharts 处理非 number 类型时图表会显示空白或异常。我一般习惯在接口返回前统一做一次 int() 和 float() 转换把格式问题掐死在源头。除了图表统计分析记录列表一般也会做一个分页接口展示最近 20 条内容、标签、概率、分析时间。它和 /stats 共用一个 service代码量不大但对完整度很有帮助演示时翻历史记录比只看两个图表更有说服力。4. 数据库设计与本地运行从建库到看见第一个图表4.1 数据表设计与建库脚本项目带 sql 目录里的 sentiment.sql先看两张核心表的建表语句CREATE DATABASE sentiment_db DEFAULT CHARACTER SET utf8mb4; USE sentiment_db; CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, phone VARCHAR(20) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sentiment_record ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, content TEXT NOT NULL, label TINYINT NOT NULL COMMENT 1 正面 0 负面, probability FLOAT DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_time (user_id, create_time), CONSTRAINT fk_record_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;两个值得注意的设计决定。第一字符集用 utf8mb4 而不是 utf8因为评论内容可能包含 emojiMySQL 的 utf8 是 3 字节编码存 4 字节的 emoji 直接报错或被替换成问号utf8mb4 才能完整存储对应到连接层PyMySQL 连接参数也要写 charsetutf8mb4否则网页端中文照样乱码。第二label 用 TINYINT 存 0/1不直接存“正面”“负面”字符串统计和索引都更高效展示层由后端代码负责转成中文文字。联合索引 idx_user_time 建在 user_id 和 create_time 上首页最近 7 天统计会走这个索引数据量上来以后明显比全表扫描稳。建库命令也很直接mysql -u root -p sql/sentiment.sql如果数据库里已经存在 sentiment_db脚本会报错需要先 DROP DATABASE或者把建库语句注释掉只保留 CREATE TABLE。4.2 环境配置与依赖安装运行环境 Python 3.8 以上依赖不复杂。requirements.txt 大致包含flask2.0 pymysql1.0 jieba0.42 torch1.8 numpy安装一行命令pip install -r requirements.txt。没有 GPU 也没关系这种规模的文本分类模型 CPU 推理也就是毫秒级torch 会自动装 CPU 版本。config.py 里需要根据自己的环境改三四个值import os class Config: SECRET_KEY your-secret-key-change-me DB_HOST 127.0.0.1 DB_PORT 3306 DB_USER root DB_PASSWORD 123456 DB_NAME sentiment_db MODEL_DIR os.path.join(os.path.dirname(os.path.abspath(__file__)), models) classmethod def get_db_uri(cls): return (fmysqlpymysql://{cls.DB_USER}:{cls.DB_PASSWORD} f{cls.DB_HOST}:{cls.DB_PORT}/{cls.DB_NAME} f?charsetutf8mb4)DB_USER、DB_PASSWORD 改成自己本机的 MySQL 账号SECRET_KEY 换一个长一点的随机串MODEL_DIR 用 os.path 基于file拼绝对路径避免从其他目录启动时相对路径失效。连接串末尾的 ?charsetutf8mb4 就是 4.1 说的连接层字符集配置漏了中文会乱码。启动服务python app.pyFlask 默认监听 5000 端口浏览器打开 http://127.0.0.1:5000 看到登录页就说明服务起来了。如果端口被占用在 app.py 的 app.run(port5001) 换一个如果 MySQL 连不上Windows 下先检查 MySQL 服务有没有启动再检查密码有没有写错。这两个属于环境问题日志里都会直接给堆栈照着错误往上查就行。4.3 完整功能验证路径跑通之后按这个顺序验证一遍能覆盖系统三大模块。第一步注册用户名、密码、手机号填全密码确认一致提交后提示注册成功第二步登录用刚注册的账号登录进入后台首页此时图表应该是空的或全为 0第三步文本分析输入“这个商品质量很好物流也快”预期返回正面再输入“客服态度差等了一周都没发货”预期返回负面第四步回到后台首页刷新柱状图和饼图应该出现对应的统计第五步查历史分析记录刚才两条应该都在列表里。这个顺序就是冒烟测试的常见做法从用户模块到推理模块再到统计模块每一环堵住了都能快速定位是哪一层的问题。压缩包里还带了一份演示视频对照视频的操作流程走一遍基本是 20 分钟能跑完的事。测试环节里如果发现某条中性评论被分到正面或负面先不要怀疑模型坏了短文本二分类模型对中性表达的判断本来就模糊属于数据分布问题换一批训练数据比调网络结构更有效这也是我反复验证过的结论。5. 常见问题与排查五类让我反复翻车的坑这个项目我前后拆过两遍第一遍被中文乱码搞得怀疑人生第二遍被模型重复加载直接吃满内存这些坑报错都不直观日志里的线索又散。我把最容易卡住人的问题整理成五条每条按现象、原因、解决三步说清楚基本覆盖新手跑这个系统八成会遇到的状况。5.1 中文乱码注册成功页面和数据库全是问号现象注册成功后后台首页用户名变成问号MySQL 里查出来的中文也是问号。原因乱码是三段链路里至少一段不是 utf8mb4——建库建表字符集、数据库连接字符集、页面响应编码。最常见的漏项是 PyMySQL 连接时没传 charset 参数默认用了 latin1。解决建库建表语句统一 utf8mb4config.py 的数据库连接串加 ?charsetutf8mb4Flask 返回页面时保证响应头是 UTF-8模板文件本身也存成 UTF-8。按这个顺序排查中文问题基本一次解决。5.2 模型越跑越卡内存被重复加载撑爆现象前几次分析正常跑十几分钟后系统越来越卡内存占用持续上涨最后新请求直接没响应。原因推理对象在视图函数内部被反复实例化每一次 predict 都会重新加载一次模型文件内存里同时存在多份权重。有些写法在循环里调 model.eval()也会触发不必要的图构建。解决把 SentimentPredictor 的实例化放到模块顶层应用启动时只加载一次。类似写法# predictor 实例全局唯一 predictor SentimentPredictor( model_pathos.path.join(Config.MODEL_DIR, ckpt/model.pt), dict_pathos.path.join(Config.MODEL_DIR, dict.pkl) )视图函数里直接用 predictor.predict()不要 new。如果你用的是 Flask 工厂模式把实例挂到 app.extensions 里也是常见做法。5.3 登录永远失败哈希密码被二次哈希现象注册成功登录时输入完全相同的密码后端一直提示用户名或密码错误。原因登录接口里把前端传进来的密码也跑了一遍 generate_password_hash再把新哈希值和库里的哈希值比较。哈希函数每次盐不同两次结果必然不一致所以永远比对不上。解决注册用 generate_password_hash登录用 check_password_hash(库里的哈希, 前端明文密码)不要自己实现 md5盐的逻辑。如果你在日志里看到 password 字段长度变成 60 左右的随机串还每次不同基本就是这个原因。5.4 首页图表空白接口有数据但前端不渲染现象文本分析能返回结果日志也正常但后台首页柱状图和饼图一直是空白控制台没有任何报错。原因ECharts 渲染失败通常不是图表库的问题而是数据格式不对——SQL 别名和前端 JS 取数的字段不一致或者数值类型不是 number。解决浏览器开发者工具 Network 面板直接看 /stats 的响应体确认字段名和前端 setOption 里用的是不是同一个后端在返回前把数值统一 int() 转换。前端初始化时 categories 给空数组而不是 undefined也能避免白屏。这类问题是最典型的“看起来是玄学实际是类型不匹配”。5.5 模型文件找不到不同目录启动就报错现象在项目根目录启动一切正常换成 python /path/to/project/app.py 或者先 cd 到 static 目录再启动立刻报 No such file or directory 找不到模型权重。原因代码里用了相对路径 models/ckpt/model.pt相对路径的参照是当前工作目录而不是项目根目录cd 到哪就相对哪里找自然找不到。解决像 4.2 那样在 config.py 里用 os.path.dirname(os.path.abspath(file)) 拼出绝对路径模型、词表、模板、静态资源全部基于这个项目根目录拼接之后无论从哪里启动都不会出事。这是我跑任何 Python 工程的第一件事先把所有路径改成绝对路径。以上这五条都踩过之后项目基本就能稳定运行了。第一遍跑通和稳定运行是两回事稳定运行通常要经历一次“配置做对 资源生命周期做对 数据格式做对”的完整循环。6. 往二次开发走把二分类改成三分类模型换成自己的数据6.1 让模型学习自己的数据项目自带的模型是通用中文评论训练出来的落地到电商、社交媒体或某个垂直领域时最好用自己的标注数据重新训练。数据格式整理成文本和标签两列按 8:2 切分训练脚本的词汇表重新生成 dict.pkl标签映射保持 0 负面、1 正面不变。重训完后把新的模型权重和 dict.pkl 替换到 models 目录就行。这里要特别小心 max_len训练时如果改成 64推理端 text_to_sequence 里的 max_len 也要同步改 64否则模型输入维度对不上报错位置会在第一层 Embedding。这是我在自己改造一个垂直电商评论项目时踩过的坑换了两版词表才反应过来是长度参数不一致。6.2 从二分类扩成三分类把正面/负面二分类改成负面/中性/正面三分类是性价比很高的一次改造。需要动四个地方数据标注阶段加入中性样本网络最后一层输出节点从 2 改成 3predict 函数里 label 映射改成 0 负面、1 中性、2 正面前端饼图增加“中性”数据项。统计接口的 GROUP BY label 不用改新标签值自动出现在结果里。这样改造之后演示效果比二分类更像真实产品中性评论不会再被强行押到某一端。这套项目整体上是个标准的「业务系统 模型推理」模板数据库表、登录态、统计模块都齐全核心算法被隔离在 predictor 里替换成本很低。我刚开始拆它的时候也犯过把密码哈希了两遍的错也眼看着内存被重复加载的模型吃满从那以后每次跑 Flask 深度学习的项目我都强制先走一遍数据库字符集、模型加载位置、密码校验方式、前端图表字段。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑