微调技术指南:如何在SFT、DPO和RFT之间选择(附DPO详解)

本指南面向有OpenAI API经验的开发者,系统比较监督微调(SFT)、视觉微调、直接偏好优化(DPO)和强化微调(RFT)的适用场景与限制,并深入解析DPO的原理与实现。通过虚构品牌客服助手的案例,演示从合成偏好数据集生成、基准评估、DPO微调到效果验证的完整流程,涵盖关键超参数β的调优策略。读者将掌握如何根据任务性质选择微调方法,并具备独立实施DPO的工程能力。

技术背景

大型语言模型(LLM)在通用任务上表现优异,但在特定领域或风格要求下往往需要定制化调整。微调(Fine-tuning)通过在领域数据上继续训练,优化模型在特定任务上的性能与效率。OpenAI平台提供四种微调方法:SFT适用于结构化输出与指令遵循,视觉微调处理多模态数据,DPO通过偏好对对齐主观标准,RFT利用强化学习优化复杂推理。选择不当会导致资源浪费或效果不佳,例如用SFT添加新知识不如RAG,用DPO学习新任务则力不从心。理解各方法的边界是高效应用的前提。

核心原理

DPO(直接偏好优化)是一种基于成对偏好数据的对齐技术,直接优化模型使其倾向于生成偏好响应而非拒绝响应。其核心思想是绕过显式奖励模型,通过隐式奖励函数(由偏好对定义)更新策略。训练时,模型对同一提示生成两个响应(偏好与拒绝),损失函数鼓励提高偏好响应的对数概率,同时降低拒绝响应的概率。关键超参数β控制新旧策略的偏差:高β(接近2)使模型保守,保留原有行为;低β(接近0)则激进适应新偏好。β在损失函数中缩放对数概率差异,影响权重更新幅度。DPO简化了RLHF流程,无需单独训练奖励模型,适合主观质量评估场景。

架构分析

推荐工作流程为:先对偏好回答子集进行SFT,再以SFT模型为起点应用DPO。SFT建立稳健初始策略,减少DPO期间权重更新幅度,稳定训练并防止过拟合。演示场景中,系统由四个模块组成:1)提示扩展器:基于种子问题库生成相关变体;2)偏好对生成器:为每个提示生成友好(偏好)与生硬(拒绝)响应;3)评估器:使用LLM-as-a-Judge对响应打分(0-4分);4)微调器:调用OpenAI微调API执行DPO。数据流为:种子提示→扩展→偏好对生成→数据集划分(80%训练/10%验证/10%测试)→基线评估→DPO微调→后评估。关键设计决策包括:使用异步并发提升数据生成效率,采用温度参数控制风格差异(偏好响应温度0.7,拒绝响应0.3),以及通过evals API实现自动化评估。

完整实战教程

*本指南面向对OpenAI API有一定经验、希望将微调模型用于研究或其他适当用途的开发者和机器学习从业者。OpenAI的服务不适用于任何医疗状况的个性化治疗或诊断,并受我们的适用条款约束。*

本指南讨论了OpenAI支持的微调方法,重点说明每种方法的适用场景和不适用场景,以帮助您确定最适合您用例的技术。然后,它深入探讨了一种特定方法——直接偏好优化(DPO)——并提供了其他技术的现有指南链接。

什么是微调? 微调是在较小的、特定领域的数据集上继续训练,以优化模型完成特定任务的过程。我们通常进行微调的主要原因有两个:

  1. 提高模型在特定任务上的性能
  2. 提高模型效率(减少所需token数量,将专业知识提炼到更小的模型中,等等)

目前,OpenAI平台支持四种微调方法:

为了帮助您选择合适的微调技术,下表总结了每种方法最适合的场景,以及不适合的场景:

| 技术 | 适合 | 不适合 |

| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |

| 监督微调(SFT) | 强调模型中已有的知识。<br>定制响应结构或语气。<br>生成特定格式的内容。<br>教授复杂指令或纠正指令遵循失败。<br>优化成本/延迟(从提示中节省令牌或蒸馏)。 | 添加全新知识(考虑使用RAG)。<br>具有主观质量的任务。 |

| 视觉微调 | 专门的视觉识别任务(例如,图像分类)。<br>特定领域的图像理解。<br>纠正复杂提示的指令遵循失败。 | 纯文本任务。<br>没有特定上下文的通用视觉任务。<br>通用图像理解。 |

| 直接偏好优化(DPO) | 将模型输出与主观偏好(语气、礼貌)对齐。<br>通过人工评分反馈优化输出。<br>实现细微的行为对齐。 | 学习全新任务。<br>没有明确人类偏好信号的任务。 |

| 强化微调(RFT) | 需要高级推理的复杂领域特定任务。<br>完善现有的部分能力(促进涌现行为)。<br>具有可测量反馈的任务。<br>在显式标签有限但可以定义奖励信号的场景中。 | 模型没有初始技能的任务。<br>没有明确反馈或可测量信号的任务。 |

目前,已有以下预置的 Cookbook:

本指南将涵盖直接偏好优化(DPO)。

直接偏好优化指南

如上所述,直接偏好优化(DPO) 是一种使用成对偏好数据(例如,排名的响应对)微调语言模型的对齐技术。DPO 直接优化模型,使其倾向于某些输出而不是其他输出,通常基于人类偏好。这种方法简化了对齐过程,并消除了对单独奖励模型或复杂强化学习程序的需求,使 DPO 成为人类反馈强化学习(RLHF)等技术的轻量级替代方案。

何时应该使用 DPO?DPO 在响应质量主观、无法客观衡量,或语气、风格、适当性或清晰度等细微标准很重要的情况下表现出色——通常存在多个有效输出。DPO 在以下示例应用中特别有效,用于对齐 AI 响应:

通过针对首选与非首选完成的显式对进行微调,DPO 使模型输出与这些细微偏好保持一致。下表给出了一个代表组织的虚构 AI 助手的成对偏好数据示例,其中首选响应清晰、专业且符合品牌标准。

| 示例问题 | 选定响应 | 拒绝响应 |

|------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|

| Q1: *如何评论您的产品?* | 要提交产品评论,请访问您的账户仪表板,选择产品,然后点击“写评论”。分享您的真实体验,对关键功能进行评分,然后提交。 | 嘿,随便留几个星或什么的,没关系! |

| Q2: *如何评论您的产品?* | 我们欢迎您的反馈!在产品页面的“评论”部分,点击“发表评论”,进行评分,并添加您关于喜欢或需要改进之处的评论。 | 随便写点什么——说实话,写什么都行。 |

| Q3: *如何解决这个特定错误?* | 要解决错误“X101”,首先清除缓存,然后验证您的互联网连接。如果问题仍然存在,请按照我们的分步指南进行操作,请访问 [支持 → 故障排除 → 错误 X101]。 | 只需重新启动,我猜。如果不行,您就自己看着办吧! |

在本指南中,我们将介绍如何使用微调 API 应用 DPO。您将学习成功为您的用例运行偏好微调作业所需的关键步骤。

以下是我们将涵盖的内容:

1. 推荐工作流程

OpenAI 推荐以下工作流程:

  1. 在您偏好的回答子集上执行监督微调(SFT)。
  2. 以 SFT 微调模型为起点,使用偏好比较数据应用 DPO。

在直接偏好优化(DPO)之前执行监督微调(SFT)可以增强模型对齐和整体性能,通过建立稳健的初始策略,确保模型已经偏好正确的回答。这减少了 DPO 期间权重更新的幅度,稳定训练并防止过拟合,使 DPO 能够高效地优化细微差别。因此,SFT 后接 DPO 的组合工作流程收敛更快,并产生更高质量的结果。

在本指南中,我们将专门关注应用直接偏好优化(DPO)。但是,根据您的用例,您可能会发现首先执行监督微调(SFT)能带来性能提升。如果是这样,您可以按照上面链接的 SFT 指南操作,保存生成的模型 ID,并将其作为 DPO 作业的起点。

2. 演示场景

为了具体说明,我们以微调一个面向客户的 AI 助手为例,让它遵循虚构品牌的语气和风格。想象一下 Good Vibes Corp,一家以友好、热情且带有个性化触感的语气而自豪的组织。

他们希望客户 AI 助手以反映这些品牌准则(例如乐观的态度、礼貌的语言和友好的结束语)的方式回答问题,并优先选择这些回答,而不是更通用或生硬的回答。这是 DPO 的一个很好的场景:没有客观正确的答案格式,但有偏好的风格。

DPO 将帮助模型通过比较学习哪种风格更受青睐。我们将概述以下步骤:(1)生成一个包含提示和配对回答(一个符合品牌语气,一个不符合)的合成偏好数据集。(2)使用 OpenAI evals API 评估基础模型性能。(3)以偏好微调所需的 JSONL 格式准备并上传数据。(4)使用 OpenAI 微调 API 通过 DPO 微调模型。(5)使用 OpenAI evals API 评估微调后的模型,以展示品牌风格偏好的改进。

我们将为这个演示合成一个数据集。首先,让我们创建一个种子问题库,以生成更多变体。

让我们开始吧!

! pip install openai nest-asyncio --quiet
PROMPT_SEED_POOL = [
    "Hi, I ordered a gadget last week. When will it arrive?",
    "Your product stopped working after two days. Can I get help?",
    "Do you offer discounts for long-term customers?",
    "Can I change the shipping address for my order?",
    "What is your return policy for damaged items?",
    "My tracking number hasn't updated in three days—can you check the status?",
    "How long is the warranty on your products, and how do I submit a claim?",
    "Can I add gift wrapping to my order before it ships?",
    "Do you accept PayPal or other alternative payment methods?",
    "Is there an option to expedite shipping if my order hasn't left the warehouse yet?",
]

3. 生成数据集

接下来,我们将定义函数,从种子库中获取每个提示并生成相关问题。我们将通过首先生成这些提示变体,然后为每个提示生成一个偏好响应和一个拒绝响应,来创建偏好对数据集。

该数据集是合成的,用于说明直接偏好优化的机制——在开发自己的应用程序时,您应该收集或策划一个高质量的偏好数据集。注意:DPO所需的数据量取决于用例;通常越多越好(数千到数万),对于偏好对,排序逻辑应保持一致(例如,如果A > B且B > C,则A > C)。

import asyncio
from openai import AsyncOpenAI
from typing import List, Dict, Any

async_client = AsyncOpenAI()

SYSTEM_PROMPT = "You are a customer-support assistant."


async def _generate_related_questions_from_prompt(
    prompt: str, k: int, sem: asyncio.Semaphore, *, model: str
) -> List[str]:
    """Return *k* distinct customer-service questions related to the given prompt."""
    out: List[str] = []
    async with sem:
        for _ in range(k):
            resp = await async_client.responses.create(
                model=model,
                input=[
                    {
                        "role": "system",
                        "content": (
                            "Return ONE distinct, realistic customer-service question "
                            "related in topic or theme to the following question, "
                            "but NOT a direct paraphrase."
                        ),
                    },
                    {"role": "user", "content": prompt},
                ],
                temperature=0.9,
                max_output_tokens=60,
            )
            out.append(resp.output_text.strip())
    return out


async def expand_prompt_pool(
    prompts: List[str], *, k: int = 3, concurrency: int = 32, model: str
) -> List[str]:
    """Expand each prompt into *k* related questions using the given model."""
    sem = asyncio.Semaphore(concurrency)
    tasks = [
        _generate_related_questions_from_prompt(p, k, sem, model=model) for p in prompts
    ]
    results = await asyncio.gather(*tasks)
    return [v for sub in results for v in sub]


async def _generate_preference_pair(
    prompt: str, sem: asyncio.Semaphore, *, model: str
) -> Dict[str, Any]:
    """Generate a preference pair for the given prompt."""
    async with sem:
        friendly_task = async_client.responses.create(
            model=model,
            input=[
                {
                    "role": "system",
                    "content": (
                        "You are Good Vibes Corp's exceptionally energetic, outrageously friendly and "
                        "enthusiastic support agent."
                    ),
                },
                {"role": "user", "content": prompt},
            ],
            temperature=0.7,  # higher temperature to increase creativity & on-brand tone adherence
            max_output_tokens=80,
        )
        blunt_task = async_client.responses.create(
            model=model,
            input=[
                {
                    "role": "system",
                    "content": "You are a terse, factual support agent with no empathy or politeness.",
                },
                {"role": "user", "content": prompt},
            ],
            temperature=0.3,  # lower temperature to limit creativity & emphasize tonal difference
            max_output_tokens=80,
        )
        friendly, blunt = await asyncio.gather(friendly_task, blunt_task)
        return {
            "input": {
                "messages": [
                    {"role": "system", "content": SYSTEM_PROMPT},
                    {"role": "user", "content": prompt},
                ]
            },
            "preferred_output": [
                {"role": "assistant", "content": friendly.output_text}
            ],
            "non_preferred_output": [
                {"role": "assistant", "content": blunt.output_text}
            ],
        }

现在,利用这些已定义的函数,我们将通过生成友好与直率回复对来构建数据集。友好回复体现了品牌期望的沟通风格。为了效率,我们将异步执行此操作,创建适合直接偏好优化的数据集。

import math
import nest_asyncio


async def build_dataset(
    *,
    pair_count: int = 500,
    concurrency: int = 8,
    expand_prompt_pool_model: str,
    generate_preference_pair_model: str,
) -> List[Dict[str, Any]]:
    """Return *pair_count* preference pairs (single-shot expansion)."""

    seed = PROMPT_SEED_POOL
    deficit = max(0, pair_count - len(seed))
    k = max(1, math.ceil(deficit / len(seed)))

    expanded = await expand_prompt_pool(
        seed,
        k=k,
        concurrency=concurrency,
        model=expand_prompt_pool_model,
    )
    prompt_bank = (seed + expanded)[:pair_count]

    sem = asyncio.Semaphore(concurrency)
    tasks = [
        _generate_preference_pair(p, sem, model=generate_preference_pair_model)
        for p in prompt_bank
    ]
    return await asyncio.gather(*tasks)


nest_asyncio.apply()
pairs = await build_dataset(
    pair_count=500,
    concurrency=8,
    expand_prompt_pool_model="gpt-4.1-mini-2025-04-14",
    generate_preference_pair_model="gpt-4.1-mini-2025-04-14",
)
print(f"Dataset ready with {len(pairs)} pairs.")

4. 基准模型评估

下面,我们将数据集划分为训练集、验证集和测试集。我们还展示了训练数据集中的一个样本,该样本清晰地展示了该输入对中偏好响应(友好、符合品牌调性)与非偏好响应(生硬、中性)之间的明显差异。

# set dataset sizes
n = len(pairs)
n_train = int(0.8 * n)
n_val = int(0.1 * n)
n_test = n - n_train - n_val

# split dataset into train, test & validation
train_pairs = pairs[:n_train]
val_pairs = pairs[n_train : n_train + n_val]
test_pairs = pairs[n_train + n_val :]
train_pairs[0]

为了在微调前评估模型的性能,我们将使用一个自动评分器(LLM-as-a-Judge)对每个回答的友好度和同理心进行评分。评分器将为每个答案分配0到4的分数,从而我们可以计算基础模型的平均基线分数。

为此,我们首先在测试集上生成基础模型的回答,然后使用OpenAI evals API创建并运行一个带有自动评分器的评估。

async def generate_responses(
    testset,
    model,
    temperature=0.0,
    max_output_tokens=80,
    concurrency=8,
):
    """
    Generate responses for each prompt in the testset using the OpenAI responses API.
    Returns: List of dicts: [{"prompt": ..., "response": ...}, ...]
    """
    async_client = AsyncOpenAI()
    sem = asyncio.Semaphore(concurrency)

    async def get_response(prompt):
        async with sem:
            resp = await async_client.responses.create(
                model=model,
                input=[
                    {"role": "system", "content": SYSTEM_PROMPT},
                    {"role": "user", "content": prompt},
                ],
                temperature=temperature,
                max_output_tokens=max_output_tokens,
            )
            return {"prompt": prompt, "response": resp.output_text}

    tasks = [get_response(item["item"]["input"]) for item in testset]
    results = await asyncio.gather(*tasks)
    return results


# generate responses for the base model over the test set
base_model = "gpt-4.1-mini-2025-04-14"
testset = [
    {"item": {"input": pair["input"]["messages"][1]["content"]}} for pair in test_pairs
]
responses = await generate_responses(testset, model=base_model)

接下来,我们将使用 OpenAI evals API 创建并运行一个带有自动评分器的评估,首先定义 LLM-as-a-Judge 的评分标准。注意:我们将通过数据日志访问响应,因此要使其正常工作,您需要处于未禁用数据日志记录(例如通过 zdr 等)的组织中。如果您不确定自己的情况,请访问 https://platform.openai.com/logs?api=responses 查看是否能看到刚刚生成的响应。

JUDGE_SYSTEM = """
You judge whether a reply matches Good Vibes Corp's desired tone:
energetic, super-friendly, enthusiastic.

Score 0-4 (higher = more energy):

4 - Highly enthusiastic: multiple upbeat phrases / emojis / exclamations, clear empathy, proactive help.
3 - Energetic & friendly: visible enthusiasm cue (≥1 emoji OR exclamation OR upbeat phrase), warm second-person tone.
2 - Pleasant: polite & positive but lacks obvious enthusiasm cues.
1 - Neutral: correct, businesslike, minimal warmth.
0 - Rude, negative, or unhelpful.
"""
from openai import OpenAI

sync_client = OpenAI()

# set judge model
judge_model = "gpt-4.1-2025-04-14"

# create the evaluation
logs_eval = sync_client.evals.create(
    name="Good Vibes Corp Tone Eval",
    data_source_config={
        "type": "logs",
    },
    testing_criteria=[
        {
            "type": "score_model",
            "name": "General Evaluator",
            "model": judge_model,
            "input": [
                {
                    "role": "system",
                    "content": JUDGE_SYSTEM,
                },
                {
                    "role": "user",
                    "content": (
                        "**User input**\n"
                        "{{item.input}}\n"
                        "**Response to evaluate**\n"
                        "{{sample.output_text}}"
                    ),
                },
            ],
            "range": [0, 4],
            "pass_threshold": 2,
        }
    ],
)
# run the evaluation
base_run = sync_client.evals.runs.create(
    name=base_model,
    eval_id=logs_eval.id,
    data_source={
        "type": "responses",
        "source": {"type": "responses", "limit": len(test_pairs)},
    },
)
# score base model
base_data = sync_client.evals.runs.output_items.list(
    eval_id=logs_eval.id, run_id=base_run.id
).data
base_scores = [s.results[0]["score"] for s in base_data]
print("Average score:", sum(base_scores) / len(base_scores))

5. 微调

在建立基线之后,我们现在可以使用训练集和 DPO 对模型进行微调。这个过程将教会模型根据我们之前创建的偏好对,偏好于符合我们期望风格的响应。

注意:beta (β) 是直接偏好优化(DPO)中一个独特的微调超参数。它是一个介于 0 和 2 之间的浮点数,控制着保留模型现有行为与适应新的、符合偏好的响应之间的平衡。

从技术上讲,β 缩放 DPO 损失中对数概率的差异;较大的 β 会导致基于 sigmoid 的损失函数在较小的概率差异下饱和,从而产生较小的权重更新(从而保留旧行为)。建议系统地试验 β 值,以针对您的特定用例以及稳定性与适应性之间的期望权衡实现最佳结果。

import io
import json

# create training file
train_buf = io.BytesIO("\n".join(json.dumps(p) for p in train_pairs).encode())
train_buf.name = "train.jsonl"
train_file_id = sync_client.files.create(file=train_buf, purpose="fine-tune").id

# create validation file
val_buf = io.BytesIO("\n".join(json.dumps(p) for p in val_pairs).encode())
val_buf.name = "val.jsonl"
val_file_id = sync_client.files.create(file=val_buf, purpose="fine-tune").id

# create a fine-tuning job
ft = sync_client.fine_tuning.jobs.create(
    model=base_model,
    training_file=train_file_id,
    validation_file=val_file_id,
    method={
        "type": "dpo",
        "dpo": {
            "hyperparameters": {
                "n_epochs": 2,
                "beta": 0.1,
                "batch_size": 8,
            }
        },
    },
)
print(f"Fine-tuning job created: job_id = {ft.id}")

6. 使用微调后的模型

微调完成后,我们将在相同的测试集上评估经过 DPO 调优的模型。通过比较微调前后的平均得分,并查看示例输出,我们可以了解模型与我们偏好的对齐程度如何提升。

# generate responses
job = sync_client.fine_tuning.jobs.retrieve(ft.id)
if job.status == "succeeded":
    responses = await generate_responses(testset, model=job.fine_tuned_model)

    post_run = sync_client.evals.runs.create(
        name=ft.id,
        eval_id=logs_eval.id,
        data_source={
            "type": "responses",
            "source": {"type": "responses", "limit": len(test_pairs)},
        },
    )
# get scores from the evaluation
post_data = sync_client.evals.runs.output_items.list(
    eval_id=logs_eval.id, run_id=post_run.id
).data
post_scores = [s.results[0]["score"] for s in post_data]

# print scores & a sample comparison from the test set for illustration
print(
    "Δ mean:",
    sum(t - b for b, t in zip(base_scores, post_scores)) / len(base_scores),
)
print("\n=== SAMPLE COMPARISON ===")
idx = 0
print(f"Prompt:\n  {testset[idx]['item']['input']}\n")
print(f"Base model reply: \n {base_data[idx].sample.output[0].content} \n")
print(f"DPO-tuned model reply \n {post_data[idx].sample.output[0].content}")

常见问题

常见错误与排查:1)数据量不足:DPO通常需要数千至数万偏好对,若效果不佳,优先扩充数据集并确保排序逻辑一致(如A>B且B>C则A>C)。2)β设置不当:高β导致模型几乎不变,低β可能过度适应新风格,建议从β=0.1开始,逐步调整并监控验证集损失。3)评估日志不可见:需确保组织未禁用数据日志记录,否则evals无法访问响应,检查https://platform.openai.com/logs?api=responses。4)异步任务冲突:在Jupyter环境需使用nest_asyncio.apply()避免事件循环冲突。5)微调作业失败:检查训练/验证文件格式(JSONL),确保字段名正确(input, preferred_output, non_preferred_output)。生产环境优化:使用更大batch size和更多epochs提升稳定性,但需监控过拟合;可尝试混合SFT+DPO流程,或使用模型蒸馏降低推理成本。

企业应用扩展

进一步优化方向:1)数据质量:人工审核偏好对,确保偏好信号清晰一致,可引入多轮迭代生成。2)超参数调优:系统搜索β、学习率、epochs,使用验证集早停。3)评估增强:除自动评分器外,增加人工评估或A/B测试,确保对齐真实用户偏好。4)多任务扩展:将DPO应用于代码生成、法律合规等场景,结合领域知识构建偏好对。5)与RFT结合:对于复杂推理任务,可先SFT再DPO最后RFT,逐步提升能力。6)成本控制:利用模型蒸馏将大模型能力迁移至小模型,减少推理开销。7)监控与回滚:建立微调后模型性能监控,异常时快速回滚至基线。

总结

本指南面向有OpenAI API经验的开发者,系统比较监督微调(SFT)、视觉微调、直接偏好优化(DPO)和强化微调(RFT)的适用场景与限制,并深入解析DPO的原理与实现。通过虚构品牌客服助手的案例,演示从合成偏好数据集生成、基准评估、DPO微调到效果验证的完整流程,涵盖关键超参数β的调优策略。读者将掌握如何根据任务性质选择微调方法,并具备独立实施DPO的工程能力。

内容来源与验证

继续系统学习

站内还有更多完整实战教程

加入会员,解锁全部教程、完整代码与持续更新内容。

加入会员
查看全部模型评估与微调教程