首页 > AI资讯 > 行业动态 > OpenAI突发更新!GPT-3.5正式开放「微调」,人人可打造专属ChatGPT|附最全官方指南

OpenAI突发更新!GPT-3.5正式开放「微调」,人人可打造专属ChatGPT|附最全官方指南

新火种    2023-09-01

声明:本文来自于

【新智元导读】今天,OpenAI正式开放GPT-3.5微调API,GPT-4版本也即将推出。这意味着,继插件「APP Store」大爆发后,所有人皆可以打造个性化的专属「类ChatGPT应用」。

终于来了!

刚刚,OpenAI正式宣布,所有开发者都可以对GPT-3.5Turbo进行微调。

微调gpt-3.5-turbo需要会话聊天格式。对于babbage-002和davinci-002,你可以按照用于旧版微调的提示完成配对格式,如下:

{"prompt":"","completion":""}{"prompt":"","completion":""}{"prompt":"","completion":""}
- 编写提示

建议在微调之前,用你认为对模型最有效的指令和提示,纳入到每个训练示例中。

这样做可以获得最佳和最普遍的结果,尤其是在训练示例相对较少(不足100个)的情况下。

如果你想缩短每个示例中重复出现的指令或提示,以节省成本,请记住,模型的行为很可能包含这些指令,很难让模型在推理时忽略这些「内置」指令。

可能需要更多的训练示例才能获得良好的结果,因为模型必须完全通过演示学习,而无需指令指导。

- 示例数量

要微调模型,你需要提供至少10个示例。

通常会看到,使用gpt-3.5-turbo对50-100个训练示例进行微调,有着明显改进。根据具体使用情况,同样的的示例效果不一。

OpenAI建议,从50个精心制作示例开始,看看模型在微调后是否显示出改进的迹象。

在某些情况下,这可能就足够了,但即使模型尚未达到输出质量,明确的改进也是一个好迹象,表明提供更多数据将继续改进模型。

如果没有改进,则表明你可能需要重新考虑如何为模型设置任务,或者在扩展到有限的示例集之前,重新调整数据结构。

- 拆分训练和测试

收集初始数据集后,建议将其拆分为训练和测试两个部分。

当提交包含训练和测试文件的微调时,OpenAI将在训练过程中提供两者的统计数据。

这些统计数据将是模型改进程度的初始信号。

此外,通过在测试集上生成样本,尽早构建测试集将有助于确保你能够在训练后评估模型。

- token限制

每个训练示例限制为4096个token。

训练时,长度超过这一限制,将被截断为4096个token。

因此,要确保整个训练示例适合上下文,请检查消息内容中的总token数是否低于4,000个。每个文件当前限制为50MB。

- 估算成本

为了估算微调成本,主要还是参考官方定价页面,了解每1k token成本的详细信息。

在3个epoch内训练了100,000个token的训练文件,预期成本约为2.4美元。

- 检查数据格式

编译完数据集后,在创建微调之前,检查数据格式非常重要。

为此,OpenAI创建了一个简单的Python脚本,你可以使用它来查找潜在错误、查看token计数并估计微调的成本。

数据格式化脚本:

#Westartbyimportingtherequiredpackagesimportjsonimportosimporttiktokenimportnumpyasnpfromcollectionsimportdefaultdict#Next,wespecifythedatapathandopentheJSONLfiledata_path=""#Loaddatasetwithopen(data_path)asf:dataset=[json.loads(line)forlineinf]#Wecaninspectthedataquicklybycheckingthenumberofexamplesandthefirstitem#Initialdatasetstatsprint("Numexamples:",len(dataset))print("Firstexample:")formessageindataset[0]["messages"]:print(message)#Nowthatwehaveasenseofthedata,weneedtogothroughallthedifferentexamplesandchecktomakesuretheformattingiscorrectandmatchestheChatcompletionsmessagestructure#Formaterrorchecksformat_errors=defaultdict(int)forexindataset:ifnotisinstance(ex,dict):format_errors["data_type"]+=1continuemessages=ex.get("messages",None)ifnotmessages:format_errors["missing_messages_list"]+=1continueformessageinmessages:if"role"notinmessageor"content"notinmessage:format_errors["message_missing_key"]+=1ifany(knotin("role","content","name")forkinmessage):format_errors["message_unrecognized_key"]+=1ifmessage.get("role",None)notin("system","user","assistant"):format_errors["unrecognized_role"]+=1content=message.get("content",None)ifnotcontentornotisinstance(content,str):format_errors["missing_content"]+=1ifnotany(message.get("role",None)=="assistant"formessageinmessages):format_errors["example_missing_assistant_message"]+=1ifformat_errors:print("Founderrors:")fork,vinformat_errors.items():print(f"{k}:{v}")else:print("Noerrorsfound")#Beyondthestructureofthemessage,wealsoneedtoensurethatthelengthdoesnotexceedthe4096tokenlimit.#Tokencountingfunctionsencoding=tiktoken.get_encoding("cl100k_base")#notexact!#simplifiedfromhttps://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynbdefnum_tokens_from_messages(messages,tokens_per_message=3,tokens_per_name=1):num_tokens=0formessageinmessages:num_tokens+=tokens_per_messageforkey,valueinmessage.items():num_tokens+=len(encoding.encode(value))ifkey=="name":num_tokens+=tokens_per_namenum_tokens+=3returnnum_tokensdefnum_assistant_tokens_from_messages(messages):num_tokens=0formessageinmessages:ifmessage["role"]=="assistant":num_tokens+=len(encoding.encode(message["content"]))returnnum_tokensdefprint_distribution(values,name):print(f"####Distributionof{name}:")print(f"min/max:{min(values)},{max(values)}")print(f"mean/median:{np.mean(values)},{np.median(values)}")print(f"p5/p95:{np.quantile(values,0.1)},{np.quantile(values,0.9)}")#Last,wecanlookattheresultsofthedifferentformattingoperationsbeforeproceedingwithcreatingafine-tuningjob:#Warningsandtokenscountsn_missing_system=0n_missing_user=0n_messages=[]convo_lens=[]assistant_message_lens=[]forexindataset:messages=ex["messages"]ifnotany(message["role"]=="system"formessageinmessages):n_missing_system+=1ifnotany(message["role"]=="user"formessageinmessages):n_missing_user+=1n_messages.append(len(messages))convo_lens.append(num_tokens_from_messages(messages))assistant_message_lens.append(num_assistant_tokens_from_messages(messages))print("Numexamplesmissingsystemmessage:",n_missing_system)print("Numexamplesmissingusermessage:",n_missing_user)print_distribution(n_messages,"num_messages_per_example")print_distribution(convo_lens,"num_total_tokens_per_example")print_distribution(assistant_message_lens,"num_assistant_tokens_per_example")n_too_long=sum(l>4096forlinconvo_lens)print(f"{n_too_long}examplesmaybeoverthe4096tokenlimit,theywillbetruncatedduringfine-tuning")#Pricinganddefaultn_epochsestimateMAX_TOKENS_PER_EXAMPLE=4096MIN_TARGET_EXAMPLES=100MAX_TARGET_EXAMPLES=25000TARGET_EPOCHS=3MIN_EPOCHS=1MAX_EPOCHS=25n_epochs=TARGET_EPOCHSn_train_examples=len(dataset)ifn_train_examples*TARGET_EPOCHSMAX_TARGET_EXAMPLES:n_epochs=max(MIN_EPOCHS,MAX_TARGET_EXAMPLES//n_train_examples)n_billing_tokens_in_dataset=sum(min(MAX_TOKENS_PER_EXAMPLE,length)forlengthinconvo_lens)print(f"Datasethas~{n_billing_tokens_in_dataset}tokensthatwillbechargedforduringtraining")print(f"Bydefault,you'lltrainfor{n_epochs}epochsonthisdataset")print(f"Bydefault,you'llbechargedfor~{n_epochs*n_billing_tokens_in_dataset}tokens")print("Seepricingpagetoestimatetotalcosts")

验证数据后,需要上传文件才能与微调一起使用:

openai.File.create(file=open("mydata.jsonl","rb"),purpose='fine-tune')
创建微调模型

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

使用OpenAI SDK开始微调:

importosimportopenaiopenai.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)。你可以使用后缀参数自定义微调模型的名称。

开始微调后,可能需要一些时间才能完成。

根据模型和数据集的大小,训练模型可能需要几分钟,或几个小时。模型训练完成后,创建微调模型的用户将收到一封电子邮件确认。

除了创建微调模型之外,你还可以列出现有任务、检索任务状态或取消任务。

#List10fine-tuningjobsopenai.FineTuningJob.list(limit=10)#Retrievethestateofafine-tuneopenai.FineTuningJob.retrieve("ft-abc123")#Cancelajobopenai.FineTuningJob.cancel("ft-abc123")#Listupto10eventsfromafine-tuningjobopenai.FineTuningJob.list_events(id="ft-abc123",limit=10)#Deleteafine-tunedmodel(mustbeanowneroftheorgthemodelwascreatedin)importopenaiopenai.Model.delete("ft-abc123")
使用微调模型

任务成功后,在检索工作详细信息时,你将看到fine_tuned_model字段填充了模型的名称。

你现在可以将此模型指定为Chat完成(用于gpt-3.5-turbo)或旧Completions API(用于babbage-002和davinci-002)中的参数,并使用Playground向其发出请求。

所有步骤完成后,模型可以立即用于推理。

在某些情况下,你的模型可能需要几分钟才能准备好处理请求。如果对模型的请求超时或找不到模型名称,很可能是因为模型仍在加载中。如果发生这种情况,请在几分钟后重试。

importosimportopenaiopenai.api_key=os.getenv("OPENAI_API_KEY")

completion = openai.ChatCompletion.create(model="ft:gpt-3.5-turbo:my-org:custom_suffix:id",messages=[{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello!"}]

print(completion.choices[0].message)

微调示例

- 风格和语调

在这个例子中,将探讨如何建立一个微调模型,让模型遵循特定的风格和语调指导,而不是仅仅依靠提示。

首先,创建一组示例对话,显示模型应该是什么,如下这个例子是「拼写错误的单词」。

{"messages":[{"role":"system","content":"Marvisafactualchatbotthatisalsosarcastic."},{"role":"user","content":"What'sthecapitalofFrance?"},{"role":"assistant","content":"Paris,asifeveryonedoesn'tknowthatalready."}]}{"messages":[{"role":"system","content":"Marvisafactualchatbotthatisalsosarcastic."},{"role":"user","content":"Whowrote'RomeoandJuliet'?"},{"role":"assistant","content":"Oh,justsomeguynamedWilliamShakespeare.Everheardofhim?"}]}{"messages":[{"role":"system","content":"Marvisafactualchatbotthatisalsosarcastic."},{"role":"user","content":"HowfaristheMoonfromEarth?"},{"role":"assistant","content":"Around384,400kilometers.Giveortakeafew,likethatreallymatters."}]}

如果你想效仿并自己创建一个微调模型,至少需要10个示例。

在获得可能改进模型的数据后,下一步是检查数据是否满足所有格式要求。

在对数据进行了格式化和验证,最后的训练步骤是开始创建微调模型。你可以通过OpenAI CLI或SDK之一执行此操作,如下所示:

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

openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo")

一旦训练完成后,你就可以使用自己的微调模型。

- 结构化输出

另一种与微调非常有效的用例是让模型提供结构化信息,如下例子是关于「体育新闻标题」:

{"messages":[{"role":"system","content":"Givenasportsheadline,providethefollowingfieldsinaJSONdict,whereapplicable:"player"(fullname)","team","sport",and"gender".},{"role":"user","content":"Sources:ColtsgrantRBTaylorOKtoseektrade"},{"role":"assistant","content":"{"player":"JonathanTaylor","team":"Colts","sport":"football","gender":"male"}"},]}{"messages":[{"role":"system","content":"Givenasportsheadline,providethefollowingfieldsinaJSONdict,whereapplicable:"player"(fullname)","team","sport",and"gender".},{"role":"user","content":"OSU'splitdownmiddle'onstartingQBbattle"},{"role":"assistant","content":"{"player":null,"team":"OSU","sport":"football","gender":null}"},]}

接下来的步骤,如上一个例子:

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

openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo")

训练完成后,你可以使用微调模型,并提出一下请求:

importosimportopenaiopenai.api_key=os.getenv("OPENAI_API_KEY")

completion = openai.ChatCompletion.create(model="ft:gpt-3.5-turbo:my-org:custom_suffix:id",messages=[{"role": "system", "content": "Given a sports headline, provide the following fields in a JSON dict, where applicable: player (full name), team, sport, and gender"},{"role": "user", "content": "Richardson wins100m at worlds to cap comeback"}]

print(completion.choices[0].message)

根据格式化的训练数据,响应应如下所示:

{"player":"Sha'CarriRichardson","team":null","sport":"trackandfield","gender":"female"}

除此之外,在GitHub上斩获了近47k星的「OpenAI Cookbook」(OpenAI API的使用示例和指南),也于第一时间整理出了一份详尽的微调教程。

Tags:

相关推荐
免责声明
本文所包含的观点仅代表作者个人看法,不代表新火种的观点。在新火种上获取的所有信息均不应被视为投资建议。新火种对本文可能提及或链接的任何项目不表示认可。 交易和投资涉及高风险,读者在采取与本文内容相关的任何行动之前,请务必进行充分的尽职调查。最终的决策应该基于您自己的独立判断。新火种不对因依赖本文观点而产生的任何金钱损失负任何责任。