探索ChatGPT的Fine-tuning和Embeddings
阅读原文时间:2023年08月29日阅读:5

1.概述

今天我们将深入探索ChatGPT的两项核心技术:Fine-tuning(微调)和Embeddings(嵌入)。这些技术在现代自然语言处理领域扮演着至关重要的角色,为模型的性能提升和适应特定任务需求提供了关键支持。ChatGPT作为GPT家族的一员,已经在多个领域展现了出色的表现,而其背后的Fine-tuning和Embeddings技术则是其成功的关键因素之一。

2.内容

Fine-tuning,又称微调,是指在预训练模型(如GPT-3)的基础上,通过在特定任务上继续训练模型,使其适应特定任务的需求。GPT-3在大规模文本语料上进行了预训练,学会了丰富的语言知识和模式。然而,要使模型在特定任务上表现出色,就需要对其进行进一步的微调。

ChatGPT的Fine-tuning涉及到将模型暴露在各种对话和语境中,以便它能够更好地理解并生成自然对话。举个例子,如果我们想要让ChatGPT用于医疗咨询,Fine-tuning的过程将包括让模型学习医学知识、专业术语和与患者交流的最佳实践。这种Fine-tuning使得ChatGPT能够根据任务的特定上下文作出更准确的回应。

微调可让你通过 API 提供以下功能,从而更充分地利用可用模型:

  • 比提示更高质量的结果
  • 能够训练超出提示范围的示例
  • 由于提示较短而节省了代币
  • 更低的延迟请求

GPT 模型已经过大量文本的预训练。为了有效地使用模型,在提示中包含说明,有时还包含几个示例。使用演示来展示如何执行任务通常称为“小样本学习”。

微调通过训练超出提示范围的更多示例来改进小样本学习,让你在大量任务上取得更好的结果。一旦模型经过微调,你就不需要在提示中提供那么多示例。这可以节省成本并实现更低延迟的请求。

在较高层面上,微调涉及以下步骤:

  • 准备并上传训练数据
  • 训练新的微调模型
  • 使用你的微调模型

目前可对以下型号进行微调:

  • gpt-3.5-turbo-0613(推荐)
  • babbage-002
  • davinci-002

目前gpt-3.5-turbo在结果和易用性方面成为大多数用户的正确模型。

微调 GPT 模型可以使其更好地适应特定应用,但这需要仔细投入时间和精力。建议首先尝试通过 Prompt 工程、Prompt Chaining(将复杂的任务分解为多个Prompt)和函数调用来获得良好的结果,主要原因是:

  • 对于许多任务,模型最初可能表现不佳,但通过更好的 Prompt,我们可以取得更好的结果,并且可能不需要进行微调
  • 迭代 Prompt 和其他策略比微调迭代具有更快的反馈循环,后者需要创建数据集并运行训练作业
  • 在仍然需要微调的情况下,最初的 Prompt 工程工作不会浪费 - 在微调数据中使用良好的 Prompt(或将 Prompt Chaining /工具使用与微调相结合)时,我们通常会看到最佳结果

GPT 最佳实践指南提供了一些最有效的策略和策略的背景知识,无需微调即可获得更好的性能。

微调可以改善结果的一些常见用例:

  • 设置风格、基调、格式或其他定性方面
  • 提高产生所需输出的可靠性
  • 纠正未能遵循复杂提示的问题
  • 以特定方式处理许多边缘情况
  • 执行难以在提示中阐明的新技能或任务

思考这些案例的一种高级方法是“展示而不是讲述”更容易。在接下来的部分中,我们将探讨如何设置用于微调的数据以及微调可以提高基准模型性能的各种示例。

微调有效的另一种情况是通过替换 GPT-4 或使用较短的 Prompt 来降低成本和/或延迟,而不牺牲质量。gpt-3.5-turbo如果您可以使用 GPT-4 获得良好的结果,那么您通常可以通过对 GPT-4 补全进行微调(可能还需要缩短指令提示)来使用微调模型达到类似的质量。

一旦你确定微调是正确的解决方案(即已经尽可能优化了 Prompt 并确定了模型仍然存在的问题),你将需要准备数据来训练模型。你应该创建一组多样化的演示对话,这些对话类似于您要求模型在生产中的推理时响应的对话。

数据集中的每个示例都应该是与我们的聊天完成 API 格式相同的对话,特别是消息列表,其中每条消息都有角色、内容和可选名称。至少一些训练示例应该直接针对提示模型未按预期运行的情况,并且数据中提供的辅助消息应该是您希望模型提供的理想响应。

在此示例中,我们的目标是创建一个偶尔给出讽刺性响应的聊天机器人,这是我们可以为数据集创建的三个训练示例(对话):

{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]}
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]}
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]}

对话式聊天格式需要微调gpt-3.5-turbo。对于babbage-002和,你可以遵循用于传统微调的davinci-002提示完成对格式,如下所示。

{"prompt": "", "completion": ""}
{"prompt": "", "completion": ""}
{"prompt": "", "completion": ""}

要微调模型,你需要提供至少 10 个示例。我们通常会看到对 50 到 100 个训练示例进行微调会带来明显的改进,gpt-3.5-turbo但正确的数量根据具体的用例而有很大差异。

建议从 50 个精心设计的演示开始,看看模型在微调后是否显示出改进的迹象。在某些情况下,这可能就足够了,但即使模型尚未达到生产质量,明显的改进也是一个好兆头,表明提供更多数据将继续改进模型。没有任何改进表明你可能需要重新考虑如何在超出有限示例集之前设置模型任务或重组数据。

编译数据集后,在创建微调作业之前,检查数据格式非常重要。为此,我们创建了一个简单的 Python 脚本,您可以使用它来查找潜在错误、检查令牌计数并估计微调作业的成本。

验证数据后,需要上传文件才能用于微调作业:

openai.File.create(
file=open("mydata.jsonl", "rb"),
purpose='fine-tune'
)

确保数据集的数量和结构正确并上传文件后,下一步是创建微调作业。

使用 OpenAI SDK 开始微调工作:

import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo")

model是您要从其开始的模型的名称(gpt-3.5-turbo、babbage-002或davinci-002)。可以使用后缀参数自定义微调模型的名称。

开始微调工作后,可能需要一些时间才能完成。你的作业可能排在我们系统中的其他作业后面,训练模型可能需要几分钟或几小时,具体取决于模型和数据集大小。

除了创建微调作业外,你还可以列出现有作业、检索作业状态或取消作业。

# List 10 fine-tuning jobs
openai.FineTuningJob.list(limit=10)

Retrieve the state of a fine-tune

openai.FineTuningJob.retrieve("ft-abc123")

Cancel a job

openai.FineTuningJob.cancel("ft-abc123")

List up to 10 events from a fine-tuning job

openai.FineTuningJob.list_events(id="ft-abc123", limit=10)

Delete a fine-tuned model (must be an owner of the org the model was created in)

import openai
openai.Model.delete("ft-abc123")

如果微调作业的结果不如预期,请考虑以下方式调整训练数据集:

  • 收集示例以解决剩余问题

    • 如果模型在某些方面仍然不擅长,请添加训练示例,直接向模型展示如何正确地完成这些方面
  • 仔细检查现有示例是否存在问题

    • 如果您的模型存在语法、逻辑或样式问题,请检查您的数据是否存在任何相同的问题。例如,如果模型现在说“我将为您安排这次会议”(实际上不应该如此),请查看现有示例是否教导模型说它可以做它不能做的新事情
  • 考虑数据的平衡性和多样性

    • 如果数据中 60% 的助理回答说“我无法回答这个问题”,但在推理时只有 5% 的回答应该这么说,那么您可能会得到过多的拒绝
  • 确保您的训练示例包含响应所需的所有信息

    • 如果我们希望模型根据用户的个人特征来赞美用户,并且训练示例包括对先前对话中未找到的特征的辅助赞美,则模型可能会学习产生幻觉信息
  • 查看训练示例中的一致性/一致性

    • 如果多个人创建了训练数据,则模型性能可能会受到人们之间的一致性/一致性程度的限制。例如,在文本提取任务中,如果人们仅同意 70% 的提取片段,则模型可能无法做得更好
  • 确保所有训练示例都采用相同的格式,正如推理所预期的那样

3.Embeddings

OpenAI 的文本嵌入衡量文本字符串的相关性。嵌入通常用于:

  • 搜索(结果按与查询字符串的相关性排名)
  • 聚类(文本字符串按相似性分组)
  • 推荐(推荐具有相关文本字符串的项目)
  • 异常检测(识别出相关性很小的异常值)
  • 多样性测量(分析相似性分布)
  • 分类(文本字符串按最相似的标签进行分类)

嵌入是浮点数的向量(列表)。两个向量之间的距离衡量它们的相关性。距离小表明相关性高,距离大表明相关性低。

要获得Embeddings,请将文本字符串以及嵌入模型 ID 的选择(例如)发送到嵌入 API 端点text-embedding-ada-002。响应将包含一个嵌入,你可以提取、保存和使用它。

请求示例:

curl https://api.openai.com/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"input": "Your text string goes here",
"model": "text-embedding-ada-002"
}'

响应示例:

{
"data": [
{
"embedding": [
-0.006929283495992422,
-0.005336422007530928,

-4.547132266452536e-05,
-0.024047505110502243
],
"index": 0,
"object": "embedding"
}
],
"model": "text-embedding-ada-002",
"object": "list",
"usage": {
"prompt_tokens": 5,
"total_tokens": 5
}
}

OpenAI 提供了一个第二代嵌入模型(-002在模型 ID 中用 表示)和 16 个第一代模型(-001在模型 ID 中用 表示)。

建议对几乎所有用例使用 text-embedding-ada-002。它更好、使用更简单。

在Github中获取数据集,该数据集包含截至 2012 年 10 月亚马逊用户留下的总共 568,454 条食品评论。出于说明目的,我们将使用 1,000 条最新评论的子集。评论是英文的,往往是正面的或负面的。每条评论都有 ProductId、UserId、Score、评论标题(摘要)和评论正文(文本)。例如:

def get_embedding(text, model="text-embedding-ada-002"):
text = text.replace("\n", " ")
return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding']

df['ada_embedding'] = df.combined.apply(lambda x: get_embedding(x, model='text-embedding-ada-002'))
df.to_csv('output/embedded_1k_reviews.csv', index=False)

要从保存的文件加载数据,你可以运行以下命令:

import pandas as pd

df = pd.read_csv('output/embedded_1k_reviews.csv')
df['ada_embedding'] = df.ada_embedding.apply(eval).apply(np.array)

3.4.1 在嵌入字符串之前如何知道它有多少个标记?

在 Python 中,您可以使用 OpenAI 的 tokenizer 将字符串拆分为标记tiktoken。

示例代码:

import tiktoken

def num_tokens_from_string(string: str, encoding_name: str) -> int:
"""Returns the number of tokens in a text string."""
encoding = tiktoken.get_encoding(encoding_name)
num_tokens = len(encoding.encode(string))
return num_tokens

num_tokens_from_string("tiktoken is great!", "cl100k_base")

对于像文本嵌入ada-002这样的第二代嵌入模型,请使用cl100k_base编码。

3.4.2 如何快速检索 K 个最近的嵌入向量?

为了快速搜索许多向量,我们建议使用向量数据库。您可以在 GitHub 上的 Cookbook 中找到使用矢量数据库和 OpenAI API 的示例。

矢量数据库选项包括:

  • Chroma,一个开源嵌入商店
  • Milvus,一个为可扩展的相似性搜索而构建的矢量数据库
  • Pinecone,一个完全托管的矢量数据库
  • Qdrant,矢量搜索引擎
  • Redis作为向量数据库
  • Typesense,快速开源矢量搜索
  • Weaviate,一个开源矢量搜索引擎
  • Zilliz,数据基础设施,由 Milvus 提供支持

3.4.3 应该使用哪个距离函数?

推荐余弦相似度。距离函数的选择通常并不重要。

OpenAI 嵌入标准化为长度 1,这意味着:

  • 仅使用点积即可稍微更快地计算余弦相似度
  • 余弦相似度和欧氏距离将导致相同的排名

4.Fine-tuning和Embeddings的挑战

尽管Fine-tuning和嵌入技术在提高ChatGPT性能方面起到了关键作用,但也存在一些挑战。其中之一是过拟合的问题。在Fine-tuning过程中,如果模型过于关注训练数据中的噪声或特定示例,可能导致模型在其他情况下表现不佳。因此,合适的正则化和数据清洗是确保模型泛化能力的重要因素。

另一个挑战是平衡预训练和Fine-tuning的关系。预训练阶段使模型具备了广泛的语言知识,但Fine-tuning阶段又需要在特定任务上进行调整。如何在这两者之间取得平衡,以及如何在Fine-tuning过程中保留预训练模型的有益特性,都需要深入研究和实验。

5.总结

ChatGPT的成功背后离不开Fine-tuning和Embeddings技术的支持。Fine-tuning使得模型能够适应特定任务,而嵌入技术则使模型能够更好地理解语义和上下文。然而,这些技术也面临着挑战,需要持续的研究和改进。随着人工智能领域的不断发展,我们可以期待看到更多关于Fine-tuning、嵌入技术以及模型个性化的创新和突破。