編輯:桃子 好困
【新智元導讀】今天,OpenAI正式開放GPT-3.5微調API,GPT-4版本也即将推出。這意味着,繼插件「APP Store」大爆發後,所有人皆可以打造個性化的專屬「類ChatGPT應用」。
終于來了!
剛剛,OpenAI正式宣布,所有開發者都可以對GPT-3.5 Turbo進行微調。
初步結果表明,微調後的GPT-3.5 Turbo,在具體任務中,性能與GPT-4實力相當,甚至反超GPT-4。
最讓人興奮的是,「地表最強」的GPT-4微調版本,也将在幾個月後正式上線。
這意味着,任何人可以根據需要,用「專有資料」對模型微調,标志着OpenAI開啟了AI商業應用的新紀元。
英偉達進階科學家Jim Fan稱,OpenAI釋出了自「插件應用商店」以來最大的産品更新:GPT-3.5的微調API。這将是有史以來最大的LoRA雲服務。預計将有一大堆新應用從各行各業湧現出來。
提示量大減90%
自GPT-3.5 Turbo釋出以來,開發者和企業一直在尋求定制化的模型,以便為使用者建立獨特和差異化的體驗。
不負等待,現在,開發者終于可以進行監督式微調,讓該模型更好地滿足自己的使用需求。
OpenAI表示,在私人測試版中,客戶已經通過微調,顯著提升了模型在常見案例中的性能,具體包括:
- 提高可控性
AI模型面臨的經典挑戰之一,便是精确地遵循指令。而微調便可以讓模型做到這一點,比如輸出更加簡潔,或始終用特定的語言回複。
舉個栗子,開發者可以通過微調確定要求模型,當使用者在使用德語時,模型總是以德語回應。
- 可靠的輸出格式
微調提高了模型一緻格式化響應的能力,這對于需要特定響應格式的APP非常重要,比如代碼補全或編寫API調用。
想象一下,開發者可以通過微調将使用者提示可靠地轉化為高品質的JSON片段,這樣,就能與自己的系統一起使用,讓任務變得更加流暢。
- 自定義語調
微調可以優化模型,輸出可以反應特定語調,進而更好地代表适應企業品牌的聲音。
不同的品牌對外發出的聲音是不一樣的,從活躍創新初創公司到較為保守的企業,都可以通過模型微調讓語調與對外形象保持一緻。
- 更短的提示,相同的性能
GPT-3.5-Turbo微調可以處理多達4k個token,是之前微調模型的2倍。
早期測試者通過指令微調模型本身,将提示大小減少了高達90%,進而加快了每次API調用,并降低了成本。
值得注意的是,當微調與提示工程、資訊檢索和函數調用等其他技術相結合時,會獲得最為強大的能力。
最後OpenAI表示,支援使用函數調用和gpt-3.5-turbo-16k的微調功能,也将在今年秋季推出。
價格x8
微調GPT-3.5的成本可以分為兩部分:初始訓練成本和使用成本。
訓練:0.008美元/1K token
使用輸入:0.012美元/1K token
使用輸出:0.016美元/1K token
例如,一個gpt-3.5-turbo微調任務的訓練檔案為100,000個token(約75,000個單詞),那麼訓練3個epoch的預期成本為2.40美元。
從使用的價格上來看,微調後的GPT-3.5是原始版本的8倍。
四步輕松搞定
第一步:準備資料
第二步:上傳檔案
第三步:建立微調任務
第四步:使用微調模型
GPT-3模型更新
7月,OpenAI曾宣布GPT-3基礎模型(ada、babbage、curie和davinci)将于2024年1月4日關閉。
同在今天,OpenAI再次更新GPT-3模型,并提供了babbage-002和davinci-002作為以上模型替代品,可以作為基礎模型或微調模型來使用。
客戶也可以通過查詢Completions API來通路這些模型。
另外,這些模型使用新的API端口/v1/fine_tuning/jobs進行微調。
該端口取代了/v1/fine-tunes舊端口(2024年1月4日關閉),提供了分頁和更多可擴充性,以支援微調API的未來發展。
網友開啟新世界
衆多網友已經基于GPT-3.5 Turbo微調功能,開始創造新世界了。
剛剛在基于Marv示例的合成樣本資料上對GPT-3.5進行了微調,Marv太搞笑了。
使用者:我怎樣才能交到女朋友?
助手:嘿Siri,給我找個女朋友。
毋庸置疑,這将徹底改變遊戲規則,大大減少我們的API成本!
不過,也有網友表示十分疑惑,如果GPT-3.5 Turbo比GPT-4還強,那為什麼還要每月支付20美元???
對此其他網友解釋道,它隻是在「特定任務」上更勝一籌,在通用型任務上依然還是GPT-4的主場。
官方微調指南
如何去微調GPT-3.5 Turbo,官方已經給出了教程。
位址:https://platform.openai.com/docs/guides/fine-tuning
首先,OpenAI介紹了通過微調,你可以API可用的模型中獲得更多資訊:
1. 比提示品質更高的結果
2. 能夠就提示中無法容納的更多示例進行訓練
3. 提示更簡潔,節省token使用
4. 降低延遲請求
GPT模型已經在大量文本上進行了預訓練。
為了有效使用模型,OpenAI在提示中加入指令,有時還包括幾個示例。通過示範,來展示模型如何執行任務通常被稱為「少樣本學習」。
微調可以通過訓練比提示中更多的示例,來改進少樣本學習,能夠讓模型在大量任務上獲得更好的結果。
一旦微調模型,你就不需要在提示符中提供更多的示例。這樣,既節省了成本,又降低了延遲的請求。
在高層次上,微調包含以下步驟:
1. 準備和上傳訓練資料
2. 訓練一個新的微調模型
3. 使用你的微調模型
目前,微調可用于以下模型:
- gpt-3.5-turbo-0613(推薦)
- babbage-002
- davinci-002
何時需要微調
微調GPT模型可以使其更适合特定APP,但這需要投入大量的時間和精力。
OpenAI建議,首先嘗試通過提示工程、提示鍊(将複雜任務分解為多個提示),以及函數調用,可以獲得良好的結果,主要原因是:
- 有許多任務,GPT模型最初可能表現不佳,但有了更好的提示,便可以獲得更好的結果,并且可能不需要微調。
- 疊代提示等其他政策,比使用微調疊代具有更快的回報循環,而微調需要建立資料集,并且訓練模型。
- 在仍需要微調的情況下,初始提示工程任務不會白費。OpenAI通常在微調資料中使用優秀的提示(或将提示鍊/工具使用與微調相結合),進而看到最佳結果。
前段時間,OpenAI釋出「GPT最佳實踐指南」中,提供一些有關提示有效的政策,無需微調即可獲得更好的性能。
https://platform.openai.com/docs/guides/gpt-best-practices
常見用例
微調可以改善結果的一些常見用例:
- 設定風格、語調、格式或其他定性方面
- 提高生産所需輸出的可靠性
- 糾正不按複雜提示操作的情況
- 以特定方式處理許多邊緣情況
- 執行一項難以用提示表達的新技能或任務
在接下來的部分中,OpenAI将探讨如何設定用于微調的資料,以及微調後提高基線模型性能的各種示例。
微調有效的另一個場景是,在不犧牲品質的情況下通過替換GPT-4,或使用更短的提示來降低成本和/或延遲。
如果可以用GPT-4取得良好的結果,那麼通過對GPT-4完成進行微調(可能使用更短的指令提示),而微調gpt-3.5-turbo後模型也能達到類似的效果。
準備資料集
當你确定微調是正确的解決方案,将需要準備訓練模型的資料。
這裡,你需要建立一組多樣化的示範對話,這些對話應與你在推理時要求模型響應的對話相似。
資料集中的每個示例都應該是,與Chat completions API相同的對話,具體來說,就是一個消息清單,每個消息都有角色、内容和可選名稱。
至少一些訓練示例,應該直接針對提示模型行為不符合預期的情況,并且資料中提供的助手消息,應該是你希望模型提供的理想響應。
- 示例格式
在這個例子中,目标是建立一個偶爾給出諷刺回應的聊天機器人。
對此,OpenAI為資料集建立了3個訓練示例(對話):
{"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": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
- 編寫提示
建議在微調之前,用你認為對模型最有效的指令和提示,納入到每個訓練示例中。
這樣做可以獲得最佳和最普遍的結果,尤其是在訓練示例相對較少(不足100個)的情況下。
如果你想縮短每個示例中重複出現的指令或提示,以節省成本,請記住,模型的行為很可能包含這些指令,很難讓模型在推理時忽略這些「内置」指令。
可能需要更多的訓練示例才能獲得良好的結果,因為模型必須完全通過示範學習,而無需指令指導。
- 示例數量
要微調模型,你需要提供至少10個示例。
通常會看到,使用gpt-3.5-turbo對50-100個訓練示例進行微調,有着明顯改進。根據具體使用情況,同樣的的示例效果不一。
OpenAI建議,從50個精心制作示例開始,看看模型在微調後是否顯示出改進的迹象。
在某些情況下,這可能就足夠了,但即使模型尚未達到輸出品質,明确的改進也是一個好迹象,表明提供更多資料将繼續改進模型。
如果沒有改進,則表明你可能需要重新考慮如何為模型設定任務,或者在擴充到有限的示例集之前,重新調整資料結構。
- 拆分訓練和測試
收集初始資料集後,建議将其拆分為訓練和測試兩個部分。
當送出包含訓練和測試檔案的微調時,OpenAI将在訓練過程中提供兩者的統計資料。
這些統計資料将是模型改程序度的初始信号。
此外,通過在測試集上生成樣本,盡早建構測試集将有助于確定你能夠在訓練後評估模型。
- token限制
每個訓練示例限制為4096個token。
訓練時,長度超過這一限制,将被截斷為4096個token。
是以,要確定整個訓練示例适合上下文,請檢查消息内容中的總token數是否低于4,000個。每個檔案目前限制為50 MB。
- 估算成本
為了估算微調成本,主要還是參考官方定價頁面,了解每1k token成本的詳細資訊。
在3個epoch内訓練了100,000個token的訓練檔案,預期成本約為2.4美元。
- 檢查資料格式
編譯完資料集後,在建立微調之前,檢查資料格式非常重要。
為此,OpenAI建立了一個簡單的Python腳本,你可以使用它來查找潛在錯誤、檢視token計數并估計微調的成本。
資料格式化腳本:
# We start by importing the required packages
import json
import os
import tiktoken
import numpy as np
from collections import defaultdict
# Next, we specify the data path and open the JSONL file
data_path = "<YOUR_JSON_FILE_HERE>"
# Load dataset
with open(data_path) as f:
dataset = [json.loads(line) for line in f]
# We can inspect the data quickly by checking the number of examples and the first item
# Initial dataset stats
print("Num examples:", len(dataset))
print("First example:")
for message in dataset[0]["messages"]:
print(message)
# Now that we have a sense of the data, we need to go through all the different examples and check to make sure the formatting is correct and matches the Chat completions message structure
# Format error checks
format_errors = defaultdict(int)
for ex in dataset:
if not isinstance(ex, dict):
format_errors["data_type"] += 1
continue
messages = ex.get("messages", None)
if not messages:
format_errors["missing_messages_list"] += 1
continue
for message in messages:
if "role" not in message or "content" not in message:
format_errors["message_missing_key"] += 1
if any(k not in ("role", "content", "name") for k in message):
format_errors["message_unrecognized_key"] += 1
if message.get("role", None) not in ("system", "user", "assistant"):
format_errors["unrecognized_role"] += 1
content = message.get("content", None)
if not content or not isinstance(content, str):
format_errors["missing_content"] += 1
if not any(message.get("role", None) == "assistant" for message in messages):
format_errors["example_missing_assistant_message"] += 1
if format_errors:
print("Found errors:")
for k, v in format_errors.items():
print(f"{k}: {v}")
else:
print("No errors found")
# Beyond the structure of the message, we also need to ensure that the length does not exceed the 4096 token limit.
# Token counting functions
encoding = tiktoken.get_encoding("cl100k_base")
# not exact!
# simplified from https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
def num_tokens_from_messages(messages, tokens_per_message=3, tokens_per_name=1):
num_tokens = 0
for message in messages:
num_tokens += tokens_per_message
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name":
num_tokens += tokens_per_name
num_tokens += 3
return num_tokens
def num_assistant_tokens_from_messages(messages):
num_tokens = 0
for message in messages:
if message["role"] == "assistant":
num_tokens += len(encoding.encode(message["content"]))
return num_tokens
def print_distribution(values, name):
print(f"\n#### Distribution of {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, we can look at the results of the different formatting operations before proceeding with creating a fine-tuning job:
# Warnings and tokens counts
n_missing_system = 0
n_missing_user = 0
n_messages = []
convo_lens = []
assistant_message_lens = []
for ex in dataset:
messages = ex["messages"]
if not any(message["role"] == "system" for message in messages):
n_missing_system += 1
if not any(message["role"] == "user" for message in messages):
n_missing_user += 1
n_messages.append(len(messages))
convo_lens.append(num_tokens_from_messages(messages))
assistant_message_lens.append(num_assistant_tokens_from_messages(messages))
print("Num examples missing system message:", n_missing_system)
print("Num examples missing user message:", 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 > 4096 for l in convo_lens)
print(f"\n{n_too_long} examples may be over the 4096 token limit, they will be truncated during fine-tuning")
# Pricing and default n_epochs estimate
MAX_TOKENS_PER_EXAMPLE = 4096
MIN_TARGET_EXAMPLES = 100
MAX_TARGET_EXAMPLES = 25000
TARGET_EPOCHS = 3
MIN_EPOCHS = 1
MAX_EPOCHS = 25
n_epochs = TARGET_EPOCHS
n_train_examples = len(dataset)
if n_train_examples * TARGET_EPOCHS < MIN_TARGET_EXAMPLES:
n_epochs = min(MAX_EPOCHS, MIN_TARGET_EXAMPLES // n_train_examples)
elif n_train_examples * TARGET_EPOCHS > MAX_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) for length in convo_lens)
print(f"Dataset has ~{n_billing_tokens_in_dataset} tokens that will be charged for during training")
print(f"By default, you'll train for {n_epochs} epochs on this dataset")
print(f"By default, you'll be charged for ~{n_epochs * n_billing_tokens_in_dataset} tokens")
print("See pricing page to estimate total costs")
驗證資料後,需要上傳檔案才能與微調一起使用:
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")
使用微調模型
任務成功後,在檢索工作詳細資訊時,你将看到fine_tuned_model字段填充了模型的名稱。
你現在可以将此模型指定為Chat完成(用于gpt-3.5-turbo)或舊Completions API(用于babbage-002和davinci-002)中的參數,并使用Playground向其送出請求。
所有步驟完成後,模型可以立即用于推理。
在某些情況下,你的模型可能需要幾分鐘才能準備好處理請求。如果對模型的請求逾時或找不到模型名稱,很可能是因為模型仍在加載中。如果發生這種情況,請在幾分鐘後重試。
import os
import openai
openai.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": "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."}]}
如果你想效仿并自己建立一個微調模型,至少需要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": "Given a sports headline, provide the following fields in a JSON dict, where applicable: "player" (full name)", "team", "sport", and "gender".},{"role": "user", "content": "Sources: Colts grant RB Taylor OK to seek trade"},
{"role": "assistant", "content": "{"player": "Jonathan Taylor", "team": "Colts", "sport": "football", "gender": "male" }"},]}
{"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": "OSU 'split down middle' on starting QB battle"},
{"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")
訓練完成後,你可以使用微調模型,并提出一下請求:
import os
import openai
openai.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 wins 100m at worlds to cap comeback"}
]
)
print(completion.choices[0].message)
根據格式化的訓練資料,響應應如下所示:
{"player": "Sha'Carri Richardson", "team": null", "sport": "track and field", "gender": "female"}
除此之外,在GitHub上斬獲了近47k星的「OpenAI Cookbook」(OpenAI API的使用示例和指南),也于第一時間整理出了一份詳盡的微調教程。
項目位址:https://github.com/openai/openai-cookbook/blob/main/examples/How_to_finetune_chat_models.ipynb