第5章 文件和微调¶
学习目标¶
- 掌握 OpenAI Files API 的文件上传、列表与删除操作
- 理解微调(Fine-tuning)的适用场景与代价
- 能够按规范准备 JSONL 训练数据
- 完成微调任务的创建、监控与部署全流程
- 学会用评估指标判断微调效果是否达标
问题导入¶
通用大模型很强大,但在特定领域总会"差一点":法律文书用语不够规范、客服回复语气与品牌不符、代码生成不符合公司内部框架约定。Prompt 工程能缓解一部分,但每次调用都要塞入大量示例,既费 token 又不稳定。微调(Fine-tuning) 通过在你的数据上继续训练模型,把领域知识"烤"进权重,从而在更短的 Prompt 下达到更好的效果。
微调不是银弹:它有数据准备成本、训练成本、维护成本,且不适合注入"频繁变化的事实"(那应该用 RAG)。OpenAI 提供了从文件上传、训练数据校验、任务创建到模型部署的完整 API。本章带你走完微调全流程,并在最后给出"何时该微调、何时该用 RAG"的决策框架。
核心概念¶
1. Files API¶
微调前必须先把训练数据上传为 OpenAI 文件。Files API 提供以下能力:
client.files.create(file=..., purpose="fine-tune"):上传文件,purpose 必须为fine-tune。client.files.list():列出已上传文件。client.files.retrieve(file_id):查询文件元信息。client.files.content(file_id):下载文件内容(可用于校验)。client.files.delete(file_id):删除文件。
文件格式为 JSONL(每行一个 JSON 对象),单文件上限与具体模型相关,需查阅官方文档。
2. 训练数据格式¶
微调数据遵循 messages 对话格式,每行是一个完整对话:
{"messages":[{"role":"system","content":"你是法律助手"},{"role":"user","content":"什么是不可抗力"},{"role":"assistant","content":"不可抗力是指..."}]}
要点:
- 每条样本的
messages必须包含 user 与 assistant,可包含 system。 - assistant 内容是模型要学习的"标准答案"。
- 建议 50 条以上起步,数百到数千条常见,质量比数量更重要。
- system 消息在训练与推理时应保持一致。
3. 微调任务管理¶
上传文件后创建微调任务:
job = client.fine_tuning.jobs.create(
training_file=file_id,
model="gpt-4.1-mini",
hyperparameters={"n_epochs": 3, "batch_size": "auto"},
)
任务状态:pending → validating_files → running → succeeded / failed。可用 client.fine_tuning.jobs.retrieve(job_id) 查询,或 client.fine_tuning.jobs.list_events(job_id) 查看事件流。训练完成后返回 fine_tuned_model 名称,即可在 Responses API 中作为 model 使用。
4. 超参与成本¶
关键超参:
n_epochs:训练轮数,通常 2-5,过多会过拟合。batch_size:默认 auto,小数据集建议手动设小。learning_rate_multiplier:学习率倍率,默认 auto。
成本按训练 token 计费,与训练数据量、轮数正相关。建议先用小数据子集试跑评估,再扩大规模。
5. 模型评估¶
评估微调效果需对比基线(未微调模型):
- 训练损失曲线:从
jobs.list_events获取,应平稳下降。 - 业务指标:用领域测试集对比准确率、格式合规率、人工评分。
- 回归测试:确保微调后通用能力未退化。
若效果不达标,常见手段:增加高质量样本、调整 system 消息一致性、降低 epoch 防过拟合。
原理图¶
flowchart TD
A[准备 JSONL 数据] --> B[Files API 上传]
B --> C[创建微调任务]
C --> D[validating_files]
D --> E[running 训练]
E --> F{succeeded?}
F -->|是| G[得到 fine_tuned_model]
F -->|否| H[查 events 排错]
H --> A
G --> I[评估测试集]
I --> J{达标?}
J -->|是| K[部署到生产]
J -->|否| A
K --> L[监控线上指标]
L --> M[定期迭代数据]
图:微调从数据准备到部署的完整闭环
示例代码¶
import os
import json
import time
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# 1. 准备训练数据并写成 JSONL
def build_training_data(path: str = "training.jsonl"):
samples = [
{
"messages": [
{"role": "system", "content": "你是公司客服助手,回答简洁礼貌。"},
{"role": "user", "content": "怎么退货?"},
{"role": "assistant", "content": "您好,签收 7 天内可在「我的订单」点击退货,审核通过后会上门取件。"},
]
},
{
"messages": [
{"role": "system", "content": "你是公司客服助手,回答简洁礼貌。"},
{"role": "user", "content": "发票怎么开?"},
{"role": "assistant", "content": "您好,下单后可在订单详情页申请电子发票,一般 1 个工作日内发送至邮箱。"},
]
},
# ... 实际场景应有数百条高质量样本
]
with open(path, "w", encoding="utf-8") as f:
for s in samples:
f.write(json.dumps(s, ensure_ascii=False) + "\n")
return path
# 2. 上传文件
def upload_file(path: str) -> str:
with open(path, "rb") as f:
resp = client.files.create(file=f, purpose="fine-tune")
print(f"上传成功 file_id={resp.id}")
return resp.id
# 3. 创建微调任务
def create_ft_job(file_id: str, base_model: str = "gpt-4.1-mini") -> str:
job = client.fine_tuning.jobs.create(
training_file=file_id,
model=base_model,
hyperparameters={"n_epochs": 3, "batch_size": "auto"},
)
print(f"任务创建 job_id={job.id} 状态={job.status}")
return job.id
# 4. 轮询任务状态
def wait_for_job(job_id: str, interval: int = 30) -> str:
while True:
job = client.fine_tuning.jobs.retrieve(job_id)
print(f"状态: {job.status}")
if job.status in ("succeeded", "failed", "cancelled"):
if job.status == "succeeded":
print(f"微调模型: {job.fine_tuned_model}")
return job.fine_tuned_model
raise RuntimeError(f"任务未成功: {job.status}")
time.sleep(interval)
# 5. 查看训练事件(损失曲线)
def list_events(job_id: str):
events = client.fine_tuning.jobs.list_events(job_id=job_id, limit=50)
for e in events.data:
print(f"{e.created_at} | {e.level} | {e.message}")
# 6. 使用微调模型
def use_finetuned(model_name: str, question: str) -> str:
resp = client.responses.create(
model=model_name,
input=question,
)
return resp.output_text
# 7. 清理文件
def cleanup(file_id: str):
client.files.delete(file_id=file_id)
print(f"已删除文件 {file_id}")
if __name__ == "__main__":
path = build_training_data()
file_id = upload_file(path)
try:
job_id = create_ft_job(file_id)
list_events(job_id)
ft_model = wait_for_job(job_id)
answer = use_finetuned(ft_model, "怎么修改收货地址?")
print("微调模型回答:", answer)
finally:
cleanup(file_id)
企业案例¶
案例 1:法律咨询领域模型定制¶
某法律科技公司收集 2000 条高质量法律问答,按统一 system 消息微调 gpt-4.1-mini,微调后模型在法律术语准确率上提升 25%,单次调用 Prompt 从 800 token 压缩到 200 token,月度成本下降 60%。
案例 2:品牌客服语气统一¶
电商品牌用 1500 条人工编写的客服话术微调模型,使自动回复统一为"亲切 + 专业"风格,客户满意度评分从 4.1 升至 4.5。每月用新产生的优质对话迭代一次模型。
案例 3:内部代码生成框架适配¶
研发团队把公司内部 RPC 框架的代码示例整理为 800 条样本微调,让模型直接生成符合内部规范的代码骨架,减少 70% 的手动改写工作。
常见问题¶
Q1:微调 vs RAG,怎么选? 微调适合固化"风格、格式、领域知识体系";RAG 适合"频繁变化的事实性信息"。两者可叠加:微调让模型懂行话,RAG 注入最新文档。决策时先问"信息是否频繁变化",是则优先 RAG。
Q2:多少条数据才够? 50 条可跑通流程,500 条起步有效果,数千条常见。质量远比数量重要——少量高质量样本胜过大量噪声样本。建议先用 100 条试跑评估。
Q3:微调后模型通用能力会退化吗? 会,称为"灾难性遗忘"。缓解手段:控制 epoch(2-3)、在训练数据中混入部分通用对话、用评估集监控通用能力指标。
Q4:微调模型可以商用吗? 可以。OpenAI 微调产出的模型可用于商用,但训练数据必须合规,不得包含敏感个人信息或侵权内容。
Q5:如何更新已微调模型的知识? 微调模型不会自动学习新知识。新知识应通过 RAG 注入,或在积累足够新数据后重新微调。建议建立定期迭代节奏(如每月一次)。
面试题¶
1. 何时应该微调而非用 Prompt 工程? 答:当 Prompt 已优化到极限但仍需大量示例、对风格/格式有强一致性要求、领域术语特殊、或需降低单次调用 token 成本时考虑微调。频繁变化的事实用 RAG。
2. 微调训练数据的格式要求?
答:JSONL 格式,每行一个 messages 数组,含 system/user/assistant 三类角色,assistant 为标准答案。所有样本的 system 消息应与推理时一致。
3. 列举微调任务的关键状态及含义。
答:pending(排队)、validating_files(校验数据格式)、running(训练中)、succeeded(成功,产出 fine_tuned_model)、failed(失败,查 events 排错)、cancelled(手动取消)。
4. 如何判断微调是否过拟合? 答:训练损失持续下降但验证集指标上升;或模型在测试集上格式合规但泛化能力下降;手段:降低 n_epochs、增加数据多样性、加入正则(learning_rate 调小)。
5. 微调与 Embedding 微调有什么区别? 答:本章节讲的是生成模型微调(继续训练 GPT 的生成能力);Embedding 微调是针对 embedding 模型的领域适配,常用于提升特定领域检索质量,二者目标与方法不同。
练习题¶
- 准备 50 条你所在领域的问答数据,按 JSONL 格式上传并跑通微调全流程,记录每个阶段的耗时与状态。
- 对比微调前后模型在同一组 20 道测试题上的回答质量(人工评分 1-5 分),计算平均提升幅度。
- 设计一个"微调 + RAG"混合方案:用微调模型处理风格与格式,用 RAG 注入实时知识,画出架构图并说明数据流。
小结¶
微调是把领域知识"烤"进模型权重的手段,适合固化风格、格式与领域行话。本章覆盖了 Files API、JSONL 数据准备、任务创建与监控、模型评估与部署。核心要点:质量胜于数量;system 消息训练与推理要一致;监控灾难性遗忘;频繁变化的事实用 RAG 而非微调。掌握本章后,你就具备了"通用模型 + 领域定制"的完整能力,本卷 OpenAI API 的学习到此告一段落,下一卷将进入 Prompt 工程。