ARTICLE DETAIL

资讯详情

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

深度学习与Flask结合的中文情感分析系统:从数据采集到部署实践

深度学习与Flask结合的中文情感分析系统:从数据采集到部署实践 简介这是一份基于PythonFlask深度学习的中文情感分析系统毕业设计文档面向计算机专业毕业生及自然语言处理初学者可用于论文撰写、系统设计参考和答辩准备。文档按绪论、技术及工具介绍、系统分析、系统设计、系统实现、系统测试和结论七章完整结构展开覆盖从研究背景到成果验证的全流程重点讲解B/S架构、MySQL数据存储、CNN与RNN深度学习算法以及Python后端逻辑和爬虫数据采集等关键环节同时明确系统需实现中文文本数据收集、预处理、特征提取、模型训练和情感分析等功能。包内仅含1个docx文件大小约1.22MB内容组织清晰从可行性分析、需求分析到数据库设计、登录界面与分析模块实现均有详细阐述便于读者快速定位所需章节。目前已有382人学习下载适合需要从零搭建中文情感分析系统或完成毕业设计的同学参考。1. 先把这份资源看透一个能跑起来的 PythonFlask 深度学习中文情感分析系统前段时间我把这份基于 PythonFlask 的深度学习中文情感分析系统完整拆了一遍结论是它不是一个只讲理论的毕业设计文档而是一条能从数据采集走到模型训练、再到 Web 展示的完整链路。系统通过爬虫抓取 B 站评论区文本交给深度学习模型做正负面分类最后用 Flask 暴露成网页服务登录后就能输入一句话、实时看到情感判断和置信度。对正在做毕设、或者想快速搭一个 NLP 演示项目的从业者来说这套资源最有价值的地方在于它把“爬虫 预处理 词向量 深度学习分类 Flask 展示”五段流程串成了闭环每段都有对应代码和数据结构不是零散的知识点堆砌。你拿到手之后顺着跑一遍就能理解整个中文情感分析系统是怎么落地的也能在上面改成自己的数据集和模型。2. 技术选型为什么这么定Flask 的轻、MySQL 的稳、深度学习的准2.1 B/S 架构与 Flask为什么这套设计选了轻量方案这套系统的架构选型是 B/S 模式也就是浏览器访问、服务器处理。正文里写得很直白不选 C/S 是因为客户端更新麻烦用户换个浏览器版本就可能不兼容而 B/S 只要服务端改完用户刷新页面就是新版。这个思路放在毕业设计或中小型项目中非常现实你不用操心用户装不装客户端只管部署好一个服务地址就能演示。Web 框架选的是 Flask而不是 Django。我在实际项目里也是这么选的原因有三条Flask 是微框架路由、模板、请求处理都很直观没有 Django 那一套 admin、ORM、中间件的默认绑定。情感分析系统的核心逻辑在模型训练和预测接口Web 层只承担表单提交、结果展示和登录会话Flask 的轻量刚好匹配。Flask 的app.route装饰器让接口定义非常集中一个人维护整个项目的成本低。再说 Python 技术栈。情感分析涉及 jieba 分词、Word2Vec、PyTorch 或 TensorFlow 模型推理这些库在 Python 生态里最成熟。Java 或 PHP 虽然也能做 Web 层但到深度学习部分会非常别扭。所以这套系统把 Python 作为前后端统一语言是省事且合理的选择。2.2 数据库设计user 表和 think 表到底存什么系统设计了两个核心表。用户表存管理员信息分析表存每次文本分析记录。按正文给出的字段我整理成下面的结构实际落地时可以按这个建表表名字段类型说明useridINT 自增主键usernameVARCHAR(50)用户名登录用usersexVARCHAR(50)性别userageINT年龄userbirthdayDATE生日userphoneVARCHAR(50)手机号注册时绑定useraddressVARCHAR(50)地址thinkidINT 自增主键thinknameVARCHAR(50)分析文本的名称或标题thinknewsTypeVARCHAR(50)文本来源类型比如 B 站评论thinkauthorVARCHAR(50)作者thinkmakeTimeDATETIME创建时间thinkmakerVARCHAR(50)创建人thinkmodiTimeDATETIME修改时间但我实际拆的时候发现一个问题think 表里没有存“文本内容”和“情感分析结果”。这不合理。分析系统最核心的输出就是“正面 / 负面 置信度”你总不能在每次分析后只存一个标题下次想看具体句子和结果却查不到。所以我在复现时建议这样扩展ALTER TABLE think ADD COLUMN text_content TEXT COMMENT 待分析的评论文本, ADD COLUMN is_positive INT COMMENT 1为正面0为负面, ADD COLUMN confidence FLOAT COMMENT 模型预测置信度;这样用户每次在文本框输入一句话后台把预测结果连同原文一起插入 think 表后台首页的柱状图、饼图就能直接按is_positive字段做统计。登录注册用 user 表分析记录用 think 表两者通过maker字段关联到user.name逻辑是通的。2.3 先把骨架搭起来目录结构、虚拟环境与依赖清单我按这套设计复现时项目目录是这么组织的sentiment_system/ ├── app.py # Flask 主入口注册路由 ├── models/ │ ├── __init__.py │ ├── db.py # MySQL 连接池工具 │ ├── user.py # 用户表操作 │ └── think.py # 分析记录表操作 ├── nlp/ │ ├── preprocess.py # 分词、去停用词 │ ├── word2vec_train.py # 词向量训练脚本 │ ├── train_model.py # 深度学习模型训练 │ └── predict.py # 模型加载与预测封装 ├── spider/ │ └── bilibili_spider.py # B 站评论爬虫 ├── static/ │ ├── css/ │ └── js/ ├── templates/ │ ├── login.html │ ├── register.html │ ├── index.html # 后台首页 │ └── analysis.html # 文本分析页 └── requirements.txt环境搭建部分我建议直接用虚拟环境别把依赖装到系统 Python 里python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install flask flask-cors pymysql requests jieba gensim torch这里有几个安装要点gensim用来训练 Word2Vec如果只需要加载别人预训练好的词向量可以不装但要自己训练就一定要。torch是深度学习框架CPU 版就行情感分类模型参数量不大没必要上 GPU。pymysql是 Python 连接 MySQL 的驱动记得在 MySQL 里先建好库比如CREATE DATABASE sentiment DEFAULT CHARSET utf8mb4;否则连接会报数据库不存在。这个阶段你可能遇到的最常见问题是pip install慢或超时。国内环境建议加上清华镜像源而不是一直等默认源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask flask-cors pymysql requests jieba gensim torch到这里框架已经能跑起来接下来就是把数据喂进来。3. 从 B 站评论到训练语料爬虫采集、清洗与向量化的完整链路3.1 评论爬取只拿公开数据频率和字段都要克制这套系统的数据来源设计是爬取 B 站评论区文本。正文里说得很直白——数据完完全全是真实的这一点对训练情感分析模型特别重要。没有真实语料后面所有东西都是空中楼阁。我用 requests 直接请求 B 站评论接口的方式复现了一次。B 站提供了一个公开的评论分页接口格式大致如下import requests import json def fetch_comments(bvid, pages3, interval1.5): 抓取B站视频评论区公开文本 bvid: 视频ID比如 BV1xx411c7XX pages: 抓取页数每页约20条评论 interval: 请求间隔建议不小于1秒避免触发风控 base_url https://api.bilibili.com/x/v1/reply headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) } comments [] for page in range(1, pages 1): params { type: 1, oid: bvid, pn: page, ps: 20, sort: 2 # 按热度排序 } try: resp requests.get(base_url, paramsparams, headersheaders, timeout10) data resp.json() if data[code] ! 0: print(f第{page}页抓取失败: {data[message]}) continue replies data[data][replies] or [] for reply in replies: # 只取评论文本和点赞数不碰用户主页等隐私信息 comments.append({ text: reply[content][message], like: reply[like] }) print(f第{page}页抓到{len(replies)}条) except Exception as e: print(f第{page}页异常: {e}) time.sleep(interval) # 保持礼貌的抓取频率 return comments if __name__ __main__: result fetch_comments(BV1xx411c7XX, pages2) with open(bili_comments.json, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) print(f共抓取 {len(result)} 条评论)这个脚本的逻辑不复杂通过 B 站公开评论接口逐页拉取每次请求之间用time.sleep(interval)控制频率解析 JSON 时只取content.message字段作为评论文本。我特意把请求间隔写在参数位置而不是写死就是因为如果你爬得太快B 站会直接返回code-412风控错误。需要注意这套设计里的爬虫定位是“采集公开评论内容用于 NLP 语料构建”不是商业爬虫项目。我自己在跑的时候只拿文本本身不做用户 ID、主页链接这类信息的持久化这样既满足需求也在边界上更稳。3.2 文本预处理分词、去停用词和“脏数据”清洗爬下来的评论不能直接丢给模型。中文文本不像英文那样天然用空格分词所以第一步是分词。这套设计里的预处理链路是清洗 → 分词 → 去停用词 → 生成词向量训练语料。import jieba import re # 加载停用词表网上有通用版本也可以自己按领域补充 with open(stopwords.txt, r, encodingutf-8) as f: stopwords set(line.strip() for line in f) def clean_text(text): 清洗原始评论文本 1. 去掉用户和超链接 2. 去掉多余空白和特殊表情符号 3. 保留中英文、数字和基础标点 text re.sub(r[\w\u4e00-\u9fa5], , text) # 去用户 text re.sub(rhttps?://\S, , text) # 去链接 text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) text re.sub(r\s, , text).strip() return text def tokenize(text): 分词并去掉停用词 words jieba.lcut(text) # 去掉空字符串、单个字和停用词 return [w for w in words if w.strip() and len(w) 1 and w not in stopwords] # 示例 raw 这个视频真的太棒了Up主加油 cleaned clean_text(raw) tokens tokenize(cleaned) print(tokens) # 输出类似: [视频, 真的, 棒, Up, 主, 加油]这里有两个细节值得展开说。第一len(w) 1这个条件我建议保留因为中文评论里大量单字词是语气词或噪声对情感分类没有帮助还会增大词表。第二停用词表里一定要加入“这个”“那个”“真的”“一个”这类高频无意义词否则词向量训练会被这些词主导。预处理的结果要落成两种文件一种是纯文本语料每行一条分词后的评论给 Word2Vec 训练用一种是标注好的训练集文本标签给分类模型用。如果你爬下来的评论没有情感标签常见做法是抽一部分人工标注或者直接找一个公开的中文情感分析数据集比如电商评论语料作为训练集爬虫数据留作真实场景验证。3.3 词向量训练把中文文本变成模型能读的数字序列深度学习模型不能直接吃字符串所以要把分词结果映射成向量。这套设计是用 Word2Vec 自己训练词向量而不是加载别人预训练好的模型。原因很实际通用预训练向量对网络用语覆盖不好而 B 站评论里的梗和缩写非常多自己训的向量更能贴合语料分布。from gensim.models import Word2Vec import logging logging.basicConfig(levellogging.INFO) def train_word2vec(corpus_pathtokenized_corpus.txt, save_pathw2v.model): 训练Word2Vec词向量 corpus_path: 分词后的语料每行一条评论词之间用空格隔开 save_path: 模型保存路径 sentences [] with open(corpus_path, r, encodingutf-8) as f: for line in f: words line.strip().split() if len(words) 3: # 过滤过短的句子 sentences.append(words) # 训练参数向量维度128上下文窗口5最少出现2次 model Word2Vec( sentencessentences, vector_size128, window5, min_count2, sg1, # skip-gram对低频词更友好 workers4, epochs10 ) model.save(save_path) print(f词表大小: {len(model.wv.key_to_index)}) return model if __name__ __main__: train_word2vec()参数选择这里我说下我的习惯vector_size128128 维对中小规模语料够用太小表达不了语义差异太大训练慢且容易过拟合。如果你的训练语料超过 10 万条可以试 200 或 256。window5上下文窗口 5 个词是 Word2Vec 的常用默认值兼顾前后语义。窗口太大可能把关系很远的词也当成上下文。min_count2出现次数少于 2 的词直接丢弃能显著缩小词表。B 站评论里大量只出现过一次的梗词留着只会让向量稀疏。sg1用 skip-gram 而不是 CBOW因为评论数据量不大skip-gram 在低频词上的表示更好。训练完之后每条评论就变成了一个向量序列——比如“这个视频真的太好了”分词后是 4-5 个词每个词对应一个 128 维向量这条评论就是一个[词数, 128]的矩阵。再配合一个固定最大长度做 padding 和截断就能送进深度学习模型了。4. 模型训练与 Flask 接口对接从权重文件到可调用的预测服务4.1 情感分类模型怎么选TextCNN 与 LSTM 的取舍摘要描述里提到这套系统使用 CNN 和 RNN 来做情感分析正文里也明确说了“循环神经网络的搭建以及情感模型的生成”。我复现时做了个对比把两个方向都跑了一遍。TextCNN 的优势在速度。它通过多个卷积核并行提取 n-gram 局部特征训练和推理都快CPU 上跑起来毫无压力。缺点是它更关注局部模式“虽然……但是……”这种转折结构它捕捉不好。LSTM 的优势在能记住序列信息。情感分析里“反转”很常见比如“电影不错但结局太烂了”LSTM 能保留前面的正面信息再叠加后面的负面判断。但它的代价是训练慢而且小数据集上容易过拟合。我的建议是如果训练语料在 1 万条以上用 LSTM/GRU 效果更稳如果只是想先把流程跑通、或者只有几千条标注数据TextCNN 更省事。这套毕业设计资源里没有限定你必须用哪个它给了双向选择的余地。我复现时用的模型结构是这个import torch import torch.nn as nn class SentimentLSTM(nn.Module): 基于LSTM的中文情感二分类模型 embedding: 词向量矩阵第一层用Word2Vec初始化 hidden_size: LSTM隐藏层维度 num_layers: LSTM层数 def __init__(self, vocab_size, embedding_dim, hidden_size128, num_layers2, num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embedding_dim) self.lstm nn.LSTM( embedding_dim, hidden_size, num_layersnum_layers, batch_firstTrue, dropout0.3 ) self.fc nn.Linear(hidden_size, num_classes) def forward(self, x): # x: [batch_size, seq_len] emb self.embedding(x) # [batch_size, seq_len, embedding_dim] out, (hidden, _) self.lstm(emb) # hidden: [num_layers, batch_size, hidden_size] last_hidden hidden[-1] # 取最后一层的隐藏状态 logits self.fc(last_hidden) # [batch_size, num_classes] return logits选两层 LSTM 而不是单层是因为单层在捕捉情感转折时常出现信息瓶颈但三层以上在小数据集上几乎必过拟合。dropout0.3是防止过拟合的关键设置训练时有效eval()模式下会自动关闭。4.2 训练与验证划分数据集、设好早停、保存最佳权重模型结构定了之后训练流程要写得规范。很多新手翻车的地方在于训练集和验证集划分不随机、没做早停、把最后一个 epoch 的权重当最优权重保存。下面是我复现时的核心训练脚本import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader import numpy as np class SentimentDataset(Dataset): 把分词后的文本转成id序列并配标签 def __init__(self, texts, labels, word2idx, max_len50): self.data [] for text, label in zip(texts, labels): ids [word2idx.get(w, 1) for w in text.split()] # 1是UNK # 固定长度超过截断不足补0 if len(ids) max_len: ids ids[:max_len] else: ids [0] * (max_len - len(ids)) self.data.append((ids, label)) def __len__(self): return len(self.data) def __getitem__(self, idx): ids, label self.data[idx] return torch.tensor(ids, dtypetorch.long), torch.tensor(label, dtypetorch.long) def train_model(model, train_loader, val_loader, epochs20, lr1e-3): optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.CrossEntropyLoss() best_val_acc 0.0 for epoch in range(epochs): model.train() total_loss 0 for batch_x, batch_y in train_loader: optimizer.zero_grad() outputs model(batch_x) loss criterion(outputs, batch_y) loss.backward() optimizer.step() total_loss loss.item() # 每个epoch结束做验证 model.eval() correct, total 0, 0 with torch.no_grad(): for batch_x, batch_y in val_loader: outputs model(batch_x) pred outputs.argmax(dim1) correct (pred batch_y).sum().item() total batch_y.size(0) val_acc correct / total print(fEpoch {epoch1}/{epochs}, Loss{total_loss:.4f}, ValAcc{val_acc:.4f}) # 只保存验证集上最好的权重 if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pth) print(f保存最佳模型ValAcc{val_acc:.4f}) print(f训练完成最佳验证准确率: {best_val_acc:.4f})几个关键参数我要单独说明max_len50B 站评论绝大多数不超过 50 个词太长会拖慢训练太短会截掉关键信息。你可以统计自己语料的长度分布再调不用照着我的数值抄。lr1e-3Adam 优化器配合 1e-3 学习率是通用起点。如果 loss 震荡不下降就降到 5e-4。早停逻辑这里用验证集准确率作为基准只在新记录时覆盖保存best_model.pth。这是最容易被忽略的地方——很多人不早停最终保存的是最后一个 epoch 的权重大概率不是最优解。训练完成后best_model.pth就是核心产物。它是 PyTorch 的权重文件不包含模型结构所以预测时还需要把SentimentLSTM这个类定义放到 prediction 脚本里。4.3 Flask 接口模型加载一次路由只做预测不做训练模型训练是离线流程Web 服务是线上流程。这两者必须分开。我见过不少项目把训练逻辑写在 Flask 路由里每次用户请求都跑一轮训练这是彻底的错误做法。正确做法是服务启动时加载模型和词向量到内存路由只做文本预处理、向量化、推理和返回结果。from flask import Flask, request, jsonify, render_template, session import jieba import torch import numpy as np from gensim.models import Word2Vec app Flask(__name__) app.secret_key your-secret-key # 全局加载一次避免每次请求都读磁盘 w2v_model Word2Vec.load(w2v.model) model SentimentLSTM(...) # 实例化模型结构 model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() # 构建词表映射词 - id0保留给padding1保留给UNK word2idx {PAD: 0, UNK: 1} for word in w2v_model.wv.index_to_key: word2idx[word] len(word2idx) MAX_LEN 50 def text_to_ids(text): 与训练时的预处理保持一致清洗、分词、截断、padding words jieba.lcut(text) ids [word2idx.get(w, 1) for w in words[:MAX_LEN]] ids [0] * (MAX_LEN - len(ids)) return torch.tensor([ids], dtypetorch.long) app.route(/predict, methods[POST]) def predict(): 接收JSON: {text: 这个视频太棒了}返回情感判断和置信度 data request.get_json() text data.get(text, ).strip() if not text: return jsonify({error: 文本不能为空}), 400 with torch.no_grad(): input_ids text_to_ids(text) outputs model(input_ids) probs torch.softmax(outputs, dim1).squeeze() # 假设0负面1正面 label int(torch.argmax(probs).item()) confidence float(probs[label].item()) result 正面 if label 1 else 负面 return jsonify({ text: text, sentiment: result, confidence: round(confidence, 4) }) app.route(/) def index(): return render_template(analysis.html) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)这个接口的逻辑链是接收 JSON 请求 → 文本清洗和分词 → 查 word2idx 转成 id 序列 → 模型推理 → softmax 转概率 → 返回标签和置信度。注意model.eval()必须在启动时调用一次它会把 dropout 和 batch normalization 切到推理模式否则同一句话每次预测结果都不一样。测试接口可以用一行 curlcurl -X POST http://127.0.0.1:5000/predict \ -H Content-Type: application/json \ -d {text: 画面质量很差看了十分钟就关了}预期返回类似{ text: 画面质量很差看了十分钟就关了, sentiment: 负面, confidence: 0.9631 }到这里从爬虫到模型再到 Web 接口的链路已经完整。但我在复现过程中踩了不少坑下面这几个是我觉得最值得记下来的。5. 部署与避坑调试时最值得记下的三条实战教训5.1 中文编码问题Windows 下读文件就乱码现象在 Windows 上用open(comments.json, r, encodingutf-8)读爬虫数据打印出来全是乱码训练出的模型也完全不可用。原因Windows 控制台默认编码是 GBKPython 的print输出 UTF-8 字符时会被错误解释更隐蔽的是如果你创建文件时没指定encodingutf-8Windows 会用 GBK 写入导致后续程序读不出来。这个坑在中文 NLP 项目里出现频率极高。解决所有读写文件的代码统一显式声明编码包括爬虫保存、预处理读取、训练脚本读取三处# 读 with open(comments.json, r, encodingutf-8) as f: data json.load(f) # 写 with open(result.txt, w, encodingutf-8) as f: f.write(结果)另外命令行动态指定 Python 环境的 UTF-8 模式可以一劳永逸set PYTHONIOENCODINGutf-8从那以后我但凡是中文 NLP 项目第一件事就是把所有文件 IO 的 encoding 写死不再依赖系统默认值。5.2 预测时遇到没见过的词词向量 OOV 导致的维度报错现象模型训练时准确率还不错部署成 Flask 接口后用户输入某些句子直接报错提示list index out of range或者“无法从词表找到该词”。原因训练语料里没出现过的词在word2idx.get(w, 1)这一步应该落到 UNK。很多人写这行代码时用的是word2idx[w]遇到不存在的 key 直接抛 KeyError还有人用了get但没定义 UNK 的索引导致返回 None 传到 Embedding 层直接爆炸。解决构建词表时显式保留两个预留位PAD 永远是 0UNK 永远是 1所有查不到的词都映射到 1。这需要在训练脚本和预测脚本里用同一套词表构建逻辑不能各自单独建。word2idx {PAD: 0, UNK: 1} for word in w2v_model.wv.index_to_key: word2idx[word] len(word2idx) # 此时UNK及OOV词统一映射到1 ids [word2idx.get(w, 1) for w in words]5.3 Flask 每次预测都卡一下模型被反复加载现象接口偶尔快偶尔慢慢的时候要等好几秒看日志发现每次请求都打印了一次“加载模型完成”。原因把Word2Vec.load()和torch.load()写在predict()路由函数内部了。每次请求都重新读磁盘、重新构建词表深度学习模型文件动辄几百 MB磁盘 IO 成为瓶颈。解决模型加载只放在模块顶层或app Flask(__name__)之前路由内只做推理不碰磁盘。从代码规范上说这也对应“训练与预测分离”的原则。我建议在 Flask 启动后先自测一次with app.test_client() as c: resp c.post(/predict, json{text: 测试一下}) print(resp.get_json())这样能在真正接入用户前就把模型加载好避免第一个用户等待过久。我在后来的项目里一直保留这个启动自测的习惯每次改完模型或词表都强制走一遍这个流程能提前暴露掉一大半的部署问题。希望这些踩坑记录能帮你在复现这套系统时少走几步弯路。本文还有配套的精品资源点击获取
返回列表