15.大语言模型如何模型微调与流式生成

15.大语言模型如何模型微调与流式生成

大部分内容来自于极客时间徐文浩-AI大模型之美

在之前介绍llama-index和LangChain的几讲里面,我们学习了如何将大语言模型和你自己的知识库组合到一起来解决问题。这个方法中,我们不需要对我们使用的模型做任何调整,而是通过将我们的数据用Embedding向量索引起来,然后在使用的时候查询索引来解决问题。

不过,其实我们也完全可以利用我们自己的数据,创建一个新的模型来回答问题。这个方法,就是OpenAI提供的模型微调(Fine-tune)功能。这也是我们要探讨的大语言模型的最后一个主题。

如何进行模型微调?

模型微调,是因为无论是ChatGPT还是GPT-4都不是全知全能的AI。在很多垂直的领域,它的回答还是常常会出错。其中很大一部分原因,是它也缺少特定领域的训练数据。而如果我们有比较丰富的垂直领域的数据,那么就可以利用这些数据来“微调”一个特别擅长这个垂直领域的模型。在这个模型“微调”完成之后,我们就可以直接向模型提问了。而不用再像之前使用llama-index或者LangChain那样,先通过Embedding来查询相关资料,然后把查找到的资料也一并提交给OpenAI来获得所需要的答案。

OpenAI模型微调的过程,并不复杂。你只需要把数据提供给OpenAI就好了,对应的整个微调的过程是在云端的“黑盒子”里进行的。需要提供的数据格式是一个文本文件,每一行都是一个Prompt,以及对应这个Prompt的Completion接口会生成的内容。

就像下面的示例:

1
2
3
4
5
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
...

模型微调的过程,就是根据输入的内容,在原来的基础模型上训练。这个基础模型 Ada、Babbage、Curie和Davinci 其中的一个。每一个示例,都会导致基础模型原有参数发生变化。整个微调过程结束之后,变化后的参数就会被固定下来,变成一个只有你可以使用的新模型。

如果你提供了很多医疗行业的文本内容,那么微调出来的新模型就会拥有更多医疗领域的知识,以及对话的风格。而如果你给的是笑话大全,那么微调出来的模型就更擅长讲笑话。而且要注意,微调之后的模型,不仅有你用来微调的数据的相关知识,原先基础模型里面的绝大部分知识和能力它也还都保留着。

来一个擅长写“历史英雄人物和奥特曼一起打怪兽”的AI

那今天我们来微调一个什么样的模型呢?我周围有不少朋友家里都有孩子,都特别迷恋奥特曼打怪兽的故事。他们就向我提过一个需求,说能不能利用ChatGPT来做一个专门讲奥特曼打怪兽故事的应用。可以是可以,不过,为了让这个故事既能精彩一点,又有点教育意义,我们就再找一些历史上的英雄人物,赋予他们一些超能力,来和奥特曼一起打怪兽。而对应的故事数据,我们也用ChatGPT的模型来帮我们生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os,openai,backoff
import pandas as pd

openai.api_key = os.getenv("OPENAI_API_KEY")
dynasties= ['唐', '宋', '元', '明', '清', '汉', '魏', '晋', '南北朝']
super_powers = ['隐形', '飞行', '读心术', '瞬间移动', '不死之身', '喷火']
story_types = ['轻松', '努力', '艰难']

@backoff.on_exception(backoff.expo, openai.error.RateLimitError)
def gpt35(prompt, max_tokens=2048, temperature=0.5, top_p=1, frequency_penalty=0, presence_penalty=0):
response = openai.Completion.create(
engine="text-davinci-003",
prompt=prompt,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty)
return response["choices"][0]["text"]

def prepare_stories(dynasties, super_powers, story_types, output_file="data/ultraman_stories.csv"):
df = pd.DataFrame()
repeat = 3
for dynasty in dynasties:
for super_power in super_powers:
for story_type in story_types:
for i in range(repeat):
prompt = f"""请你用中文写一段300字的故事,情节跌宕起伏,讲述一位{dynasty}朝时期的英雄人物,穿越到现代,拥有了{super_power}这样的超能力,通过{story_type}的战斗,帮助奥特曼一起打败了怪兽的故事。"""
story = gpt35(prompt)
row = {"dynasty": dynasty, "super_power": super_power, "story_type": story_type, "story": story}
row = pd.DataFrame([row])
df = pd.concat([df, row], axis=0, ignore_index=True)

df.to_csv("data/ultraman_stories.csv")

prepare_stories(dynasties, super_powers, story_types)

这部分代码非常简单,我们定义了一系列朝代、超能力和故事的类型。然后通过三重循环,让AI根据这三者的组合来生成一系列故事。这些生成出来的故事,也就构成了我们用来微调模型的训练数据。因为数据量不大,我就直接用CSV把它存下来了。在这个过程中,数据是一条条生成的,比较慢,也比较消耗Token,你可以不用运行,直接拿我运行后生成的结果数据就好。

拿到了这些数据,我们就可以来微调模型了。我们之前已经通过pip安装了OpenAI的包,这里面自带了命令行工具,方便我们把对应的CSV格式的数据转换成微调模型所需要的JSONL格式的文件。

1
2
3
4
5
6
7
8
9
10
df = pd.read_csv("data/ultraman_stories.csv")
df['sub_prompt'] = df['dynasty'] + "," + df['super_power'] + "," + df['story_type']
prepared_data = df.loc[:,['sub_prompt','story']]
prepared_data.rename(columns={'sub_prompt':'prompt', 'story':'completion'}, inplace=True)
prepared_data.to_csv('data/prepared_data.csv',index=False)

import subprocess

subprocess.run('openai tools fine_tunes.prepare_data --file data/prepared_data.csv --quiet'.split())

输出结果:

1
2
3
4
5
6
7
8
9
10
……
Wrote modified file to `data/prepared_data_prepared.jsonl`
Feel free to take a look!
Now use that file when fine-tuning:
> openai api fine_tunes.create -t "data/prepared_data_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=["."]` so that the generated texts ends at the expected place.
Once your model starts training, it'll approximately take 8.82 minutes to train a `curie` model, and less for `ada` and `babbage`. Queue will approximately take half an hour per job ahead of you.

CompletedProcess(args=['openai', 'tools', 'fine_tunes.prepare_data', '--file', 'data/prepared_data.csv', '--quiet'], returncode=0)

上面的代码主要做了两个动作。首先,是对数据做了一些处理,来准备微调。对于微调,我们使用的Prompt不再是一个完整的句子,而是只用了“朝代”+“超能力”+“故事类型”拼接在一起的字符串,中间用逗号隔开。然后把这个字符串和生成的故事,用Prompt和Completion作为列名存储成了一个CSV。

其次,我们通过subprocess调用了命令行里的OpenAI工具,把上面的CSV文件,转化成了一个JSONL格式的文件。从输出的日志里面可以看到,这个文件叫做 data/prepared_data_prepared.jsonl。

如果我们打开这个JSONL文件看一眼,是下面这样的。

1
2
3
{"prompt":"唐,隐形,轻松 ->","completion":" \n\n一位叫做李明的英雄人物,出生在唐朝时期。他是一个勇敢的将军,在他的一生中,他打败了许多敌人,为自己的国家和人民做出了许多贡献。\n\n一天,李明意外被一道神秘的光束穿越到了现代。他发现自己拥有了超能力,可以隐形,可以飞行,可以发射光束,可以放出火焰,可以穿墙而过等等。\n\n李明发现,在现代,怪兽们正在摧毁地球,他决定和奥特曼一起,用自己的超能力,帮助奥特曼消灭怪兽们。\n\n于是,李明和奥特曼开始了一场艰苦的战斗,他们用自己的超能力,一招一式,击退了怪兽们,最终成功地拯救了地球。\n\n在这场战斗之后,地球上的人们都对李明充满了敬佩,他从此成为了一位英雄,他的事迹也被传颂了很久。李明也因此被永远铭记在人们心中,成为了一位不朽的英雄。."}
{"prompt":"唐,隐形,轻松 ->","completion":" \n\n这是一个关于英雄的故事,发生在唐朝时期的中国。一个叫李自成的勇士,他拥有过人的勇气,英勇的行为让他成为当时最受尊敬的人物。\n\n一天,李自成被一道神秘的光芒笼罩住,他突然发现自己似乎穿越时空,来到了现代。他惊讶地发现,自己竟然拥有了一种超能力,可以让自己隐形。\n\n李自成接受了这种超能力,他发现这种能力可以让自己变得更加强大,他决定利用这种能力来帮助人们。\n\n一次,李自成发现有一群怪兽正在破坏城市,他决定和奥特曼一起出动,利用自己的超能力,把怪兽一个个击败,最终成功拯救了城市,令众人欢呼雀跃。\n\n自此,李自成受到了众人的尊敬,他成为了这个城市的英雄,他也把自己的超能力用在了正义的事业上,为人们做出了许多贡献,他也成为了一个英雄。."}

可以看到,转换后的数据文件,在Prompt的最后,多了一个“->”符号。而在Completion的开头,多了两个“\n\n”的换行,结尾则是多了一个“.”。这是为了方便我们后续在使用这个模型生成数据的时候,控制生成结果。未来在使用模型的时候,Prompt需要以“->\n”这个提示符结束,并且将stop设置成“.”。这样,模型就会自然套用我们微调里的模式来生成文本。

有了准备好的数据,我们只要再通过subprocess调用OpenAI的命令行工具,来提交微调的指令就可以了。

1
2
subprocess.run('openai api fine_tunes.create --training_file data/prepared_data_prepared.jsonl --model curie --suffix "ultraman"'.split())

输出结果:

1
2
3
4
5
6
7
8
9
Upload progress: 100%|██████████| 446k/446k [00:00<00:00, 201Mit/s]
Uploaded file from data/prepared_data_prepared.jsonl: file-yn0BfnPmgvf7n0sfQzQRbbeE
Created fine-tune: ft-3oxkr1zBVB4fJWogJDDjQbr0
Streaming events until fine-tuning is complete...
(Ctrl-C will interrupt the stream, but not cancel the fine-tune)
[2023-04-04 10:51:51] Created fine-tune: ft-3oxkr1zBVB4fJWogJDDjQbr0

CompletedProcess(args=['openai', 'api', 'fine_tunes.create', '--training_file', 'data/prepared_data_prepared.jsonl', '--model', 'curie', '--suffix', '"ultraman"'], returncode=0)

在这个微调的指令里面,我们指定了三个参数,分别是用来训练的数据文件、一个基础模型,以及生成模型的后缀。这里,我们选用了Curie作为基础模型,因为是讲奥特曼的故事,所以模型后缀我给它取了一个ultraman的名字。

我们的数据量不大,所以微调很快,几分钟就能完成。那接下来我们就可以使用这个模型了。我们可以通过下面的fine_tunes.list指令,找出所有我们微调的模型。

1
2
subprocess.run('openai api fine_tunes.list'.split())

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"data": [
{
"created_at": 1680576711,
"fine_tuned_model": "curie:ft-bothub-ai:ultraman-2023-04-04-03-03-26",
"hyperparams": {
"batch_size": 1,
"learning_rate_multiplier": 0.2,
"n_epochs": 4,
"prompt_loss_weight": 0.01
},
"id": "ft-3oxkr1zBVB4fJWogJDDjQbr0",
"model": "curie",
"object": "fine-tune",
"organization_id": "YOUR_ORGANIZATION_ID",
"result_files": [
{
"bytes": 107785,
"created_at": 1680577408,
"filename": "compiled_results.csv",
"id": "RESULT_FILE_ID",
"object": "file",
"purpose": "fine-tune-results",
"status": "processed",
"status_details": null
...
}
],
"object": "list"
}
CompletedProcess(args=['openai', 'api', 'fine_tunes.list'], returncode=0)

在输出的JSON里面,你可以看到我们有一个fine_tuned_model字段,里面的值叫做“curie:ft-bothub-ai:ultraman-2023-04-04-03-03-26”,这个就是刚刚让OpenAI给我们微调完的模型。

这个模型的使用方法,和我们使用text-davinci-003之类的模型是一样的,只要在API里面把对应的model字段换掉就好了,对应的代码我也放在了下面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os
import openai

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

def write_a_story(prompt):
response = openai.Completion.create(
model="curie:ft-bothub-ai:ultraman-2023-04-04-03-03-26",
prompt=prompt,
temperature=0.7,
max_tokens=2000,
top_p=1,
stop=["."])
return response["choices"][0]["text"]

story = write_a_story("宋,发射激光,艰难 ->\n")
print(story)

输出结果:

1
2
3
4
5
6
宋朝时期,有一位叫林先生的英雄人物,他勇敢而又坚韧,曾经拯救过无数的人民,他的英勇表现让他赢得了众多的尊敬。
一天,林先生突然发现自己穿越到了现代,他发现自己拥有了一种神奇的超能力,可以发射激光,他开始研究自己的能力,发现自己可以用激光来攻击敌人。
林先生决定把自己的能力用来拯救人类,于是他和奥特曼一起出发,开始与怪兽作战。他们一路走来,林先生用他的激光来打击怪兽,奥特曼则用他的武器来打击怪兽。
在一场艰苦的战斗中,林先生和奥特曼终于击败了怪兽,拯救了人类。林先生也因此获得了无数的赞誉,他也成为了一位传奇英雄。
林先生的故事被传唱了几百年,他的英勇事迹也成为了一个永恒的传奇,让人们永远不忘。

对应在调用模型的时候,我们使用的提示语就是“朝代”+“超能力”+“故事类型”,并且跟着“->\n”,而stop则是设置成了“.”。

因为这是一个微调的模型,它不仅拥有我们训练数据提供的知识,也包括基础模型里的各种信息。所以我们使用的朝代、超能力和故事类型也可以是在之前微调数据里面没有出现过的。比如,上面的例子里,我们使用的超能力叫做“发射激光”,并不是我们拿来微调的数据里面有的一种超能力。你可以试试看,使用别的朝代、故事的类型,效果会是怎么样的。

1
2
3
story = write_a_story("秦,龙卷风,辛苦 ->\n")
print(story)

输出结果:

1
2
3
4
5
6
7
曾经有一位叫苏轼的英雄人物,他曾经英勇地抵抗过许多强大的敌人,拯救了许多被危险封印的百姓。他曾经在一次战争中发挥过自己的作用,赢得了许多胜利,被尊为英雄。
然而,苏轼却在一次激烈的战斗中牺牲了,他的灵魂被封印在一个古老的石头里,隔着一层玻璃,一直沉睡了几百年。
苏轼的灵魂在穿越时空,来到了现代,他发现自己拥有了一种超能力,这就是龙卷风,他可以使自己的身体具有超强的力量,甚至可以抵抗恶魔的攻击。
苏轼在现代的世界里,发现了一种可怕的怪兽,它们正在摧毁着人类的家园,苏轼决定要拯救这个世界,于是他和奥特曼一起出发,开始了一场史诗般的战斗。
在苏轼和奥特曼的帮助下,苏轼利用自己的超能力,一次次击退怪兽的攻击,最终他们成功地打败了怪兽,拯救了人类。
苏轼的事迹在这里传唱了很久,他成为了一位永恒的英雄,他的故事也被传唱了下来,让人们永远不会忘记他的英勇事迹。

模型微调的成本考量

细心的人可能注意到了,我们这里选用的基础模型是Curie,而不是效果最好的Davinci。之所以做出这样的选择,是出于成本的考虑。

image.png

注:数据来源于 https://openai.com/pricing#language-models

使用微调模型的成本要远远高于使用OpenAI内置的模型。 以Davinci为基础微调的模型,使用的时候,每1000个Token的成本是0.12美元,是使用内置的text-davinci-003的6倍,是我们最常用的 gpt-3.5-turbo 的60倍。所以,如果只是一般的讲故事的应用,这个成本实在是太高了。就算是我们选择基于Curie微调,1000个Token的使用成本也在0.012美元,虽然比text-davinci-003要便宜,但也是gpt-3.5-turbo的6倍。

对于模型微调的效果,我们也可以通过一个OpenAI提供的命令fine_tunes.results来看。对应的,我们需要提供给它一个微调任务的id。这个id,可以在fine_tunes.list列出的fine_tunes模型的id参数里找到。

1
2
subprocess.run('openai api fine_tunes.results -i ft-3oxkr1zBVB4fJWogJDDjQbr0'.split())

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
step,elapsed_tokens,elapsed_examples,training_loss,training_sequence_accuracy,training_token_accuracy
1,625,1,0.8805545861742778,0.0,0.75
2,1258,2,0.8059815050491868,0.0,0.7766830870279147
3,1859,3,0.7964038042175758,0.0,0.7862068965517242
4,2548,4,0.805052303553852,0.0,0.7774436090225564
5,3197,5,0.7503930440556053,0.0,0.7808
6,3846,6,0.7992317049403261,0.0,0.7770700636942676
7,4775,7,0.6649006477473822,0.0,0.7927232635060639
8,5432,8,0.6493354803676822,0.0,0.8049921996879875
9,6265,9,0.6568901059838095,0.0,0.802937576499388
10,7122,10,0.6578856167468091,0.0,0.8100358422939068
11,7827,11,0.5687322367928961,0.0,0.8279411764705882
12,8404,12,0.6334827334911788,0.0,0.8172043010752689
13,9061,13,0.5771709139683721,0.0,0.825
14,9822,14,0.6079089517825593,0.0,0.8100407055630936
15,10399,15,0.6481047367374327,0.0,0.8154121863799283
16,11208,16,0.5528688982071029,0.0,0.8352490421455939
17,11913,17,0.6525803676480848,0.0,0.8093841642228738
18,12546,18,0.5230526420679229,0.0,0.8363047001620746
19,13163,19,0.6065665546680247,0.0,0.8236272878535774
20,13796,20,0.5983224045073889,0.0,0.8199672667757774
21,14549,21,0.6440337136896056,0.0,0.8267394270122783
22,15190,22,0.6029605409912032,0.0,0.8110749185667753
23,15759,23,0.5089513997451476,0.0,0.838475499092559
24,16440,24,0.557213810807506,0.0,0.8265460030165912
...
1855,1228711,1855,0.2610049068084409,0.0,0.9219765929778934
1856,1229312,1856,0.21196416716076574,0.0,0.9312714776632303
1857,1229945,1857,0.14050147435694596,0.0,0.9556650246305419

在这个命令的输出结果里,你可以在第二列elapsed_tokens看到训练消耗的Token数量。而最后一列 training_token_accuracy,则代表微调后的模型,成功预测微调的数据里下一个Token的准确率。在我们使用的这个例子里面,可以看到一开始准确率只有75%,但是随着训练数据迭代轮数的增加,准确率越来越高,达到了95%以上。

增量训练,不断优化模型

微调模型比较高昂的价格,限制了它的使用。 不过,微调模型还有一个能力,就是我们可以在已经微调了的模型上根据新数据做进一步地微调。 这个在很多垂直领域是非常有用,比如在医学、金融这样的领域,我们就可以不断收集新的数据,不断在前一个微调模型的基础之上继续微调我们的模型,让模型的效果越来越好。而这些领域往往也能承受更高一些的成本。

进一步地微调其实操作起来并不复杂,就是再准备一些数据,以之前已经微调好的模型为基础模型来操作就好了。

生成一些额外的数据:

1
2
3
4
5
6
7
dynasties= ['秦', '五代', '隋']
super_powers = ['龙卷风', '冰冻大海', '流星火雨']
story_types = ['轻松', '努力', '艰难', '勇敢', '辛苦']

new_stories = "data/ultraman_stories_more.csv"
prepare_stories(dynasties, super_powers, story_types, repeat=3, output_file=new_stories)

转换数据:

1
2
3
4
5
6
7
8
9
df = pd.read_csv(new_stories)
df['sub_prompt'] = df['dynasty'] + "," + df['super_power'] + "," + df['story_type']
prepared_data = df.loc[:,['sub_prompt','story']]
prepared_data.rename(columns={'sub_prompt':'prompt', 'story':'completion'}, inplace=True)
new_stories_prepared = 'data/prepared_data_more.csv'
prepared_data.to_csv(new_stories_prepared, index=False)

subprocess.run('openai tools fine_tunes.prepare_data --file data/prepared_data_more.csv --quiet'.split())

继续微调:

1
2
subprocess.run('openai api fine_tunes.create --training_file data/prepared_data_more_prepared.jsonl --model curie:ft-bothub-ai:ultraman-2023-04-04-03-03-26 --suffix "ultraman" --learning_rate_multiplier 0.2'.split())

在原有的模型上微调的时候,我们要修改两个参数。

  1. 第一个是model参数,我们把Curie换成了我们刚才微调之后的模型 curie:ft-bothub-ai:ultraman-2023-04-04-03-03-26
  2. 第二个是learning_rate_multiplier,这个参数的默认值是根据你的样本数量在0.05 到 0.2 不等。如果你继续微调的样本数要比之前微调的数据量小很多,你就可以调得大一点。

微调更新之后,模型的名称没有变,老的模型就被更新成了微调后的新模型,我们再来试一下这个新模型。

1
2
3
fine_tuned = write_a_story("五代,流星火雨,艰难 ->\n")
print(fine_tuned)

输出结果:

1
2
3
4
5
这是一个发生在一个古老的世界,一个叫做“六代”的世界。这个世界有着一种叫做“超能力”的特性,可以让人穿越时空,穿越到现代。
一位叫做“英雄”的人物,他来自于六代,但他拥有了一种叫做“流星火雨”的超能力,他可以把自己的身体变成一个火焰,然后穿越时空,来到现代。
他来到现代,发现这个世界变得越来越危险,有一种叫做“怪兽”的存在,他们想要毁灭这个世界。英雄决定帮助奥特曼一起打败怪兽,于是他们开始了一场激烈的战斗。
英雄凭借着自己的超能力,以及奥特曼的力量,战胜了怪兽,拯救了这个世界。最后,英雄又一次穿越回六代,这次他拥有了一种叫做“流星火雨”的超能力,他可以把自己的身体变成一个火焰,然后穿越时空,拯救又一次六代。

流式生成

通过模型微调,我们拥有了一个可以讲故事的AI模型。不过,故事生成的体验稍微有点差。它不像是我们在ChatGPT的Web界面里那样一个词一个词地蹦出来,就像一个真人在给你讲故事那样。不过要做到这一点也并不难,因为OpenAI的Completion接口是提供了这样返回结果的模式的,你只需要把代码小小地修改一下就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def write_a_story_by_stream(prompt):
response = openai.Completion.create(
model="curie:ft-bothub-ai:ultraman-2023-04-04-03-03-26",
prompt=prompt,
temperature=0.7,
max_tokens=2000,
stream=True,
top_p=1,
stop=["."])
return response

response = write_a_story_by_stream("汉,冰冻大海,艰难 ->\n")

for event in response:
event_text = event['choices'][0]['text']
print(event_text, end = '')

输出结果:

1
2
3
4
一位叫李英的汉朝时期的英雄人物,穿越到了现代,拥有了一种超能力,可以把自己的身体冰冻到极限,他发现自己可以拥有超越情感的力量,可以把任何人都冻僵,他也发现自己可以控制全局,可以控制时间,可以控制物质,可以控制情景,他发现自己可以控制一切,他变得更加强大。
李英发现,地球正面临着一个叫做怪兽的强大敌人的威胁,他决定去帮助奥特曼一起打败怪兽。于是,他和奥特曼一起开始了一系列的战斗,他们一起抵抗着怪兽的攻击,最终,他们成功地消灭了怪兽,拯救了地球。
李英受到了所有人的赞赏,他也成为了一个英雄,他的事迹被传颂了几百年,他的故事也被记录在历史书中,他也成为了一个永恒的传奇。

我们在调用Completion接口的时候,启用了stream=True这个参数。然后对于返回结果,我们不再是直接拿到整个response然后打印出来。而是拿到一个可以通过迭代器访问的一系列events,每一个event都包含了一部分新生成的文本。你试着运行一下这段代码,就能体验到AI把一个个词吐给你,好像真的在实时讲故事一样的感觉了。

小结

这篇文章里一起学习了OpenAI大语言模型里的最后两个功能。

第一个是模型微调,模型微调给我们提供了一个非常实用的能力, 我们可以利用自己的数据,在OpenAI的基础模型上,调整模型参数生成一个新模型。这样我们就能够根据自己专有的垂直领域的数据,来生产一个专属于我们自己的模型。而且,我们可以根据新收集到的数据,不断在这个模型上继续微调迭代。不过,微调后的模型使用成本比较高,你需要自己核算一下,究竟是微调模型ROI比较高,还是使用前面的外部知识库的方式更划算一些。

在模型微调之外,我们还了解了OpenAI接口上的一个小功能,也就是 流式地数据生成。通过开启流式地文本生成,我们可以交付给用户更好的交互体验。特别是在使用比较慢的模型,比如GPT-4,或者生成的文本很长的时候,效果特别明显。用户不需要等上几十秒才能看到结果。

那到这里,大语言模型部分我们也就介绍完了。从最基本的两个API,Completion和Embedding开始,介绍了各种各样的应用场景和使用方法。可以看到,现在的大语言模型几乎是“万能”的。下可以拿来做机器学习的输入数据,上可以直接让它自己决定调用什么API,怎么解决用户的问题。相信看到这里的你,已经掌握如何使用大语言模型了,接下来就要多想想在你的实际工作里如何把它用起来了。

推荐阅读

OpenAI在自己的 官方文档 里,推荐了通过 Weight & Bias 这个公司的产品,来追踪微调后的模型的实验、模型与数据集。Weight & Bias 也在自己的 文档 里,提供了一个对WIT数据集进行模型微调的 Notebook,你有兴趣的话也可以去看一下。


15.大语言模型如何模型微调与流式生成
https://blog.longpi1.com/2023/11/24/15-大语言模型如何模型微调与流式生成/