既然您已经熟悉了LLMs(大型语言模型)和OpenAI API的基础知识,现在是时候将您的技能提升到更高的水平了。本章将介绍一些强大的策略,使您能够充分发挥ChatGPT和GPT-4的潜力。从提示工程、零-shot学习、少-shot学习到为特定任务进行微调模型,本章将为您提供创建任何您能想象的应用所需的所有知识。
提示工程
在深入讨论提示工程之前,让我们简要回顾一下聊天模型的完成函数,因为本节将广泛使用它。为了使代码更加紧凑,我们将该函数定义如下:
def chat_completion(prompt, model="gpt-4", temperature=0):
res = openai.ChatCompletion.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=temperature,
)
print(res["choices"][0]["message"]["content"])
这个函数接收一个提示并在终端中显示完成的结果。模型和温度是两个可选的特性,默认情况下分别设置为 GPT-4 和 0。 为了演示提示工程,我们将回到文本示例:“正如笛卡尔所说,我思故”。如果将这个输入传递给 GPT-4,那么模型自然会通过迭代添加最有可能的标记来完成这个句子:
chat_completion("As Descartes said, I think therefore")
结果,我们得到以下输出消息:
I am. This famous philosophical statement, also known as "Cogito, ergo sum,"
emphasizes the existence of the self through the act of thinking or doubting.
Descartes used this statement as a foundational principle in his philosophy,
arguing that one's own existence is the most certain and indubitable fact
that can be known.
提示工程是一门新兴的学科,专注于开发构建最佳输入以尽可能以可编程方式产生期望输出的最佳实践,其重要性不言而喻。作为一名AI工程师,您必须知道如何与AI互动以获得可利用的应用程序结果,如何提出正确的问题以及如何编写质量良好的提示;这些都是我们将在本节中讨论的主题。
值得注意的是,提示工程可能会影响使用OpenAI API的成本。使用OpenAI API发送到OpenAI并从OpenAI接收的令牌数量与您支付的费用成正比。如第2章所述,强烈建议使用max_token参数,以避免在账单上出现不愉快的惊喜。
还请注意,您应该考虑在openai方法中可以使用的不同参数,因为如果使用参数如temperature、top_p和max_token,即使使用相同的提示,您也可以获得显着不同的结果。
设计有效的提示
许多任务可以通过提示来完成,这些任务包括摘要、文本分类、情感分析和问题回答。在所有这些任务中,通常在提示中定义了三个要素:一个角色、一个上下文和一个任务,如图4-1所示。
这三个元素并不总是必需的,它们的顺序可以改变,但是如果您的提示构建得很好,元素被明确定义,那么您应该能够获得良好的结果。请注意,即使使用了这三个元素,对于复杂的任务,您可能还需要使用更高级的技术,如零-shot学习、少-shot学习和微调。这些高级技术将在本章后面讨论。
上下文
带有上下文的详细信息,可以更好地指导模型生成相关的响应。例如,如果你想让模型为你的午餐提供主菜建议,提供更多细节有助于模型更好地理解任务和生成相关的建议。在你的应用程序中,提供有关用户的偏好、饮食习惯和任何特殊要求的更多信息可能会导致更个性化的建议。所以,提供尽可能多的上下文信息通常是一个好策略。
chat_completion("Give me a suggestion for the main course for today's lunch.")
得到以下输出消息:
Grilled chicken with roasted vegetables and quinoa.
现在,有关输入消息的更多上下文细节:
prompt = """
Context: I do 2 hours of sport a day. I am vegetarian, and I don't like green
vegetables. I am conscientious about eating healthily.
Task: Give me a suggestion for a main course for today's lunch."""
chat_completion(prompt)
我们获得以下输出消息:
For today's lunch, you can try a Chickpea and Sweet Potato Curry served with
Quinoa. This dish is packed with protein, fiber, and essential nutrients,
while also being vegetarian and not relying on green vegetables. The curry
can be made with chickpeas, sweet potatoes, tomatoes, onions, garlic, and a
blend of spices like turmeric, cumin, and coriander. Cook the quinoa
separately and serve it alongside the curry for a delicious and healthy meal.
在第二个示例中,句子更加详细,因为模型有更多的上下文:它建议了一个富含蛋白质的健康素食菜肴。
上下文引导GPT的“思考”,为其响应增加了价值。构建具有良好上下文的提示是一个迭代的过程,通常需要反复试验。确定上下文必须包含的关键要点有时会有些棘手。为了帮助确定可能的改进,您可以向GPT-4提出建议。在以下示例中,我们更改了提示:不再要求模型回答我们的问题,而是要求它在上下文中提出问题,以获得更好的完成。
使用以下输入消息:
prompt = """
Context: I do 2 hours of sport a day. I am vegetarian and I don't like green
vegetables. I am very careful to eat healthily.
Task: Give me a suggestion for a main course for today's lunch?
Do not perform the requested task! Instead, can you ask me questions about the
context so that when I answer, you can perform the requested task more
efficiently?
"""
chat_completion(prompt)
我们从GPT-4模型得到以下问题:
Sure, I have a few questions to help me suggest a suitable main course for you:
1. What type of sport do you do, and do you have any specific nutritional
requirements related to it?
2. Are there any other vegetables or ingredients that you particularly enjoy
or dislike?
3. Do you prefer a hot or cold meal for lunch?
4. Are you open to trying new recipes or do you prefer sticking to
familiar dishes?
模型提出的问题通常是相关的。根据这些问题,您可以更新您在提示中的上下文。因此,我们建议您在构建提示时经常使用这个技巧。
任务
任务定义了您对GPT-4模型的使用,应该被明确定义并具体。您应该为任务提供足够的信息,并在提示中使用适当的短语来引导模型达到期望的结果。
在前面的部分中,任务是要求模型为午餐建议一道主菜。我们可以添加细节来澄清我们的要求。在这里,我们要求模型添加一张表格,其中包含每种原料和所需数量(以克为单位):
prompt = """
Context: I do 2 hours of sport a day. I am vegetarian, and I don't like green
vegetables. I am conscientious about eating healthily.
Task: Give me a suggestion for a main course for today's lunch.
With this suggestion, I also want a table with two columns where each row
contains an ingredient from the main course.
The first column in the table is the name of the ingredient.
The second column of the table is the number of grams of that ingredient needed
for one person. Do not give the recipe for preparing the main course.
"""
chat_completion(prompt)
模型给出了以下输出:
Suggestion for today's lunch: Stuffed Bell Peppers with Quinoa and Black Beans
| Ingredient | Grams per person |
|---------------------|------------------|
| Bell peppers | 200g |
| Quinoa | 60g |
| Black beans | 75g |
| Onion | 50g |
| Garlic | 5g |
| Olive oil | 10g |
| Tomato sauce | 50g |
| Cumin | 1g |
| Chili powder | 1g |
| Salt | 1g |
| Pepper | 1g |
| Shredded cheese | 30g |
在OpenAI API示例页面上,有一个包含48个GPT模型可以执行的任务示例的列表,始终附有相应的提示和演示。虽然这些示例使用了GPT-3模型和完成端点,但原理对于聊天端点也是一样的,这些示例很好地说明了如何向OpenAI模型提供任务。我们不会在这里详细介绍所有示例,但以下是其中的一些示例:
-
语法纠正
- 将句子纠正为标准英语。
- 提示:将以下句子改为标准英语:She no went to the market.
-
为二年级学生总结
- 将复杂的文本翻译成更简单的概念。
- 提示:将以下内容总结给一个二年级的学生听:Jupiter is the fifth planet […]
-
TL;DR总结
- TL;DR代表“太长了,没时间读”。可以观察到,一个文本可以通过在末尾添加Tl;dr来总结。
- 提示:一个中子星[…]的原子核。Tl;dr
-
Python转自然语言
- 用人们可以理解的语言解释一段Python代码。
- 提示:# Python 3 def hello(x): print(‘hello ‘+str(x)) # 代码的功能解释
-
计算时间复杂度
- 查找函数的时间复杂度。
- 提示:# Python 3 def hello(x, n): for i in range(n): print(‘hello ‘+str(x)) # 此函数的时间复杂度是
-
修复Python代码中的错误
- 修复包含错误的代码。
- 提示:### 有错误的Python def hello(x, n): for i in rang(n): print(‘hello ‘+str(x)) ### 修复后的Python
-
SQL查询构建
- 构建简单的SQL查询。
- 提示:创建一个SQL查询,查找所有住在加利福尼亚并且信用超过1000的用户。
-
制作类比
- 可以在两个词之间建立类比。
- 提示:为这个短语创建一个类比。问题就像是箭头,因为…
-
总结会议记录
- 总结会议记录。
- 提示:将我的速记转化为会议的第一手记录。汤姆:利润增长50%。简:新服务器已上线。凯尔:需要更多时间来修复软件。
这些示例涵盖了各种不同类型的任务,展示了如何构建有效的提示以获取所需的输出。
角色
影响模型在编写提示时的一种最后方式是给它分配一个角色。当然,角色和上下文可以独立使用,但同时使用两者可以增加对模型输出的控制。在以下示例中,我们将模型放在体育营养专家的角色中:
prompt = """
Role: You are a nutritionist designing healthy diets for high-performance
athletes. You take into account the nutrition needed for a good recovery.
Context: I do 2 hours of sport a day. I am vegetarian, and I don't like green
vegetables. I am conscientious about eating healthily.
Task: Based on your expertise defined in your role, give me a suggestion for a
main course for today's lunch. With this suggestion, I also want a table with
two columns where each row in the table contains an ingredient from the main
course. The first column in the table is the name of the ingredient.
The second column of the table is the number of grams of that ingredient needed
for one person. Do not give the recipe for preparing the main course.
"""
chat_completion(prompt)
我们得到了以下结果:
Main Course Suggestion: Quinoa and Chickpea Stuffed Bell Peppers
Ingredient Table:
| Ingredient | Grams per Person |
|---------------------|------------------|
| Quinoa | 60 |
| Chickpeas | 100 |
| Bell Peppers | 200 |
| Olive Oil | 10 |
| Onion | 50 |
| Garlic | 5 |
| Tomato | 100 |
| Feta Cheese | 30 |
| Kalamata Olives | 20 |
| Fresh Basil | 5 |
| Salt | 2 |
| Black Pepper | 1 |
正如你所见,提示可以用来调整像GPT模型这样的LLM的概率分布集。它们可以被看作是一种指导,引导模型产生特定类型的结果。虽然没有一个确定的提示设计结构,但一个有用的框架是考虑上下文、角色和任务的组合。 重要的是要理解,这只是一种方法,可以创建不需要明确定义这些元素的提示。一些提示可能会从不同的结构中受益,或者根据你的应用程序的具体需求需要更有创意的方法。因此,在适当的情况下,这个上下文-角色-任务框架不应该限制你的思维,而应该成为帮助你有效设计提示的工具。
逐步思考
正如我们所知,GPT-4 不适用于计算。它无法计算 369 × 1,235:
prompt = "How much is 369 * 1235?"
chat_completion(prompt)
我们得到的答案是:454965
正确答案是455,715。GPT-4不能解决复杂的数学问题吗?请记住,模型通过逐个预测答案中的每个标记来生成答案,从左侧开始。这意味着GPT-4首先生成最左边的数字,然后将其用作上下文的一部分来生成下一个数字,以此类推,直到形成完整的答案。这里的挑战在于,每个数字都是独立于最终的正确值进行预测的。GPT-4将数字视为标记;它没有数学逻辑。
有一个技巧可以增加语言模型的推理能力。例如,当要求解369×1235时,我们可以看到模型试图直接一次性回答。考虑到您可能也无法在没有铅笔和纸帮助进行计算的情况下解决这个乘法。通过在提示的结尾添加“让我们一步一步思考”已经经过实证证明可以促使模型进行更复杂的推理。这种技巧称为零射击-CoT策略,它是由Kojima等人于2022年发表的科学论文“大型语言模型是零射击推理者”中介绍的。
CoT代表思维链条;它在这里指的是使用提示来鼓励模型模仿逐步推理的技术。零射击一词意味着模型不依赖于任务特定的示例来执行这种推理;它已经通过一般培训准备好处理新任务。与需要模型看到任务示例的few-shot学习等技术不同,零射击学习试图在不需要任务特定示例的情况下进行推广。
正如我们将看到的那样,通过将这句话添加到提示中,模型开始通过将问题分解为子问题来进行推理。因此,它可能需要时间进行推理,从而使其能够找到以前无法一次性解决的问题的解决方案。让我们看看如果我们改变输入提示会发生什么:
prompt = "How much is 369 * 1235 ? Let's think step by step."
chat_completion(prompt)
我们得到以下输出:
Step 1: Multiply 369 by 5 (the last digit of 1235).
369 * 5 = 1845
Step 2: Multiply 369 by 30 (the second last digit of 1235 is 3, so multiply by
3 and add a 0 at the end).
369 * 30 = 11070
Step 3: Multiply 369 by 200 (the third digit of 1235 is 2, so multiply by 2 and
add two 0s at the end).
369 * 200 = 73800
Step 4: Multiply 369 by 1000 (the first digit of 1235 is 1, so multiply by 1 and
add three 0s at the end).
369 * 1000 = 369000
Step 5: Add the results from steps 1 to 4.
1845 + 11070 + 73800 + 369000 = 455715
So, 369 * 1235 = 455,715.
模型的方法发生了显著变化。它现在将主要问题分解成更小、更容易处理的步骤,而不是试图直接解决问题。
当然,很难从一个例子中确定这个技巧是否通常有效,或者我们只是碰巧成功了。在各种数学问题的基准测试中,经验性实验证明,这个技巧显著提高了GPT模型的准确性。尽管这个技巧对大多数数学问题都很有效,但并不适用于所有情况。《大型语言模型是零-shot推理器》的作者们发现,它对多步算术问题、涉及符号推理的问题、涉及策略的问题以及其他涉及推理的问题最有益。它并不适用于常识问题。
实施少样本学习
少样本学习,由Brown等人在《语言模型是少样本学习者》中引入,指的是语言模型(LLM)仅仅通过在提示中提供少量示例就能够推广并产生有价值的结果的能力。使用少样本学习,您会提供一些关于您希望模型执行的任务的示例,如图4-2所示。这些示例将引导模型处理所需的输出格式。
在这个示例中,我们要求LLM将特定单词转化为表情符号。很难想象要放入提示中的指令来执行此任务。但是通过少样本学习,这变得很容易。提供示例,模型将自动尝试重现它们:
prompt = """
I go home --> go
my dog is sad --> my is
I run fast --> run
I love my wife --> my wife
the girl plays with the ball --> the with the
The boy writes a letter to a girl -->
"""
chat_completion(prompt)
从上面的示例中,我们得到以下消息作为输出:
The a to a
Few-shot learning是一种示例,展示了所需输出的输入示例。然后,在最后一行,我们提供了我们想要完成的提示。这个提示与之前的示例具有相同的形式。自然地,语言模型将根据给定示例的模式执行完成操作。
我们可以看到,仅仅通过几个示例,模型就能够复制这些指令。通过利用LLM在培训阶段获得的广泛知识,它们可以迅速适应,并基于仅仅几个示例生成准确的答案。
当您在提示中提供示例时,确保上下文清晰且相关是至关重要的。清晰的示例可以提高模型匹配所需输出格式并执行问题解决过程的能力。相反,不足或模糊的示例可能会导致意外或不正确的结果。因此,仔细编写示例并确保它们传达正确的信息可以显着影响模型执行任务的准确性。
另一种引导LLM的方法是一次性学习。正如其名称所示,在这种情况下,您只提供一个示例来帮助模型执行任务。尽管这种方法提供的指导较少,但对于更简单的任务或者LLM已经对主题具有丰富的背景知识的情况下,它可以是有效的。一次性学习的优点包括简单、更快的提示生成以及更低的计算成本,因此更低的API成本。然而,对于复杂的任务或需要更深入理解所期望的结果的情况,few-shot learning可能是确保准确结果的更合适的方法。
虽然本节探讨了各种单独使用的提示工程技术,但请注意,您可以将这些技术结合起来以获得更好的结果。作为开发人员,您的任务是找到适用于特定问题的最有效的提示。请记住,提示工程是一种试验和错误的迭代过程。
提高提示效果
我们已经看到了几种提示工程技巧,允许我们影响GPT模型的行为,以获得满足我们需求的更好结果。在本节中,我们将提供一些在为GPT模型编写提示时可以在不同情况下使用的技巧和窍门。
指示模型提出更多问题
在提示的末尾添加一个问题,询问模型是否理解了问题,并指示模型提出更多问题,对于构建基于聊天机器人的解决方案是一种有效的技巧。您可以在提示的末尾添加以下文本:
Did you understand my request clearly? If you do not fully understand my request,
ask me questions about the context so that when I answer, you can
perform the requested task more efficiently.
格式化输出
有时候,您可能希望在更长的过程中使用LLM的输出:在这种情况下,输出格式很重要。例如,如果您想要一个JSON输出,模型往往会在JSON块之前和之后写入输出。如果您在提示中添加输出必须由json.loads接受,则它倾向于效果更好。这种类型的技巧可以在许多情况下使用。
例如,使用以下脚本:
prompt = """
Give a JSON output with 5 names of animals. The output must be accepted
by json.loads.
"""
chat_completion(prompt, model='gpt-4')
我们得到以下的JSON代码块:
{
"animals": [
"lion",
"tiger",
"elephant",
"giraffe",
"zebra"
]
}
重复说明
经验表明,反复说明指令可以获得良好的结果,特别是当提示很长时。思路是在提示中多次添加相同的指令,但每次都以不同的方式表述。
这也可以通过负面提示来完成。
使用负面提示
在文本生成的背景下,负面提示是一种通过指定不希望在输出中看到的内容来引导模型的方法。它们充当了限制条件或指导方针,以过滤掉某些类型的响应。当任务复杂时,这种技术特别有用:模型在不同方式多次重复任务时往往更加精确地遵循指示。
继续前面的例子,我们可以通过添加“不要在 json 文本之前或之后添加任何内容。”的负面提示来坚持输出格式。在第三个项目中,我们在第三章中使用了负面提示:
Extract the keywords from the following question: {user_question}. Do not answer
anything else, only the keywords.
没有将这个提示添加到提示中,模型往往不会遵循指示。
添加长度约束
长度约束通常是个好主意:如果您期望只有一个单词的答案或10个句子,可以将其添加到提示中。这就是我们在第3章的第一个项目中所做的:我们指定了LENGTH:100个字来生成一篇足够长的新闻文章。在第四个项目中,我们的提示也有一个长度的指令:如果你可以回答这个问题:ANSWER,如果你需要更多信息:MORE,如果你无法回答:OTHER。只回答一个词… 没有最后一句,模型往往会形成句子,而不是遵循指示。
Fine-tuning(微调)
OpenAI提供了许多现成可用的GPT模型。尽管这些模型在各种任务上表现出色,但对它们进行特定任务或上下文的微调可以进一步提高它们的性能。
入门
让我们想象一下,您想为公司创建一个电子邮件回复生成器。由于您的公司在特定行业中使用特定的词汇,您希望生成的电子邮件回复保留您当前的写作风格。有两种策略可以实现这一目标:一种是使用前面介绍的提示工程技术来强制模型输出您想要的文本,另一种是对现有模型进行微调。本节探讨了第二种技术。
对于此示例,您需要收集大量包含有关您特定业务领域的数据、客户的查询以及对这些查询的回复的电子邮件。然后,您可以使用此数据对现有模型进行微调,以学习您公司的特定语言模式和词汇。微调后的模型本质上是一个新模型,由OpenAI提供的原始模型之一构建,其中调整了模型的内部权重以适应您的特定问题,使新模型在类似于其在微调数据集中看到的示例的任务上提高了准确性。通过对现有的LLM进行微调,可以创建一个高度定制和专门化的电子邮件回复生成器,专门针对您特定业务中使用的语言模式和词汇。
图4-3说明了微调过程,其中来自特定领域的数据集用于更新现有GPT模型的内部权重。目标是使新的微调模型在特定领域中比原始GPT模型更好地进行预测。应强调这是一个全新的模型。这个新模型位于OpenAI的服务器上:与以前一样,您必须使用OpenAI的API来使用它,因为它无法在本地访问。
调整GPT基础模型以满足特定领域需求
目前,微调仅适用于davinci、curie、babbage和ada等基础模型。每个模型都在准确性和所需资源之间进行权衡。作为开发者,你可以选择最适合你的应用的模型:较小的模型,如ada和babbage,可能对于简单的任务或资源有限的应用来说更快速且更具成本效益,而较大的模型,如curie和davinci,则提供更高级的语言处理和生成功能,使它们非常适合需要更高准确性的复杂任务。
这些都是原始模型,不属于InstructGPT系列模型。例如,它们没有受益于与人类在回路中的强化学习阶段。通过对这些基础模型进行微调,例如根据自定义数据集调整它们的内部权重,你可以将它们定制为特定的任务或领域。虽然它们没有InstructGPT系列的处理和推理能力,但通过利用它们预训练的语言处理和生成能力,它们为构建专门应用提供了坚实的基础。
微调与少样本学习的区别
微调是一种通过对特定任务的数据进行重新训练,以提高模型性能并使其答案更准确的过程。在微调中,你会更新模型的内部参数。正如我们之前所看到的,少样本学习通过输入提示向模型提供了有限数量的良好示例,这些示例指导模型基于这些少量示例产生期望的结果。在少样本学习中,模型的内部参数不会被修改。
微调和少样本学习都可以用来增强GPT模型。微调会生成一个高度专门化的模型,可以为特定任务提供更准确和上下文相关的结果。这使得它成为在大量数据可用的情况下的理想选择。这种个性化确保生成的内容更符合目标领域的特定语言模式、词汇和语气。
少样本学习是一种更灵活和数据高效的方法,因为它不需要对模型进行重新训练。这种技术在只有有限示例可用或需要快速适应不同任务的情况下非常有益。少样本学习允许开发人员快速进行原型设计和尝试不同的任务,使其成为许多用例的多功能和实用选择。选择两种方法之间的关键标准之一是使用和训练使用微调的模型更昂贵。
微调方法通常需要大量的数据。可用示例的不足经常限制了这种类型技术的使用。为了让你了解微调所需的数据量,你可以假设对于相对简单的任务或仅需要进行轻微调整的情况,你可以通过几百个输入提示示例及其相应的期望完成来获得良好的微调结果。这种方法适用于预训练的GPT模型已经在任务上表现得相当不错,但需要进行轻微的改进以更好地与目标领域保持一致的情况。然而,对于更复杂的任务或需要更多自定义的应用情况,你的模型可能需要使用成千上万个示例进行训练。这可以对应到我们之前提出的用例,即自动回复邮件以符合你的写作风格。你还可以为需要数十万甚至数百万示例的非常专业的任务进行微调。这种微调规模可以显著提高性能,并更好地使模型适应特定领域。
使用OpenAI API进行微调
本节将指导您如何使用OpenAI API进行LLM微调。我们将解释如何准备数据、上传数据集并使用API创建微调模型。
准备您的数据
要更新一个LLM模型,需要提供一个包含示例的数据集。数据集应该是一个JSONL文件,其中每一行对应一个提示和完成的对应关系:
{"prompt": "<prompt text>", "completion": "<completion text>"}
{"prompt": "<prompt text>", "completion": "<completion text>"}
{"prompt": "<prompt text>", "completion": "<completion text>"}
…
JSONL文件是一个文本文件,每一行代表一个JSON对象。您可以使用它来高效地存储大量数据。OpenAI提供了一个工具,可以帮助您生成这个训练文件。此工具可以接受各种文件格式作为输入(CSV、TSV、XLSX、JSON或JSONL),只要它们包含提示和完成列/键,并且它们输出一个准备好进行微调过程的训练JSONL文件。此工具还会验证并提供改进数据质量的建议。
在终端中使用以下代码行运行此工具:
$ openai tools fine_tunes.prepare_data -f <LOCAL_FILE>
该应用程序将提出一系列建议,以改善最终文件的结果;您可以接受或不接受这些建议。您还可以指定选项“-q”,该选项将自动接受所有建议。
如果您有足够的数据,该工具会询问是否需要将数据分成训练集和验证集。这是一种推荐的做法。算法将使用训练数据来修改模型的参数,而验证集可以衡量模型在未使用来更新参数的数据集上的性能。
对于LLM的微调,最好使用高质量的示例,最好由专家审核。当使用现有数据集进行微调时,请确保对数据进行筛选,以排除冒犯性或不准确的内容,或者如果数据集太大无法手动审核所有条目,则可以检查随机样本。
使您的数据可用
您一旦准备好包含训练示例的数据集,就需要将其上传到OpenAI服务器。OpenAI API提供了不同的功能来操作文件。以下是最重要的功能之一:
- 上传文件:
openai.File.create(
file=open("out_openai_completion_prepared.jsonl", "rb"),
purpose='fine-tune'
)
有两个参数是强制的:file
和 purpose
。将 purpose
设置为 fine-tune
。这将验证用于 fine-tune 的下载文件格式。此函数的输出是一个字典,其中您可以在 id
字段中检索到 file_id
。目前,文件的总大小可以高达1 GB。如果需要更多,请联系OpenAI。
- 删除文件:
openai.File.delete("file-z5mGg(...)")
一个参数是强制的:file_id
。
- 列出所有文件:
openai.File.list()
当你开始 fine-tuning 过程时,检索文件的 ID 可能会有所帮助。
创建一个 fine-tuned 模型
将上传的文件进行 fine-tuning 是一个简单的过程。openai.FineTune.create() 端点在 OpenAI 服务器上创建一个作业,用于从给定的数据集中优化指定的模型。此功能的响应包含排队作业的详细信息,包括作业的状态、fine_tune_id,以及在过程结束时模型的名称。
主要输入参数如下表所述:
列出 fine-tuning 作业
您可以使用以下函数获得在OpenAI服务器上运行的所有fine-tuning作业的列表:
openai.FineTune.list()
结果是一个包含有关所有已优化模型的信息的字典。
取消fine-tuning作业
您可以通过以下函数立即中断在OpenAI服务器上运行的作业:
openai.FineTune.cancel()
此函数仅有一个强制参数:fine_tune_id
。fine_tune_id
参数是一个以ft-
开头的字符串;例如,ft-Re12otqdRaJ(...)
。您可以在使用函数openai.FineTune.create()
创建作业后获取它。如果您忘记了fine_tune_id
,可以使用openai.FineTune.list()
来检索它。
Fine-Tuning应用
Fine-Tuning提供了一种强大的方式来提升模型在各种应用中的性能。本节将介绍几种已经成功应用Fine-Tuning的用例。您可以从这些示例中获得灵感!也许您的使用情况与这些示例类似。再次提醒,与基于提示工程的其他技术相比,Fine-Tuning成本更高,因此大多数情况下并不需要。但是当需要时,这种技术可以显著改善您的结果。
法律文件分析
在这个用例中,LLM 被用于处理法律文件并提取有价值的信息。这些文件通常使用特定的行业术语,这使得非专业人士难以理解这些文本。我们在第1章已经看到,在统一司法考试中,GPT-4 的得分位于第90百分位。在这种情况下,通过对特定主题的法律文献或特定类型的最终用户进行精细调整,可以使模型在处理法律语言方面更为熟练,从而更擅长执行与特定最终用户相关的任务。
通过使用大量数据对LLM进行微调,以分析法律文件,可以潜在地显著提高模型在这些任务中的性能,使其能够更好地处理通常超出提示工程技术能力的法律语言的微妙之处。
自动化代码审查
在这个用例中,微调可以帮助模型分析并为开发人员编写的代码提出改进建议。这需要在一个包含代码片段和注释的数据集上对模型进行训练,使其能够处理特定于编程语言的语法、语义和最佳实践。请注意,这个用例与GitHub的Copilot工具类似,该工具旨在通过实时提供代码和整个函数的建议来协助开发人员编写代码。您可以使用微调来训练模型执行特定任务,比如代码审查,并为自己的代码项目构建符合特定标准或专用于特定编程框架的项目。
作为这个用例的输入文件示例,您的JSONL文件可以包含代码和相应审查评论的配对。这将帮助模型学习如何提供关于代码质量的准确和相关的反馈意见。JSONL文件中的一行可能是这样的:
{"prompt": "Review the following Python code:
'def sum(a, b): return a + bnresult = sum(5, '5')'",
"completion": "Type error: The 'sum' function is adding an integer and a string.
Consider converting the string to an integer using int() before passing it
to the function."}
金融文件摘要化
在金融文件摘要生成中,经过微调的LLM可以生成金融文件的简明准确摘要,例如盈利报告、财务报表和分析师报告。通过在与金融记录明确相关的数据集上进行微调,生成的模型可以更准确地理解这些文档的术语和背景。例如,该模型可以将一份有关央行利率上调的详细报告压缩成简洁但信息丰富的摘要。
要调整现有模型以获得更好的金融文件摘要,您需要已经拥有大量的示例摘要。然后,您需要提供一个包含金融文件及其理想摘要配对的JSONL文件给模型。例如:
{"prompt": "The central bank announced a 0.25% increase in the benchmark
interest rate, citing concerns about inflationary pressures. This move is
expected to affect borrowing costs for consumers and businesses.",
"completion": "Bank increases rate 0.25% for inflation worry. May affect people,
business borrowing costs."}
技术文档翻译
使用微调来翻译技术文档可以显著提高基础模型的性能,相比之下,与少样本学习相比,您可以获得更好的结果。主要原因是技术文档通常包含专业词汇和复杂的句子结构,而少样本学习无法有效处理。基础模型是未经调整的GPT-3模型,类似于InstructGPT-3模型,它们没有经过强化学习与人类反馈技术的调整。要使用它们,需要进行微调。为了调整现有的基础模型,您需要准备一个包含培训数据的JSONL文件。对于技术文档翻译的用例,该文件的内容将包括将技术文本翻译成目标语言的内容。
利基主题的新闻文章生成
在这种情况下,微调模型可以为高度专业化的主题生成高质量、引人入胜且上下文相关的新闻文章,而基础模型可能没有见过足够多的相关内容以确保准确性。与其他用例一样,您需要创建一个训练数据集,以使您的模型专门针对文章写作进行优化。为此,您需要拥有许多有关特定利基主题的文章。这些数据将用于创建包含提示-完成对的JSONL文件。以下是一个小例子:
{"prompt": "Write an introductory article about a new environmentally friendly
cryptocurrency: 'EcoCoin: The Green Cryptocurrency Taking
the Market by Storm'",
"completion": "As concerns over the environmental impact of cryptocurrency
mining (...) mining process and commitment to sustainability."}
生成和微调合成数据用于电子邮件营销活动
在这个示例中,我们将创建一个文本生成工具,用于为电子邮件营销机构制定有针对性的内容,以创建个性化的电子邮件营销活动,用于推广企业的产品或服务。这些电子邮件旨在吸引受众并推广产品或服务。
假设我们的电子邮件营销机构有一家客户,该客户属于支付处理行业,要求帮助他们进行直接电子邮件营销活动,向商家提供一种新的电子商务支付服务。电子邮件营销机构决定在此项目中使用精细调整技术。为了进行这种精细调整,我们的电子邮件营销机构需要大量的数据。
在我们的情况下,为了演示目的,我们将需要合成数据。通常情况下,最好的结果是使用来自人类专家的数据,但在某些情况下,合成数据生成可以是一个有用的解决方案。
创建一个合成数据集
在以下示例中,我们将从GPT-3.5 Turbo生成人工数据。为此,我们将在提示中指定,我们需要促销句子,以向特定商户销售电子商务服务。商户由活动领域、商店所在城市和商店大小来刻画。我们通过将提示发送到之前定义的chat_completion函数,从GPT-3.5 Turbo获得促销句子。
我们的脚本从定义三个列表开始,这些列表分别对应于商店类型、商店所在城市和商店大小:
l_sector = ['Grocery Stores', 'Restaurants', 'Fast Food Restaurants',
'Pharmacies', 'Service Stations (Fuel)', 'Electronics Stores']
l_city = ['Brussels', 'Paris', 'Berlin']
l_size = ['small', 'medium', 'large']
然后,我们在一个字符串中定义了第一个提示。在这个提示中,角色、上下文和任务都很明确,因为它们是使用本章前面描述的提示工程技巧构建的。在这个字符串中,大括号之间的三个值将在代码后面被替换为相应的值。这个第一个提示用于生成合成数据:
f_prompt = """
Role: You are an expert content writer with extensive direct marketing
experience. You have strong writing skills, creativity, adaptability to
different tones and styles, and a deep understanding of audience needs and
preferences for effective direct campaigns.
Context: You have to write a short message in no more than 2 sentences for a
direct marketing campaign to sell a new e-commerce payment service to stores.
The target stores have the following three characteristics:
- The sector of activity: {sector}
- The city where the stores are located: {city}
- The size of the stores: {size}
Task: Write a short message for the direct marketing campaign. Use the skills
defined in your role to write this message! It is important that the message
you create takes into account the product you are selling and the
characteristics of the store you are writing to.
"""
以下提示仅包含三个变量的值,用逗号分隔。它不用于创建合成数据;仅用于微调:
[`f_sub_prompt = "{sector}, {city}, {size}"`]()
接下来是代码的主要部分,它对我们之前定义的三个值列表进行迭代。我们可以看到循环中的代码非常简单。我们将两个提示中的花括号中的值替换为适当的值。变量 prompt 与函数 chat_completion 一起使用,以生成保存在 response_txt 中的广告。然后,将 sub_prompt 和 response_txt 变量添加到 out_openai_completion.csv 文件中,这是我们用于微调的训练集:
[`df = pd.DataFrame()
for sector in l_sector:
for city in l_city:
for size in l_size:
for i in range(3): ## 3 times each
prompt = f_prompt.format(sector=sector, city=city, size=size)
sub_prompt = f_sub_prompt.format(
sector=sector, city=city, size=size
)
response_txt = chat_completion(
prompt, model="gpt-3.5-turbo", temperature=1
)
new_row = {"prompt": sub_prompt, "completion": response_txt}
new_row = pd.DataFrame([new_row])
df = pd.concat([df, new_row], axis=0, ignore_index=True)
df.to_csv("out_openai_completion.csv", index=False)`]()
请注意,对于每种特征的组合,我们生成了三个示例。为了最大化模型的创造力,我们将温度设置为1。在此脚本的末尾,我们有一个存储在文件 out_openai_completion.csv 中的 Pandas 表格。它包含162个观察结果,有两列,分别包含提示和相应的完成内容。以下是此文件的前两行内容:
"Grocery Stores, Brussels, small",Introducing our new e-commerce payment service -
the perfect solution for small Brussels-based grocery stores to easily and
securely process online transactions. "Grocery Stores, Brussels, small",
Looking for a hassle-free payment solution for your small grocery store in
Brussels? Our new e-commerce payment service is here to simplify your
transactions and increase your revenue. Try it now!
现在,我们可以调用工具从 out_openai_completion.csv 生成训练文件,如下所示:
[`$ openai tools fine_tunes.prepare_data -f out_openai_completion.csv`]()
正如您在以下代码行中所看到的,这个工具提出了改进我们的提示-完成对的建议。在文本末尾,它甚至提供了如何继续微调过程以及在完成微调过程后如何使用模型进行预测的说明:
[``Analyzing... - Based on your file extension, your file is formatted as a CSV file - Your file contains 162 prompt-completion pairs - Your data does not contain a common separator at the end of your prompts.
Having a separator string appended to the end of the prompt makes it clearer
to the fine-tuned model where the completion should begin. See
https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset
for more detail and examples. If you intend to do open-ended generation,
then you should leave the prompts empty - Your data does not contain a common ending at the end of your completions.
Having a common ending string appended to the end of the completion makes it
clearer to the fine-tuned model where the completion should end. See
https://oreil.ly/MOff7 for more detail and examples. - The completion should start with a whitespace character (` `). This tends to
produce better results due to the tokenization we use. See
https://oreil.ly/MOff7 for more details
Based on the analysis we will perform the following actions: - [Necessary] Your format `CSV` will be converted to `JSONL` - [Recommended] Add a suffix separator ` ->` to all prompts [Y/n]: Y - [Recommended] Add a suffix ending `n` to all completions [Y/n]: Y - [Recommended] Add a whitespace character to the beginning of the completion [Y/n]: Y
Your data will be written to a new JSONL file. Proceed [Y/n]: Y
Wrote modified file to `out_openai_completion_prepared.jsonl`
Feel free to take a look!
Now use that file when fine-tuning: > openai api fine_tunes.create -t "out_openai_completion_prepared.jsonl"
After you’ve fine-tuned a model, remember that your prompt has to end with the
indicator string ` ->` for the model to start generating completions, rather
than continuing with the prompt. Make sure to include `stop=["n"]` so that the
generated texts ends at the expected place.
Once your model starts training, it'll approximately take 4.67 minutes to train
a `curie` model, and less for `ada` and `babbage`. Queue will approximately
take half an hour per job ahead of you.``]()
在此过程结束时,会生成一个名为out_openai_completion_prepared.jsonl的新文件,准备好用于发送到OpenAI服务器以运行微调过程。
请注意,正如函数的消息中所解释的那样,已经通过在末尾添加字符串->来修改提示,并且已经将以n结尾的后缀添加到所有完成项中。
使用合成数据集微调模型
以下代码上传文件并进行微调。在此示例中,我们将使用davinci作为基础模型,并将生成的模型命名为direct_marketing作为后缀:
ft_file = openai.File.create(
file=open("out_openai_completion_prepared.jsonl", "rb"), purpose="fine-tune"
)
openai.FineTune.create(
training_file=ft_file["id"], model="davinci", suffix="direct_marketing"
)
这将启动使用我们的数据更新davinci模型的过程。这个微调过程可能需要一些时间,但当完成时,您将拥有一个适用于您的任务的新模型。完成微调所需的时间主要取决于您的数据集中的示例数量、示例中的标记数量以及您选择的基础模型。为了让您了解微调所需的时间,举个例子,在我们的示例中,微调不到五分钟。但是,我们也见过一些情况下微调需要超过30分钟的情况:
$ openai api fine_tunes.create -t out_openai_completion_prepared.jsonl
-m davinci --suffix "direct_marketing"
Upload progress: 100%|| 40.8k/40.8k [00:00<00:00, 65.5Mit/s]
Uploaded file from out_openai_completion_prepared.jsonl: file-z5mGg(...)
Created fine-tune: ft-mMsm(...)
Streaming events until fine-tuning is complete...
(Ctrl-C will interrupt the stream, but not cancel the fine-tune)
[] Created fine-tune: ft-mMsm(...)
[] Fine-tune costs $0.84
[] Fine-tune enqueued. Queue number: 0
[] Fine-tune started
[] Completed epoch 1/4
[] Completed epoch 2/4
[] Completed epoch 3/4
[] Completed epoch 4/4
要重新连接到服务器并获取正在运行的微调作业的状态,您可以使用以下命令fine_tunes.follow,其中fine_tune_id是微调作业的ID:
$ openai api fine_tunes.follow -i fine_tune_id
此ID在创建作业时提供。在我们之前的示例中,我们的fine_tune_id是ft-mMsm(…)。如果您丢失了fine_tune_id,可以通过以下方式显示所有模型:
$ openai api fine_tunes.list
要立即取消 fine-tune 作业,使用以下命令:
$ openai api fine_tunes.cancel -i fine_tune_id
要删除 fine-tune 作业,使用以下命令:
$ openai api fine_tunes.delete -i fine_tune_id
使用经过微调的模型进行文本补全
构建好您的新模型后,可以通过不同方式访问它以进行新的文本补全。最简单的测试方式可能是通过 Playground 工具。要在此工具中访问您的模型,可以在 Playground 界面右侧的下拉菜单中搜索它们(参见图4-4)。所有您经过微调的模型都位于此列表的底部。选择您的模型后,就可以使用它进行预测。
在下面的示例中,我们使用了经过微调的LLM,输入提示为”Hotel, New York, small ->”。在没有进一步的说明下,模型自动为纽约的一家小型酒店生成了一则广告,以销售一项面向小型酒店的电子商务支付服务。 我们已经在一个包含仅162个示例的小型数据集上获得了出色的结果。对于微调任务,通常建议拥有数百个实例,理想情况下是数千个。此外,我们的训练集是通过合成生成的,而理想情况下应该由营销领域的专家编写。 要在OpenAI API中使用它,我们与之前一样使用openai.Completion.create(),只是我们需要使用我们新模型的名称作为输入参数。不要忘记在所有提示的末尾加上”->”,并将”n”设置为停止词:
openai.Completion.create(
model="davinci:ft-book:direct-marketing-2023-05-01-15-20-35",
prompt="Hotel, New York, small ->",
max_tokens=100,
temperature=0,
stop="n"
)
我们获得以下回答:
<OpenAIObject text_completion id=cmpl-7BTkrdo(...) at 0x7f2(4ca5c220> JSON: {
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"text": " "Upgrade your hotel's payment system with our new e-commerce
service, designed for small businesses.
}
],
"created": 1682970309,
"id": "cmpl-7BTkrdo(...)",
"model": "davinci:ft-book:direct-marketing-2023-05-01-15-20-35",
"object": "text_completion",
"usage": {
"completion_tokens": 37,
"prompt_tokens": 8,
"total_tokens": 45
}
}
正如我们所展示的,微调可以让Python开发人员根据他们独特的业务需求定制LLM,尤其是在像我们的电子邮件营销示例中这样的动态领域。这是一种强大的方法,可以定制您需要的应用程序的语言模型。最终,这可以帮助您更好地为客户提供服务并推动业务增长。
微调的成本
微调模型的使用成本很高。首先,您必须支付训练费用,一旦模型准备好,每次预测都会比使用OpenAI提供的基础模型要贵一些。
价格可能会有变化,但截至撰写本文时,情况如下表所示。
作为对比,gpt-3.5-turbo 模型的价格是每 1,000 个标记 0.002 美元。正如前面提到的,gpt-3.5-turbo 具有最佳的性价比。
要获取最新的价格信息,请访问 OpenAI 的定价页面。