
简介一个可直接基于自有语料训练的中文聊天机器人项目以 Python 为核心同时提供 TensorFlow 2.x 与 PyTorch 两个主流框架的版本。项目内置 seq2seq、SeqGAN、分布式 seq2seq 等模型路线适合算法学习者、服务端开发者在智能客服、在线问答、闲聊机器人等场景中快速落地和二次开发。压缩包共 85 个文件大小约 37.94MB以 18 个 Python 脚本为核心并搭配 20 个 js、15 个 css、5 个 html 构成可视化与交互前端另有 vocab 词表、data 示例数据和 md 使用说明。目录按 Tensorflow2.0、SeqGANchatbot、Distribute_seq2seqchatbot、Chatbot_pytorch 等模块拆分方便按需调用工程在 1.0 版本中重新整合了整体结构使用更统一。目前已有 1069 人浏览学习。拿到后可直接替换自己的语料进行训练也能借鉴 FAQ 问答与闲聊的无缝切换机制搭建更自然的对话服务项目还规划了基于 Transformer 的预训练模型接入方向并提供依托 horovod 的分布式训练配置支持 batch_size 训练模式。对照多个版本可清晰理解生成式对话模型从基础到分布式训练的演进节省从零搭建和调参的时间。1. 为什么“能自己训练”的中文聊天机器人落点其实是微调而不是从零训练一个可以自己训练的中文聊天机器人听上去像是要把一堆语料喂给某个程序然后它就学会用你想要的口吻回答问题了。这正是标题背后真正的需求用 Python 代码和自有数据训练出一个领域、语气、边界都由自己控制的中文对话模型。但真动起手来你会发现“训练”这个词的门道比想象中多它不等于把文本丢给模型而是“语料整理 → 基底模型选择 → 训练参数调整 → 部署验证”一整套工程。下面我以 Python 生态里最常见的开源微调工具链为例把这条路完整走一遍。运维、后端、算法新人照着做能跑通第一版有模型基础的人重点看参数设置和验证方法。2. 训练前想清楚为什么是“微调LoRA”而不是“从零训练”2.1 从零训练为什么撑不起来语料量级和算力账一笔账先算在前面。所谓“自己的语料”绝大多数情况下是几百到几万条对话记录。假设你整理出 1 万条、平均每条 100 字也才约 100 万字换算成 token 只有数百万。这个量级离从零预训练模型的需求差着两三个数量级——一个 1B 参数级别的 Transformer预训练语料通常要数十亿 token7B 模型的预训练语料更是以万亿 token 计。把这一小把数据反复喂给模型网络大概率会“背答案”而不是“学规律”遇到问法一换就会崩。另一个问题是算力。即便抛开数据量从零训练一个 10B 量级的模型单卡训练周期以月为单位多卡集群又不是个人项目能随手获得的。而开源社区里已经有一批中文理解能力相当强的底座模型它们完成了耗资巨大的预训练语言能力、常识、推理能力都是现成的。你要做的不是教模型学中文而是在它已有的能力上做“行为修正”让它知道在你的业务场景里什么问题该用什么语气答、答到什么详细程度。所以这个标题下真正普遍的做法是选择一个开源中文底座模型整理自有语料在其上进行微调。这也解释了为什么你在搜索时会看到大量“lora训练”“模型训练”“llama factory”相关的结果——它们讨论的都是同一条技术路线。2.2 用 LoRA 控制显存消耗全参微调与 QLoRA 的账那为什么不直接全参数微调算一笔显存账就清楚了。以一个 7B 参数的模型为例bf16 权重本身约占 14GB训练时还要额外保存梯度、优化器状态AdamW 的动量和方差这些加起来很容易超过 100GB这不是一张消费级显卡能承受的。LoRA 的思路是冻结原模型全部权重只在每层 Transformer 中注入两个低秩矩阵 A 和 B只训练这两个矩阵。7B 模型可训练参数只有几百万到千余万显存压力骤减。再配合 4-bit NF4 量化即 QLoRA把冻结权重压缩到约 3.5GB整卡 16GB 就能训练 7B 模型24GB 的 4090 则跑得相当从容。方案可训练参数量所需显存7B 模型估算适用场景全参数微调约 6.9B100GB 以上有数据中心级资源不适用于个人项目LoRAbf16 基座1000 万以内约 24GB单卡 4090 可跑效果稳定QLoRA4-bit 基座1000 万以内约 16GB单卡 3060/3080 可跑个人首选2.3 中文底座模型怎么选从 Qwen2.5 系列起步底座模型的选择直接决定微调效果的上限。个人微调中文聊天机器人目前社区里最省心的选择是 Qwen2.5 系列中文预训练语料占比高分词器对中文友好Instruct 版本自带一套清晰的对话模板微调时可以少处理很多对齐问题。如果你的语料场景更接近严肃客服ChatGLM 系列也可以作为备选。不过要注意各模型的许可证条款商用场景需逐一确认。如果你只有一张低显存卡如 8GB可以考虑 Qwen2.5-3B 或更小的 1.5B 版本训练速度更快但对复杂业务语义的理解会弱一些。选择时可以先拿几个典型问题在小模型上试跑再决定是否升级到 7B。3. 用自己的语料跑通第一轮 LoRA 训练3.1 训练环境的最小配置与 Python 依赖安装训练环境是卡住新手的第一道坎。硬件上推荐 NVIDIA 显卡显存 16GB 以上可以流畅跑 7B QLoRA12GB 也能跑但要控制序列长度和 batch size8GB 建议改用 3B 模型。CPU 只能做推理训练效率低到难以接受。最近常有朋友问 python 安装教程这里给出我的固定建议不管 Windows 还是 Linux用 Anaconda 管理 Python 环境别用系统自带的 Python否则后面 PyTorch 和 CUDA 版本冲突时会很痛苦。如果你日常在 VS Code 里写代码记得装好 Python 扩展后把解释器切换到创建的虚拟环境。conda create -n chatbot python3.10 -y conda activate chatbot pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install transformers datasets peft trl accelerate pip install llama-factory说明torch 安装命令中的cu121表示 CUDA 12.1 对应的版本驱动较新的机器也能兼容。后面四个库分别是模型加载、数据集处理、LoRA 实现、训练器llama-factory是封装好的微调平台内部已经依赖了前面这些组件。若下载慢可在 pip 后追加-i参数指向国内 PyPI 镜像模型权重下载同样可以设置镜像源。3.2 把日常语料整理成 SFT 的 instruction/input/output 格式微调用的语料有固定的组织格式。当前主流工具链统一采用“指令、输入、输出”三段式结构instruction放系统指令或角色设定input是用户提问output是期望回答。这种格式的好处是训练模板统一换工具时不用重写数据。{instruction: 你是某电商平台的客服助手回答要简短、礼貌、口语化, input: 我下单后多久发货, output: 您好默认48小时内发出哦~} {instruction: 你是某电商平台的客服助手回答要简短、礼貌、口语化, input: 发货后可以修改地址吗, output: 发货后就改不了了建议您下单前仔细核对地址。}LLaMA Factory 需要把数据集注册到data/dataset_info.json中文件结构与上面的字段一一对应{ my_chat: { file_name: my_chat.json, columns: { prompt: instruction, query: input, response: output } } }注意同一个instruction不要反复出现一千次模型会误以为这是必须原样复述的内容指令变化太少等于在教模型“所有问题都用同一套话回答”。另外数据量最少也要几百条低于这个量级训练基本无意义。更多时候质量比数量更重要——后面第 5.3 节会讲怎么用少量种子数据扩充语料。3.3 清洗语料的几个必须写的过滤规则语料进入训练前至少要过一遍清洗脚本。坏数据对训练结果的污染远大于数据量不足一条带 HTML 标签的回答可能让模型在真实场景里突然吐出“br”一条重复出现十次的样本会让模型对某个问题产生固定输出。下面这个脚本覆盖了最基本的四类过滤。import re import json def clean_text(text: str) - str: # 去掉 HTML 标签和多余空白 text re.sub(r[^], , text) text re.sub(r\s, , text).strip() return text def is_valid_pair(inp: str, out: str) - bool: # 过滤空样本、过短样本、复读机样本 if len(inp) 4 or len(out) 2: return False if out.count(out[:10]) 3: return False return True with open(raw.json, r, encodingutf-8) as f: lines [json.loads(line) for line in f if line.strip()] seen set() deduped [] for item in lines: inp clean_text(item[input]) out clean_text(item[output]) if not is_valid_pair(inp, out): continue key (inp[:20], out[:20]) if key in seen: continue seen.add(key) deduped.append({instruction: item[instruction], input: inp, output: out}) with open(my_chat.json, w, encodingutf-8) as f: for item in deduped: f.write(json.dumps(item, ensure_asciiFalse) \n)clean_text处理明显的非对话噪声is_valid_pair里的复读检测专门对付“嗯嗯嗯嗯”“好的好的好的”这类敷衍样本seen集合通过前 20 个字符做轻量去重避免同一问题在数据集中占比过高。清洗完再人工抽查 50 条确认没有格式错位就可以注册到dataset_info.json里了。3.4 用 LLaMA Factory 跑起第一轮微调命令与参数选择LLaMA Factory 是当前训练环境里最顺手的微调平台数据注册、LoRA 训练、模型导出、推理验证都可以用命令行完成。训练 7B 模型的最小命令如下llamafactory-cli train \ --model_name_or_path Qwen/Qwen2.5-7B-Instruct \ --dataset my_chat \ --template qwen \ --finetuning_type lora \ --quantization_bit 4 \ --output_dir ./output/chatbot \ --num_train_epochs 3 \ --learning_rate 2e-4 \ --lora_rank 8 \ --lora_alpha 16 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 8 \ --max_seq_length 1024 \ --logging_steps 10 \ --save_steps 200template要按底座模型的对话模板填Qwen 就是qwen换 ChatGLM 则改为chatglm。quantization_bit 4表示用 NF4 量化基座显存不足时优先检查这一步。output_dir是 LoRA 权重的输出目录。下面这几个参数值得单独列出来参数推荐初始值调参方向learning_rate2e-4数据量少可降到 1e-4防止过拟合num_train_epochs3语料少于 1000 条时升到 5-8lora_rank8领域专业性强可升至 16但显存随之上涨lora_alpha16一般保持为 rank 的 2 倍max_seq_length1024回答过长才需要加大越大越耗显存gradient_accumulation_steps8显存不够就把 batch 再拆细训练时终端会滚动输出 loss。正常情况 loss 应该缓慢下降最终落在 0.5-1.5 区间如果 loss 从一开始就趋近于 0说明数据里有和基座原分布严重重复的文本如果 loss 反复震荡不降优先检查语料里是否存在大量相互矛盾的问答对。训练中途 OOM分两步处理先降per_device_train_batch_size到 1再降max_seq_length。4. 把训练好的模型拉起来对话4.1 导出合并后的模型避免运行时依赖 adapter训练产出的是 LoRA adapter 权重体积只有几百 MB。推理时有两种用法一种是运行时加载 adapter灵活但容易踩 dtype 不一致的坑另一种是直接把 LoRA 权重合并进底座模型导出一个完整的模型目录我一般推荐后者省心且方便部署。导出命令如下llamafactory-cli export \ --model_name_or_path Qwen/Qwen2.5-7B-Instruct \ --adapter_name_or_path ./output/chatbot \ --template qwen \ --finetuning_type lora \ --export_dir ./exported_modelexport_dir就是合并后的模型目录里面有config.json、model.safetensors、tokenizer等文件。导出完成后可以先加载这个目录做一个 10 分钟的冒烟测试确认回复风格符合预期再进入服务化阶段。合并后的模型体积约 15GB7B bf16如果磁盘吃紧也可以在导出时追加--export_quantization_bit 4直接把导出结果量化成 int4。4.2 写一个带 system prompt 的本地对话脚本对话脚本的核心是正确组装 messages 列表并传给底座模型。LLaMA Factory 的template qwen在训练时即按 Qwen 的 ChatML 格式拼接所以推理时也要保持一致用system消息固定角色把历史对话放进history最后追加当前问题。from transformers import AutoModelForCausalLM, AutoTokenizer model_dir ./exported_model tokenizer AutoTokenizer.from_pretrained(model_dir, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_dir, torch_dtypeauto, device_mapauto ) system_prompt 你是某电商平台的客服助手回答简短、礼貌、口语化。 history [] while True: user_input input(你: ).strip() if user_input in (exit, quit): break messages [{role: system, content: system_prompt}] for h in history[-4:]: messages.append({role: user, content: h[0]}) messages.append({role: assistant, content: h[1]}) messages.append({role: user, content: user_input}) inputs tokenizer.apply_chat_template( messages, add_generation_promptTrue, return_tensorspt ) outputs model.generate( inputs.to(model.device), max_new_tokens512, temperature0.7, top_p0.9, repetition_penalty1.05, ) reply tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokensTrue) print(机器人:, reply.strip()) history.append((user_input, reply.strip()))history[-4:]只保留最近四轮对话防止上下文超出max_seq_length。temperature越低回复越保守客服场景建议 0.7 左右闲聊场景可以提升到 0.9。repetition_penalty大于 1 可以抑制复读太低则可能让回答变得啰嗦。如果发现模型只回几个字把max_new_tokens调大如果回答不相关优先怀疑 system prompt 和训练时的角色设定不一致。4.3 用 FastAPI 包装成 HTTP 服务供其他系统调用本地脚本只能自己玩要接入实际业务得把它包成一个 HTTP 接口。FastAPI 是当前最简单的方式请求体传消息和历史记录接口返回回复文本。这样 QQ 聊天机器人、网页客服、企业微信机器人等场景只需要让网关把收到的消息 POST 到这个接口再把返回值发回对话窗口即可。from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class ChatRequest(BaseModel): message: str history: list[list[str]] [] app.post(/chat) def chat(req: ChatRequest): messages [{role: system, content: system_prompt}] for h in req.history[-4:]: messages.append({role: user, content: h[0]}) messages.append({role: assistant, content: h[1]}) messages.append({role: user, content: req.message}) inputs tokenizer.apply_chat_template( messages, add_generation_promptTrue, return_tensorspt ) outputs model.generate( inputs.to(model.device), max_new_tokens512, temperature0.7, top_p0.9, repetition_penalty1.05, ) reply tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokensTrue) return {reply: reply.strip()}启动服务后用curl -X POST http://localhost:8000/chat -H Content-Type: application/json -d {message: 今天能发货吗, history: []}验证即可。注意两个生产环境必须处理的问题一是接口没有任何鉴权不能暴露在公网二是一次请求触发一次完整推理并发太高会 OOM可以在前面加一个简单的排队或改用更专业的推理框架加速。首次请求会触发模型加载响应时间较长上线前最好在启动脚本里做一次预热请求。5. 训练后必做的验证与语料迭代技巧5.1 训练后先做这 5 个验证别急着拿去用训练完成不等于可以上线。拿同一批固定问题去测训练前后的模型你会发现很多隐藏问题只有在对比时才会暴露。第一验证训练语料本身能否被准确回复这是最基础的“记住了”测试。第二换几种说法问同一个问题比如把“多久发货”改成“发货要等几天”考察模型是否理解语义而不是死记硬背。第三测一个和业务无关的常识问题比如“中国的首都是哪里”如果在微调后答错了说明出现了灾难性遗忘训练过度了。第四连续追问五轮观察回复是否出现重复短句或逻辑断裂LoRA 训练不足时最容易出现这种症状。第五把用户可能用来“越狱”的敏感输入跑一遍确认模型没有被自己的语料带偏。这几项验证建议写成一个evaluate_case.py脚本固化在仓库里每次训练后统一执行。5.2 语料太少时的三个补救办法混合指令、调低学习率、轮次递减自有语料不足 500 条时直接训练会很快过拟合。常见的补救方案有三个。第一个是混合指令数据在my_chat之外混入 30% 左右的通用指令数据比如 Alpaca 中文版的子集让模型在学业务的同时保留通用对话能力这比只拿少量业务语料硬练稳得多。第二个是调低学习率语料越少学习率越低1e-4 甚至 5e-5 都可以试虽然收敛慢一点但不容易把底座模型原有的参数冲坏。第三个是轮次递减先以 5 个 epoch 跑一版观察验证集 loss如果第 3 轮之后 loss 就开始回升说明已经过拟合回退到第 3 轮的 checkpoint 即可。5.3 把一条 FAQ 扩写成一组多样化对话语料扩充是提升效果投入产出比最高的环节。以一条 FAQ 为例——“多久发货答默认48小时内发出”——不要只放原句而是扩写成一组语料把问题换成口语说法“今天下单明天能到吗”换一种回答方式“一般两个工作日左右发急的话可以备注加急”再加一条多轮对话“在吗在的请问有什么可以帮您我想问下发货时间”。这样模型学到的是“用户问法多样但意图一致”的映射而不是一个固定的 QA 对。每次扩写后重新训练再跑 5.1 的验证脚本看“未见过的说法”这一栏的通过率有没有提升。这个“扩充语料 → 重新训练 → 跑验证脚本”的循环就是所谓“自己训练”真正迭代起来的样子。把每次训练的数据版本和验证结果一起提交进 Git比单靠记忆靠谱得多。本文还有配套的精品资源点击获取