跳转至

第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 模型的领域适配,常用于提升特定领域检索质量,二者目标与方法不同。

练习题

  1. 准备 50 条你所在领域的问答数据,按 JSONL 格式上传并跑通微调全流程,记录每个阶段的耗时与状态。
  2. 对比微调前后模型在同一组 20 道测试题上的回答质量(人工评分 1-5 分),计算平均提升幅度。
  3. 设计一个"微调 + RAG"混合方案:用微调模型处理风格与格式,用 RAG 注入实时知识,画出架构图并说明数据流。

小结

微调是把领域知识"烤"进模型权重的手段,适合固化风格、格式与领域行话。本章覆盖了 Files API、JSONL 数据准备、任务创建与监控、模型评估与部署。核心要点:质量胜于数量;system 消息训练与推理要一致;监控灾难性遗忘;频繁变化的事实用 RAG 而非微调。掌握本章后,你就具备了"通用模型 + 领域定制"的完整能力,本卷 OpenAI API 的学习到此告一段落,下一卷将进入 Prompt 工程。