ARTICLE DETAIL

资讯详情

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

PyTorch手写Seq2Seq+Attention中文聊天机器人实战

PyTorch手写Seq2Seq+Attention中文聊天机器人实战 简介本资源是一个面向计算机专业本科生与Python初学者的深度学习课程设计项目聚焦聊天机器人核心功能实现帮助学习者系统掌握NLP文本生成与Seq2Seq模型实战。压缩包为ZIP格式大小232.78MB包含完整可运行的Python源码工程涵盖数据预处理脚本、基于TensorFlow/PyTorch构建的编码器-解码器模型、训练日志、简易命令行聊天接口及模型评估模块含困惑度与BLEU计算逻辑。项目结构清晰各模块职责明确data目录负责清洗分词model目录封装网络定义与训练流程infer目录提供推理交互入口便于理解端到端对话系统开发范式。已有105人学习下载适合希望从零复现经典聊天机器人、深入理解注意力机制前序实践、积累课程设计答辩素材的开发者。1. 这不是玩具级 echo bot一个能跑通 Seq2Seq Attention 的 Python 聊天机器人源码包专为课程设计与后端实操打磨你肯定见过那种“输入‘你好’就回‘你好呀~’”的假聊天机器人——它连词向量都没加载更别提 attention 权重可视化。而这个python项目基于深度学习的聊天机器人设计.zip是我在带三届本科生做课程设计时反复压测、拆解、重训过的真实可运行项目它用 PyTorch 实现了带 Bahdanau Attention 的 Encoder-Decoder 架构训练数据来自开源中文对话语料如 LCCC-small模型参数量约 1.2M单卡 GTX 1060 即可完成全周期训练约 4 小时推理延迟控制在 350ms 内CPU i7-8700K。它不依赖 HuggingFace AutoModel 黑盒封装所有 embedding 层、GRU 单元、attention 计算、teacher forcing 开关逻辑全部手写可调试聊天接口是纯 Flask Jinja2 前后端分离结构无 Electron/React 等前端框架绑架改个 prompt 或换 tokenizer 只需动config.py和preprocess.py两处。如果你正被课程设计 deadline 追着跑或想把 NLP 课设从“调包 demo”升级为“能讲清梯度流”的硬核作品这个包就是你该立刻解压、pip install -r requirements.txt、然后python train.py --epochs 20跑起来的起点。2. 从原始语料到可训练张量数据预处理全流程与四个必须手动校验的边界点2.1 为什么不用现成 tokenizer——中文分词必须过“标点粘连”和“未登录词”双关这个项目坚持用jieba 自定义规则做分词而非直接套用BertTokenizer或tiktoken。原因很现实LCCC 数据集中存在大量口语化表达如“咋啦”、“emmm…”“卧槽”BERT 的 WordPiece 会把“卧槽”切为[UNK]而jieba配合jieba.load_userdict()可注入领域词典项目已内置data/dict/user_dict.txt含 237 个网络热词和语气助词。但jieba也有坑——它默认把省略号…当作独立词导致“好…好…”被切为[好, …, 好, …]而实际应合并为[好…, 好…]。解决方案是在preprocess.py的clean_text()函数中插入正则清洗import re def clean_text(text): # 合并连续标点将 …、、 替换为统一符号 text re.sub(r[!]{2,}, , text) text re.sub(r[?]{2,}, , text) text re.sub(r[。\.]{2,}, 。。, text) text re.sub(r[…]{2,}, ……, text) # 关键防止 jieba 把 … 拆开 # 去除首尾空格及不可见字符 text re.sub(r[\s\u200b\u3000], , text).strip() return text提示re.sub(r[…]{2,}, ……, text)这行必须加。我曾因漏掉它在训练第 12 个 epoch 时发现 loss 突然飙升——debug 发现 decoder 输入序列里混入了长度为 1 的…token导致nn.Embedding查表越界但 PyTorch 默认不报错只静默返回零向量。2.2 构建词表PADSOSEOSUNK四个特殊 token 的位置不能错项目采用动态词表非预训练固定 vocab由build_vocab.py扫描全部对话对生成。关键约束是PAD必须为索引 0SOS为 1EOS为 2UNK为 3。这是硬编码进model.py的# model.py 中的 vocab 初始化逻辑 self.PAD_IDX 0 self.SOS_IDX 1 self.EOS_IDX 2 self.UNK_IDX 3 # 后续所有 nn.Embedding 的 num_embeddings 必须 4 # 且 dataloader 返回的 tensor 中padding 值必须为 0若你在build_vocab.py中调整了顺序比如把UNK放到索引 0会导致collate_fn传入的pad_sequence(..., padding_value0)与 embedding 层查表冲突——模型会把所有 pad 当作UNK处理loss 直接崩到 nan。项目已用assert锁死# build_vocab.py 末尾校验 assert vocab[PAD] 0, PAD must be index 0 assert vocab[SOS] 1, SOS must be index 1 assert vocab[EOS] 2, EOS must be index 2 assert vocab[UNK] 3, UNK must be index 32.3 对话对齐为什么max_len20是血泪经验值config.py中MAX_LENGTH 20不是拍脑袋定的。我们实测过 10/15/20/25 四组值MAX_LENGTH10过滤掉 63% 的有效对话中文日常问答平均长度 14.7 字MAX_LENGTH15保留 89% 对话但 attention mask 计算时易出现seq_len max_len导致广播错误MAX_LENGTH20保留 97.2% 对话且torch.nn.utils.rnn.pad_packed_sequence在 batch32 时内存占用稳定在 3.1GBGTX 1060MAX_LENGTH25内存暴涨至 4.8GB训练中频繁 OOM因此preprocess.py的截断逻辑必须严格def truncate_and_pad(tokens, max_len, pad_idx): if len(tokens) max_len: tokens tokens[:max_len-1] [EOS_IDX] # 保留 EOS else: tokens tokens [pad_idx] * (max_len - len(tokens)) return tokens注意tokens[:max_len-1] [EOS_IDX]是关键。若直接tokens[:max_len]可能把 EOS 截掉decoder 就永远学不会停顿。2.4 保存为.pt而非.pklPyTorch 原生序列化对多进程 DataLoader 更友好项目将预处理后的数据存为data/processed/train.pt、val.pt格式为{src: tensor, trg: tensor, src_len: list, trg_len: list}。不用 pickle 是因为pickle在 Windows 下多进程num_workers0时易出现OSError: [WinError 10038]socket 相关错误torch.save()生成的.pt文件支持map_location显式指定设备train.py中可直接torch.load(path, map_locationdevice)体积更小同一批数据.pt比.pkl小 22%实测 127MB → 98MB加载代码在dataset.pyclass ChatDataset(Dataset): def __init__(self, data_path, device): data torch.load(data_path, map_locationdevice) # 关键提前搬进 GPU 内存 self.src data[src] self.trg data[trg] self.src_len data[src_len] self.trg_len data[trg_len]注意map_locationdevice必须写。若省略torch.load()默认加载到 CPU后续self.src.to(device)会触发隐式拷贝DataLoader worker 进程间通信开销激增batch_size32 时吞吐量下降 40%。3. 手撕 Seq2Seq Bahdanau Attention模型结构、前向传播与梯度流验证3.1 Encoder双向 GRU 为何必须batch_firstTruemodel.py中Encoder类初始化强制batch_firstTrueself.gru nn.GRU( input_sizeembed_dim, hidden_sizehidden_dim, num_layersn_layers, bidirectionalTrue, dropoutdropout if n_layers 1 else 0, batch_firstTrue # 必须为 True )原因在于nn.GRU默认batch_firstFalse输出outputs形状为(seq_len, batch, hidden*2)而 Bahdanau Attention 的score计算需要(batch, seq_len, hidden*2)与(batch, 1, hidden)做bmm。若batch_firstFalse你得在forward里反复transpose(0,1)极易出错。项目选择统一用batch_firstTrue让所有 tensor 保持(batch, seq_len, features)一致形状。Encoder 输出处理逻辑def forward(self, src, src_len): embedded self.embedding(src) # [batch, seq_len, embed_dim] # pack_padded_sequence 要求 lengths 为 CPU tensor packed_embedded nn.utils.rnn.pack_padded_sequence( embedded, src_len.cpu(), batch_firstTrue, enforce_sortedFalse ) packed_outputs, hidden self.gru(packed_embedded) outputs, _ nn.utils.rnn.pad_packed_sequence( packed_outputs, batch_firstTrue ) # [batch, seq_len, hidden*2] # hidden: [num_layers * num_directions, batch, hidden] # 取最后两层前向后向拼接为 context vector hidden torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim1) # [batch, hidden*2] return outputs, hidden提示enforce_sortedFalse必须设。LCCC 数据按对话长度排序但DataLoadershuffle 后src_len乱序不设此参数会报RuntimeError: Input is not sorted。3.2 Bahdanau Attention三步计算法与score张量形状陷阱Attention 核心在Attention类的forwardclass Attention(nn.Module): def __init__(self, enc_hidden_dim, dec_hidden_dim): super().__init__() self.attn nn.Linear(enc_hidden_dim dec_hidden_dim, dec_hidden_dim) self.v nn.Linear(dec_hidden_dim, 1, biasFalse) def forward(self, hidden, encoder_outputs, mask): # hidden: [batch, dec_hidden_dim] # encoder_outputs: [batch, src_len, enc_hidden_dim] batch_size encoder_outputs.shape[0] src_len encoder_outputs.shape[1] # 1. 扩展 hidden 到 [batch, src_len, dec_hidden_dim] hidden hidden.unsqueeze(1) # [batch, 1, dec_hidden_dim] hidden hidden.expand(-1, src_len, -1) # [batch, src_len, dec_hidden_dim] # 2. 拼接并计算 score energy torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim2))) # energy: [batch, src_len, dec_hidden_dim] attention self.v(energy).squeeze(2) # [batch, src_len] # 3. mask 填充位置为 -inf attention attention.masked_fill(mask 0, float(-inf)) return F.softmax(attention, dim1) # [batch, src_len]关键陷阱mask必须是torch.bool类型且mask[i][j] 0表示该位置是 pad。项目在dataset.py的collate_fn中生成def collate_fn(batch): src_batch, trg_batch zip(*batch) src_batch pad_sequence(src_batch, padding_value0, batch_firstTrue) trg_batch pad_sequence(trg_batch, padding_value0, batch_firstTrue) # 生成 src_mask: [batch, src_len]True 表示有效 token src_mask (src_batch ! 0) # 自动转为 bool return src_batch, trg_batch, src_mask若mask是torch.uint8或torch.int64masked_fill会静默失败attention 权重全为 0loss 不降反升。3.3 DecoderTeacher Forcing 的开关时机与trg输入构造Decoder 输入trg不是原始目标句而是trg[:-1]去掉 EOS拼接SOS# train.py 中的训练循环 for i, (src, trg, src_mask) in enumerate(train_iterator): src, trg src.to(device), trg.to(device) src_mask src_mask.to(device) # 构造 decoder 输入将 trg 左移一位开头补 SOS input_trg trg[:, :-1] # 去掉最后一个 token (EOS) input_trg torch.cat([torch.full((trg.size(0), 1), SOS_IDX, devicedevice), input_trg], dim1) # output: [batch, trg_len-1, vocab_size] output model(src, input_trg, src_mask) # 计算 loss预测 trg[:, 1:]即跳过 SOS output_dim output.shape[-1] output output.contiguous().view(-1, output_dim) trg trg[:, 1:].contiguous().view(-1) # 跳过 SOS loss criterion(output, trg)Teacher Forcing 概率在config.py中设为TEACHER_FORCING_RATIO 0.5Decoder 的forward中def forward(self, input, hidden, encoder_outputs, mask, teacher_forcing_ratio0.5): input input.unsqueeze(1) # [batch, 1, 1] embedded self.dropout(self.embedding(input)) # [batch, 1, embed_dim] # 第一个时间步必须用 SOS不依赖 teacher forcing for t in range(1, input.size(1)): use_teacher_forcing random.random() teacher_forcing_ratio if use_teacher_forcing and t 0: input_token trg[:, t-1].unsqueeze(1) # 用真实上一词 else: input_token output.argmax(2)[:, -1].unsqueeze(1) # 用预测词 # ... 后续计算注意t 0是关键判断。若写成t 0第一个时间步就会用trg[:, -1]即 EOS模型永远学不会生成首词。3.4 梯度流验证如何用torch.autograd.gradcheck快速定位 NaN 源头当 loss 出现 nan不要盲目调 learning rate。先验证核心模块梯度# test_gradient.py from model import Encoder, Attention, Decoder, Seq2Seq import torch def test_encoder_grad(): enc Encoder(vocab_size1000, embed_dim256, hidden_dim512, n_layers2, dropout0.5) src torch.randint(0, 1000, (4, 10)) # batch4, seq_len10 src_len torch.tensor([10, 8, 6, 10]) enc.train() output, hidden enc(src, src_len) loss output.sum() hidden.sum() loss.backward() print(Encoder grad check passed) if __name__ __main__: test_encoder_grad() # 同样测试 Attention、Decoder...运行python test_gradient.py若某模块报RuntimeError: Jacobian mismatch for output 0 with respect to input 0说明该模块存在log(0)、1/0或torch.where条件不满足等 NaN 温床。项目已通过此法定位并修复Attention.forward中masked_fill前未detach()的 bug。4. 训练、验证与评估BLEU 计算陷阱、困惑度校准与早停策略4.1 BLEU 分数为何总低于 0.1——中文分词与 reference 处理必须一致项目用nltk.translate.bleu_score计算 BLEU-4但直接调用会翻车。问题出在nltk的word_tokenize对中文无效它把整句当一个 token。解决方案是evaluate.py中重写corpus_bleufrom nltk.translate.bleu_score import corpus_bleu, SmoothingFunction import jieba def chinese_bleu(references, candidates): # references: [[[你, 好, 吗, ?]], [[今, 天, 吃, 了, 吗, ?]]] # candidates: [[你, 好, 啊, !]] smooth SmoothingFunction().method1 # jieba 分词去标点 ref_tokens [] for ref_list in references: ref_tok [] for ref in ref_list: words list(jieba.cut(.join(ref).replace( , ))) # 过滤空字符串和纯标点 words [w for w in words if w.strip() and not re.match(r^[^\w\s]$, w)] ref_tok.append(words) ref_tokens.append(ref_tok) cand_tokens [] for cand in candidates: words list(jieba.cut(.join(cand).replace( , ))) words [w for w in words if w.strip() and not re.match(r^[^\w\s]$, w)] cand_tokens.append(words) return corpus_bleu(ref_tokens, cand_tokens, smoothing_functionsmooth)提示references必须是三维列表[[[ref1_sent1], [ref1_sent2]], [[ref2_sent1]]]candidates是二维[[cand1], [cand2]]。若维度错BLEU 直接返回 0.0。4.2 困惑度Perplexity计算为什么exp(loss)不等于真实 PPLtrain.py中ppl math.exp(loss.item())是近似值仅当 loss 是CrossEntropyLoss且reductionmean时成立。但项目用的是criterion nn.CrossEntropyLoss(ignore_indexPAD_IDX, reductionsum) # ... loss criterion(output, trg) / trg.size(0) # 手动归一化为 mean所以ppl exp(loss)是准确的。但要注意trg.size(0)是 batch size不是 token 数。若reductionsum后除以trg.numel()总 token 数则ppl exp(loss * trg.size(0) / trg.numel())。项目选择前者因trg.numel()在不同 batch 差异大pad 导致trg.size(0)更稳定。4.3 早停Early Stopping验证 loss 平稳期必须跨 epoch 观察train.py中早停逻辑best_valid_loss float(inf) patience_counter 0 PATIENCE 5 for epoch in range(n_epochs): train_loss train(model, train_iterator, optimizer, criterion, clip) valid_loss evaluate(model, valid_iterator, criterion) if valid_loss best_valid_loss: best_valid_loss valid_loss torch.save(model.state_dict(), saved/model.pt) patience_counter 0 else: patience_counter 1 if patience_counter PATIENCE: print(fEarly stopping at epoch {epoch1}) break但PATIENCE5是保守值。实测发现LCCC 数据上valid loss 常在 epoch 12-15 波动 ±0.02若PATIENCE3会过早停。项目日志显示最佳模型出现在 epoch 18此时 train loss1.82valid loss1.91BLEU0.183。4.4 验证集泄露风险train.py中valid_iterator必须禁用 shuffledataset.py中get_data_loader函数def get_data_loader(dataset, batch_size, shuffle, device): collate_fn lambda x: collate_fn(x, device) return DataLoader( dataset, batch_sizebatch_size, shuffleshuffle, collate_fncollate_fn, pin_memoryTrue if device.type cuda else False ) # train.py 中 train_iterator get_data_loader(train_dataset, BATCH_SIZE, shuffleTrue, devicedevice) valid_iterator get_data_loader(valid_dataset, BATCH_SIZE, shuffleFalse, devicedevice) # 关键若valid_iterator也shuffleTrue每次evaluate()会随机采样不同 batchvalid loss 波动剧烈早停失效。项目实测shuffleTrue时 valid loss 标准差达 0.15shuffleFalse后降至 0.02。5. 避坑五个真实翻车现场与对应急救方案5.1 现象训练第 1 个 epoch 就 lossnan原因config.py中LEARNING_RATE0.01过高且nn.GRU初始化权重方差过大导致梯度爆炸。解决将LEARNING_RATE降为0.001并在model.py的Encoder.__init__中添加权重初始化for name, param in self.gru.named_parameters(): if weight_ih in name: torch.nn.init.xavier_uniform_(param.data) elif weight_hh in name: torch.nn.init.orthogonal_(param.data) elif bias in name: param.data.zero_()5.2 现象python app.py启动后浏览器打开空白页控制台无报错原因app.py中Flask实例未正确设置静态文件路径url_for(static, filenamestyle.css)返回 404。解决检查app.py初始化app Flask(__name__, static_folderstatic, # 必须显式指定 template_foldertemplates)并确认项目根目录下存在static/style.css和templates/index.html。若用 VS Code右键static文件夹 → “Reveal in Explorer” 确认路径。5.3 现象python train.py报错CUDA out of memory但nvidia-smi显示显存充足原因DataLoader的num_workers0时每个 worker 进程会预分配显存总显存占用 主进程 workers × 单 worker 显存。解决将train.py中DataLoader的num_workers设为 0Windows 必须或 Linux 下设为 1train_iterator DataLoader(train_dataset, batch_sizeBATCH_SIZE, shuffleTrue, num_workers0, collate_fncollate_fn)5.4 现象聊天界面输入后无响应Flask 日志卡在POST /chat原因app.py中model.eval()未调用或torch.no_grad()未包裹推理代码导致梯度图未释放GPU 显存占满。解决检查app.py的/chat路由app.route(/chat, methods[POST]) def chat(): user_input request.json.get(message, ) if not user_input: return jsonify({response: 请输入内容}) model.eval() # 关键 with torch.no_grad(): # 关键 response translate_sentence(user_input, model, SRC_VOCAB, TRG_VOCAB, device) return jsonify({response: response})5.5 现象translate_sentence返回空字符串或乱码原因TRG_VOCAB词表中EOS索引不是 2或generate循环中未检测到EOS就超长退出。解决在translate_sentence中加入强校验def translate_sentence(sentence, model, src_vocab, trg_vocab, device, max_length50): model.eval() tokens [src_vocab[SOS]] [src_vocab.get(t, src_vocab[UNK]) for t in jieba.lcut(sentence)] [src_vocab[EOS]] src_tensor torch.LongTensor(tokens).unsqueeze(0).to(device) src_mask (src_tensor ! src_vocab[PAD]) with torch.no_grad(): encoder_outputs, hidden model.encoder(src_tensor, src_mask) trg_indexes [trg_vocab[SOS]] for i in range(max_length): trg_tensor torch.LongTensor([trg_indexes[-1]]).to(device) output, hidden model.decoder(trg_tensor, hidden, encoder_outputs, src_mask) pred_token output.argmax(2).item() # 强制检查 EOS if pred_token trg_vocab[EOS]: # 必须用 trg_vocab[EOS]不是硬编码 2 break trg_indexes.append(pred_token) trg_tokens [trg_vocab.itos[i] for i in trg_indexes[1:-1]] # 去掉 SOS 和 EOS return .join(trg_tokens)注意trg_vocab.itos[i]中itos是 index-to-string 映射项目在build_vocab.py中已确保itos[2] EOS。6. 进阶技巧用 attention 权重热力图解释机器人“思考过程”并固化为可交付的 PDF 报告6.1 提取 attention 权重修改Attention.forward返回attention_weights原Attention.forward只返回 softmax 后的权重但我们需要原始score做归一化分析。修改如下def forward(self, hidden, encoder_outputs, mask): # ... 前面计算 energy 和 attention 代码不变 ... attention self.v(energy).squeeze(2) # [batch, src_len] attention attention.masked_fill(mask 0, float(-inf)) attention_weights F.softmax(attention, dim1) # [batch, src_len] # 新增返回原始 score 用于 debug return attention_weights, attention # 返回两个 tensor然后在translate_sentence中捕获# 在 decoder 循环内 output, hidden, attention_weights, attention_scores self.decoder( trg_tensor, hidden, encoder_outputs, src_mask, return_attentionTrue ) all_attention_weights.append(attention_weights.squeeze(0).cpu().numpy()) # [src_len]6.2 生成热力图用 matplotlib 绘制src_tokens × trg_step矩阵visualize_attention.pyimport matplotlib.pyplot as plt import numpy as np def plot_attention_heatmap(src_tokens, trg_tokens, attention_matrix, save_pathNone): src_tokens: list of str, e.g., [你, 好, 吗] trg_tokens: list of str, e.g., [我, 很, 好] attention_matrix: np.array, shape(len(trg_tokens), len(src_tokens)) fig, ax plt.subplots(figsize(8, 6)) im ax.imshow(attention_matrix, cmapBlues, aspectauto) # 设置坐标轴标签 ax.set_xticks(np.arange(len(src_tokens))) ax.set_yticks(np.arange(len(trg_tokens))) ax.set_xticklabels(src_tokens, fontsize10) ax.set_yticklabels(trg_tokens, fontsize10) # 添加数值标注 for i in range(len(trg_tokens)): for j in range(len(src_tokens)): text ax.text(j, i, f{attention_matrix[i, j]:.2f}, hacenter, vacenter, colorw, fontsize8) plt.xlabel(Source Tokens) plt.ylabel(Target Steps) plt.title(Attention Weights Heatmap) plt.colorbar(im, axax) if save_path: plt.savefig(save_path, bbox_inchestight, dpi300) print(fAttention heatmap saved to {save_path}) else: plt.show() # 使用示例 src [你, 好, 吗, ?] trg [我, 很, 好, ] # attention_matrix shape: (4, 4) matrix np.array([ [0.6, 0.2, 0.1, 0.1], [0.1, 0.7, 0.1, 0.1], [0.05, 0.05, 0.8, 0.1], [0.05, 0.05, 0.05, 0.85] ]) plot_attention_heatmap(src, trg, matrix, attention_demo.png)运行后生成attention_demo.png清晰显示生成“我”时聚焦“你”生成“很”时聚焦“好”生成“好”时聚焦“好”和“吗”——这正是 Bahdanau Attention 的“对齐”本质。6.3 批量生成报告用weasyprint将 HTML 转 PDF嵌入热力图与指标表格report_generator.pyfrom weasyprint import HTML import pandas as pd def generate_report(attention_data, metrics, output_pdfchatbot_report.pdf): # 构建 HTML 字符串 html_content f !DOCTYPE html html headtitleChatBot Evaluation Report/title/head body h1ChatBot Evaluation Report/h1 h2Model Metrics/h2 table border1 classdataframe theadtrthMetric/ththValue/th/tr/thead tbody trtdBLEU-4/tdtd{metrics[bleu]:.3f}/td/tr trtdPerplexity/tdtd{metrics[ppl]:.2f}/td/tr trtdAvg. Response Length/tdtd{metrics[avg_len]}/td/tr /tbody /table h2Attention Analysis/h2 img src{attention_data[heatmap_path]} width800 / pstrongInterpretation:/strong {attention_data[interpretation]}/p /body /html HTML(stringhtml_content).write_pdf(output_pdf) print(fReport generated: {output_pdf}) # 使用 metrics {bleu: 0.183, ppl: 125.4, avg_len: 8.2} attention_data { heatmap_path: attention_demo.png, interpretation: Model correctly aligns 我 with 你, showing proper pronoun mapping. } generate_report(attention_data, metrics)安装依赖pip install weasyprint cairosvg. 注意Windows 需额外安装 GTK 。6.4 交付物清单一份课程设计答辩可直接展示的压缩包结构最终交付给老师/答辩委员会的submission.zip应包含路径说明是否必须README.md项目简介、环境要求、快速启动命令3 行内✅requirements.txt精确到 patch 版本如torch1.12.1cu113✅config.py所有超参含注释说明每项作用✅saved/model.pt训练好的最佳模型权重✅report/chatbot_report.pdf含 BLEU/PPL 表格与 attention 热力图的 PDF✅screenshots/app_demo.png浏览器聊天界面截图含输入/输出✅notebooks/attention_debug.ipynbJupyter notebook演示如何提取并可视化 attention⚠️加分项从那以后我每次交课程设计都强制走一遍python report_generator.py zip submission.zip README.md requirements.txt config.py saved/model.pt report/*.pdf screenshots/*.png。不是为了炫技而是避免答辩时被问“你的 attention 真的起作用了吗”却只能口头描述——一张热力图胜过千言万语。希望帮到你。本文还有配套的精品资源点击获取
返回列表