恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于LSTM与TensorFlow的带情绪检测聊天机器人毕业设计落地指南
首页
资讯中心
/
基于LSTM与TensorFlow的带情绪检测聊天机器人毕业设计落地指南
基于LSTM与TensorFlow的带情绪检测聊天机器人毕业设计落地指南
发布时间:2026/9/12 14:14:59
简介基于长短期记忆网络LSTM与TensorFlow的聊天机器人毕业设计项目源码集成情绪抑郁检测功能适用于计算机、通信、人工智能、自动化等相关专业本科或研究生作为毕业设计、课程设计或实际项目参考。项目采用Seq2Seq框架结合LSTM和Attention机制使用TensorFlow 2.0与Keras搭建模型后端由Python实现前端采用HTML、Vue与Ajax构建交互界面可通过网页实时对话并在聊天过程中调用抑郁分类模型对用户情绪进行初步判断。压缩包共45个文件大小约66.81MB包含11个Python源码、4个Jupyter Notebook训练脚本、2个模型权重文件、6个Pickle序列化数据、4个HTML页面以及配置、图片、文档等目录结构清晰方便查阅。目前已有104人浏览学习代码经过调试与测试确保能够直接运行既适合零基础入门也便于深入理解循环神经网络与深度学习项目开发。项目完整覆盖文本预处理、模型构建与训练、网页设计实现、情绪检测等核心流程具有一定工程性和学术性基础扎实的读者还可在此基础上扩展其他情感分析或对话功能具备较高二次开发价值。1. 基于LSTM与TensorFlow的带情绪检测的聊天机器人毕业设计怎么从零落地聊天机器人在毕业设计里属于“看起来简单、做起来全是坑”的选题。很多同学上来就打算用预训练模型微调结果卡在显存、环境、数据集上两周都跑不出一个像样的对话。与其在部署和算力上内耗不如回到 LSTM TensorFlow 这个经典组合上它仍然是理解序列模型最清晰的路径而且在小规模语料上完全可以跑出一个效果能看的机器人。这篇文给的方案是带情绪检测的版本——不只有对话能力还能识别用户情绪并据此改变应答策略。以寒暄类和情绪支持类场景为主比如给大学生心理健康中心做前端助手这类项目落地感强答辩也有东西可讲。整个实现分几条线数据处理与词表构建、LSTM 对话模型训练、情绪分类模型训练、两条线在推理阶段合并。训练用 CPU 也能跑显存只是加快速度我用 TensorFlow 2.x 的 Keras API 实现避免自定义训练循环的复杂度。代码全部给出含注释你可以直接改数据路径就跑。2. 准备环境与语料格式Anaconda 装 TensorFlow聊天数据与情绪标签怎么组织2.1 Anaconda 安装 TensorFlow 的推荐组合与版本选择TensorFlow 安装是毕业生踩坑重灾区。先明确一点不要直接pip install tensorflow到全局环境也别用 Python 3.12 去装旧版 TF否则你会被一堆依赖错误耗尽耐心。规范路径是先装 Anaconda再建独立环境锁版本。conda create -n chatbot python3.9 -y conda activate chatbot pip install tensorflow-cpu2.10.0 pip install jieba pandas numpy scikit-learn提示tensorflow-cpu是纯 CPU 版安装包小训练 LSTM 足够。若电脑有 NVIDIA 显卡并且 CUDA 环境已配好可换tensorflow2.10.0但别用 2.11 以上版本Windows 上 GPU 支持被移除了。版本说明TensorFlow 2.10 是最后一个在 Windows 上原生支持 GPU 的版本2.11 之后 Windows 只有 CPU 版。Python 3.9 与 2.10 配合良好Keras 内置不单独安装。装好后验证python -c import tensorflow as tf; print(tf.__version__)2.2 聊天语料的组织格式问答对与情绪标签训练数据是对话质量的天花板。毕业设计不建议爬取微博或知乎做语料数据噪声大、清洗成本高。常见做法是自建 5000 到 10000 条问答对覆盖问候、自我经历、观点、心理安抚四个方向。每条数据由三部分组成问题、回答、情绪标签用空格或制表符分隔内容中不包含分隔符。你好|你好呀今天过得怎么样|happy 最近压力很大|我理解你的感受愿意和我说说具体发生了什么吗|sad 我不想上课|听起来你很抗拒上课是因为课程内容还是老师|angry情绪标签建议分成四类happy、sad、angry、neutral覆盖大部分对话场景。中文文本需要分词这里用 jieba 并同步构建词表。词表大小控制在 8000 到 20000 之间覆盖训练语料中出现频率最高的词未登录词统一映射为UNK。import jieba import pandas as pd df pd.read_csv(chat_data.txt, sep|, headerNone, names[question, answer, emotion]) def tokenize(text): return list(jieba.cut(text)) df[q_tokens] df[question].apply(tokenize) df[a_tokens] df[answer].apply(tokenize) # 构建词表 from collections import Counter word_count Counter() for tokens in df[q_tokens]: word_count.update(tokens) for tokens in df[a_tokens]: word_count.update(tokens) vocab [word for word, count in word_count.most_common(15000)] word2idx {word: idx 3 for idx, word in enumerate(vocab)} word2idx[PAD] 0 word2idx[UNK] 1 word2idx[EOS] 2提示词表里保留EOS是给回答句末尾加结束符用的生成阶段以此判断回答是否结束。词表构建完成后将每句话转成索引序列统一 padding 到固定长度。问题序列最长设为 20 个 token回答序列最长设为 30。padding 策略使用后置填充因为 LSTM 对序列尾部信息敏感度更高后置填充能保持有效信息集中在末尾。MAX_Q_LEN 20 MAX_A_LEN 30 def encode(tokens, max_len): ids [word2idx.get(token, word2idx[UNK]) for token in tokens] ids ids[:max_len] ids ids [0] * (max_len - len(ids)) return ids df[q_encoded] df[q_tokens].apply(lambda x: encode(x, MAX_Q_LEN)) df[a_encoded] df[a_tokens].apply(lambda x: encode(x, MAX_A_LEN))到这里数据准备工作完成。注意分词质量直接影响训练效果jieba 默认词典对网络用语和表情符号处理不太好建议后续根据对话数据补充自定义词典。3. LSTM 聊天机器人核心序列到序列模型结构、训练参数与生成策略3.1 从 RNN 到 LSTM为什么对话生成必须用 LSTM聊天机器人本质是序列到序列问题输入一句话输出另一句话。早期 RNN 在短文本上表现尚可一旦句子长度超过 20 个 token梯度消失问题会凸显模型学不到很远位置的信息。LSTM 引入门控机制就是为解决这个长依赖问题三个关键门分别是遗忘门、输入门和输出门。很多同学学到lstm遗忘门的输入是什么数据时会卡住。遗忘门的输入是前一个时间步的隐藏状态和当前时间步的输入经过 sigmoid 变换后得到一个取值 0 到 1 的向量这个向量决定细胞状态中哪些信息要保留、哪些要丢弃。在对话场景中LSTM 的优势体现在三个方面。第一它能记住用户在前文提到过的关键名词比如“我养了一只猫叫小白”后续对话提到小白时模型不至于完全丢失上下文。第二门控机制让训练时梯度能传播的更远不至于句子长一点就训练不动。第三LSTM 的运算结构适合 TensorFlow 的静态图优化在 CPU 上也能达到令人满意的推理速度。3.2 编码器-解码器架构与注意力机制的必要性常见做法是使用编码器-解码器结构。编码器将输入的问题编码成固定维度的语义向量解码器基于这个向量逐词生成回答。问题在于固定向量是信息瓶颈问题一长前面的词的信息会被后面的词覆盖。所以 LSTM 机器人在毕业设计中若想效果好建议在解码器端加上注意力机制让每个生成步骤直接聚焦到问题序列中与当前生成位置最相关的部分。TensorFlow 实现中可以通过AdditiveAttention层直接集成。import tensorflow as tf from tensorflow.keras.layers import LSTM, Dense, Embedding, AdditiveAttention class ChatbotModel(tf.keras.Model): def __init__(self, vocab_size, embedding_dim, hidden_dim): super().__init__() self.embedding Embedding(vocab_size, embedding_dim, mask_zeroTrue) self.encoder_lstm LSTM(hidden_dim, return_sequencesTrue, return_stateTrue) self.decoder_lstm LSTM(hidden_dim, return_sequencesTrue, return_stateTrue) self.attention AdditiveAttention() self.dense Dense(vocab_size, activationsoftmax) def call(self, inputs): encoder_input, decoder_input inputs encoder_emb self.embedding(encoder_input) encoder_outputs, state_h, state_c self.encoder_lstm(encoder_emb) decoder_emb self.embedding(decoder_input) decoder_outputs, _, _ self.decoder_lstm(decoder_emb, initial_state[state_h, state_c]) # 注意力机制query是解码器输出value是编码器输出 context_vector self.attention([decoder_outputs, encoder_outputs]) combined tf.concat([decoder_outputs, context_vector], axis-1) output self.dense(combined) return output提示mask_zeroTrue让 Embedding 层跳过填充位置的 0避免 padding 参与注意力计算这一步不设置的话生成时多余的空位也会影响解码结果。模型结构与训练语料规模匹配。embedding_dim 设为 128hidden_dim 设为 256词表 15000 左右总参数量约 700 万CPU 训练每个 epoch 大约十分钟。这组参数在 8000 条语料上能体现 LSTM 的拟合能力又不至于过拟合。训练阶段有一个关键参数teacher_forcing。它指在解码器每步输入时以一定概率使用真实回答的上一个词而非模型自己生成的词可以显著加速收敛。一般前 10 个 epoch 设教师强制率为 0.5后面逐步降到 0。自定义训练循环可以更好地控制这个值。# 编译与训练 model ChatbotModel(vocab_sizelen(word2idx), embedding_dim128, hidden_dim256) model.compile(optimizertf.keras.optimizers.Adam(learning_rate0.001), losstf.keras.losses.SparseCategoricalCrossentropy(from_logitsFalse), metrics[accuracy]) # 训练数据构造输入为问题序列和回答序列右移一位作为解码器输入 def build_dataset(q_ids, a_ids): encoder_input q_ids decoder_input a_ids[:, :-1] # 解码器输入是回答去掉最后一个词 target a_ids[:, 1:] # 预测目标是回答去掉第一个词 return encoder_input, decoder_input, target X_encoder df[q_encoded].values X_decoder df[a_encoded].apply(lambda x: x[:-1]).values y_target df[a_encoded].apply(lambda x: x[1:]).values dataset tf.data.Dataset.from_tensor_slices((X_encoder, X_decoder, y_target)) dataset dataset.shuffle(1000).batch(64).prefetch(tf.data.AUTOTUNE)训练过程中两个最常见的坑一是 loss 下降不明显查数据是否 padding 到等长、词表是否统一二是 loss 下降但输出全是UNK说明词表截断太狠把出现频率低但有意义的词都裁掉了。3.3 生成策略贪心搜索与束搜索的取舍训练完成后进入推理阶段。生成回答的方式决定对话质量。最简单的是贪心搜索每一步取概率最高的词作为当前输出。贪心策略的问题在于缺乏全局最优性一步错后续全错。束搜索beam search在每个时间步保留概率最大的前 K 个候选序列K 通常设为 3 或 5最终选择整体概率最高的序列。在毕业设计答辩中束搜索与贪心搜索的效果对比是很不错的展示点。实现常规做法是用 TensorFlow 的tf.random.categorical做采样也可以用tf.keras.layers.LSTMCell手动循环调用。def generate_response(model, input_seq, max_len30, beam_width3): encoder_emb model.embedding(input_seq) encoder_outputs, state_h, state_c model.encoder_lstm(encoder_emb) start_token word2idx.get(EOS, 2) target_seq tf.constant([[start_token]], dtypetf.int32) # 存储beam搜索候选 candidates [(0.0, target_seq, state_h, state_c)] # (log_prob, sequence, h, c) final_results [] for _ in range(max_len): all_candidates [] for log_prob, seq, h, c in candidates: decoder_emb model.embedding(seq[:, -1:]) decoder_out, new_h, new_c model.decoder_lstm(decoder_emb, initial_state[h, c]) context model.attention([decoder_out, encoder_outputs]) combined tf.concat([decoder_out, context], axis-1) logits model.dense(combined)[:, -1, :] top_k tf.math.top_k(logits, kbeam_width) for i in range(beam_width): token_id top_k.indices[0, i].numpy() token_log_prob top_k.values[0, i].numpy() new_seq tf.concat([seq, [[token_id]]], axis1) all_candidates.append((log_prob token_log_prob, new_seq, new_h, new_c)) # 剪枝保留前beam_width个候选 all_candidates.sort(keylambda x: x[0], reverseTrue) candidates all_candidates[:beam_width] # 最大概率序列中取第一个结束符前的内容 best_seq candidates[0][1].numpy().flatten() output_tokens [] for token_id in best_seq: if token_id word2idx.get(EOS, 2): break if token_id not in [0, 1, 2]: output_tokens.append(token_id) return output_tokens提示注意生成的时候要加长度限制。如果一个样本解码超过 30 步还没遇到EOS强制截断否则推理阶段会陷入死循环。4. 情绪检测模型从文本分类到对话策略联动4.1 情绪特征提取用 LSTM 还是直接用词向量情绪检测本质上是个文本分类问题与对话生成共用词表会省很多事。常见做法是用双向 LSTM 取最后时间步的隐藏状态接一个全连接层输出四分类概率。双向 LSTM 能同时编码前文和后文的信息比如“我一点也不开心”单向 LSTM 在看到“不”时可能已经丢掉部分权重信息双向网络则能在后文语境中修正判断。数据标注是最大痛点。公开中文情感数据集大多面向电商评论分类是正面负面和对话场景四分类不完全匹配。替代方案用大模型预标注一版再人工抽检修正。例如让 ChatGPT 对 3000 条语料中的每一条输出四个人工标签猜测人工抽 200 条校验一致性能节省大量标注时间。class EmotionClassifier(tf.keras.Model): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_classes4): super().__init__() self.embedding Embedding(vocab_size, embedding_dim, mask_zeroTrue) self.bilstm tf.keras.layers.Bidirectional(LSTM(hidden_dim, return_sequencesFalse)) self.dropout tf.keras.layers.Dropout(0.3) self.dense Dense(num_classes, activationsoftmax) def call(self, x): emb self.embedding(x) lstm_out self.bilstm(emb) out self.dropout(lstm_out) return self.dense(out)训练时 loss 使用SparseCategoricalCrossentropy优化器仍用 Adam学习率降到 0.0005因为情绪分类任务相对简单学习率过高容易振荡。4.2 对话与情绪联动的应答策略情绪检测模块不应该孤立存在。聊天机器人有一个核心变量当检测到用户处于负面情绪时系统应先提供情感支持再做信息反馈。这就需要一个策略层将两个模型的输出合并。EMOTION_POLICY { sad: [我听到你表达了很多难过愿意多说一些吗, 我在这里陪着你不用急着调整心情。], angry: [我能感觉到你很生气如果愿意可以说说发生了什么。, 听起来这件事让你非常不满对吗], happy: [太好了继续保持这种状态, 看到你开心我也很高兴], neutral: None } def chatbot_pipeline(question, threshold0.6): question_tokens list(jieba.cut(question)) question_ids encode(question_tokens, MAX_Q_LEN) question_tensor tf.constant([question_ids], dtypetf.int32) emotion_probs emotion_model(question_tensor).numpy()[0] emotion_label [happy, sad, angry, neutral][np.argmax(emotion_probs)] # 情绪概率低于阈值时直接用生成回答 if np.max(emotion_probs) threshold: response_ids generate_response(chat_model, question_tensor) response decode_tokens(response_ids) return response, emotion_label, np.max(emotion_probs) policy_responses EMOTION_POLICY.get(emotion_label) if policy_responses is not None: response random.choice(policy_responses) return response, emotion_label, np.max(emotion_probs) # neutral时走正常生成 response_ids generate_response(chat_model, question_tensor) response decode_tokens(response_ids) return response, emotion_label, np.max(emotion_probs)情绪联动策略有两个参数需要调。一是threshold设太高会让情绪检测形同虚设设太低会把正常对话也拦截到情感回应里。我常用 0.6 作为默认值再根据测试结果微调。极端情况比如用户骂人模型预测 angry 概率 0.9此时应该直接走安抚话术不再调用生成模型因为生成模型对攻击性话语可能学出负面反馈循环。4.3 情绪检测的评估指标与可视化答辩时不能只看几个测试用例的效果要把量化指标摆出来。情绪分类模型可以计算每个类别的 precision、recall、F1-score并使用混淆矩阵展示分类错误集中在哪些类别之间。from sklearn.metrics import classification_report, confusion_matrix import numpy as np y_true [] y_pred [] for batch in test_dataset: encoder_input, decoder_input, target batch pred_probs emotion_model(encoder_input).numpy() pred_labels np.argmax(pred_probs, axis1) # target的emotion标签另取 y_true.extend(batch_emotion_labels) y_pred.extend(pred_labels) print(classification_report(y_true, y_pred, target_names[happy, sad, angry, neutral]))混淆矩阵中常见错误是sad与neutral互相混淆。原因是部分“我没事”这类反讽型表达在标注上容易有分歧这种情况可以在训练数据中增加否定结构和转折结构样本比如“虽然……但是……”加强模型对转折关系的辨识。5. 常见坑与优化技巧过拟合、损失不降、生成重复三个主要问题及对策5.1 过拟合小语料下的正则化参数调整小数据量训练 LSTM 最容易出现过拟合。8000 条语料训练 50 个 epoch 后训练准确率能到 95%但验证集准确率可能停在 60%。模型把训练样本中的噪声当成规律记住了。解决方案按调整顺序有增加 Dropout 层并调大比率、降低 LSTM 隐层维度、提高 L2 正则系数、做早停。# 在LSTM层中加入recurrent_dropout encoder_lstm LSTM(hidden_dim, return_sequencesTrue, return_stateTrue, recurrent_dropout0.2, dropout0.2)注意recurrent_dropout在 CPU 训练时会降低速度因为 TensorFlow 无法对其做 cuDNN 加速但此处理解模型泛化优先仍然值得开启。5.2 损失不降与生成重复的根因与排查损失不降的最常见原因就是词表里有大量稀有词、模型需要预测的类别过多。对于 15000 词的词表每个时间步都要从 15000 个候选词里选一个这对训练量有较高要求。建议的措施过滤掉训练语料中出现次数低于 3 的词语使用tf.keras.layers.Embedding的mask_zero选项将序列长度限制在 20 以内扩大 batch size 到 128 提升训练稳定性。生成重复是 LSTM 机器人的高发性问题——模型会在连续几个时间步输出同一个词如“你好你好你好你好”。根因在于训练时 teacher forcing 让解码器习惯了有真实上文可参考推理时第一个词选偏了后续无法纠偏。解决方案是在解码时加入重复惩罚机制def generate_with_repeat_penalty(model, input_seq, penalty1.5): encoder_emb model.embedding(input_seq) encoder_outputs, state_h, state_c model.encoder_lstm(encoder_emb) start_token word2idx.get(EOS, 2) target_seq tf.constant([[start_token]], dtypetf.int32) generated_tokens [] for _ in range(30): decoder_emb model.embedding(target_seq[:, -1:]) decoder_out, state_h, state_c model.decoder_lstm(decoder_emb, initial_state[state_h, state_c]) context model.attention([decoder_out, encoder_outputs]) combined tf.concat([decoder_out, context], axis-1) logits model.dense(combined)[:, -1, :] # 对已经生成过的token施加惩罚 for token_id in generated_tokens: logits[0, token_id] / penalty next_token tf.argmax(logits, axis-1).numpy()[0] if next_token word2idx.get(EOS, 2): break generated_tokens.append(next_token) target_seq tf.concat([target_seq, [[next_token]]], axis1) return generated_tokens5.3 模型部署为 Web APIFlask 封装与前端界面毕业设计需要演示界面可以快速搭建一个 Flask 后端封装对话接口前端页面保持简洁能够显示用户输入、机器人回复和识别出的情绪类别。from flask import Flask, request, jsonify app Flask(__name__) app.route(/chat, methods[POST]) def chat(): data request.get_json() question data.get(message, ) response, emotion, confidence chatbot_pipeline(question) return jsonify({ response: response, emotion: emotion, confidence: float(confidence) }) if __name__ __main__: app.run(host0.0.0.0, port5000)下一步可以做的优化方向很明确LSTM 模型的深度加速。当前编码器和解码器各一层 LSTM可以尝试堆叠两层解码头加一层Dense中间层做特征变换。训练时间虽增加 30%但回答的上下文相关性会有明显提升。情绪模型也可以从单向 LSTM 换到 GRU 或加入预训练词向量准确率通常能提升三个百分点左右。这些改动建议单独保存一个分支版本方便对比实验结果答辩时直接展示调优前后的指标变化。本文还有配套的精品资源点击获取