0. 系列闭环
| 本篇位置 | 上游 | 本篇产出 | 下游 |
|---|---|---|---|
| 第 2/10 篇 | 第 01 篇场景定义 | 可训练的 JSONL 规范 | 第 05 篇 apply_chat_template · 第 08 篇 SYSTEM_PROMPT |
数据错误会直接体现在 loss 上,但更隐蔽的是格式错误:模型学会了特殊 token 排列,却没学会共情。本篇所有规则都对齐 load_jsonl_data 的实际消费方式。
1. 要解决什么问题
SFT 不是「把文档塞进模型」,而是让模型在 (system, user) 条件下,生成接近标注 assistant 的 token 序列。
本项目的数据难点:
- 风格高度一致:assistant 普遍「先共情 + 再陪伴」,句式重复度高,适合小数据 SFT,也容易被 critic 说成「模板」——这是场景取舍,不是数据失误。
- user 必须口语化:均长约 20 字,像老人说话,不像问卷。
- system 必须固定:1000 条样本共用同一段 system(见 JSONL 第 1 行),与
verify_lora.py第 33–36 行、vLLM请求里的 system 保持一致。
2. 实现位置
| 文件 | 作用 |
|---|---|
LoRA_Demo/data/elderly_chat.jsonl |
训练语料,1000 行 |
LoRA_Demo/train_lora_single.py → load_jsonl_data() |
第 102–127 行,JSONL → Dataset({"text": ...}) |
LoRA_Demo/verify_lora.py → SYSTEM_PROMPT |
推理时必须与 JSONL system 一致 |
hexo-cli/docs/xx.jsonl |
同源扩展集(语义训练集生成脚本产出,与 Demo 内文件 MD5 不同,结构相同) |
训练日志显示加载 1000 条(all_logs.log 第 31 行 1000/1000)。本地 wc -l 若为 999,应检查末尾是否缺换行,避免少训一条而不自知。
3. JSONL 单条规范
3.1 结构
1 | |
3.2 三个 role 的分工
| role | 是否必须 | 训练中的作用 |
|---|---|---|
| system | 是 | 全局人设;推理时也要传入 |
| user | 是 | 条件输入,模拟老人倾诉 |
| assistant | 是 | 监督标签,SFT 学习其 token 序列 |
不要拆成两个文件或只用 user-assistant 对:Qwen3.5 的 chat_template 期望完整 messages,缺 system 会导致训练/推理分布不一致。
4. system prompt 逐句设计 rationale
全文(与仓库一致):
1 | |
| 句 | 约束类型 | 针对的失败模式 |
|---|---|---|
| 温柔、耐心、善解人意 | 人格 | 冷冰冰的助手腔 |
| 慢一点、软一点 | 节奏 | 短促命令式 |
| 多共情、多倾听、多肯定 | 行为 | 跳过情绪直接给方案 |
| 不说教、不反驳、不催促 | 负向 | 「您应该…」「别这么想」 |
负向约束在通用模型里尤其重要:它们默认是「helpful assistant」,容易进入教导模式。
5. 五类心理主题与样本分布
| 类别 | 条数 | 覆盖心理 | user 示例方向 |
|---|---|---|---|
| 孤独 & 渴望陪伴 | 200 | 被冷落、无人说话 | 「家里静得慌」「连个搭伴的都没有」 |
| 健康焦虑 & 怕死 | 200 | 疾病恐惧、死亡焦虑 | 「血压高了怕哪天没了」「不敢看体检单」 |
| 害怕添麻烦 & 自责 | 200 | 负罪、怕拖累子女 | 「净给孩子添麻烦」「不该让他们请假」 |
| 怀旧 & 渴望认同 | 200 | 价值感、被看见 | 「以前日子苦但开心」「没人听我讲过去」 |
| 情绪低落 & 渴望被需要 | 200 | 抑郁倾向、无用感 | 「活着没劲儿」「没人需要我」 |
扩充数据时按类均衡,避免某一类过多导致模型只会回「别孤独了」式空话。
6. 好样本 / 坏样本(质检用)
合格 assistant(来自 JSONL 第 2 条):
1 | |
特征:承认情绪 → 轻量开放式邀请 → 无命令句。
不合格 assistant(不要进训练集):
1 | |
特征:清单式建议、无「您」的情感对齐、像健康 App 而非陪伴。
7. 数据如何进入训练循环
核心代码:LoRA_Demo/train_lora_single.py 第 102–127 行
1 | |
7.1 训练 vs 推理的 template 差异
| 阶段 | add_generation_prompt |
文件位置 |
|---|---|---|
| 训练 | False |
train_lora_single.py |
| 推理 | True |
verify_lora.py 第 159–164 行 |
训练时要让模型看到 完整多轮格式;推理时在末尾加 assistant 起始,让模型开始生成。
7.2 TRL 二次处理(日志可见)
all_logs.log 第 32–33 行:
1 | |
之后由 SFTConfig(max_length=512) 截断。若单条对话 token 超 512,尾部被截断——对本项目(短句对话)通常无影响;若将来加长回复,需提高 MAX_SEQ_LEN 并重新评估显存。
8. 数据量:1000 条是否够
对本项目(窄域风格 SFT):
- Step 250 时 loss 已从 2.81 降到 0.24(
all_logs.log),说明 1 epoch 已学到主要格式。 - 3 epoch 到 0.13,边际收益递减,符合小数据预期。
若扩展到知识型(用药指南、政策解读),1000 条不够;若仍是情感陪伴风格,1000 条 + 均衡五类是合理起点。
9. 踩坑
坑 1:JSONL 合法但 UTF-8 BOM 导致首行解析失败
首条样本静默丢失,loss 仍能降,少一条无感。用 json.loads 前确保无 BOM,或用 wc -l 对账日志里的 1000。
坑 2:assistant 复制粘贴导致 user-assistant 完全重复
SFT 会过拟合重复句,验证时对未见 user 泛化差。扩数据时用模板组合时要做去重(见 hexo-cli 扩充脚本里的 existing_users set)。
坑 3:Mac 验证时 system 与训练不一致verify_lora.py 的 SYSTEM_PROMPT 必须与 JSONL 逐字相同;改了一处忘了另一处,会误判「微调无效」。
坑 4:实测泛化边界(Mac MPS 验证)
对「我怕体检单不敢看,看了就睡不着」,LoRA 回复可能套相近的「怕拖累别人」话术,而未精确点名「体检单」。写产品文档时要诚实:风格迁移成功 ≠ 每句都精准定制。
10. 小结
- 数据格式:
messages三角色,一行一条 JSONL。 - system 是全系列「人格宪法」,训练/验证/ vLLM 必须一致。
- 五类心理主题各 200 条,控制风格与覆盖面。
load_jsonl_data+apply_chat_template是训练入口,参数与推理相反。- 质检重点:口语 user、共情 assistant、无说教、无重复。
附录:load_jsonl_data 逐段说明
1 | |
系列导航
| 篇目 | 链接 |
|---|---|
| 上一篇 | 01 · 为什么做 |
| 下一篇 | 03 · LoRA 原理 |
| 索引 | README |