赞
踩
随着自然语言处理(Natural Language Processing, NLP)的发展,此技术现已广泛应用于文本分类、识别和总结、机器翻译、信息提取、问答系统、情感分析、语音识别、文本生成等任务。
研究人员发现扩展模型规模可以提高模型能力,由此创造了术语——大语言模型(Large Language Model, LLM),它代指大型的预训练语言模型(Pre-training Language Model, PLM),其通常包含数千亿(甚至更多)个参数。大语言模型的一个最显著的进展是OpenAI基于LLM开发的聊天机器人ChatGPT,在此篇博客中,我将介绍大语言模型的历史演进、基础知识、核心技术以及未来展望等,并通过调用API介绍ChatGPT是如何搭建的。
语言模型(LM)是为了对词序列的生成概率进行建模,从而预测未来或缺失的词的概率,其发展主要有以下三个阶段:
模型的预训练首先需要高质量的训练数据,这些数据往往来自于网页、书籍、对话、科学文献、代码等,收集到这些数据后,需要对数据进行预处理,特别是消除噪声、冗余、无关和潜在有害的数据。一个典型的预处理数据流程如下:
目前大语言模型的主流架构可分为三大类型:编码器-解码器、因果解码器和前缀解码器,还有一种利用上述三种架构搭建的混合架构:
为了使大语言模型适配特定的任务,可使用指令微调(Instruction Tuning)和对齐微调(Alignment Tuning)等技术方法;由于大语言模型包含了大量的任务,如果进行全参数微调将会有较大开销,对参数进行高效微调的方法有:适配器微调(Adapter Tuning)、前缀微调(Prefix Tuning)、提示微调(Prompt Tuning)和低秩适配(LoRA)等,高效微调的方法在此暂不展开介绍,有兴趣的小伙伴可以自行查阅相关资料。
指令微调通过使用自然语言描述的混合多任务数据集进行有监督地微调,从而使得大语言模型能够更好地完成下游任务,具备更好的泛化能力。在此过程中伴随着参数的更新。
对齐微调旨在将LLM的行为与人类价值观或偏好对齐。它需要从人类标注员(需要具备合格的教育水平甚至满足一定学历要求)中收集高质量的人类反馈数据,然后利用这些数据对模型进行微调。典型的微调技术包括:基于人类反馈的强化学习(RLHF)。
为了使大语言模型与人类价值观保持一致,学者提出了基于人类反馈的强化学习(RLHF),即使用收集到的人类反馈数据结合强化学习对LLM进行微调,有助于改善模型的有用性、诚实性和无害性。RLHF采用强化学习(RL)算法,例如近端策略优化(Proximal Policy Optimization, PPO)通过学习奖励模型使LLM适配人类反馈。
模型微调的优势:
(1)定制化模型,拓展应用能力;
(2)更高质量的结果;
(3)节省token成本(因为不再需要大篇幅的提示);
(4)更低的时延。
但是ChatGPT鼓励优先使用提示开发,因为相较于提示工程,微调开发需要准备更多的数据,投入更多的时间成本,所以如果使用提示而不使用微调可以达到一个优异的结果,那么优先使用提示开发,否则可以使用微调开发。以下是一些常见的场景,使用微调可能带来更好的结果:
(1)设置风格、基调、格式或其他定性方面;
(2)提高所需输出的可靠性;
(3)纠正未能遵循复杂的提示;
(4)存在较多的边缘情况;
(5)面对一个新任务或应用。
一旦确定微调是正确的解决方案(即已经尽可能优化了提示并确定模型仍然存在的问题),那么下面我将演示如何进行微调开发。
1、准备并上传数据
创建数据集,数据集的每个示例应该是与ChatGPT聊天的消息格式相类似的对话,每条消息都包含有role(角色)、content(内容)和其他可选名称。至少一些训练示例应该直接针对提示模型未按预期运行的情况,并且数据中提供的辅助消息应该是您希望模型提供的理想响应。
在此示例中,我们的目标是创建一个偶尔给出讽刺性响应的聊天机器人,这是我们可以为数据集创建的三个训练示例(对话):
{"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 个示例。我们通常会看到对 50 到 100 个训练示例进行微调会带来明显的改进,但正确的数量根据具体的应用而有很大差异。建议从 50 个精心设计的演示开始,看看模型在微调后是否显示出改进的迹象。在某些情况下可能就足够了,但即使模型尚未达到预期的质量,明显的改进也是一个好兆头,表明提供更多数据将继续改进模型。没有任何改进表明您可能需要重新考虑如何在超出有限示例集之前设置模型任务、重组数据(考虑数据的多样性、平衡性或检查数据的格式、语法或逻辑等是否存在问题)。注意:较少量的高质量数据通常比较大量的低质量数据更有效。
将数据集划分为训练集和测试集,每个训练示例的token数限制在4096以内,如果超出了4096将对示例进行截断。每个文件限制在50M以内。上传训练文件和测试文件后,系统会提供一个统计数据,用于评估微调后模型是否提升以及提升了多少。
在数据上传前,有必要对数据集示例的数据格式进行检验,检验的python文件如下:
# 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")
上传数据用于微调:
import openai
import os
openai.api_key = os.getenv("OPENAI_API_KEY")
openai.File.create(
file=open("mydata.jsonl", "rb"),
purpose='fine-tune'
)
2、模型微调
使用OpenAI的SDK开始模型微调作业:
openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo")
开始微调工作后,可能需要一些时间才能完成。您的作业可能排在系统中的其他作业后面,训练模型可能需要几分钟或几小时,具体取决于模型和数据集大小。模型训练完成后,创建微调作业的用户将收到一封确认电子邮件。
除了创建微调作业外,您还可以列出现有作业、检索作业状态或取消作业:
# 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)
openai.Model.delete("ft-abc123")
3、模型使用
模型训练完成后,在作业的详细信息可以看到模型名称,利用此模型便可以搭建你的定制化任务:
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)
4、模型分析
在模型训练过程中,系统会提供以下训练指标:训练损失、训练token准确性、测试损失和测试token准确性。这些统计数据旨在提供模型的评估,确保训练顺利进行(其中损失应该减少,token准确性应该增加)。
为了使语言模型完成一些特定任务,利用在模型的输入中加入提示的机制,使得模型得到预想的结果或引导模型得到更好的结果,注意与微调不同,在提示这一过程中,无需额外的训练和参数更新。
上下文学习(In-context Learning, ICL)是由GPT-3正式引入,它的关键思想是从类比中学习,它将查询的问题和一个上下文提示(一些相关的样例)连接在一起,形成带有提示的输入,并将其输入到语言模型中进行预测。
思维链(Chain-of-thought, CoT)是一种改进的提示策略,旨在提高LLM在复杂推理任务中的性能,例如算术推理、常识推理和符号推理。具体做法是将中间推理步骤纳入到提示中,引导模型预测出正确结果。据相关论文,这种能力可能是在代码上训练而获得。
ChatGPT是使用OpenAI开发的大语言模型进行聊天的web网站,其本质是调用ChatGPT的API完成各项任务,下面演示了使用ChatGPT的API完成总结的任务,除此之外,它还可以完成推理、翻译、问答、校对、扩展等多项任务,有时需要借助ICL或CoT获得更好的结果(前提是你需要从OpenAI官网获得API的密钥key)
import openai
import os
fron dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
openai.api_key = os.getenv("OPENAI_API_KEY")
def get_completion(prompt, temperature=0, model="gpt-3.5-turbo"):
messages = [{"role": "user", "content": prompt)]
response= openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature, # temperature为模型的探索程度或随机性,其值是范围在0~1的浮点数,值越高则随机性越大,说明更有创造力。
)
return response.choices[0].message["content"]
text = f"""
XXXXXXXX
"""
prompt = f"""
Summarize the text delimited by triple backticks into a single sentence.
```{text}```
"""
response = get_completion(prompt)
print(response)
ChatGPT的web网站或者聊天机器人通常包含三个角色(role)的消息(messages),包括:用户(user)的消息,ChatGPT/聊天机器人(assistant)的消息和系统(system)的消息。下面以搭建一个“订餐机器人”为例:
代码示例如下:
import openai
import os
fron dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
openai.api_key = os.getenv("OPENAI_API_KEY")
def get_completion_from_messages(messages, temperature=0, model="gpt-3.5-turbo"):
response= openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature, # temperature为模型的探索程度或随机性,其值是范围在0~1的浮点数,值越高则随机性越大,说明更有创造力。
)
return response.choices[0].message["content"]
messages = [
"role": "system",
"content": "你现在一个订餐机器人,你需要根据菜单收集用户的订餐需求。菜单:汉堡、薯条、炸鸡、可乐、雪碧。",
"role": "user",
"content": "你好,我想要一个汉堡。",
"role": "assistant",
"content": "请问还有其他需要的吗?",
"role": "user",
"content": "再要一份可乐。",
]
response=get_completion_from_messages(messages)
print(response)
# 输出示例:
# 好的,一份汉堡和可乐,已为您下单。
借助上述代码示例,设计一个GUI或Web界面就可以实现人机交互,修改system messages即可更改聊天机器人的行为并让其扮演不同的角色。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。