您的位置:  首页 > 技术杂谈 > 正文

落地领域大模型应知必会 (1) :主要微调方法总览

2023-07-03 15:00 https://my.oschina.net/IDP/blog/10086063 Baihai_IDP 次阅读 条评论

编者按:随着大规模预训练模型的发展和应用,大模型微调技术已经在很多领域都有了突破性的进展,并推动了人工智能技术的发展与应用。

本文会简要介绍上下文学习(in-context learning)的含义,并介绍对LLMs进行微调的各种可行方式。还能够帮助我们了解如何选择大语言模型的微调方法。

快快阅读此文,开启一趟大模型微调学习之旅吧!

以下是译文,Enjoy!

本文经原作者授权,由Baihai IDP编译。如需转载译文,请联系获取授权。

原文链接:https://magazine.sebastianraschka.com/p/finetuning-large-language-models

作者 | SEBASTIAN RASCHKA, PHD

编译 | 岳扬

在如今高速发展的人工智能领域,高效地利用大语言模型(LLMs)已经变得越来越重要。但是,利用大语言模型的方式太多了,如果你才刚刚开始接触它,可能会感到不知所措。

实质上,我们可以通过两种主要方式将预训练大语言模型用于新任务:上下文学习(in-context learning)以及微调(finetuning)

本文我们将简要介绍上下文学习(in-context learning)的含义,并介绍对LLMs进行微调的各种可行方式。

01 In-Context Learning和Indexing

自从GPT-2(Radford等人[1])和GPT-3(Brown等人[2])问世以来,我们发现,在通用文本语料库上进行预训练的生成式大语言模型(LLMs)可以进行上下文学习。这意味着,如果我们想要执行新的特定任务,我们无需对预训练的LLM进行进一步的训练或微调,可以通过输入Prompt直接提供一些目标任务的示例,如下面的例子所示。

图片

上下文学习的例子

如果我们不能直接访问模型(例如如果我们通过API使用模型),那么上下文学习就用处非常大。

与上下文学习相关的是hard prompt tuning,即试图通过修改输入改善输出,如下图所示。

图片

An illustration of (hard) prompt tuning顺便说一下,我们称这种方法为hard prompt tuning,因为在这种方法中,我们直接修改了输入的单词或tokens。稍后我们将讨论另外一种版本的方法,称为soft prompt tuning(或通常称为prompt tuning)。

刚刚提到的prompt tuning方法为参数微调(parameter finetuning)提供了一种替代方案(且这种方案更节省资源)。然而,它的性能通常不及微调(finetuning),因为这种方法不会针对特定任务更新模型的参数,这可能会限制它对不同特定任务间细微差别的适应能力。此外,prompt tuning可能需要进行大量工作,耗费大量人力财力,因为这种方法需要人工参与比较不同prompt的质量。

在我们更进一步地讨论微调之前,先来了解另一种利用纯粹的上下文学习的方法——indexing。在大语言模型领域中,indexing可以被视为一种上下文学习的变通方法,其使得LLMs能够转变为信息检索系统(information retrieval systems),从外部资源和网站中提取数据。在这个过程中,indexing模块将文档或网站分解成较小的片段,并将它们转换为可以存储在向量数据库中的向量(vectors) 。然后,当用户提交查询(query)时,indexing模块计算嵌入查询(embedded query)与数据库中每个向量之间的向量相似度(vector similarity)。最终,indexing模块检索出与查询最相似的前k个嵌入(embeddings),生成响应。

图片

An illustration of indexing.

02 常规的基于特征的方法和微调方法

如果我们无法直接访问大型语言模型(LLM),例如通过API或使用用户界面与LLM进行交互,那么上下文学习是一种有价值且用户友好的方法。

然而,如果我们可以直接接触到LLM,使用目标领域的数据对其进行调整(adapting)和微调(finetuning)通常会获得更好的结果。那么,如何使一个模型适应目标任务呢?下图概述了三种常见的方法:

图片

3种常规的基于特征的方法和微调的方法

下文中,我们将以对编码器类型(encoder-style)的大型语言模型(例如BERT)微调为例,解析具体的微调方式原理和示例代码。下述示例微调任务为分类器任务,在这个任务中,我们试图预测电影评论是正面情感还是负面情感。需要注意的是,除了对编码器类型(encoder-style)的LLM进行微调外,同样的方法也适用于类似GPT的解码器类型(decoder-style)的LLM。在后续的文章中,我将会举例说明这一点。

此外,我们还可以对解码器类型(decoder-style)的LLM进行微调,以生成对特定指令的多句子回答(multiple-sentence answers),而不仅仅是进行文本分类。关于这个问题,我会在未来的文章中提供实战案例,敬请期待。

2.1 基于特征的方法

在基于特征的方法(feature-based approach)中,我们需要加载一个预训练LLM,并将其应用于目标数据集。在这种方法中,我对生成训练集的输出嵌入(output embeddings)特别感兴趣,这些嵌入可以作为输入特征(input features)用于训练分类模型(classification model)。虽然这种方法在像BERT这样以嵌入为重点的模型中非常常见,但我们也可以从生成式的 GPT-style 模型中提取嵌入。

接下来,我们可以使用逻辑回归模型(logistic regression model)、随机森林(random forest)或XGBoost等任何我们想要使用的模型作为分类模型。(然而,根据我的经验,在这种情况下,像逻辑回归(logistic regression)这样的线性分类器(linear classifiers)表现最佳

理论上,我们可以用下面这段代码来解释基于特征的方法:

model = AutoModel.from_pretrained("distilbert-base-uncased")

# ...
# tokenize dataset
# ...

# generate embeddings
@torch.inference_mode()
def get_output_embeddings(batch):
output = model(
batch["input_ids"],
attention_mask=batch["attention_mask"]
).last_hidden_state[:, 0]
return {"features": output}

dataset_features = dataset_tokenized.map(
get_output_embeddings, batched=True, batch_size=10)

X_train = np.array(imdb_features["train"]["features"])
y_train = np.array(imdb_features["train"]["label"])

X_val = np.array(imdb_features["validation"]["features"])
y_val = np.array(imdb_features["validation"]["label"])

X_test = np.array(imdb_features["test"]["features"])
y_test = np.array(imdb_features["test"]["label"])

# train classifier
from sklearn.linear_model import LogisticRegression

clf = LogisticRegression()
clf.fit(X_train, y_train)

print("Training accuracy", clf.score(X_train, y_train))
print("Validation accuracy", clf.score(X_val, y_val))
print("test accuracy", clf.score(X_test, y_test))

(对此感兴趣的读者可以在此处[4]找到完整的代码)

2.2 微调方法 I —— 更新输出层(Output Layers)

有一种方法与上述基于特征的方法比较相关,叫做微调输出层(本文称之为微调方法 I)。与基于特征的方法类似,这种方法不改变预训练LLM的参数,只会训练新增加的输出层,类似于在嵌入特征(embedded features)上训练逻辑回归分类器(logistic regression classifier)或小型多层感知器(small multilayer perceptron)。

相关代码如下:

model = AutoModelForSequenceClassification.from_pretrained(
    "distilbert-base-uncased",
     num_labels=2
) 

# freeze all layers
for param in model.parameters():
    param.requires_grad = False
    
# then unfreeze the two last layers (output layers)
for param in model.pre_classifier.parameters():
    param.requires_grad = True

for param in model.classifier.parameters():
    param.requires_grad = True
    
# finetune model
lightning_model = CustomLightningModule(model)

trainer = L.Trainer(
    max_epochs=3,
    ...
)

trainer.fit(
  model=lightning_model,
  train_dataloaders=train_loader,
  val_dataloaders=val_loader)

# evaluate model
trainer.test(lightning_model, dataloaders=test_loader)

(对此感兴趣的读者可以在此处[4]找到完整的代码)

从理论上讲,这种方法的建模性能和速度(modeling performance and speed)应该与基于特征的方法差不多,因为都会去冻结骨干模型(frozen backbone model)(译者注:通过冻结骨干模型,我们可以保持其模型权重不变,仅对任务模块进行训练。这样可以减少需要更新的参数数量,提高训练效率,并在一定程度上保留原始骨干模型的特征提取能力)。然而,由于基于特征的方法使得预计算(pre-compute)和存储训练数据集的嵌入特征(embedded features)稍微更容易一些,所以在具体场景中,基于特征的方法可能更加方便

2.3 微调方法 II - 更新所有层(All Layers)

虽然BERT那篇论文(Devlin等人)说过,仅微调输出层(output layer)可以实现与微调所有层相当的模型性能,但由于后者涉及的参数更多,所以成本要高得多。 例如,BERT基础模型约有1.1亿个参数。然而,用于二元分类的BERT基础模型最后一层仅包含1500个参数。此外,BERT基础模型的最后两层共有6万个参数,这仅占总模型大小的0.6%左右。

我们选择走哪条路径会根据目标任务和目标领域与模型预训练的数据集的相似程度而有所不同。但是在具体实践中,微调所有层几乎总是能获得更优的模型性能。

因此,在优化模型性能时,使用预训练LLM的黄金标准(the gold standard)是更新所有层(本文称之为微调方法 II)。从理论上讲,微调方法 II 与微调方法 I 非常相似。唯一的区别是方法II不会冻结预训练LLM的参数,而是对其进行微调

model = AutoModelForSequenceClassification.from_pretrained(
    "distilbert-base-uncased",
     num_labels=2
) 

# freeze layers (which we don't do here)
# for param in model.parameters():
#    param.requires_grad = False
    

# finetune model
lightning_model = LightningModel(model)

trainer = L.Trainer(
    max_epochs=3,
    ...
)

trainer.fit(
  model=lightning_model,
  train_dataloaders=train_loader,
  val_dataloaders=val_loader)

# evaluate model
trainer.test(lightning_model, dataloaders=test_loader)

(对此感兴趣的读者可以在此处[4]找到完整的代码)

上面的这段代码被用来训练一个基于DistilBERT基础模型的电影评论分类器(你可以去the code notebooks看一看[4])

1)基于特征的方法与逻辑回归:83%的测试准确率(accuracy);

2)微调方法 I,更新最后两层:87%准确率(accuracy);

3)微调方法 II,更新所有层:92%准确率(accuracy)。

这些准确率结果符合预期,即微调更多层通常会获得更好的性能,但也需要花费更高的成本。

图片

本文3种方法的训练效率和模型性能权衡经验之谈

上述场景强调了微调的三种最不同或最极端的情况:即仅训练最后一层(或几层)与训练所有层。当然,具体效果可能会因模型和数据集而异,但在探索两者之间的各种情况也是值得的。 例如,有时候我们只需训练一半的模型,就能获得与训练完成的模型相同的性能(更多关于轻量化微调(parameter-efficient finetuning)的内容将在下一节介绍)。如果你是一个好奇宝宝,下图描述了在IMDB电影评论数据集(IMDB movie review dataset)的2万个训练实例上进行微调的DistilBERT模型的预测性能(predictive performances)和训练时间。

图片

在IMDB电影评论数据集上微调的预训练DistilBERT模型的性能。相关代码可以在GitHub上找到[5]

正如我们可以看到的,仅训练最后一层是最快速的,但模型性能也最差。 符合预期的是,训练更多层会提高模型性能,但也会增加计算成本。 有一件事情非常有趣,可以看到当训练两个全连接的输出层(fully connected output layers)和最后两个transformer blocks(从左边数起的第三个block)时,预测性能(predictive performance)就已经趋于饱和。因此,在这种特定情况下(即对于这个特定的模型和数据集组合),训练超过这些层(layers)似乎是一种计算资源的浪费。

03 轻量化微调(Parameter-Efficient Fine-Tuning)

轻量化微调让我们能够重复使用预训练的模型,同时最大限度地减少算力和资源的占用。总体来说,轻量化微调具有以下五个优点:

1. 能够降低计算成本(需要更少的GPU和GPU运行时间);

2. 拥有更快的训练时间(更快地完成训练);

3. 具备更低的硬件要求(适用于较小显存的GPU和较小的内存);

4. 具有更好的模型性能(降低过拟合);

5. 需要更少的存储空间(大部分weights可以在不同任务(tasks)之间共享)。

在之前的章节中,我们了解到微调更多的层通常会带来更好的结果。上面的实验是基于一个相对较小的DistilBERT模型。但是,如果我们想微调那些几乎无法容纳在GPU显存中的较大模型(例如最新的生成式LLM模型),我们该怎么办呢?当然,我们可以使用上述基于特征的方法或者微调方法I。但假设我们想要获得与微调方法II类似的模型质量呢?

这些年来,研究人员们开发了几种技术(Lialin等人[6])来微调LLM,使其只需要训练少量的参数,也能具有较高的模型性能。这些方法通常被称为参数高效微调技术(parameter-efficient finetuning techniques,PEFT,本文亦译作轻量化微调)。

一些目前最受欢迎的PEFT技术在下图中可见。

图片

最受欢迎的轻量化微调(PEFT)技术可选择项

那么,这些技术是如何工作的呢?简单来说,都涉及到引入少量额外的参数进行微调(而不是像我们在上面的微调方法 II中那样对所有层进行微调)。从某种意义上说,微调方法 I(只微调最后一层)也可以被认为是一种轻量化微调(PEFT)技术。然而,像前缀微调(prefix tuning)、adapters和Low-Rank Adaptation (LoRA,低秩自适应))等技术,它们都“修改”了多个层(layers),以极低的成本实现了更好的预测性能(predictive performance)。

由于本文的篇幅目前已经很长了,而且这些都是超级有趣的技术,我将在未来单独介绍这些技术。

04 基于人类反馈的强化学习(Reinforcement Learning with Human Feedback)

在基于人类反馈的强化学习(Reinforcement Learning with Human Feedback,RLHF)中,使用一种结合了监督学习(supervised learning)和强化学习(reinforcement learning)的方法对预训练模型进行微调——这一方法被 ChatGPT 使用而得到大力推广,而 ChatGPT 又是基于InstructGPT(Ouyang等人[7])的。

在RLHF中,通过让人类对不同的模型输出进行排序或评分来收集人类反馈,从而提供奖励信号(reward signal) 。收集到的奖励标签(reward labels) 可以用来训练奖励模型(reward model) ,进而反过来指导LLM(Language Model)适应人类的喜好。

奖励模型本身是通过监督学习(supervised learning)来学习的(通常使用预训练的LLM作为基础模型)。接下来,使用奖励模型来更新预训练的LLM,使其适应人类偏好——训练过程使用一种被称为近端策略优化(proximal policy optimization,Schulman等人[8])的强化学习方法。

图片

InstructGPT相关论文的截图,概述了RLHF的过程

为什么要使用奖励模型而不是直接在人类反馈的基础上训练预训练模型?这是因为让人类参与模型的学习过程会产生瓶颈(bottleneck),因为我们无法实时获取反馈。

这篇文章的篇幅已经很长了,所以我把更详细的解释推迟到以后的文章中,敬请期待!

05 总结

对预训练LLM的所有层(layers)进行微调仍然是适应新目标任务(new target tasks)的黄金标准(the gold standard),但是对于预训练的Transformer模型,有几种有效的替代方法。诸如基于特征的方法、上下文学习和轻量化微调等这些方法能够有效地将LLM应用于新的任务,同时还能最大限度地减少计算成本和计算资源

此外,基于人类反馈的强化学习(RLHF)作为监督微调(supervised finetuning,SFT)的一种替代方法,可以提升模型性能。

END

参考资料

1.https://d4mucfpksywv.cloudfront.net/better-language-models/language_models_are_unsupervised_multitask_learners.pdf

2.https://arxiv.org/abs/2005.14165

3.https://arxiv.org/abs/1810.04805

4.https://github.com/rasbt/LLM-finetuning-scripts/tree/main/conventional/distilbert-movie-review

5.https://github.com/rasbt/LLM-finetuning-scripts/tree/main/conventional/distilbert-movie-review/layerwise-experiment

6.https://arxiv.org/abs/2303.15647

7.https://arxiv.org/abs/2203.02155

8.https://arxiv.org/abs/1707.06347

本文经原作者授权,由Baihai IDP编译。如需转载译文,请联系获取授权。

原文链接

https://magazine.sebastianraschka.com/p/finetuning-large-language-models

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接