使用Python和PyTorch的生成式AI——LLM基础

87 阅读26分钟

尽管大型语言模型(LLMs)似乎已经在人工智能领域占据了主导地位,但实际上,这一现象只持续了几年。人工智能的真正热潮始于2022年11月OpenAI发布ChatGPT,仅用一周时间就达到了百万用户。这一成就非常显著——尤其是考虑到最接近的对比对象Instagram,花了八周才达到了百万次下载。人工智能领域的前一个关键时刻发生在2012年,当时AlexNet赢得了ImageNet竞赛,尽管这一突破主要是在学术界引起了共鸣。

在本章中,我们将扩展对自然语言处理(NLP)概念的理解,并探讨大型语言模型(LLMs)与我们迄今为止讨论的模型有什么不同。具体来说,我们将涵盖以下内容:

  • Transformer架构的简要回顾
  • LLM的训练设置及InstructGPT的作用
  • 实践练习,应用这些学习内容

本章中展示的所有代码片段可以直接在Google Colab中运行。由于篇幅原因,依赖项的导入语句没有包含,但读者可以参考GitHub仓库中的完整代码:github.com/PacktPublis…

为了确保我们都在同一页面上,我们将从快速回顾transformer架构、它们的变体以及其训练设置开始。

回顾:Transformer架构

Transformer是当今一代模型的核心。在之前的几章中,我们不仅讨论了NLP模型能力如何随着时间的推移而变化,还深入探讨了transformer本身的内部结构(详细内容请见第3章和第4章)。在本节中,我们将简要回顾transformer设置的高层次方面,然后在接下来的章节中基于此进行扩展。图5.1提供了一个高层次的示意图,我们将逐步讲解。

image.png

Transformer是由多个智能和专门化组件构建的复杂模型,像乐高积木一样组合在一起。图5.1(A)展示了这种设置的内部结构,并显示了关键组件。简而言之,一个基础的transformer模型由单独的编码器和解码器堆栈组成。每个编码器块包括多头自注意力,能够捕捉token之间的关系,而不考虑它们的位置。残差连接有助于保持梯度流动,防止梯度消失问题。层归一化确保了训练的稳定性,而前馈层引入非线性并学习复杂的token交互。解码器块包含相同的组件,但还包括一个编码器-解码器注意力机制,以便从编码器中引入上下文信息。模型使用嵌入层将token转换为连续的潜在空间进行上下文学习,并使用位置编码来保持序列中token的顺序。

虽然基础的transformer提供了一种革命性的文本建模方式,但进一步的改进无疑推动了该领域的前所未有的发展。图5.1(B)展示了transformer架构的三个关键变体及其显著/流行的例子。仅编码器模型利用编码器堆栈在需要深度上下文理解的任务中表现出色,如掩码语言建模和问答任务,其中BERT模型引领了这一类别。仅解码器模型,如GPT,利用解码器堆栈进行自回归任务,如语言生成,并可以针对各种NLP任务进行微调。最后一种类型,将编码器和解码器堆栈结合起来,在序列到序列的任务中表现出色,如翻译,克服了上下文窗口的限制。T5和BART是这一类别的关键示例。

最后,图5.1(C)展示了transformer模型的两步训练范式,首先是在大型原始数据集(如开放网络文本)上进行预训练,使模型能够学习广泛的语言模式和概念。这为各种NLP任务奠定了坚实的基础。第二步是微调,使用特定任务的数据集来定制模型以适应特定任务或领域。例如,一个预训练的GPT-2可能在情感分类任务中表现尚可,但如果在IMDb电影评论数据集上进行微调,它对电影评论的理解将得到改善,从而提高其表现并生成更相关的文本补全(详细工作示例请参见第4章)。

总体而言,transformers大幅提升了NLP模型的能力和性能,吸引了主流关注,并引发了业界和学术界的重大兴趣。然而,尽管这些模型具有强大的潜力,但它们仍面临一些问题,如上下文长度的限制以及生成几个token后容易失去上下文的问题。在接下来的部分,我们将更深入地探讨这一问题,并探讨是什么真正将transformer转化为LLM——当然,不仅仅是它的规模。

更新的训练设置

在前一部分中,我们提到了微调后的语言模型在生成几个token后容易失去上下文的问题,这一问题通常被称为对齐问题。这个挑战限制了模型保持一致输出上下文的能力,从而影响任务性能。虽然微调后的模型在少样本和零样本任务上有所改进(请参考第4章中关于GPT-2和GPT-3的部分),但它们并不总是可靠地生成所需的结果。例如,一个模型在少样本设置下可能能够很好地进行情感分析,但在类似的设置下处理翻译任务时可能会遇到困难。

为了解决这一限制,Ouyang等人在2022年初提出了InstructGPT。尽管InstructGPT的架构与以前的GPT模型相似,但它的规模明显较小,仅有13亿参数,而GPT-3有1750亿参数。其关键创新在于两个额外的训练步骤:指令微调和基于人类反馈的强化学习(RLHF)。

在常规的预训练步骤之后,朝着更好对齐的第一步是指令微调。在这一阶段,模型使用一个较小的标记示范数据集进行进一步微调,数据集包含在各种输入提示下所需行为的示例。图5.2(A)展示了InstructGPT的扩展训练设置,而图5.2(B)则可视化了示范数据集的结构。

image.png

Ouyang等人指出,这些额外的训练步骤(如图5.2所示)使得InstructGPT(以及一般的语言模型)在遵循指令方面比GPT-3表现得更好。这项工作为一系列更强大且更对齐的模型铺平了道路。在接下来的部分中,我们将详细探讨这两个步骤,并进行实践操作以加深理解。

指令微调

指令微调类似于监督微调(SFT),其数据集由特定任务的输入-输出对组成。然而,关键的不同之处在于,在指令微调中,每个数据点的输入不仅包括上下文,还包括一个明确的任务指令,而模型则使用相同的语言建模目标进行训练。这与SFT不同,后者的数据集由输入-输出对组成,训练目标是针对特定任务量身定制的(例如,使用交叉熵训练分类器)。指令微调帮助模型更好地泛化并与任务对齐,同时保持其语言建模能力。图5.3对比了SFT和指令微调的示例。

image.png

InstructGPT论文的作者展示了,加入指令使得模型能更好地理解任务,从而在更广泛的任务中表现得更加稳健。接下来,我们将通过对GPT-2模型进行指令微调,应用这一方法来进行语言翻译任务。

实践:指令微调

在本节中,我们将使用Hugging Face库和公开数据集,探索如何对语言模型进行指令微调。

问题陈述

在指令微调的背景下,使用预训练的transformer模型将英语翻译成德语。当前任务是通过指令微调,扩展GPT-2模型的能力,使其能够将英语文本翻译为德语。指令微调的训练目标与语言建模相同(如预训练步骤中所做),与典型的监督微调(SFT)场景不同,后者通常使用顺序建模来处理这种任务。

原始论文介绍了基于GPT-3架构的InstructGPT模型。为了在保持计算需求最小化的同时加深理解,我们使用GPT-2来说明指令微调的设置。如果你有更大的计算资源或更多的GPU内存,可以轻松将笔记本改为适用于更大模型,例如Phi-2或Llama系列。

数据集准备

指令微调要求我们以特定方式准备数据集,其中每个输入都附有上下文或指令。准备指令微调数据集有多种方式(有时也取决于底层模型的要求)。我们将使用斯坦福Alpaca格式,这是最常见和广泛使用的格式之一。以下代码片段展示了标准模板的稍微修改版,并将一个示例数据点转换为所需的格式:

alpaca_template="""
###{instruction}: {input}
###Output:{output}
"""
# 示例数据点(输入,输出)
('monster tomatoes','monster-tomaten')
alpaca_formatted_datapoint=
"""
###Translate to German:monster tomatoes
###Output:monster-tomaten
"""

我们准备了一个简单的格式化函数,该函数接受输入-输出对的列表,并以Alpaca格式返回它们。我们使用了Hugging Face Datasets库的接口来简化操作。

我们为指令微调任务准备了原始数据集,这一任务是第4章新闻标题生成任务的扩展。我们从英文标题开始,使用GPT-4o/Llama 3.1生成相应的德语翻译。此数据集仅用于演示目的,尚未进行预处理或错误清理。请参阅仓库中的相关笔记本,了解数据集生成的过程。你可以修改笔记本进行进一步改进。

以下代码片段展示了此管道的数据集准备步骤:

from datasets import Dataset
# 配置
TOKENIZER = "gpt2"
MODEL = "raghavbali/gpt2-finetuned-headliner"
OUTPUT_MODEL_NAME = "gpt2-instruct-tuned-translator2"
DATASET = 'news_english_german_instruction_dataset_20240909.json'

# 加载数据集
instruction_dataset = list()
with open(DATASET, "r") as jsonfile:
    instruction_dataset = json.load(jsonfile)
print(f"Total Records={len(instruction_dataset)}")

# 基本清理,去除非常短或为空的翻译
instruction_dataset = [{
    'input': record['input'],
    'output_gpt4omini': record['output_gpt4omini']
} for record in instruction_dataset if record['output_gpt4omini'] != '#' and len(record['output_gpt4omini']) > 2]
print(f"Total Records Remaining={len(instruction_dataset)}")

# 训练集和测试集划分
X_train, X_test = train_test_split(instruction_dataset[:5000],
    test_size=0.1, random_state=42
)

# 分词函数
def tokenize_function(examples):
    examples["text"] = [f"###Translate to German:{ed['input']}\n###Output:{ed['output_gpt4omini']}<|endoftext|>" for ed in examples["text"]]
    return tokenizer(
        examples["text"],
        truncation=True,
        max_length=512,
    )

# 分词后的数据集
tokenized_train_dataset = Dataset.from_dict({'text': X_train}).map(
    tokenize_function,
    batched=True,
    num_proc=8,
    remove_columns=["text"],
)

tokenized_test_dataset = Dataset.from_dict({'text': X_test}).map(
    tokenize_function,
    batched=True,
    num_proc=8,
    remove_columns=["text"],
)

这个步骤包括了数据集的加载、基础清理(去除短翻译和空白翻译)、训练集和测试集的划分、以及分词处理。这些步骤为我们在接下来的训练中准备了数据集。

训练设置

一旦我们准备好了数据集,接下来的步骤与预训练阶段的设置相同。与第4章类似,我们将使用Hugging Face的trainer接口来微调我们的模型。以下代码片段展示了训练部分的设置:

model = AutoModelForCausalLM.from_pretrained(MODEL, device_map="auto").to(DEVICE)

training_args = TrainingArguments(
    OUTPUT_MODEL_NAME,  # 输出目录
    overwrite_output_dir=True,  # 覆盖输出目录内容
    num_train_epochs=2,  # 训练轮数
    per_device_train_batch_size=16,  # 训练的批次大小
    per_device_eval_batch_size=16,  # 评估的批次大小
    eval_steps=16,  # 两次评估之间的更新步数
    save_steps=32,  # 每隔多少步保存一次模型
    warmup_steps=4,  # 学习率调度器的预热步数
    push_to_hub=True,
    logging_steps=16,
    # use_mps_device=True,
    # use_cpu=True  # 如果有GPU可用,请注释此行
)

trainer = Trainer(
    model=model,
    args=training_args,
    data_collator=data_collator,
    train_dataset=tokenized_train_dataset,
    eval_dataset=tokenized_test_dataset,
)

trainer.train()

为了方便学习,整个设置已经简化,可以在低RAM的GPU上运行(即使是CPU-only设置,虽然会非常慢)。在Google Colab的免费版中,使用T4 GPU进行训练大约需要15分钟。

分析结果

现在,我们有了一个经过指令微调的新闻标题生成模型,能够将英语翻译为德语。接下来,让我们准备一些工具,查看它的实际效果。以下代码片段展示了如何从微调后的模型生成输出:

from transformers import GenerationConfig

generate_kwargs = {
    "temperature": 0.5,
    "eos_token_id": 50256,
    "max_new_tokens": 50,
}
generate_config = GenerationConfig(**generate_kwargs)

# 加载指令微调后的模型
pretrained_model = AutoModelForCausalLM.from_pretrained(MODEL, device_map="auto").to(DEVICE)
inst_tuned_model = AutoModelForCausalLM.from_pretrained(OUTPUT_MODEL_NAME).to(DEVICE)

# 如果使用Apple Silicon设备,请注释掉.to(DEVICE)
pretrained_model.resize_token_embeddings(len(tokenizer))
inst_tuned_model.resize_token_embeddings(len(tokenizer))

# 设置生成管道
translator_pipeline = pipeline('text-generation',
                              model=inst_tuned_model,
                              tokenizer='gpt2',
                              pad_token_id=0,
                              eos_token_id=50256,
                              device=DEVICE,
                              model_kwargs=generate_kwargs
                             )

pretrained_pipeline = pipeline('text-generation',
                              model=pretrained_model,
                              tokenizer='gpt2',
                              pad_token_id=0,
                              eos_token_id=50256,
                              device=DEVICE,
                              model_kwargs=generate_kwargs
                             )

def get_translated_headline(_pipeline, seed_text="News"):
    return _pipeline(seed_text)[0]['generated_text']

# 测试集中的示例
for _str in X_test[25:30]:
    input_str = f"###Translate to German:{_str['input']}\n###Output:"
    response = get_translated_headline(translator_pipeline, seed_text=input_str)
    print(response)
    print(f"GPT-Translation:{_str['output_gpt4omini']}")
    print()

从模型生成的输出结果如下:

###Translate to German:warner smith return for blues
###Output:Warner Smith schnell vor den blues
GPT-Translation:Warner Smith Rückkehr für Blues

###Translate to German:gold coast could have superyacht marina boyle
###Output:Gold Coast gewinnt wirtsicher schafft Marle in der Stadt Gold.
GPT-Translation:Die Goldküste könnte einen Superyacht-Hafen in Boyle haben.

###Translate to German:bid offered for hamilton is
###Output:Schließer, der in Brandwurf auf hamilton.
GPT-Translation:Das Gebot für Hamilton ist

###Translate to German:bhp ordered to assess seismic risks
###Output:Berichkeit erfasst vor Geowarsenheit
GPT-Translation:BHP beauftragt, seismische Risiken zu bewerten

###Translate to German:nsw premier says health authorities need to watch
###Output:Die Premierminister für die Entwicklung vor Gericht auf die Überlokalien
GPT-Translation:Der Premier von New South Wales sagt, die Gesundheitsbehörden müssen aufpassen.

从生成的输出可以看出,模型似乎已经很好地掌握了这项技能,但并非每次翻译都合乎逻辑。我们怀疑,可能需要比本节中用来演示指令微调的数据集更大且更高质量的数据集。接下来,我们将讨论第二个提出的步骤,以实现更好的对齐。

基于人类反馈的强化学习(RLHF)

InstructGPT论文中的第二步训练过程引入了强化学习的有趣应用。强化学习是一种独特的学习范式,与监督学习、无监督学习和半监督学习方法并列。在这一范式中,代理(agent)与环境进行交互,采取行动以最大化奖励,同时追求特定的目标。例如,考虑一个迷宫游戏(环境),其中玩家(代理)可以向左、向右、向上或向下移动(行动),以最少的步骤找到出口(目标)(奖励)。虽然强化学习主要应用于游戏和约束环境,但InstructGPT的作者将其引入了语言建模领域,提出了RLHF变体。让我们从NLP的角度分解这一额外的训练步骤(见图5.4)。

image.png

如图5.4(步骤2)所示,在获得我们预训练模型的指令微调版本(来自图5.4,步骤1的输出)后,我们首先训练一个奖励模型。这个奖励模型学习如何根据与输入提示的对齐情况将响应从最佳到最差进行排序。奖励模型的训练数据包括提示和从指令微调模型中采样的各种输出。这个数据集是由标注人员手动策划的,他们根据偏好、与提示的对齐程度以及其他预定义标准对响应进行排名(请参见图5.5以获取参考)。

值得注意的是,这个数据集比早期训练阶段使用的数据集要小得多。RLHF设置中的人类反馈简化了识别任何提示的最佳响应的开放性问题(你能想出如果没有人类反馈这会为什么如此困难吗?)。

image.png

下一步是使用前一步中的奖励模型训练一个策略(该策略最终成为对齐的语言模型),并应用一种名为**近端策略优化(Proximal Policy Optimization,PPO)**的强化学习算法。

在本章中,我们从实践者的角度介绍了强化学习和RLHF,提供了详细的信息以帮助清晰理解这些概念。对强化学习的深入探讨超出了本书的范围。感兴趣的读者可以通过参考材料进一步探索。

在本工作中使用的PPO算法,采用了一个简单的设置,其中每个采样的输入提示及其相应的输出响应形成一个“回合”。在强化学习中,回合指的是一个训练步骤,在该步骤中,模型(称为策略)采取行动并积累奖励。在每个回合结束时,这些奖励用于更新模型的权重。这被称为一个老虎机环境,在该环境中,随机提示被采样作为输入传入指令微调模型,并为每个回合生成响应。然后,奖励模型评估提示和响应,并为策略模型提供奖励反馈。

此外,作者应用了KL惩罚作为正则化技术。这个惩罚机制会防止策略模型生成与指令微调模型的分布相差太远的响应。这有助于防止过度优化,确保策略模型不会仅仅为了最大化奖励而牺牲质量、连贯性或泛化能力。

让我们总结一下PPO如何逐步训练一个对齐的语言模型的伪算法:

  1. 初始策略:指令微调模型是该算法的起点,记作policy_model

  2. 循环,直到达到停止标准(例如更新次数、损失值、进展等):

    • policy_output: 从策略模型生成输出。

    • reward_score: 使用奖励模型对policy_output的质量评分。

    • 使用PPO优化policy_model

      • 通过最大化期望奖励来迭代更新policy_model的权重。
      • 如果更新后的响应偏离初始policy_model的输出分布过多,则对更新进行惩罚。可以使用KL散度或其他剪枝策略来实现。
      • 更新总体评分、模型权重和进展。

有许多其他算法可以(并且已经)用于替代PPO。例如,**直接偏好优化(Direct Preference Optimization,DPO)是一个广泛使用的有效算法,常常替代PPO。如其名称所示,DPO通过奖励模型与简单的分类损失来直接实现对齐,无需单独的策略模型。使用的数据集与PPO设置类似,由输入提示及获胜/偏好的响应和失败/不偏好的响应组成。此外,还有通过如身份偏好优化(Identity Preference Optimization,IPO)卡尼曼-特韦尔斯基优化(Kahneman-Tversky Optimization,KTO)**等工作提出的进一步改进。

步骤3(见图5.4)后的最终输出是一个比仅使用语言建模目标进行无监督预训练的模型更好地对齐任务/提示,并且更加有帮助、诚实和无害的模型。

现在我们已经理解了RLHF如何融入整体设置,让我们开始一些实践操作。

实践:使用PPO进行RLHF

为了更好地理解RLHF如何帮助实现更好的对齐提示,我们将使用Hugging Face的trl库设置一个简单的用例。

Transformer强化学习(trl)提供了用于SFT和奖励建模的易用接口,以及多个训练算法,包括PPO和KPTO。更多细节请参见参考文献15。

问题陈述

IMDb网站是一个获取电影评论的绝佳平台。该网站允许评论员/成员分享他们对任何电影的评论,以自由文本的形式。IMDb数据集是成千上万条此类评论的集合,以及它们的情感。

我们的任务是训练一个语言模型,生成性质为积极的电影评论。

数据集准备

这一阶段的数据集准备相当直接。我们将使用Hugging Face的Datasets库加载IMDb数据集。我们将筛选出长度为512个字符以内的评论,但使用trl中的LengthSampler工具类准备不同长度的批次输入。

这使我们能够准备一个混合批次进行训练,可以缓解一些问题,比如模型依赖输入长度来最大化奖励。然后,我们使用tokenizer为每个数据点准备input_ids列表。以下代码片段展示了我们数据集的准备和相应的对象:

from datasets import load_dataset, Dataset
from transformers import AutoTokenizer, pipeline
from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead, create_reference_model
from trl.core import LengthSampler

ppo_config = PPOConfig(
    model_name="raghavbali/gpt2-movie_reviewer",
    steps=200,
    learning_rate=1.41e-5,
    remove_unused_columns=False,
    log_with="tensorboard",
    project_kwargs={"logging_dir": "./logs"},
)

tokenizer = AutoTokenizer.from_pretrained(ppo_config.model_name)
tokenizer.pad_token = tokenizer.eos_token

def prepare_dataset(
    tokenizer, dataset_name="imdb",
    input_min_text_length=2,
    input_max_text_length=8
):
    # 加载IMDb数据集
    ds = load_dataset(dataset_name, split="train")
    ds = ds.rename_columns({"text": "review"})
    ds = ds.filter(lambda x: len(x["review"]) < 500, batched=False)
    input_size = LengthSampler(input_min_text_length, input_max_text_length)

    def tokenize(sample):
        sample["input_ids"] = tokenizer.encode(
                sample["review"]
            )[: input_size()]
        sample["query"] = tokenizer.decode(sample["input_ids"])
        return sample

    ds = ds.map(tokenize, batched=False)
    ds.set_format(type="torch")
    return ds

dataset = prepare_dataset(tokenizer)

def data_collator(data):
    return dict((key, [d[key] for d in data]) for key in data[0])

一旦我们准备好数据集,接下来的步骤就是准备训练对象。

PPO设置

PPOTrainer类通过提供一个非常干净和易用的接口,简化了整体管道。我们需要一个初始的策略模型和一个参考模型作为输入。以下代码片段展示了如何准备这些必要的对象:

generation_kwargs = {
    "min_length": -1,
    "top_k": 0.0,
    "top_p": 1.0,
    "do_sample": True,
}

ALIGNED_MODEL_NAME = f"aligned-{ppo_config.model_name.split('/')[1]}"
model = AutoModelForCausalLMWithValueHead.from_pretrained(ppo_config.model_name)

# 创建参考模型
ref_model = create_reference_model(model, num_shared_layers=6)

generation_kwargs["pad_token_id"] = tokenizer.eos_token_id

ppo_trainer = PPOTrainer(
    ppo_config,
    model,
    ref_model,
    tokenizer,
    dataset,
    data_collator=data_collator,
)

正如代码片段所示,我们利用了一个预训练的GPT-2版本,它已经被微调以生成电影评论(为了改善整体设置,您可以进一步对该模型进行指令微调,然后执行RLHF作为练习)。参考模型是该模型的副本,我们共享了一些层以减少整体的内存和计算需求。

奖励模型

该设置还需要一个奖励模型。为了简化操作,我们将使用一个经过IMDb数据集微调的DistilBERT模型来将每个输出分类为正面或负面。使用这样的模型可以帮助我们避免额外准备偏好数据集并为其训练奖励模型的要求(尽管这样做会稍微影响最终策略模型的质量)。以下代码片段展示了如何为奖励模型准备对象:

## 获取奖励模型
distilbert_tokenizer = AutoTokenizer.from_pretrained("lvwerra/distilbert-imdb", eos_token='</s>')
sentiment_pipe = pipeline("sentiment-analysis", "lvwerra/distilbert-imdb",
    tokenizer=distilbert_tokenizer, device=device
)

# 测试管道
text = "this movie was really bad!!"
output = sentiment_pipe(text, **sentiment_pipe_kwargs)
output
# 输出: [{'label': 'NEGATIVE', 'score': 2.3350484371185303}, {'label': 'POSITIVE', 'score': -2.726576328277588}]

在这个设置中,最后剩下的部分是奖励函数。由于奖励模型仅为每个标签生成一个分数,因此如果识别的情感是正面的,我们会将其按4倍缩放,否则按0.5倍缩放。换句话说,我们试图向模型传达,如果生成的输出是正面的,它将获得一个大的正奖励;如果输出是负面的,奖励则会非常低(我们基本上将其减少一半)。以下代码片段展示了奖励函数的实现:

def get_rewards(output):
    if output[0]['score'] > output[1]['score']:
        if output[0]['label'] == 'POSITIVE':
            return torch.tensor(4 * output[0]['score'])
        else:
            return torch.tensor(0.5 * output[0]['score'])
    elif output[1]['score'] > output[0]['score']:
        if output[1]['label'] == 'POSITIVE':
            return torch.tensor(4 * output[1]['score'])
        else:
            return torch.tensor(0.5 * output[0]['score'])
    return -1

训练循环

最后一步是将所有内容结合起来并准备训练循环。训练循环的每一次迭代都执行我们在“基于人类反馈的强化学习(RLHF)”部分中概述的PPO步骤。我们从生成策略模型的输出开始。然后,使用奖励模型对输出进行评分,并将奖励分数传递给ppo-trainer对象,用于更新策略模型的权重。参考模型用于训练的稳定性,通过使用KL散度来比较输出生成分布。以下代码片段展示了训练循环的实现:

for epoch, batch in tqdm(enumerate(ppo_trainer.dataloader)):
    if epoch >= num_steps:
        break
    query_tensors = batch["input_ids"]
    
    #### 从gpt2获取响应
    response_tensors = []
    for query in query_tensors:
        gen_len = output_length_sampler()
        generation_kwargs["max_new_tokens"] = gen_len
        response = ppo_trainer.generate(query, **generation_kwargs)
        response_tensors.append(response.squeeze()[-gen_len:])
    
    batch["response"] = [tokenizer.decode(r.squeeze()) 
                         for r in response_tensors]
    
    #### 计算情感分数
    texts = [q + r for q, r in zip(batch["query"], batch["response"])]
    pipe_outputs = sentiment_pipe(texts, **sentiment_pipe_kwargs)
    
    rewards = list()
    for output in pipe_outputs:
        rewards.append(get_rewards(output))
    
    overall_rewards.append(rewards)
    
    # PPO步骤
    stats = ppo_trainer.step(query_tensors, response_tensors, rewards)
    print(f'objective/kl: {stats["objective/kl"]}')
    print(f'ppo/returns/mean: {stats["ppo/returns/mean"]}')
    print(f'ppo/policy/advantages_mean: {stats["ppo/policy/advantages_mean"]}')
    print("-".join("" for x in range(100)))
    
    ppo_trainer.log_stats(stats, batch, rewards)

分析训练结果

图5.6可视化了训练步骤中的奖励分数。

image.png

我们可以清楚地看到,在图5.6中,随着训练的进行,出现了更多较高分数的峰值,这表明模型输出的逐渐正向对齐/强化。这一渐进变化也表明了我们设置的训练稳定性。

在结束之前,让我们使用经过微调和PPO微调版本的模型生成一些评论,以了解行为上的细微变化:

hub_model = AutoModelForCausalLMWithValueHead.from_pretrained(f'./{ALIGNED_MODEL_NAME}').to(device)  # ,cache_dir="/workspace/"
hub_tokenizer = AutoTokenizer.from_pretrained(f'./{ALIGNED_MODEL_NAME}')  # cache_dir="/workspace/"
hub_tokenizer.pad_token = tokenizer.eos_token

reviews = [
    "No big names",
    "The director",
    "What",
    "Lame",
    "Space invaders",
    "Here are my 2 cents on the movie",
]

for review in reviews:
    inputs = hub_tokenizer(review, return_tensors="pt", return_token_type_ids=False).to(device)
    display(Markdown((f"### Prompt: {review}...")))
    display(Markdown(("#### ALIGNED-MODEL ")))
    outputs = hub_model.generate(**inputs, max_new_tokens=25, temperature=0.8, do_sample=True, pad_token_id=tokenizer.eos_token_id)
    display(Markdown((tokenizer.decode(outputs[0], skip_special_tokens=True))))
    
    display(Markdown(("#### NON-ALIGNED-MODEL ")))
    outputs = ref_model.generate(**inputs, max_new_tokens=25, temperature=0.8, do_sample=True, pad_token_id=tokenizer.eos_token_id)
    display(Markdown((tokenizer.decode(outputs[0], skip_special_tokens=True))))
    
    display(Markdown(("---")))

图5.7展示了对比几个输入提示时,两种模型的输出。我们再次从hub下载了PPO微调模型,并将其标记为aligned_model,而最初微调的模型在此被标记为非对齐模型。

image.png

如图所示,虽然这两个模型通常都不是非常有毒(可能是由于最初微调版本的策略模型训练数据集有限),但对齐模型确实似乎避免了某些负面词汇(例如,参见最后一个示例)。

接下来,我们将简要总结我们对InstructGPT的学习,并讨论它如何推动我们进入LLM时代。

LLMs

InstructGPT的作者展示了,指令微调和基于人类反馈的强化学习(RLHF)在初始预训练步骤之后,如何显著改善语言模型的对齐性和整体实用性。InstructGPT的规模约为GPT-3的1/100,但在多个评估标准上超越了GPT-3。随后推出的GPT-3.5(更常见的名字是ChatGPT)普及了“大型语言模型”(LLM)这一术语。

从那时起,LLMs已经发展成为一个综合领域,涵盖了之前需要专用模型的大多数NLP任务(直到2021年)。GPT-3.5后继的是GPT-4,拥有1.76万亿参数,以及GPT-4o和o1(截至写作时),提供了更大的输入/上下文窗口和多模态能力,包括支持音频和图像的输入/输出。其他 notable的专有模型包括谷歌的Gemini系列、Anthropic的Claude系列等,通常由于专有和财务原因,作为封闭权重的API提供。

开源领域也迅速发展,模型越来越接近封闭源的产品。Meta的Llama系列、谷歌的Gemma系列和Mistral AI的Mistral系列是开源模型的例子。我们将在第6章中进一步探讨开源LLM。

总结

本章介绍了对整个语言建模范式至关重要的关键概念。我们首先回顾了transformer架构和通常的预训练大型模型方式,然后进行特定任务的微调。我们还触及了此类模型在对齐任务方面的局限性。本章接着提供了一个扩展训练设置的概述,涉及了指令微调和RLHF的额外步骤,以改善模型的对齐性和整体性能。接下来的部分详细评论了每个主题,并提供了实践练习,用于指令微调GPT-2模型以将英语新闻标题翻译成德语,并使用PPO对齐的GPT-2模型生成大多数积极的电影评论。

本章最后简要讨论了这一扩展训练设置如何启动LLM时代,并预告了即将到来的章节内容,包括开源LLM、提示工程等。后续章节将在这一基础上,介绍开源LLM、提示工程等内容。

参考文献

  • Ortiz, Sabrina. 2022. “What is ChatGPT? How the world’s most popular AI chatbot can benefit you.” ZDNet. www.zdnet.com/article/wha….
  • Bilton, Nick. 2017. “Instagram Quickly Passes 1 Million Users.” The New York Times Bits Blog. bits.blogs.nytimes.com/2010/12/21/….
  • Large Scale Visual Recognition Challenge 2012. 2012. image-net.org/challenges/….
  • Gao, Leo, Stella Biderman, Sid Black, Laurence Golding, Travis Hoppe, Charles Foster, Jason Phang, et al. 2020. “The Pile: An 800GB Dataset of Diverse Text for Language Modeling.” arXiv. arxiv.org/abs/2101.00….
  • Maas, Andrew L., et al. 2011. “Learning Word Vectors for Sentiment Analysis.” In Proceedings of the 49th Annual Meeting of the Association for Computational Linguistics: Human Language Technologies, Portland, Oregon, USA. Association for Computational Linguistics. www.aclweb.org/anthology/P….
  • Hern, Alex. 2019. “New AI Fake Text Generator May Be Too Dangerous to Release, Say Creators.” The Guardian. www.theguardian.com/technology/….
  • Ouyang, Long, et al. 2022. “Training Language Models to Follow Instructions with Human Feedback.” arXiv. arxiv.org/abs/2203.02….
  • Taori, Rohan, Ishaan Gulrajani, Tianyi Zhang, Yann Dubois, Xuechen Li, Carlos Guestrin, Percy Liang, and Tatsunori B. Hashimoto. 2023. “Stanford Alpaca: An Instruction-Following LLaMA Model.” GitHub. github.com/tatsu-lab/s….
  • François-Lavet, Vincent, Peter Henderson, Riashat Islam, Marc G. Bellemare, and Joelle Pineau. 2018. “An Introduction to Deep Reinforcement Learning.” arXiv. arxiv.org/abs/1811.12….
  • Schulman, John, Filip Wolski, Prafulla Dhariwal, Alec Radford, and Oleg Klimov. 2017. “Proximal Policy Optimization Algorithms.” arXiv. arxiv.org/abs/1707.06….
  • Sutton, Richard S., and Andrew G. Barto. 2018. Reinforcement Learning: An Introduction. incompleteideas.net/sutton/book….
  • Rafailov, Rafael, Archit Sharma, Eric Mitchell, Stefano Ermon, Christopher D. Manning, and Chelsea Finn. 2023. “Direct Preference Optimization: Your Language Model is Secretly a Reward Model.” arXiv. arxiv.org/abs/2305.18….
  • Azar, Mohammad Gheshlaghi, Mark Rowland, Bilal Piot, Daniel Guo, Daniele Calandriello, Michal Valko, and Rémi Munos. 2023. “A General Theoretical Paradigm to Understand Learning from Human Preferences.” arXiv. arxiv.org/abs/2310.12….
  • Ethayarajh, Kawin, Winnie Xu, Dan Jurafsky, and Douwe Kiela. 2023. “KTO: Model Alignment as Prospect Theoretic Optimization” arXiv. arxiv.org/abs/2402.01….
  • Hugging Face. “TRL - Transformer Reinforcement Learning.” huggingface.co/docs/trl/en….