生成式AI原理与实战(Part 3)学习笔记:嵌入模型、表示微调与生成模型微调
- 约 14 分钟读完 - 全文 5333 字生成式AI原理与实战(Part 3)学习笔记:嵌入模型、表示微调与生成模型微调
这是《生成式AI原理与实战》学习笔记的第三篇。Part 1 聊了大模型的“底层零件”,Part 2 聊了提示工程、Agent 和 RAG;Part 3 则进入“模型训练与微调”领域:怎么训练一个好的文本嵌入模型、怎么在数据少的情况下微调分类模型、怎么用 LoRA/DPO/PPO 把通用大模型变成业务模型。目标读者仍然是刚接触 AI 的初中级开发者。
写在前面:为什么开发者要懂微调?
调用 API 能解决 80% 的问题,但剩下的 20% 往往需要你接触模型本身:
- 公司私有文档的检索效果差,要不要自己训练嵌入模型?
- 标注数据只有几百条,能微调分类模型吗?
- 想让大模型输出符合公司风格的文案,除了写 prompt 还能做什么?
- 排行榜第一的模型,为什么到自己业务里表现一般?
这篇文章会用“概念 + 图 + 工程直觉”来回答这些问题。
一、文本嵌入模型:让“语义”变成可计算的向量
嵌入模型(Embedding Model)的作用,是把文本变成固定长度的向量。语义相近的文本,向量距离就小;语义不同的文本,向量距离就大。它是 RAG、语义搜索、推荐系统的核心组件。
1.1 为什么对比学习需要负样本?
训练嵌入模型时,如果只告诉模型“这两句话很相似,向量要靠近”,模型可能偷懒:把所有文本都编码成同一个向量。这样无论输入什么,相似度都很高,模型就失去了区分能力。
负样本的作用就是告诉模型:这些不相似的文本,向量必须拉开。
flowchart LR
subgraph 只有正样本
P1[A] --> C[中心点]
P2[B] --> C
P3[C] --> C
end
subgraph 加入负样本
N1[A] -.->|拉近| P[正样本对]
N2[B] -.->|拉近| P
N3[C] -->|推远| P
end
高质量训练的关键是难负样本(Hard Negatives):那些看起来很像正确答案、但实际不对的样本。比如问“北京在哪里”,错误答案“上海是中国城市”就是一个难负样本,它会让模型学会更细粒度的区分。
1.2 双编码器 vs 交叉编码器
工业界的语义搜索通常分两阶段:
flowchart LR
Q[查询] --> B[双编码器 Bi-Encoder]
D[文档库] --> B
B --> C[候选 Top-k]
C --> X[交叉编码器 Cross-Encoder]
X --> R[精排结果]
| 特性 | 双编码器 | 交叉编码器 |
|---|---|---|
| 计算方式 | 查询和文档分别编码成向量 | 查询和文档拼接后一起输入模型 |
| 速度 | 快,文档向量可离线预计算 | 慢,需要实时成对计算 |
| 精度 | 较低,缺乏细粒度交互 | 高,能捕捉词级关系 |
| 用途 | 召回阶段:从海量文档中快速找候选 | 精排阶段:对少量候选做精确排序 |
对初级开发者的建议:不要一上来就用交叉编码器处理百万文档,会慢得无法接受。标准做法是“双编码器召回 Top-100 + 交叉编码器精排”。
1.3 训练嵌入模型的损失函数怎么选?
| 损失函数 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| MNR(多负例排序) | 适合对比学习,能利用批量内负样本 | 依赖高质量负样本,计算随负例增加 | 大规模检索、对比预训练 |
| 余弦相似度损失 | 方向敏感,计算轻量,适合归一化嵌入 | 对难负样本区分度不足 | 文本相似度、资源受限场景 |
| Softmax 损失 | 直接优化分类,输出有概率意义 | 嵌入通用性差,不适合检索 | 意图识别、明确分类任务 |
1.4 没标注数据怎么办?TSDAE 无监督预训练
TSDAE(Transformer-based Sequential Denoising Auto-Encoder)的思路是:把输入文本加噪声(如随机删词、替换),再让模型重建原文。模型为了重建,必须学会抓住句子的全局语义。
flowchart LR
I[原始句子] --> N[加噪声]
N --> E[Encoder 编码]
E --> D[Decoder 重建]
D --> O[还原原始句子]
TSDAE 用特殊词元(如 [CLS])作为句子表征,而不是平均池化。原因是:[CLS] 在训练中被迫压缩全局信息以重建整个句子,信号更强;而平均池化会被无关词稀释。
1.5 怎么评测嵌入模型?
MTEB(Massive Text Embedding Benchmark)是比 STS-B 更全面的评测基准,包含 8 类任务:
- 语义文本相似度(STS)
- 检索(Retrieval)
- 重排序(Reranking)
- 分类(Classification)
- 聚类(Clustering)
- 配对分类(Pair Classification)
- 摘要评估(Summarization)
- 双语文本挖掘(Bitext Mining)
如果你做的业务是问答检索,就不要只看 STS 分数,要看 Retrieval 和 Reranking 指标。
1.6 增量学习:模型需要“与时俱进”
世界在变化,新词、新概念不断出现。嵌入模型也需要更新:
flowchart TD
A[收集新数据] --> B{分布是否变化?}
B -->|是| C[概念漂移检测]
B -->|否| D[继续监控]
C --> E{性能是否退化?}
E -->|是| F[增量微调]
E -->|否| D
F --> G[更新模型]
G --> A
检测方法包括:监控 KL 散度、KS 检验、在线评估指标等。更新时可以用小步微调,而不是从头训练。
二、表示模型微调:把小数据用出大价值
很多时候我们只有少量标注数据,却想做一个不错的分类或 NER 模型。这就需要“表示微调”的技巧。
2.1 微调时冻结哪些层?
BERT 这类预训练模型,底层学习通用语法和语义,高层学习任务相关特征。微调时可以根据自己的情况决定:
flowchart LR
I[输入] --> L1[低层 Transformer<br/>通用语法语义]
L1 --> L2[高层 Transformer<br/>任务相关语义]
L2 --> H[分类头]
H --> O[输出]
style L1 fill:#e1f5e1
style L2 fill:#fff2cc
style H fill:#f8cecc
| 场景 | 策略 |
|---|---|
| 数据很少 | 冻结底层,只微调高层 + 分类头 |
| 数据较多 | 全模型微调 |
| 任务与预训练差异大 | 多微调几层 |
| 资源有限 | 冻结更多层,节省显存 |
2.2 SetFit:几百条数据也能做文本分类
SetFit 是专门做少样本文本分类的框架。它的核心思想是:先用对比学习微调 Sentence Transformer,再在它生成的向量上训练一个轻量分类器。
flowchart LR
S[少量标注样本] --> P[构造正负样本对]
P --> C[对比学习微调 Encoder]
C --> E[生成文本嵌入]
E --> CL[训练分类器<br/>SVM / Logistic Regression]
CL --> M[最终分类模型]
为什么有效?因为即使是每类只有 5 条样本,两两组合也能生成很多训练对。对比学习让同类样本靠近、不同类样本远离,相当于学到了一个“任务专属的嵌入空间”。
2.3 继续预训练:拿到领域知识,但别丢了通用能力
让通用 BERT 适应医疗、法律等垂直领域,常用做法是继续预训练。但直接在全领域数据上训练,可能让模型忘掉通用语言知识,这叫灾难性遗忘。
缓解方法:
- 混合训练:领域数据 + 通用数据一起训练。
- 分阶段训练:先用通用数据 warm-up,再逐步增加领域数据,最后再用少量通用数据巩固。
- Adapter / LoRA:只训练少量新增参数,保留主干模型。
- EWC(弹性权重巩固):对重要参数加约束,防止大幅偏离。
- 知识蒸馏:用原始通用模型当教师,保留其预测分布。
2.4 垂直领域分类:三种方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 直接微调通用 BERT | 成本低、稳定 | 领域术语理解弱 | 快速验证、术语不复杂的任务 |
| 继续预训练 BERT 再微调 | 领域适应性强 | 需要大量无标注领域数据 | 有资源、有数据的团队 |
| 从头预训练领域模型 | 最强领域定制 | 资源消耗巨大、风险高 | 超大规模语料和算力 |
对大多数团队来说,方案(b)是性价比最高的。
2.5 NER 中的子词标签对齐
BERT 用 WordPiece 分词,会把一个词拆成多个子词。例如 “playing” → “play” + “##ing”。但 NER 标签是按词标注的,这就需要对齐:
- 只标注第一个子词:最简单常用,其余子词不参与损失计算。
- 标签传播到所有子词:每个子词都复制原词标签。
- 标签传播 + 修复规则:第一个子词用原标签,后续子词改成
I-或O,避免 BIO 格式混乱。
2.6 嵌入式设备上的小模型多任务训练
如果要在手机、IoT 设备上跑模型,需要:
- 选轻量模型:MiniLM、TinyBERT、DistilBERT。
- 多任务学习:共享一个编码器,接不同任务头。
- 模型压缩:量化(INT8)、剪枝、知识蒸馏。
- 部署格式:ONNX、TFLite。
flowchart LR
I[输入文本] --> E[Shared Encoder]
E --> C[分类头]
E --> N[NER 头]
E --> V[Embedding 头]
C --> O1[分类结果]
N --> O2[实体标签]
V --> O3[语义向量]
三、生成模型微调:让通用模型变成“你的模型”
3.1 SFT:监督微调是最常用的起点
SFT(Supervised Fine-Tuning)就是准备好 (prompt, response) 数据对,继续训练模型。比如你想让模型输出更简洁的微信聊天风格,就准备大量“问题 → 简短回答”的样本。
flowchart LR
D[准备 prompt-response 数据] --> S[SFT 微调]
S --> E[评估效果]
E -->|不够好| A[补充/清洗数据]
A --> D
E -->|效果好| P[上线部署]
数据量建议(QA 对):
| 模型大小 | 目标 | 推荐数据量 |
|---|---|---|
| 1B - 7B | 记住有限文档知识 | 1k - 20k |
| 13B - 30B | 强记忆 + 通用指令 | 10k - 100k |
| 70B+ | 企业问答 + 任务泛化 | 50k - 500k |
3.2 LoRA 与 QLoRA:大模型微调的省钱方案
全参数微调 70B 模型需要大量 GPU。LoRA 的核心思想是:不在原始权重上直接更新,而是在每层旁边加两个小矩阵(低秩分解),只训练这两个小矩阵。
flowchart LR
X[输入] --> W[原始权重 W]
W --> O[输出]
X --> A[低秩矩阵 A]
A --> B[低秩矩阵 B]
B --> O
LoRA 的参数量大约是全量微调的 1 / (d / 2r) 倍。例如 d=12288,r=8,参数量减少约 768 倍。
QLoRA 在此基础上做了分块量化:把权重矩阵切成小块,每块独立量化,保留各自的 scale 和 zero-point。这样比全局量化更精确,能在消费级 GPU 上微调大模型。
3.3 超参数设置与停止训练
| 超参数 | 推荐范围 | 说明 |
|---|---|---|
| 学习率 | 1e-5 - 2e-4 | LoRA 可用稍大学习率 |
| LoRA rank | 4 - 64 | 小任务 8/16,复杂任务 32+ |
| LoRA alpha | rank × 2 - rank × 8 | 控制 LoRA 对主模型的影响强度 |
什么时候停止?
- 验证集 loss 连续 N 个 epoch 不下降就停。
- 不要只看 loss,还要看具体指标(准确率、F1、BLEU、ROUGE)。
- 生成任务最好加人工评估,loss 低不代表回答好。
3.4 损失函数应该计算输入部分还是输出部分?
推荐:只计算输出部分。
原因:
- 推理时模型只负责生成输出,训练时只对输出优化更符合实际使用场景。
- 输入部分是人类写好的 prompt,让模型预测它没有意义,还会引入噪声。
- 计算量更小,训练更快。
3.5 PPO 与 DPO:让模型符合人类偏好
SFT 让模型“学会回答问题”,但不一定“回答得好”。RLHF(基于人类反馈的强化学习)就是解决这个问题。
经典 RLHF 三阶段
flowchart LR
S[SFT 模型] --> R[训练奖励模型<br/>Reward Model]
R --> P[PPO 强化学习]
P --> A[对齐后的模型]
- SFT:训练模型学会基本对话。
- 训练奖励模型:收集人类偏好数据,训练一个打分模型。
- PPO 强化学习:用奖励模型指导策略模型优化。
PPO 里的四个角色
flowchart TD
Prompt --> A[Actor<br/>生成回答]
A --> C[Critic<br/>评估状态价值]
A --> R[Reward Model<br/>给回答打分]
A --> Ref[Reference Model<br/>计算 KL 散度<br/>防止跑偏]
| 模型 | 作用 | 是否训练 |
|---|---|---|
| Actor | 生成回答的主模型 | 是 |
| Critic | 估计价值,计算优势函数 | 是 |
| Reward Model | 给回答质量打分 | 否(通常预训练好) |
| Reference Model | 计算 KL 散度,防止策略偏离初始模型 | 否 |
PPO vs DPO
| 对比项 | PPO | DPO |
|---|---|---|
| 训练范式 | 强化学习(on-policy) | 监督学习(off-policy) |
| 是否需要奖励模型 | 需要 | 不需要 |
| 实现复杂度 | 高 | 低 |
| 数据利用率 | 低,只能用当前策略生成的数据 | 高,历史偏好数据都能用 |
| 稳定性 | 较敏感,容易 reward hacking | 更稳定 |
数据少的时候优先用 DPO,它只需要 (prompt, chosen, rejected) 三元组;等数据量和工程能力都上来了,再考虑 PPO。
3.6 DPO 的 beta 参数
beta 控制模型对偏好差异的敏感程度:
- beta 小:偏好信号平滑,训练稳定,但可能学不到足够强的偏好。
- beta 适中(0.5-1.0):常用默认值。
- beta 大:偏好差异被放大,模型对偏好更敏感,但可能过拟合或不稳定。
3.7 把用户行为变成 DPO 数据
如果你运营一个 ChatGPT 类网站,用户行为天然就是偏好信号:
| 用户行为 | 含义 | DPO 用法 |
|---|---|---|
| 点赞 | 喜欢这个回答 | 当前回答为 chosen |
| 点踩 | 不喜欢 | 当前回答为 rejected |
| 重新生成 | 否定当前,偏好新的 | 新回答为 chosen,旧的为 rejected |
| 复制/分享 | 非常满意 | 当前回答为 chosen |
对于小红书这类图文混合平台,停留时长受图片影响大,需要结合点赞、收藏等行为综合判断;知乎这类长文本平台,停留时长、评论互动更能反映内容质量。
3.8 对齐、隐私与提示词注入
对齐问题:模型目标函数与人类真实意图不一致。解决办法包括 RLHF/DPO、安全微调、拒绝训练、输出审查等。
隐私保护:
- 训练前脱敏、移除 PII。
- 训练中使用差分隐私优化。
- 训练后扫描模型是否记住了敏感信息。
- 部署时加输出拦截和 prompt 注入检测。
通过微调防御提示词注入:
- 在训练数据里加入大量注入攻击样本,标注正确拒答。
- 对抗式微调:生成各种攻击变体让模型学习。
- 强化系统角色一致性,让模型坚持原始身份。
四、从面试题到工程实践
- 嵌入模型优先用现成的。MTEB 上排名靠前的模型(如 BGE、E5、GTE)通常足够好用,不要自己从头训练。
- 数据少时试试 SetFit。几百条标注也能做出不错的分类器。
- 大模型微调先 LoRA/QLoRA。全参数微调太贵,LoRA 是性价比之选。
- RLHF 先 DPO 后 PPO。DPO 实现简单、数据利用率高,适合起步。
- 不要迷信排行榜。榜单分数高不代表业务效果好,要结合实际场景评估。
- 评估指标要对齐业务目标。分类看 F1,检索看 Recall@K,生成看人工评估,不要只看 loss。
结语
Part 3 的内容从“怎么训练嵌入模型”讲到“怎么微调分类模型”,再到“怎么对齐生成模型”。对初级开发者来说,重点是理解这些技术的适用场景和 trade-off:
- 嵌入模型:双编码器快但粗,交叉编码器慢但准;负样本决定对比学习效果。
- 表示微调:数据少就用 SetFit,数据多就全量微调;领域适配要注意灾难性遗忘。
- 生成模型微调:SFT 是起点,LoRA 省钱,DPO/PPO 对齐人类偏好。
掌握这些,你就能从“会调用模型”进阶到“会根据业务需求选择和调整模型”。
参考:《生成式AI原理与实战 —— Part 3》