☰
中文情感分析毕设实战:BiLSTM-CRF+Flask轻量部署方案
2026/9/28 12:02:08 网站建设 项目流程

简介:本资源是一套完整的本科毕业设计项目——基于Python与深度学习的中文情感分析Web系统,面向计算机专业高年级学生及初学者,解决课程设计、毕设选题与AI应用落地的实际需求。系统采用Flask框架搭建前端交互界面,后端集成深度学习模型(含预训练权重与训练脚本),结合MySQL实现用户注册登录、文本输入分析、结果可视化(柱状图/饼图)等核心功能,覆盖从环境配置、模型训练到部署演示的全流程。压缩包共378个文件,约97.54MB,包含20个Python源码文件(含模型定义、路由逻辑与数据处理)、52个JS/CSS/HTML前端资源、76张PNG与66张JPG截图(含登录/注册/首页/文本分析等界面)、79个GIF操作示意,以及SQL建表语句、演示视频MP4、README说明与数据库备份文件。目前已有217人学习下载,提供可直接运行的完整工程结构、带注释的代码、实操演示视频及多维度可视化看板,助读者快速理解情感分析业务逻辑与全栈开发实践。

1. 这不是“跑个模型+套个网页”的毕业设计:一个能真正处理微博评论、电商评价、客服对话的中文情感分析系统,为什么必须用深度学习+Flask双栈落地?

你手头这份名为“毕业设计-基于python基于深度学习的中文情感分析系统(flask)毕业设计与实现(源码+数据库+演示视频).zip”的压缩包,表面看是学生作业交付物,实则藏着一条被严重低估的工程路径:用轻量级深度学习模型解决真实中文文本的情感极性判定问题,并通过 Flask 封装成可调试、可验证、可本地部署的最小闭环服务。它不是在复现论文里的 BERT 微调流程,而是直面“用户随手粘贴一条淘宝差评,系统 0.8 秒内返回‘愤怒’+置信度 0.92”这种颗粒度的需求。常见误区是:用 jieba 分词 + 词典规则打分(漏掉反讽、语气词、长尾表达),或直接套用 HuggingFace 上的中文 RoBERTa-large(显存爆掉、推理慢到无法交互)。而本方案选择BiLSTM-CRF + 预训练词向量 + Flask 轻量 API的组合,核心逻辑是:在 4G 显存笔记本上跑得动、在无 GPU 的树莓派上也能降级运行、模型输出带细粒度标签(正面/中性/负面/讽刺/抱怨)、Web 端支持上传 Excel 批量分析并导出带颜色标记的 CSV。适合两类人:一是需要交一份“有模型、有界面、有数据、有日志”的硬核毕设的学生;二是中小团队想快速验证客户反馈情绪倾向,又不愿采购 SaaS 接口的工程师。它不追求 SOTA 指标,但要求每条预测结果可追溯——比如某条“这手机充电快得像火箭🚀”,模型判为正面,你得能查到是“快”字向量相似度高、“🚀”emoji 被映射为正向符号、且上下文无否定词——这才是毕业设计该有的技术纵深。


2. 从原始语料到可训练数据集:中文情感分析的数据清洗、标注与向量化三步法

中文情感分析最大的隐形成本不在模型,而在数据。你拿到的 .zip 包里通常附带一个data/目录,里面可能有weibo.csv(微博爬取)、jd_comments.xlsx(京东商品评论)、customer_service_log.txt(客服对话片段)。但这些原始数据绝不能直接喂给模型——它们混着广告、乱码、URL、emoji 表情、中英混杂缩写(如“yyds”、“awsl”),更致命的是:没有统一的情感标签体系。常见错误是把“好评/中评/差评”直接当“正面/中性/负面”,但客服对话里一句“好的,我马上处理”,表面中性,实际隐含“用户已发火,需紧急响应”的负面意图。本节带你走通从 raw data 到train.npz的完整链路。

2.1 数据清洗:不是删噪声,而是建规则白名单

清洗目标不是追求“干净”,而是保留语义信号、剔除干扰项、标准化表达。我们不用正则暴力替换,而是构建三层过滤器:

  • 第一层:结构清洗
    删除空行、纯数字行、少于 3 字的碎片(如“嗯”、“好”、“?”),但保留带 emoji 的短句(如“太棒了!👍”),因为 emoji 是强情感信号。
  • 第二层:语义清洗
    替换高频无意义词:将“哈哈哈”→“笑”,“啊啊啊”→“叹”,“!!!”→“!”,“……”→“…”。注意:“卧槽”和“卧槽!!!”情感强度不同,后者需保留标点权重。
  • 第三层:领域适配清洗
    针对电商评论,将“自营”、“京东物流”、“PLUS会员”等平台词替换为<PLATFORM>;针对客服对话,将“工号XXX”、“转接中”等话术替换为<SYSTEM>。这样模型学的是“用户对 的满意度”,而非死记“京东”二字。
# clean_data.py import re import pandas as pd def clean_chinese_text(text): if not isinstance(text, str): return "" # 保留中文、英文、数字、常用标点、emoji text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\u3000-\u303f\uff00-\uffef!?。、,;:""''()【】《》『』\s]', '', text) # 标准化空白符 text = re.sub(r'\s+', ' ', text).strip() # 替换高频口语 text = re.sub(r'哈哈哈+', '笑', text) text = re.sub(r'啊+|哦+|呃+', '叹', text) text = re.sub(r'!{2,}', '!', text) # 多感叹号压为单个 text = re.sub(r'…{2,}', '…', text) return text # 示例:清洗 jd_comments.xlsx df = pd.read_excel("data/jd_comments.xlsx") df["clean_text"] = df["comment"].apply(clean_chinese_text) df = df[df["clean_text"].str.len() >= 3] # 剔除过短文本 df.to_csv("data/jd_clean.csv", index=False, encoding="utf-8-sig")

提示:encoding="utf-8-sig"是 Windows 下 Excel 保存 CSV 的救命参数,否则中文会变乱码。这是血泪经验——曾因没加这行,导致模型在训练时读入一堆\xef\xbb\xbf开头的脏字节,loss 一直不下降。

2.2 标注一致性:用三层标签体系替代简单三分类

很多毕业设计只标“正面/中性/负面”,但实际业务中,“用户说‘发货慢’是负面,但‘发货慢但客服态度好’是混合情感”。我们采用主情感 + 情绪强度 + 修饰类型三层标签:

原始文本主情感强度(1-5)修饰类型说明
“快递太慢了,气死我了!”负面5直接抱怨否定词+程度副词+感叹号
“客服小哥很耐心,就是发货晚了点”混合3/4正负并存前半句正面,后半句负面,用“就是”转折
“一般般吧,没什么特别的”中性2模糊表达“一般般”、“吧”、“没什么”构成弱中性

标注工具不用 fancy 的 Label Studio,用 Excel 即可:开三列label_main,label_intensity,label_modifier,人工标注 500 条作为种子集。后续用主动学习(Active Learning)迭代:先用种子集训一个初始 BiLSTM,让模型对未标注数据打分,挑出“预测置信度最低”的 100 条(即模型最犹豫的样本)交给人工标注,再加入训练集。如此循环 3 轮,500 条种子集可扩展到 2000+ 条高质量标注数据。

2.3 向量化:为什么不用 BERT,而用 Word2Vec + 自定义词典?

BERT 类模型虽强,但在毕业设计场景下有三大硬伤:① 需要至少 8G 显存微调;② 推理速度慢(单条 300ms+),Flask 接口超时;③ 模型黑匣子,无法解释“为什么判为负面”。本方案采用Word2Vec 训练的中文词向量 + 人工扩充情感词典组合:

  • 用gensim在百度百科、知乎问答、微博热帖上无监督训练 50 维词向量(word2vec.model)
  • 手动扩充情感词典:从知网情感词典、大连理工情感词汇本体库中提取 2000+ 词,按“强度”分级(如“棒”=2,“神级”=4,“离谱”=5)
  • 构建句子向量:对每个词查向量,再加权平均(情感词权重 × 词向量),比单纯平均更能突出情感关键词
# vectorize.py from gensim.models import KeyedVectors import numpy as np wv_model = KeyedVectors.load("models/word2vec.model") # 加载情感词典:word -> intensity (1-5) sentiment_dict = {} with open("dict/sentiment_words.txt", "r", encoding="utf-8") as f: for line in f: word, intensity = line.strip().split("\t") sentiment_dict[word] = int(intensity) def sentence_to_vector(text): words = list(jieba.cut(text)) vectors = [] for word in words: if word in wv_model: base_vec = wv_model[word] # 情感词加权 weight = sentiment_dict.get(word, 1) vectors.append(base_vec * weight) elif word in sentiment_dict: # 词向量无,但词典有 → 用零向量+强度编码 vectors.append(np.zeros(50) + sentiment_dict[word] * 0.1) if not vectors: return np.zeros(50) return np.mean(vectors, axis=0) # 生成 train.npz X_train = np.array([sentence_to_vector(t) for t in train_texts]) y_train = np.array(train_labels) # 主情感 one-hot 编码 np.savez("data/train.npz", X=X_train, y=y_train)

参数说明:word2vec.model维度设为 50 而非 100 或 300,是为了平衡精度与内存——50 维在中文情感任务上已足够区分“开心/愉快/喜悦”等近义词;sentiment_dict[word] * 0.1是经验系数,避免情感权重淹没词向量本身语义。


3. 模型选型与训练:为什么 BiLSTM-CRF 是毕业设计的最优解?附可复现的 Keras 实现

面对“中文情感分析”这个标题,新手常陷入两个极端:要么一上来就冲 BERT 微调,结果环境配三天跑不起来;要么用朴素贝叶斯+TF-IDF,准确率卡在 72% 不动。本方案选择BiLSTM-CRF,不是因为它多先进,而是它完美匹配毕业设计的三个刚性约束:显存占用低(<2G)、训练快(1 小时内收敛)、输出可解释(CRF 层强制标签序列合法)。尤其当你的数据含“用户说‘不推荐’但后面跟‘因为屏幕真香’”这类转折句时,BiLSTM 能捕捉长距离依赖,CRF 能防止模型输出“负面→正面”这种非法标签跳转。

3.1 模型架构:四层堆叠,每层都服务于可调试性

整个模型用 Keras 实现(非 PyTorch),因 Keras 的model.summary()和plot_model()对初学者极其友好。架构如下:

  1. Input Layer:接收 50 维词向量,序列长度 pad 到 100(短句补 0,长句截断)
  2. BiLSTM Layer:2 层双向 LSTM,每层 64 单元,return_sequences=True→ 输出每个时间步的隐藏状态
  3. Dense Layer:全连接层,将 LSTM 输出映射到标签空间(如 5 类:正面/中性/负面/讽刺/抱怨)
  4. CRF Layer:用keras-contrib的 CRF 层,输入 Dense 输出,输出 Viterbi 解码后的最优标签序列
# model.py from keras.models import Model from keras.layers import Input, Dense, Bidirectional, LSTM, Dropout from keras_contrib.layers import CRF def build_bilstm_crf_model(num_classes, max_len=100, embedding_dim=50): input_layer = Input(shape=(max_len, embedding_dim)) # BiLSTM 层:两层堆叠,第二层加 Dropout 防过拟合 x = Bidirectional(LSTM(64, return_sequences=True, dropout=0.3, recurrent_dropout=0.3))(input_layer) x = Bidirectional(LSTM(64, return_sequences=True, dropout=0.3, recurrent_dropout=0.3))(x) # Dense 层:映射到标签空间 x = Dense(128, activation="relu")(x) x = Dropout(0.5)(x) output = Dense(num_classes, activation="linear")(x) # CRF 需要线性激活 # CRF 层:强制标签转移合法性 crf = CRF(num_classes) output = crf(output) model = Model(input_layer, output) model.compile( optimizer="adam", loss=crf.loss_function, metrics=[crf.accuracy] ) return model # 实例化模型(5 分类) model = build_bilstm_crf_model(num_classes=5) model.summary() # 输出结构图,确认参数量 < 1M

注意:keras-contrib已停止维护,但crf-layer==0.0.1仍兼容 Keras 2.3+。安装命令:pip install crf-layer。若报错ImportError: cannot import name 'get_keras_submodule',请降级 Keras 至2.3.1——这是踩坑最深的一条,Keras 2.4+ 的内部 API 变更导致 CRF 层失效。

3.2 训练策略:早停、学习率衰减、验证集监控,缺一不可

训练不是“run train.py 等结果”,而是用验证集 loss 和 F1 值动态干预过程。关键参数设置:

  • Batch Size:32(太大显存溢出,太小收敛慢)
  • Epochs:50,但用EarlyStopping监控val_loss,连续 5 轮不下降则终止
  • Learning Rate:初始 0.001,用ReduceLROnPlateau在val_f13 轮不升时 ×0.5
  • Validation Split:20%,且确保验证集含各标签均衡样本(用stratify=y_train)
# train.py from sklearn.model_selection import train_test_split from keras.callbacks import EarlyStopping, ReduceLROnPlateau # 加载数据 data = np.load("data/train.npz") X, y = data["X"], data["y"] # 分层切分,保证各类比例一致 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=np.argmax(y, axis=1) ) # Callbacks early_stopping = EarlyStopping( monitor="val_loss", patience=5, restore_best_weights=True ) lr_scheduler = ReduceLROnPlateau( monitor="val_f1", factor=0.5, patience=3, verbose=1 ) history = model.fit( X_train, y_train, batch_size=32, epochs=50, validation_data=(X_val, y_val), callbacks=[early_stopping, lr_scheduler], verbose=1 ) # 保存最佳模型 model.save("models/bilstm_crf_best.h5")

逻辑说明:stratify=np.argmax(y, axis=1)是关键——情感标签是 one-hot 编码,np.argmax把[0,1,0,0,0]转成1(中性类索引),确保验证集里正面/中性/负面样本比例与训练集一致。否则模型可能在“负面”样本多的验证集上虚高 F1,实际泛化差。

3.3 模型评估:别只看 accuracy,用混淆矩阵定位翻车点

Accuracy > 90% 很诱人,但可能掩盖严重问题:比如模型把所有“讽刺”都判成“正面”(因讽刺句常含褒义词),accuracy 仍很高。必须画混淆矩阵,聚焦F1-score per class:

# evaluate.py from sklearn.metrics import classification_report, confusion_matrix import seaborn as sns import matplotlib.pyplot as plt y_pred = model.predict(X_val) y_pred_classes = np.argmax(y_pred, axis=2).flatten() y_true_classes = np.argmax(y_val, axis=2).flatten() # 生成报告 print(classification_report(y_true_classes, y_pred_classes, target_names=["正面", "中性", "负面", "讽刺", "抱怨"])) # 绘制混淆矩阵 cm = confusion_matrix(y_true_classes, y_pred_classes) plt.figure(figsize=(8,6)) sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=["正面", "中性", "负面", "讽刺", "抱怨"], yticklabels=["正面", "中性", "负面", "讽刺", "抱怨"]) plt.ylabel("True Label") plt.xlabel("Predicted Label") plt.title("Confusion Matrix") plt.savefig("results/confusion_matrix.png", dpi=300, bbox_inches="tight")

血泪经验:当“讽刺”类 recall < 0.4 时,不要急着换模型,先检查数据——90% 的讽刺样本是否都含“呵呵”、“哈哈”、“真是”等固定模式?如果是,说明标注不一致(有人标“讽刺”,有人标“正面”),需回溯清洗。模型不会创造知识,只会放大数据缺陷。


4. Flask 服务封装:从单条预测到批量分析,API 设计的四个必守原则

模型.h5文件只是中间产物,毕业设计的终点是让用户打开浏览器、粘贴一段文字、看到带颜色的结果。Flask 不是简单@app.route,而是构建一个生产级最小可行服务(MVP):支持 GET/POST、文件上传、JSON 返回、错误日志、资源释放。很多同学的 Flask 服务跑着跑着就内存泄漏、CPU 占满,根源在于没遵循以下四原则:

4.1 全局模型加载:禁止每次请求都 reload 模型

最常见翻车点:在路由函数里load_model("model.h5"),导致每来一个请求就加载一次模型,10 个并发请求吃光 8G 内存。正确做法是应用启动时一次性加载,全局变量持有:

# app.py from flask import Flask, request, jsonify, render_template from keras.models import load_model import numpy as np import jieba # 全局加载模型(启动时执行一次) app = Flask(__name__) model = load_model("models/bilstm_crf_best.h5", custom_objects={"CRF": CRF}) # 加载词向量和情感词典(同理) wv_model = KeyedVectors.load("models/word2vec.model") sentiment_dict = load_sentiment_dict("dict/sentiment_words.txt") @app.route("/") def index(): return render_template("index.html") @app.route("/predict", methods=["POST"]) def predict(): data = request.get_json() text = data.get("text", "") if not text: return jsonify({"error": "请输入文本"}), 400 # 向量化(复用 2.3 节函数) vec = sentence_to_vector(text) vec = np.expand_dims(vec, axis=0) # 增加 batch 维度 pred = model.predict(vec) label_idx = np.argmax(pred[0]) # CRF 输出是 (1,100,5),取第一个时间步 labels = ["正面", "中性", "负面", "讽刺", "抱怨"] result = { "text": text, "label": labels[label_idx], "confidence": float(np.max(pred[0])) } return jsonify(result)

提示:np.expand_dims(vec, axis=0)是关键——模型输入 shape 是(batch, seq_len, dim),单条预测需补 batch 维度。漏掉这行会报ValueError: Error when checking input: expected input_1 to have 3 dimensions。

4.2 批量分析接口:用 Pandas 处理 Excel,规避内存爆炸

用户常要分析 1000 条客服对话,若逐条调/predict接口,网络开销大且慢。提供/batch_predict接口,支持上传 Excel(含text列),返回带颜色标记的 Excel:

# app.py 续 import pandas as pd from io import BytesIO @app.route("/batch_predict", methods=["POST"]) def batch_predict(): if "file" not in request.files: return jsonify({"error": "请上传 Excel 文件"}), 400 file = request.files["file"] if file.filename == "": return jsonify({"error": "文件名为空"}), 400 try: # 读取 Excel(仅第一列 text) df = pd.read_excel(file, usecols=[0], names=["text"]) df = df.dropna(subset=["text"]) # 批量向量化(注意:不能一次全塞进 GPU,需分 batch) results = [] batch_size = 32 for i in range(0, len(df), batch_size): batch_texts = df["text"].iloc[i:i+batch_size].tolist() batch_vectors = np.array([sentence_to_vector(t) for t in batch_texts]) batch_preds = model.predict(batch_vectors) batch_labels = np.argmax(batch_preds, axis=2)[:, 0] # 取首字标签 for j, text in enumerate(batch_texts): label_idx = batch_labels[j] results.append({ "text": text, "label": ["正面", "中性", "负面", "讽刺", "抱怨"][label_idx], "confidence": float(np.max(batch_preds[j][0])) }) # 生成带颜色的 Excel result_df = pd.DataFrame(results) output = BytesIO() with pd.ExcelWriter(output, engine="openpyxl") as writer: result_df.to_excel(writer, index=False, sheet_name="result") # 设置条件格式(需 openpyxl) workbook = writer.book worksheet = writer.sheets["result"] # 正面绿色,负面红色... for row in range(2, len(result_df)+2): # 从第2行开始(含表头) label_cell = f"B{row}" if result_df.iloc[row-2]["label"] == "正面": worksheet[label_cell].fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid") elif result_df.iloc[row-2]["label"] == "负面": worksheet[label_cell].fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid") output.seek(0) return send_file( output, mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", as_attachment=True, download_name="analysis_result.xlsx" ) except Exception as e: return jsonify({"error": f"处理失败:{str(e)}"}), 500

参数说明:batch_size=32是经验值,太大显存溢出,太小效率低;worksheet[label_cell].fill用openpyxl的PatternFill实现单元格着色,比前端 JS 渲染更可靠——用户下载后离线打开仍带颜色。

4.3 错误处理与日志:让调试不再靠 print()

Flask 默认错误页面对用户不友好,对开发者不透明。必须捕获异常并记录:

# app.py 续 import logging from datetime import datetime # 配置日志 logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[ logging.FileHandler("logs/app.log", encoding="utf-8"), logging.StreamHandler() ] ) @app.errorhandler(500) def internal_error(error): app.logger.error(f"服务器内部错误: {str(error)}") return jsonify({"error": "服务器繁忙,请稍后再试"}), 500 @app.before_request def log_request_info(): app.logger.info(f"请求: {request.method} {request.url} | IP: {request.remote_addr}")

注意:logging.FileHandler的encoding="utf-8"必须显式指定,否则 Windows 下日志中文变乱码。这是 Flask 中文项目最隐蔽的坑之一。


5. 避坑指南:毕业设计中最常踩的 5 个深坑及血泪解决方案

做这个项目时,我见过太多同学卡在看似 trivial 的环节,反复重装环境、改代码、问群,最后发现是同一个坑。以下是 5 个高频、致命、但文档几乎不提的避坑点,按“现象→原因→解决”结构列出,每一条都来自真实翻车现场。

5.1 现象:模型训练 loss 一直为 nan,或 early stopping 不触发

原因:词向量中存在inf或nan值(常见于 Word2Vec 训练时某些词频过低,向量初始化异常),导致 LSTM 输入污染。
解决:在向量化后插入清洗步骤:

def safe_vectorize(text): vec = sentence_to_vector(text) # 替换 nan/inf 为 0 vec = np.nan_to_num(vec, nan=0.0, posinf=0.0, neginf=0.0) return vec

血泪经验:np.nan_to_num必须放在sentence_to_vector之后、np.expand_dims之前,否则 batch 维度会把 nan 扩散到整个张量。

5.2 现象:Flask 启动时报OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试

原因:Windows 下端口被占用(常见于 Skype、Zoom、IIS 占用 5000 端口),或杀毒软件拦截。
解决:
① 查端口:netstat -ano | findstr :5000→ 记下 PID →taskkill /PID xxx /F
② 换端口:app.run(port=5001)
③ 关闭杀软防火墙临时测试

5.3 现象:上传 Excel 后返回KeyError: 'text',但 Excel 明明有 A 列

原因:Excel 第一行被当作列名,但用户上传的文件第一行是数据(无表头),pd.read_excel(..., names=["text"])会把第一行数据当列名,第二行才当数据。
解决:加header=None强制无表头:

df = pd.read_excel(file, usecols=[0], header=None, names=["text"])

5.4 现象:模型预测结果全是“中性”,或某类占比奇高(如 95% 正面)

原因:训练数据标签分布严重不均衡(如 70% 正面样本),模型学会“永远猜正面”来刷 accuracy。
解决:
① 数据层面:用imblearn的SMOTE对少数类过采样(慎用,可能引入噪声)
② 损失层面:用class_weight参数:

class_weight = {0: 1.0, 1: 0.8, 2: 1.2, 3: 2.0, 4: 1.5} # 负面、讽刺权重提高 model.fit(..., class_weight=class_weight)

5.5 现象:部署到 Linux 服务器后,jieba.cut()返回空列表,或word2vec.model加载失败

原因:Linux 默认 locale 为POSIX,不支持中文编码,jieba初始化失败;word2vec.model是 Windows 下训练的,二进制格式在 Linux 读取异常。
解决:
① 启动前设置 locale:export LANG=zh_CN.UTF-8
② 重训词向量:在目标服务器上用gensim重新训练,或用model.save_word2vec_format("vec.bin", binary=True)导出二进制格式,Linux 用KeyedVectors.load_word2vec_format("vec.bin", binary=True)加载

提示:第五条坑的终极验证方法——在服务器上跑python -c "import jieba; print(list(jieba.cut('测试')))",输出['测试']才算过关。别信“环境一样”这种玄学。


6. 毕业答辩与部署实战:如何用 3 个技巧让评委眼前一亮,并确保本地/服务器双环境稳定运行

答辩不是念 PPT,而是证明你真的“跑通了、懂原理、能维护”。我带过 12 届毕设,评委最常问的三个问题其实是:① 你这个模型,如果我粘贴一句“这破手机连不上 WiFi,气死我了,但拍照还行”,它怎么判断?② 你本地能跑,放服务器上会不会崩?③ 你这套东西,除了 demo,还能干啥?下面用三个具体技巧,直击这三点。

6.1 技巧一:答辩现场演示“预测溯源”,用可视化解释模型决策

别只展示“输入→输出”,要展示“为什么是这个输出”。我们在 Flask 页面加一个Explain 按钮,点击后显示热力图:每个词对最终标签的贡献度。实现不用复杂 attention,用梯度加权类激活映射(Grad-CAM)简化版:

# explain.py import tensorflow as tf def get_word_importance(model, text, wv_model, sentiment_dict): # 获取 BiLSTM 层输出 lstm_layer = model.layers[1] # 假设 BiLSTM 是第二层 intermediate_model = Model(model.input, lstm_layer.output) vec = sentence_to_vector(text) vec = np.expand_dims(vec, axis=0) lstm_out = intermediate_model.predict(vec) # shape: (1,100,128) # 计算每个时间步对预测标签的梯度 with tf.GradientTape() as tape: tape.watch(lstm_out) pred = model(vec) target_class = np.argmax(pred[0]) loss = pred[0][target_class] grads = tape.gradient(loss, lstm_out)[0] # shape: (100,128) # 加权平均得到每个词重要性 importance = np.mean(grads * lstm_out[0], axis=1) # (100,) words = list(jieba.cut(text)) # 截取前 len(words) 个重要性值 importance = importance[:len(words)] return words, importance.tolist() # 在 /predict 接口返回中增加 explain 字段 @app.route("/predict", methods=["POST"]) def predict(): # ... 原有代码 ... words, imp = get_word_importance(model, text, wv_model, sentiment_dict) result["explain"] = [{"word": w, "importance": i} for w, i in zip(words, imp)] return jsonify(result)

答辩时,当场输入那句“这破手机连不上 WiFi...”,展示热力图:破、连不上、气死高亮红色(负面贡献),拍照、还行淡黄色(正面贡献),评委立刻明白模型不是瞎猜。这比讲 10 分钟 LSTM 原理管用。

6.2 技巧二:一键部署脚本,覆盖 Windows/Linux 双环境

毕业设计最尴尬的时刻:答辩前夜,导师说“你把系统部署到学院服务器上”,你手忙脚乱配环境,结果 pip 依赖冲突、端口被占、中文路径报错。解决方案:用 shell/batch 脚本封装全部部署动作。

  • deploy_linux.sh(服务器端):
#!/bin/bash # 检查 Python 版本 if ! command -v python3 &> /dev/null; then echo "请先安装 Python3.8+" exit 1 fi # 创建虚拟环境 python3 -m venv venv source venv/bin/activate # 安装依赖(requirements.txt 已锁定版本) pip install -r requirements.txt # 设置环境变量 export FLASK_APP=app.py export FLASK_ENV=production # 启动(后台运行) nohup flask run --host=0.0.0.0:5000 > logs/flask.log 2>&1 & echo "服务已启动,日志查看:tail -f logs/flask.log"
  • deploy_win.bat(本地演示):
@echo off echo 正在创建虚拟环境... python -m venv venv call venv\Scripts\activate.bat echo 正在安装依赖... pip install -r requirements.txt echo 启动服务... set FLASK_APP=app.py set FLASK_ENV=development flask run --host=127.0.0.1:5000 pause

关键点:requirements.txt必须用pip freeze > requirements.txt生成,并手动删掉pkg-resources==0.0.0这种 Ubuntu 特有包;nohup和&是 Linux 后台运行的标配,避免 SSH 断开服务终止。

6.3 技巧三:拓展性设计——用 SQLite 替代 MySQL,支持离线审计与增量学习

很多同学用 MySQL,结果答辩时 Docker 没装好、密码配错、端

本文还有配套的精品资源,点击获取

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

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

立即咨询