☰
Transformer对话模型实战:打造可微调的智能陪伴机器人
2026/9/28 23:46:57 网站建设 项目流程

简介:基于Transformer模型的智能聊天机器人女友项目完整资源包,面向人工智能、深度学习方向的毕业设计学生与NLP开发者。项目采用Transformer-big结构,使用小黄鸡对话语料训练,并通过NeurST与LightSeq分别完成训练和推理加速,整体回复流畅自然;随包附带可直接加载的词表和模型文件,简单配置即可开启对话体验。资源共20个文件、约79.7MB,包含Python脚本、YAML训练与预测配置、src/trg对话语料、TensorFlow模型、SentencePiece词表以及详细说明文档,从分词、TFRecord构建、模型训练到LightSeq快速推理,链路完整,能够帮助读者快速掌握对话系统从数据集准备、模型训练到部署演示的关键环节。包内目录按data、configs、demo划分,便于逐层研读和二次开发;文档还给出词表扩至32k与升级Transformer-big的提示,可继续优化回复质量。目前已有265人学习下载,适合作为Transformer在NLP对话任务上的毕业设计参考或实战入门项目。

1. 智能聊天机器人:Transformer 对话模型从一个毕设标题到可复用的情感陪伴系统

如果你在检索“智能聊天机器人 + Transformer + 源码”,大概率不是想看又一个 API 封装演示,而是想要一套能本地跑起来、能微调、能跟用户连续聊上几十轮不崩的对话模型工程。标题里带“女友”二字,本质上要的是一个有角色人设、有记忆、会接住情绪化表达的陪伴型对话系统,这对 Transformer 类模型的要求远高于通用问答机器人:上下文要长、回复要像人、不能每轮都端着“作为AI助手”的架子。这篇文章会从模型选型、数据构造、训练细节到推理加速全部过一遍,直接按可复现的工程来拆,不画饼。

我假设你手上已经有一份包含源码、模型权重、文档和训练数据的毕设压缩包,或者你正准备从零搭一个同类型的项目。很多毕设源码包的通病是:代码能跑通 demo,但换到自己的语料、自己的显卡上,loss 不降、回复复读机、显存溢出,这类坑占了整个调参周期的七成时间。下文会把这些坑按现象、原因、解决方式一条条列出来。

2. Transformer 对话模型的技术底座:为什么是它,选哪个变体

2.1 从 N-gram 到 Transformer:对话任务需要什么样的序列建模能力

聊天机器人不是第一代就长这样的。早年基于检索的问答系统只能从知识库匹配相似问句,用户换个说法就哑火;后来 Seq2Seq 时代用 LSTM 编码上下文、解码回复,能生成新句子,但长对话里早期说过的话基本被“遗忘”了——LSTM 的隐藏状态容量有限,超过 20 轮以上的关键信息会衰减,这是结构决定的。

Transformer 把“遗忘”问题转移到了注意力机制上:每个 token 在计算时都能直接看到输入序列里的任意位置,距离不再是信息传递的障碍。这个特性决定了它特别适合“陪伴式聊天”,因为这类任务的输入不是单轮 query,而是把前面所有轮次拼成一段历史文本,模型要能从这一段混着用户语气、自己之前回复、时间线错乱的内容里找出该延续什么话题、该回避什么雷区。

这里要澄清一个关键认知:对话场景用的是解码器(Decoder-only),不是编码器-解码器(Encoder-Decoder)。GPT 系列是 Decoder-only,T5/BART 是 Encoder-Decoder,两者在对话任务上的实践差别很大。Encoder-Decoder 适合“输入理解 + 受控生成”的任务比如摘要、翻译,对话需要的是“延续历史”,Decoder-only 学的是语言本身的先验分布。做陪伴式聊天,用 Decoder-only 架构几乎是行业共识,别被一些旧教程带偏。

提示:打开源码包第一件事,去model.py或配置文件里确认用的是 GPT2LMHeadModel 还是 BertForSequenceClassification。很多毕设源码号称“Transformer 聊天机器人”,实际只是用 BERT 做意图分类 + 检索库拼接,那叫“BERT + 检索”,不是生成式对话。这个判断决定你后面能不能微调出有灵魂的回复。

2.2 GPT-2、GPT-Neo、Chinese-LLaMA 还是 Claude 类大模型:显存与效果的天平

对个人开发者跑“聊天机器人女友”项目,模型体量基本被显卡焊死了。常见可选范围如下:

模型参数量显存占用(fp16 推理)中文对话质量微调难度
GPT-2 Chinese124M约 2-3GB中下,可训练出基础人设低,单卡可做
GPT-2 中文扩写版300M+约 6GB中等,语料够能出风格中,需耐心调参
GPT-Neo 1.3B1.3B约 8-10GB中上,英文更强,中文需要中文语料微调中高,LoRA
ChatGLM-6B / LLaMA 类6B+量化后 6-12GB高,但毕设项目难以摆脱“大厂模型即插即用”的嫌疑高,需要 P-Tuning / LoRA

我的建议很直接:毕设场景里选 124M 到 300M 的 GPT-2 中文变体作为主干模型。理由有三:一,参数量小,单张 6G 显存就能做全参数微调,不需要降级到 LoRA,代码复杂度低一个量级;二,对话质量足够撑起演示,只要数据构造得当,它能把“吃醋”“撒娇”“回忆共同经历”这类情绪对话学得像模像样;三,推理速度快,CPU 上也能跑出可以接受的延迟,答辩现场不用依赖高性能机器。如果你手里有 24G 显存的卡,再考虑往上够一够,否则容易陷入“卡跑不动、代码调不通、答辩做不完”的三不管地带。

2.3 Position Embedding 和 Attention Mask:两个常被忽略但决定对话质量的细节

Transformer 的骨架是 Self-Attention,但对话场景有两个细节不处理,模型训练出来就是个鹦鹉:

第一个是位置编码。标准 Transformer 用正弦位置编码,但 GPT 类模型用的是 learned positional embedding。在一个从头训练的对话模型里,位置编码学到的是“这个 token 在序列中的绝对位置”,而不是“它与当前 token 的相对距离”。对长对话来说,绝对位置到 512 以上就能感觉到质量滑坡,因为训练语料里超过这个长度的样本太少了。

第二个是 Attention Mask——对话历史里的 padding token 必须 mask 掉。这个听起来像废话,但真实源码包里写错的概率极高。常见错误:把历史轮次拼成[CLS] 用户: ... [SEP] 助手: ... [SEP] 用户: ... [SEP]后,整个序列只用了同一个 attention mask,padding 部分虽不参与计算,但[SEP]之间的位置关系乱套,导致模型学会把“用户的话”和“助手的话”混在一起。正确做法是保证每个 token 只能 attend 到它之前的历史 token,这对应 GPT-2 里的causal mask,也就是下三角矩阵。

# 以 HuggingFace transformers 的 GPT2LMHeadModel 为例 import torch from transformers import GPT2Tokenizer, GPT2LMHeadModel tokenizer = GPT2Tokenizer.from_pretrained("mymodel/chinese-gpt2") model = GPT2LMHeadModel.from_pretrained("mymodel/chinese-gpt2") tokenizer.pad_token = tokenizer.eos_token # GPT2 没有 pad token,用 eos 做 padding # 构造一段多轮对话历史 history = [ "用户: 今天工作好累啊", "助手: 抱抱你,是不是又被领导加活了?", "用户: 对啊,改了一天的方案", ] # 编码并拼接成单序列 input_text = tokenizer.eos_token.join(history) + tokenizer.eos_token + "助手:" inputs = tokenizer(input_text, return_tensors="pt", padding=True, truncation=True, max_length=512) # causal mask 由模型内部处理,不需要手动传入,但 padding 部分要改成 -100 避免计算 loss labels = inputs["input_ids"].clone() labels[inputs["attention_mask"] == 0] = -100 # padding 位置不参与 loss 计算 outputs = model(**inputs, labels=labels) loss = outputs.loss loss.backward()

这段代码的关键点是labels的处理:语言模型的 loss 默认对所有 token 计算交叉熵,包括 padding 部分,如果不把 padding 位置的 label 换为-100,模型会在“对齐到 padding”这件事上学到错误信号——loss 看着在降,但生成的时候全在复读[PAD]。这是对话模型训练里最常见、也最隐蔽的坑,后面在避坑章节会展开。

3. 数据决定人格:把语料做成 Transformer 能学透的样子

3.1 陪伴式对话的数据结构:多轮扁平化与角色分隔符的设计

拿到手的数据可能是从网上爬的贴吧对话、开源的中文闲聊语料,甚至是一堆影视台词。这些原始文本不能直接丢给模型训练,要按统一 schema 重排成“扁平化多轮序列”。结构如下:

[EOS] 用户: xxx [EOS] 助手: xxx [EOS] 用户: xxx [EOS] 助手: xxx [EOS]

为什么用[EOS]做轮次分隔符而不是[SEP]?因为[EOS]本身就是 GPT 类模型的标准结束符,用推论里的特殊 token 做分隔,可以让模型学习到“当前回复结束时,我会产生一个 EOS,然后用户接下来会说……”这个跨轮次的流动规律。如果换成一个训练时很少见到的[SEP],模型从没学过这个 token 的分布,会把它当成普通词来生成,整个对话节奏就会乱。

角色标识“用户:”“助手:”不参与训练吗?参与,而且要保留。这个前缀是模型区分“谁在说话”的唯一信号,删掉后模型会把用户的历史发言当成自己的来延续,回复变得自说自话。

# 数据预处理脚本的核心逻辑 # 输入原始多轮对话 jsonl:{"id": "0", "conversation": ["说了什么", "回了什么", "说了什么", "回了什么"]} python preprocess_chat_data.py \ --input_dir ./raw_data/ \ --output_file ./processed/train.jsonl \ --max_seq_len 512 \ --role_user "用户:" \ --role_bot "助手:" \ --min_rounds 2 \ --max_rounds 20

参数说明:

  • --max_seq_len 512别轻易调大,位置编码到 512 外是盲区,硬调大只会让 padding 比例升高,训练效率下降。
  • --min_rounds 2意味着单轮问答样本会被过滤掉,因为单轮样本对学习“人格连续性”没有帮助,它们只会拖慢收敛。
  • --max_rounds 20对应上下文窗口上限,超过的部分做截断,但要注意截断时尽量保尾弃头——最近的对话内容对当前回复影响最大。

3.2 角色人设注入:用 system prompt 还是用特殊 token?

“女友”类项目的核心难点不是“能聊天”,而是“像特定的人聊天”。很多源码直接用一句system="你是我的女朋友,温柔体贴"拼到开头,这在六七年前的模型上管用,但在 1B 以下的小模型上几乎无效,因为小模型对指令跟随的理解很弱,它会把 system 里的每一句话都当成对话历史来续写。

更可靠的做法是“人设语料注入”:准备一批固定在对话开头出现的自我介绍片段,作为每条训练样本的前缀。这个片段不是指令,而是角色自我描述,比如“助手: 我是小雅,你的女朋友,我最喜欢听你讲今天发生的烦心事。”然后接着进入正常对话。这样模型学习到的是“这个文本风格的角色怎么说人话”,而不是“去执行一条指令”。

我建议把这部分数据单独做,占总训练样本的 20% 左右。如果全部样本都只学开放域闲聊,模型确实能对话,但缺乏人格侧写;如果只学人设样本,又容易产生模板化回复,聊几轮就穿帮。这个比例是实践里试过比较稳的。

# 人设硬编码注入的示例:训练时拼在序列开头 persona_prompts = [ "你是小雅,一个22岁的大学生,说话俏皮,偶尔会撒娇,但遇到正事会很认真。", "你是小雅,记得用户叫阿杰,你们在一起一年了,你喜欢喝奶茶和看动漫。", ] def build_sample(persona, history): # persona 作为开头 token 序列,history 拼接在其后 prefix = f"{persona} {tokenizer.eos_token}" full_text = prefix + tokenizer.eos_token.join(history) + tokenizer.eos_token + "助手:" return full_text

人设文本的长度不宜太长,超过 30 个 token 后,小模型的重点会偏移到“记忆这一段文本”而不是“按这个人设去对话”。

3.3 负样本与安全回复:教模型踩刹车,而不是只会接话

一个只会接话的陪伴机器人是灾难。用户说“今天不想活了”,模型回“那我们一起躺平吧”——这在真实系统里绝对不行。Transformer 对话模型的天然倾向是“继续生成”,它不具备对危险输入的判断力,必须在数据层就教它“什么时候不能顺着说”。

做法是在训练语料中混杂一对特殊构造的样本:用户发出偏负面/危险/擦边内容时,助手的回复不是展开话题,而是安全回应,比如“别这样说,我喜欢你好好活着。”这种样本占比 3%-5% 即可,不用太多,模型能从统计上捕捉到“这类输入对应温和回应”的模式。

更进阶的做法是训练后再跑一次安全过滤层,用规则或一个小分类模型拦截危险回复。毕设项目里通常用前者(数据混合)就够,不推荐再堆一个模型上去,训练成本翻倍。

4. 从零训练与微调:让模型学会聊天、记住人设的完整步骤

4.1 最小可复现训练脚本:数据加载、优化器、Loss 的细节

模型定了、数据整理好了,接下来是最枯燥也最容易翻车的训练环节。这里给一个完整的可跑模板,基于 HuggingFace Transformers 库,环境要求 torch>=1.10, transformers>=4.20。

# train_chat_model.py import torch from torch.utils.data import Dataset, DataLoader from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup class ChatDataset(Dataset): def __init__(self, file_path, tokenizer, max_len=512): self.data = [] with open(file_path, "r", encoding="utf-8") as f: for line in f: self.data.append(line.strip()) self.tokenizer = tokenizer self.max_len = max_len def __len__(self): return len(self.data) def __getitem__(self, idx): text = self.data[idx] enc = self.tokenizer(text, truncation=True, max_length=self.max_len, padding="max_length", return_tensors="pt") input_ids = enc["input_ids"].squeeze() attention_mask = enc["attention_mask"].squeeze() labels = input_ids.clone() labels[attention_mask == 0] = -100 return input_ids, attention_mask, labels tokenizer = GPT2Tokenizer.from_pretrained("mymodel/chinese-gpt2") tokenizer.pad_token = tokenizer.eos_token # 如果是接手已有权重做微调,得先确认 base model 的 tokenizer 词汇表覆盖了语料里的常用字 train_dataset = ChatDataset("processed/train.jsonl", tokenizer) train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True) model = GPT2LMHeadModel.from_pretrained("mymodel/chinese-gpt2") model.train() optimizer = AdamW(model.parameters(), lr=5e-5, weight_decay=0.01) total_steps = len(train_loader) * 3 # 假设跑 3 个 epoch scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=200, num_training_steps=total_steps) for epoch in range(3): for step, (input_ids, attention_mask, labels) in enumerate(train_loader): outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() scheduler.step() optimizer.zero_grad() if step % 100 == 0: print(f"Epoch {epoch}, Step {step}, Loss {loss.item():.4f}") # 顺手保存一个 checkpoint,以防后面显存溢出崩溃白跑 model.save_pretrained(f"checkpoints/chat_model_epoch{epoch}_step{step}")

这段代码里的几个参数值得停下来说:

  • 学习率5e-5是 GPT-2 中文微调的安全区。大于1e-4很容易在几轮后就出现 loss 震荡,表现为生成内容从“还像话”突然退化成“标点符号复读机”。小于1e-5则收敛太慢,训练十几个 epoch 还是老样子。
  • max_norm=1.0的梯度裁剪是必须的,对话模型的 loss 曲线经常出现瞬时尖峰,不裁剪一次 step 就能把权重毁掉,前面的训练时间全部白费。
  • batch_size=8是在 8G 显存下 124M 模型的安全值,如果想调大,建议先跑一个 step 测显存占用,别直接上 16。

提示:Loss 值不是评判模型好坏的唯一指标。固定在 3.0 左右下不来,可能是数据里 padding 比例太高(超过 40% 就得查预处理);降到 2.5 以下但生成全是“嗯嗯”“好的”,那是语料多样性不够,不是继续调参能解决的。

4.2 预训练 vs 微调:从哪里拿到权重,怎么判断是否适合继续训练

手头有源码包的人会面临一个选择:是直接用包里的pytorch_model.bin,还是从 HuggingFace 那下载一个开源中文 GPT-2 权重重新微调。

如果包里的模型已经在一个中等规模的中文语料上做过预训练,那么直接微调即可。但我见过太多毕设源码的所谓“模型”,其实只是用同一份训练数据跑了几十个 epoch,换来的是把训练语料逐字背下来了——它在训练集上 loss 极低,换到用户输入就答非所问。

判断方法很简单:随机从训练集里抽几句对话喂给模型,如果它几乎一字不差地把训练语料里的回复吐出来,说明模型已经过拟合,这种权重不能再作为微调起点。解决方案是去 HuggingFace 找中文 GPT-2 权重(版权和许可需自行核实),或者在源码包里找config.json里的vocab_size和n_layer,如果结构一样,可以随机初始化权重从头训——从头训需要的数据量和时间都很大,对小项目来说更推荐前者。

4.3 判别训练与生成训练的差异:为什么用语言模型 Loss 而不是分类 Loss

一个容易混淆的问题是“训练女友模型是分类任务还是生成任务”。很多初学者会用 BERT + Softmax 把回复当成一个标签分类问题来做——选一个最合适的回复。但分类做法天花板极低:候选回复集有限,无法生成新句子,“女朋友”聊几轮就露出复读机本质。

GPT 类模型的训练目标是最大化条件概率:Loss = -Σ log P(token_i | token_1, ..., token_{i-1})这个 loss 是逐步预测下一个 token 的概率,让模型学会“这个语境下,接下来最可能说这句”。它不依赖固定标签集,所以才能生成“从没在训练集里出现过”的句子。

这两种训练方向直接影响了项目里调参的逻辑。分类任务的指标是 accuracy,而生成任务看的是 loss 曲线趋势、生成样本的困惑度和人工评估。如果你看到群里有人用“正确率 93%”吹嘘对话模型,可以直接判定他做的是检索式,不是标题里的 Transformer 生成式。

5. 避坑与排查:Transformer 对话模型在真实运行中的九死一生

5.1 现象与原因对照表:显存溢出、Loss 不降、复读机

现象可能原因处理方式
显存溢出(OOM)序列长度、batch size 超出显存梯度累积:gradient_accumulation_steps=4,分步更新
Loss 降得很慢学习率过小或 padding 比例过高调大 lr 到 1e-4,检查训练样本平均长度
Loss 降了但生成极差模型欠拟合/训练步数不够用训练好的 checkpoint 做生成测试,不是等全部训练完才看
生成复读机(“嗯嗯嗯嗯”)采样参数 temperature 过低temperature 调到 0.8-0.9,top_p=0.9
回复与用户输入无关训练数据里“用户”与“助手”角色混乱检查预处理脚本的角色分隔符是否一致
一到长文本就炸位置编码长度限制逐段做摘要或上下文截断,别硬扩 max_len
文本生成长度控制max_length 设得过短会切成半句话设 60-80;多轮对话时另加 max_new_tokens 控制单次输出
权重差异过大训练后期梯度爆炸梯度裁剪必须开,max_norm 1.0

5.2 坑 1:从零训练时 GPU 利用率忽高忽低、Loss 波动剧烈,源自 DataLoader 的 collate 写错

现象:loss 曲线在 3.0 到 4.5 之间来回跳,训练速度时快时慢。

原因:DataLoader里没写collate_fn,每个 batch 里的序列长度参差不齐,pytorch 默认会按 batch 内最大长度做 padding。如果某几条特别长的文本混入,这个 batch 的 padding 比例突然提高,有效学习信号变少,loss 自然大幅波动。同时,GPU 端到端的计算量忽大忽小,利用率就上不去。

解决:在DataLoader里加collate_fn,先按文本长度做 bucket 分桶,每个 batch 内文本长度尽量接近,减少 padding 浪费。代码如下:

def collate_fn(batch): input_ids, attention_mask, labels = zip(*batch) # 这里已按 pad_token 统一长度,可再按真实长度排序做动态 padding return torch.stack(input_ids), torch.stack(attention_mask), torch.stack(labels) train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, collate_fn=collate_fn)

做了分桶后,显存占用能降 20%-30%,训练速度也会有明显提升。这个坑在大部分毕设源码里都存在,属于查漏补缺必看项。

5.3 坑 2:微调完成后生成的回复总是带着“用户:”前缀,把角色标识当成了自己要说的内容

现象:模型回复变成了“助手: 用户: 你说的对”,生成的文本里出现了角色标签。

原因:训练时没有专门处理回归标签,模型把序列里的所有位置(包括“用户:”这个 token)都当成了预测目标,学会了在生成时自己补一个“用户:”再接内容。这是对话模型训练里特有且非常高发的 bug。

解决:只用“助手:”后面的 token 参与 loss 计算。实现方式是在构造 labels 时,把“用户:”和“助手:”之前的所有 token 全部设为 -100,只对助手回复部分计算 loss。这样模型学到的是“看到用户:开头的话,接着生成助手:开头的话”,而不是“在所有位置都续写话题”。

def build_labels(enc, tokenizer): labels = enc["input_ids"].clone() user_token_id = tokenizer.encode("用户:")[0] bot_token_id = tokenizer.encode("助手:")[0] # 把所有前缀(含历史里的用户/助手标记)的 label 设为 -100,只保留最后一段助手回复 last_bot_pos = (enc["input_ids"] == bot_token_id).nonzero(as_tuple=True)[-1][-1].item() labels[:last_bot_pos+1] = -100 return labels

训练完以后生成时,也要手动在输入尾部加上“助手:”,模型才会进入回复模式。很多 demo 代码里忘了这步,导致生成的文本全是历史内容续写,看起来像答非所问。

5.4 坑 3:训练集包含脏数据,模型学会了骂人

现象:回复里偶尔出现与人格设定完全不符的侮辱性词汇。

原因:开源中文语料里脏话和性别歧视内容的占比远比想象的高,模型会把高频脏话学进分布里,尤其当用户输入长时间的负面情绪时,模型会从“接住情绪”滑向“学习负面对话模式”。

解决:预处理阶段做三层过滤:一是构建敏感词表踢掉整句含脏话的样本;二是统计每条样本的情感极性,负面情绪的样本按一定比例丢弃(但要保留一部分用于安全回复训练);三是训练完成后用一批负面输入做测试,如果发现仍有漏网,把这类输入对应的正确回复单独构造 500 条样本重新微调。人工审核不可省略,这个步骤决定项目能不能安全落地。

5.5 坑 4:过拟合和泛化能力不足——验证集 loss 远高于训练集,但训练集本身太小

现象:训练集 loss 降到 1.8,验证集 loss 还有 3.5,模型在训练集上生成的回复非常流畅,但在测试集上全是套话。

原因:训练样本太少(比如只有 2 万条对话),模型记住了训练集的句式和话题分布。

解决:如果没法扩数据,可以做“数据增强”:把多轮对话随机裁剪出不同长度作为新样本(比如 4 轮变 3 轮、2 轮),这会增加模型对上下文长度的鲁棒性;或者做“角色互换增强”,把用户和助手的身份调换生成新样本,但这个操作对聊天机器人来说伤感情表达,建议慎用,只做轮次裁剪就很有效。

# 轮次裁剪增强:从原始对话中随机截取连续片段 python augment_chat_data.py \ --input ./processed/train.jsonl \ --output ./processed/train_aug.jsonl \ --min_rounds 2 \ --max_rounds 10 \ --num_augment 3

6. 推理端到端落地的技巧:采样参数、注入记忆与本地部署

训练只是走了半程,真正让模型看起来像“智能聊天机器人”的是推理阶段的设计。这里有两个层面:一是生成策略的参数调节,二是上下文记忆的管理。

生成策略上,我推荐一套稳定参数:temperature=0.85, top_k=40, top_p=0.9, repetition_penalty=1.1,max_new_tokens=80。temperature低于 0.7 时回复会趋于平淡保守,高于 1.0 时开始出现语法断裂;repetition_penalty对缓解复读机很有效,但超过 1.3 会让回复变得前言不搭后语。特别提醒:HuggingFace 的generate()默认关闭采样,如果不开do_sample=True,你调半天 temperature 都没用,只会走 greedy decoding。

记忆管理上,最简单的方案是“滑动窗口 + 最近 N 轮优先”。把对话历史按[EOS]拼成一个序列,超过max_seq_len - 预留生成长度的部分从头部裁剪。剪的时候优先保留最近的 6-8 轮,其余全部丢弃。如果想让模型记住用户名字、住的城市这些长期信息,就在每次拼接历史时,把一段固定的“记忆块”放在最前面,这段内容由外部规则维护,每次对话时动态更新,不需要模型自己记忆。

def generate_reply(model, tokenizer, history, memory_text="", max_new_tokens=80): # memory_text 如 "你叫阿杰,小雅喜欢喝奶茶,你们在一起半年了。" input_text = memory_text + tokenizer.eos_token for role, text in history: if role == "user": input_text += "用户:" + text + tokenizer.eos_token else: input_text += "助手:" + text + tokenizer.eos_token input_text += "助手:" inputs = tokenizer(input_text, return_tensors="pt", max_length=512, truncation=True) with torch.no_grad(): outputs = model.generate( input_ids=inputs["input_ids"], attention_mask=inputs["attention_mask"], max_new_tokens=max_new_tokens, do_sample=True, temperature=0.85, top_k=40, top_p=0.9, repetition_penalty=1.1, pad_token_id=tokenizer.eos_token_id ) reply = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) return reply.strip()

实测中,加了memory_text之后模型会稳定地称呼用户的名字和二人在“记忆块”里约定的事,这在答辩演示时非常加分。这块代码放在inference.py里单独成模块,比把推理逻辑写进训练脚本里干净得多。

本地部署层面,如果你只有 CPU,也可以用model.to("cpu")跑,124M 模型单次生成 80 token 大约耗时 3-6 秒,交互体验勉强能说得过去;有 GPU 则建议开启torch.inference_mode()替代torch.no_grad(),并设置torch.backends.cudnn.benchmark=True,能再快一点。如果对延迟更敏感,可以上onnxruntime,但需要处理 GPT-2 的缓存结构,投入产出比不高,毕设阶段不建议为这个折腾。

我想提醒的是推理和训练不要混用同一块显存空间:训练完的模型直接把model.train()切到model.eval()并不算完,Dropout 层的状态和训练时的 LayerNorm 统计还在,必须重新加载一遍from_pretrained再做推理,否则线上效果和训练时看到的测试结果会有肉眼可见的差距。

最后说一个老话题:这类“女友”模型的道德边界。它本质上是把情感陪伴需求做成技术方案,标题怎么写是你的事,落到产品上要守住底线——不教唆、不自我伤害强化、不替代真实人际关系。我会在记忆块里硬编码一段“安全边界人设”,比如聊到自杀倾向时会固定回复“这些事听起来很痛苦,但你愿意的话,我们一起去看看身边的专业帮助好不好?”这个设计在技术上是几行代码,在意义上是对用户负责。希望帮到你。

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

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

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

立即咨询