如何微调聊天模型:GPT-4o mini 微调实战指南

本教程面向有编程基础的开发者,深入讲解如何使用 OpenAI API 微调 GPT-4o mini 模型。通过 RecipeNLG 数据集进行实体提取任务,涵盖数据准备、文件上传、微调任务创建、状态监控及推理调用。教程强调微调在特定领域的效果,提供完整代码示例和最佳实践,帮助开发者掌握微调流程并应用于实际项目。

技术背景

大型语言模型(LLM)在通用任务上表现优异,但在特定领域或格式要求下,通过提示工程可能无法达到理想效果。微调(Fine-tuning)通过在大量领域相关示例上继续训练模型,使其适应特定任务,提升准确性和一致性。GPT-4o mini 作为高效模型,微调成本较低,适合开发者快速迭代。本教程以 RecipeNLG 数据集为例,展示实体提取(NER)任务的微调流程,帮助开发者理解微调的核心概念和操作步骤。

核心原理

微调的核心原理是在预训练模型的基础上,使用特定任务的数据进行有监督训练,调整模型参数以优化目标函数。在 ChatCompletion 格式中,每个训练样本由 messages 列表组成,包含 system、user 和 assistant 角色。训练时,模型将最后一条 assistant 消息作为目标输出,其余消息作为输入提示。通过反向传播更新权重,使模型学会从输入生成期望的输出。关键设计决策包括:数据聚焦于特定领域(cookbooks.com),确保样本代表性;使用验证集监控过拟合;设置合理的训练轮次和超参数。

架构分析

系统架构包括数据准备、文件上传、微调任务管理和推理调用四个模块。数据准备模块将原始 CSV 转换为 JSONL 格式,每条记录包含 messages 列表。文件上传模块通过 Files API 将训练和验证文件上传至 OpenAI 服务器,获得文件 ID。微调任务管理模块使用 Fine-tuning API 创建任务,传入文件 ID、基础模型和可选后缀,并轮询任务状态。推理模块通过 Chat Completions API 调用微调后的模型,传入测试消息获取结果。组件间通过 API 交互,数据流清晰,便于扩展和集成。

完整实战教程

微调通过在提示中无法容纳的更多示例上训练模型来改进模型,使您能够在大量任务上获得更好的结果。本笔记本为我们的新 GPT-4o mini 微调提供了逐步指南。我们将使用 RecipeNLG 数据集 执行实体提取,该数据集提供了各种食谱以及每个食谱的通用成分列表。这是命名实体识别(NER)任务的常用数据集。

注意:GPT-4o mini 微调适用于我们 Tier 4 和 Tier 5 使用层级 中的开发者。 您可以通过访问微调仪表板、点击“创建”并从基础模型下拉菜单中选择“gpt-4o-mini-2024-07-18”来开始微调 GPT-4o mini。

我们将按照以下步骤进行:

  1. 设置: 加载我们的数据集,并筛选到一个领域进行微调。
  2. 数据准备: 通过创建训练和验证示例来准备微调数据,并将它们上传到 Files 端点。
  3. 微调: 创建您的微调模型。
  4. 推理: 使用您的微调模型对新输入进行推理。

通过本指南,您应该能够训练、评估和部署一个微调后的 gpt-4o-mini-2024-07-18 模型。

有关微调的更多信息,您可以参考我们的 文档指南 或 API 参考。

设置

# make sure to use the latest version of the openai python package
!pip install --upgrade --quiet openai
import json
import openai
import os
import pandas as pd
from pprint import pprint

client = openai.OpenAI(
    api_key=os.environ.get("OPENAI_API_KEY"),
    organization="<org id>",
    project="<project id>",
)

微调在专注于特定领域时效果最佳。重要的是确保你的数据集既足够聚焦以便模型学习,又足够通用以免遗漏未见过的示例。考虑到这一点,我们从 RecipesNLG 数据集中提取了一个子集,仅包含来自 cookbooks.com 的文档。

# Read in the dataset we'll use for this task.
# This will be the RecipesNLG dataset, which we've cleaned to only contain documents from www.cookbooks.com
recipe_df = pd.read_csv("data/cookbook_recipes_nlg_10k.csv")

recipe_df.head()

数据准备

我们首先准备数据。在使用 ChatCompletion 格式进行微调时,每个训练样本都是一个简单的 messages 列表。例如,一个条目可能如下所示:

[{'role': 'system',
'content': 'You are a helpful recipe assistant. You are to extract the generic ingredients from each of the recipes provided.'},

{'role': 'user',
'content': 'Title: No-Bake Nut Cookies\n\nIngredients: ["1 c. firmly packed brown sugar", "1/2 c. evaporated milk", "1/2 tsp. vanilla", "1/2 c. broken nuts (pecans)", "2 Tbsp. butter or margarine", "3 1/2 c. bite size shredded rice biscuits"]\n\nGeneric ingredients: '},

{'role': 'assistant',
'content': '["brown sugar", "milk", "vanilla", "nuts", "butter", "bite size shredded rice biscuits"]'}]

在训练过程中,这段对话将被拆分,最后一条作为模型将生成的completion,而其余的messages则作为提示。在构建训练示例时请考虑这一点——如果你的模型将处理多轮对话,请提供有代表性的示例,以免在对话开始扩展时表现不佳。

请注意,目前每个训练示例有4096个token的限制。任何超过此长度的内容将被截断至4096个token。

system_message = "You are a helpful recipe assistant. You are to extract the generic ingredients from each of the recipes provided."


def create_user_message(row):
    return f"Title: {row['title']}\n\nIngredients: {row['ingredients']}\n\nGeneric ingredients: "


def prepare_example_conversation(row):
    return {
        "messages": [
            {"role": "system", "content": system_message},
            {"role": "user", "content": create_user_message(row)},
            {"role": "assistant", "content": row["NER"]},
        ]
    }


pprint(prepare_example_conversation(recipe_df.iloc[0]))

现在让我们对数据集的一个子集进行此操作,用作我们的训练数据。你可以从30-50个精心修剪的示例开始。你应该会看到性能随着训练集大小的增加而线性提升,但你的任务也会花费更长时间。

# use the first 100 rows of the dataset for training
training_df = recipe_df.loc[0:100]

# apply the prepare_example_conversation function to each row of the training_df
training_data = training_df.apply(prepare_example_conversation, axis=1).tolist()

for example in training_data[:5]:
    print(example)

除了训练数据之外,我们还可以可选地提供验证数据,这些数据将用于确保模型不会过拟合你的训练集。

validation_df = recipe_df.loc[101:200]
validation_data = validation_df.apply(
    prepare_example_conversation, axis=1).tolist()

然后我们需要将数据保存为 .jsonl 文件,每一行是一个训练示例对话。

def write_jsonl(data_list: list, filename: str) -> None:
    with open(filename, "w") as out:
        for ddict in data_list:
            jout = json.dumps(ddict) + "\n"
            out.write(jout)
training_file_name = "tmp_recipe_finetune_training.jsonl"
write_jsonl(training_data, training_file_name)

validation_file_name = "tmp_recipe_finetune_validation.jsonl"
write_jsonl(validation_data, validation_file_name)

这是我们训练 .jsonl 文件的前 5 行内容:

# print the first 5 lines of the training file
!head -n 5 tmp_recipe_finetune_training.jsonl

上传文件

现在你可以将文件上传到我们的 Files 端点,以供微调后的模型使用。

def upload_file(file_name: str, purpose: str) -> str:
    with open(file_name, "rb") as file_fd:
        response = client.files.create(file=file_fd, purpose=purpose)
    return response.id


training_file_id = upload_file(training_file_name, "fine-tune")
validation_file_id = upload_file(validation_file_name, "fine-tune")

print("Training file ID:", training_file_id)
print("Validation file ID:", validation_file_id)

微调

现在我们可以使用生成的文件和一个可选的标识后缀来创建微调任务。响应将包含一个 id,你可以用它来获取任务的更新。

注意:文件必须首先由我们的系统处理,因此你可能会遇到 File not ready 错误。在这种情况下,只需几分钟后重试即可。

MODEL = "gpt-4o-mini-2024-07-18"

response = client.fine_tuning.jobs.create(
    training_file=training_file_id,
    validation_file=validation_file_id,
    model=MODEL,
    suffix="recipe-ner",
)

job_id = response.id

print("Job ID:", response.id)
print("Status:", response.status)

检查任务状态

你可以向 https://api.openai.com/v1/alpha/fine-tunes 端点发送 GET 请求,以列出你的 alpha 微调任务。在这种情况下,你需要检查上一步获得的 ID 最终是否变为 status: succeeded。

一旦完成,你可以使用 result_files 从验证集(如果你上传了验证集)中抽样查看结果,并使用 fine_tuned_model 参数中的 ID 来调用你训练好的模型。

response = client.fine_tuning.jobs.retrieve(job_id)

print("Job ID:", response.id)
print("Status:", response.status)
print("Trained Tokens:", response.trained_tokens)

我们可以通过 events 端点跟踪微调的进度。你可以多次重新运行下面的单元格,直到微调完成。

response = client.fine_tuning.jobs.list_events(job_id)

events = response.data
events.reverse()

for event in events:
    print(event.message)

现在任务已完成,我们可以从任务中获取微调后的模型 ID:

response = client.fine_tuning.jobs.retrieve(job_id)
fine_tuned_model_id = response.fine_tuned_model

if fine_tuned_model_id is None:
    raise RuntimeError(
        "Fine-tuned model ID not found. Your job has likely not been completed yet."
    )

print("Fine-tuned model ID:", fine_tuned_model_id)

推理

最后一步是使用你微调后的模型进行推理。与经典的 FineTuning 类似,你只需调用 ChatCompletions,并将新的微调模型名称填入 model 参数即可。

test_df = recipe_df.loc[201:300]
test_row = test_df.iloc[0]
test_messages = []
test_messages.append({"role": "system", "content": system_message})
user_message = create_user_message(test_row)
test_messages.append({"role": "user", "content": user_message})

pprint(test_messages)
response = client.chat.completions.create(
    model=fine_tuned_model_id, messages=test_messages, temperature=0, max_tokens=500
)
print(response.choices[0].message.content)

结论

恭喜,你现在已经准备好使用 ChatCompletion 格式来微调你自己的模型了!我们期待看到你构建的成果。

常见问题

常见错误包括:1. 文件未就绪错误(File not ready),需等待系统处理,稍后重试;2. 微调任务状态长时间未完成,可检查事件日志;3. 模型 ID 为空,可能任务未完成,需等待;4. 数据格式错误,确保 JSONL 每行是合法 JSON 对象;5. 超出 token 限制,需截断或减少样本长度。排查方法:使用 client.fine_tuning.jobs.list_events 查看进度,检查文件上传状态,验证数据格式。生产环境优化:使用更多高质量数据,调整超参数(如学习率、批次大小),使用早停防止过拟合,监控验证集性能。

企业应用扩展

本教程可扩展至其他 NLP 任务,如文本分类、情感分析、摘要生成等。开发者可调整数据准备函数以适应不同输入输出格式。此外,可探索使用 OpenAI 的 Weights & Biases 集成进行实验跟踪,或使用 Azure OpenAI 服务进行部署。对于大规模应用,可考虑使用分布式训练或模型量化以降低成本。微调后的模型可集成到应用中,通过 API 提供个性化服务。

总结

本教程面向有编程基础的开发者,深入讲解如何使用 OpenAI API 微调 GPT-4o mini 模型。通过 RecipeNLG 数据集进行实体提取任务,涵盖数据准备、文件上传、微调任务创建、状态监控及推理调用。教程强调微调在特定领域的效果,提供完整代码示例和最佳实践,帮助开发者掌握微调流程并应用于实际项目。

内容来源与验证

继续系统学习

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

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

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