你知道吗,咱们用那些已经训练好的大型语言模型,其实有好多不同的玩法。最常见的有三种:一种是用模型提取的特征来训练新的小模型;另一种是直接给模型看新任务的例子,让它学着怎么回答,这招叫做提示;最后一种就是调整模型里面的一些参数,可能是全部,或者就那么几个,来让它更好地完成任务,这就涉及到微调了。
先说说第一种,就是拿现成的模型当工具用,不用改它,就用它提取的那些信息来训练我们的新模型,比如线性分类器。第二种呢,就是我们给模型出个示范,让它看看我们想要的答案是什么样的,这样它就能学着模仿了。最后一种情况,如果我们想要更精确地控制模型,那就得动动它的参数了。
咱们接下来再细聊聊这些方法。
使用转换器(Transformers)进行分类任务
先聊聊怎么用那些已经训练好的转换器吧。咱们有几种老派的方法,比如在模型提取的特征上再训练一个新模型,或者调整模型的输出层,甚至是把整个模型的层都调整一遍。
基于特征的方法
先说说这个用特征的方法。就是咱们把已经训练好的模型拿出来,但是让它保持原样,不改它的任何设置。我们就用它来提取新数据的特征,然后拿这些特征去训练另一个模型。这个新模型啥样的都行,比如随机森林或者XGBoost,但通常简单的线性分类器效果最好。为啥呢?因为像BERT、GPT这些已经训练好的模型,它们提取的特征质量特别高,信息量也大。这些特征能捕捉到数据里的复杂关系和模式,让线性分类器能更容易地把数据分到不同的类别里。
而且,线性分类器这种东西,比如逻辑回归或者支持向量机,它们还有很好的防止过拟合的特性。这点在用高维特征空间的时候特别有用。因为咱们不需要更新原来的转换器模型,所以这个方法特别高效。还有,因为这些特征提取出来就不变了,所以咱们可以在训练开始之前就一次性算好,用在好几个训练周期里。
图1就给我们展示了一下,怎么用微调的方法让这些大型语言模型去做一些具体的任务,比如把德语翻译成英语。咱们就是拿一个在很多文本上训练好的模型,然后稍微调整一下,让它能做这种语言翻译的活儿。
图1:大型语言模型的通用微调工作流程
微调
微调呢,基本上就是让模型更好地适应咱们手头的任务。这事儿咱们一般有两种做法:一种是微调I,就是只动模型的输出层;另一种是微调II,得动模型的所有层。
微调I这招,其实跟上头说的用特征的方法有点像。就是咱们在模型上加几层输出层,但是模型的主体部分咱们还是保持原样,不动它。这样的好处是,不用把整个网络都重新训练一遍,所以相对来说,对计算资源的要求没那么高。
然后是微调II,这个就复杂点了。咱们还是得加输出层,但是这次,我们得让整个模型都参与到训练中来,包括那些之前保持不变的层。这就意味着,咱们得从头到尾都进行反向传播,这事儿可就费劲儿了,计算成本也高。不过,要是咱们手头的数据特别专业,或者模型需要更精细的调整,这种方法往往能带来更好的效果。
简单来说,微调I适合快速上手,资源消耗少;微调II呢,就是精雕细琢,虽然费时费力,但效果可能更棒。咱们得根据实际情况来选。
图2:利用预训练LLMs的三种传统方法。
咱们再看图2,这里总结了咱们刚聊完的那三种微调方法,还教了咱们一些怎么判断训练效率的小技巧。简单来说,微调II因为要动的层和参数比微调I多,所以它在反向传播的时候,需要的计算量和资源自然就更多,成本也就上去了。同样的道理,跟那个只需要动动输出层的微调I,或者是更简单、直接用模型提取特征的方法比起来,微调II的开销肯定是要大的。
上下文学习、索引和提示调整
再来说说上下文学习这个概念,这可是GPT-2和GPT-3这些大型语言模型(LLMs)带火的。上下文学习,有时候也叫零样本学习或者少样本学习,意思是咱们不需要给模型喂一大堆数据,它也能学会新任务。
图3:为上下文学习提示LLM
那上下文学习有什么技巧呢?就像图3里画的那样。这个技巧的核心就是给模型提供一些上下文信息或者例子,让它能够自己琢磨出来应该怎么完成任务。这就好比咱们给学生一些例题,他们看了之后就能理解怎么解题了。
比如说,咱们想用GPT-3这样的大型预训练语言模型来做德语到英语的翻译,而且是只用很少的样本。那就给模型几个德语句子和它们的英语翻译,让模型看看。比如:
-
德语说“Ich liebe Pfannkuchen”,英语就是“I love pancakes。”
-
德语说“Das Wetter ist heute schön”,英语就是“The weather is nice today。”
然后,就可以让模型试着翻译新的句子,比如“Wo ist die nächste Bushaltestelle?”
不过,说实话,上下文学习在某些情况下可能没有直接微调模型效果好,因为它主要是靠模型自己从训练数据里学的东西来解决问题,而不是专门针对这个任务去调整参数。
但是,上下文学习也有它的好处。比如,咱们手头没有足够的标记数据来做微调,或者不能直接改模型,只能通过界面或者API来用模型的时候,上下文学习就特别有用了。
还有一个跟上下文学习很像的概念,叫做硬提示调整。硬提示调整就是咱们不改模型里面的参数,而是改咱们给模型的提示,让它能更好地完成任务。比如,可以换几种方式来提示模型做翻译:
-
“将德语句子'{german_sentence}'翻译成英语:{english_translation}”
-
“德语:'german_sentence}'英语:{english_translation”
-
“从德语到英语:'{german_sentence}' -> {english_translation}”
硬提示调整不需要改模型的参数,所以相对来说资源消耗小,但是效果可能没有全面微调模型那么好,因为它不能让模型针对特定任务去做深入的调整。而且,找一个好的提示可能得花不少力气,有时候还得人工来比较不同的提示哪个更好。当然,也有人提出用另一个语言模型来自动生成和评估提示,这就是另一种玩法了。
另一种利用纯粹的上下文学习基础方法是LLM索引,如图4所示。
图4:LLM索引从外部文档检索信息。
在大型语言模型(LLMs)的世界里,索引就像是咱们给模型的一个小技巧,让它们能够变身成信息检索的小能手,去网上或者其他地方找信息。就像图4里展示的,索引模块会把文档或者网页拆成小块小块的。然后,这些小块会被转换成向量,就像给它们贴上标签一样,存到一个向量数据库里。
等到有人来问问题的时候,索引模块就忙起来了。它会算算用户的问题变成向量后,跟数据库里存的那些向量哪个最相似。最后,它会找出最匹配的前几个向量,用这些信息来合成答案。
这么说吧,如果咱们把信息比作图书馆里的书,索引模块就像是图书馔的目录,帮助读者快速找到他们想要的那本书。在这个情况下,读者的问题就像是图书馔的搜索词,索引模块会根据这个搜索词,找出最相关的几本书,也就是最接近的向量,然后给出答案。
参数高效微调
近年来,开发了许多方法,以更高效地适应新的预训练转换器的目标任务。这些方法通常被称为参数高效微调,写作时最受欢迎的方法总结在图5中。
图5:参数高效微调技术的主要类别,以及流行的示例
软提示调整
软提示调整,就是咱们在输入的文本变成向量之前,先加一个可以训练的参数组,咱们就叫它“软提示”。这个软提示是活的,可以根据需要调整,不像硬提示那样死板。咱们用梯度下降这种数学方法来慢慢调整这个软提示,让模型在处理特定数据集的时候表现得更好。在代码里,这个软提示就像是给输入的向量前面加了个扩展包,让模型能从更长的句子里理解更多信息。
在类似Python的伪代码中,软提示调整可以描述为
x = EmbeddingLayer(input_ids)
x = concatenate([soft_prompt_tensor, x],
dim=seq_len)
output = model(x)
其中soft_prompt_tensor
与嵌入层产生的嵌入输入具有相同的特征维度。因此,修改后的输入矩阵具有额外的行(就好像它通过额外的令牌扩展了原始输入序列,使其更长)。
前缀调整
前缀调整这个方法跟软提示调整挺像的,区别在于咱们不是只在输入前加东西,而是在模型的每个处理单元前都加个软提示。这样做的好处是,可以让整个模型在训练的时候更稳定,就像给船加了锚,不容易翻。前缀调整的时候,咱们还是用类似的方法,只是在模型的不同部分加这个软提示,让模型在处理信息的时候更灵活。
简单来说,软提示和前缀调整都是为了让模型更好地适应咱们的问题,只是它们加提示的地方和方式不太一样。软提示调整更注重在输入阶段,而前缀调整则是在模型的每个处理单元前都做点小动作,让模型整体表现更上一层楼。
与前面讨论的硬提示方法不同,软提示策略优化嵌入版本的提示。虽然在硬提示调整中我们修改离散输入令牌,在软提示调整中我们使用可训练的参数张量。
软提示调整
软提示调整的理念是在嵌入查询令牌之前添加一个可训练的参数张量(“软提示”)。然后使用梯度下降调整附加张量,以提高在目标数据集上的建模性能。在类似Python的伪代码中,软提示调整可以描述为
x = EmbeddingLayer(input_ids)
x = concatenate([soft_prompt_tensor, x],
dim=seq_len)
output = model(x)
其中soft_prompt_tensor
与嵌入层产生的嵌入输入具有相同的特征维度。因此,修改后的输入矩阵具有额外的行(就好像它通过额外的令牌扩展了原始输入序列,使其更长)。
前缀调整
前缀调整类似于软提示调整,只是在前缀调整中,我们在每个转换器块之前添加可训练的张量(软提示),而不仅仅是嵌入输入,这可以使训练更稳定。前缀调整的实现如图6所示。
图6:普通转换器块与前缀调整转换器块的比较
两个软提示调整和前缀调整都被认为是参数高效的,因为它们只需要训练添加的参数张量,而不需要LLM参数本身。
适配器方法
咱们来聊聊适配器方法,这可是跟前缀调整有亲戚关系的技巧。适配器方法也是在转换器层里加东西,不过它加的是额外的全连接层,这就像是在模型的每个处理单元后面又加了个小助手。
在原始的适配器方法里,咱们在每个转换器块的多头自注意力和已有的全连接层之后,再加上一些新的全连接层。只有这些新加的适配器层在训练的时候会更新,其他的层就保持原样,不用动。因为这些适配器层一般个头不大,第一个全连接层把输入压扁到一个更低的维度,第二个全连接层再把它拉回到原来的维度。这样一来,适配器方法就挺省事儿的,不需要动用太多的参数。
如图7所示。
图7:普通转换器块(左)和带有适配器层的转换器块的比较。
就好比咱们在足球队里加了几个新教练,只训练这几个新教练,其他的球员还是按老规矩来。这样既能带来一些新战术,又不用大动干戈,挺经济实惠的。
在伪代码中,原始适配器方法可以编写如下:
def transformer_block_with_adapter(x):
residual = x
x = SelfAttention(x)
x = FullyConnectedLayers(x) # Adapter
x = LayerNorm(x + residual)
residual = x
x = FullyConnectedLayers(x)
x = FullyConnectedLayers(x) # Adapter
x = LayerNorm(x + residual)
return x
低秩适应
再来聊聊一个挺火的微调方法,叫做低秩适应,简称LoRA。这个方法就是用一种特殊的数学技巧来重新配置那些已经训练好的大型语言模型(LLMs)的权重。低秩变换这玩意儿,就是用小的维度来近似表示大的矩阵,就像是用几块积木来搭一个大致的形状。
咱们这么想,如果有一个特别大的权重矩阵,尺寸是( A \times B ),LoRA方法就是把这个大矩阵分解成两个小矩阵的乘积,也就是( W_A )和( W_B )。这样,咱们就不用动原来的大矩阵,只训练这两个小矩阵就行。
为啥说它参数效率高呢?因为这两个新矩阵可以小得多。比如说,如果原来的矩阵是25乘以50,那参数就有1250个。但如果咱们用LoRA方法,只训练( W_A )和( W_B ),假设它们各自都只跟5个维度有关,那( W_A )就有125个参数,( W_B )有250个参数,加起来才375个参数,省了不少呢。
这就像是咱们要搬家,本来得搬一大堆东西,但用LoRA这个方法,就像是只挑了几样最重要的东西搬,既省力又省心。这样,模型在学新任务的时候,就不需要记住那么多旧的东西,能更专注地学习新知识。
咱们学完怎么更新权重矩阵之后,下一步就是写代码来实现全连接层的矩阵乘法。就像这段伪代码一样:
def lora_forward_matmul(x):
h = x . W # 这是常规的矩阵乘法
h += x . (W_A . W_B) * scalar # 这是加上我们的低秩适应部分
return h # 返回结果
这里的scalar
就像是一个旋钮,用来调节最终结果的大小。它的作用是平衡模型原来学到的知识跟新任务需要的调整。
按照最早提出LoRA方法的那篇论文里说的,用LoRA调整过的模型在一些针对特定任务的测试里,表现得比用适配器方法的模型要好一点。而且,LoRA通常还能比咱们之前聊的那种全面微调的模型(微调II)表现得更棒。
简单来说,LoRA这个方法就像是给模型一个小小的助推器,让它在新任务上表现得更上一层楼,同时还保留了它之前学到的所有知识。
通过人类反馈的强化学习
咱们聊聊怎么让大型语言模型(LLMs)更懂人心,这就是通过人类反馈来做强化学习,简称RLHF。
通常,咱们让模型适应新任务,就是用有标签的数据来告诉它怎么干。比如,用一堆标了积极、中立、消极的情感标签的句子来训练模型,让它学会情感分类。
但这还不够,咱们想让模型更贴近人类的想法,这时候就得用到RLHF了。RLHF就是让模型在人类的指导下,通过奖励和惩罚来学习。比如,ChatGPT和InstructGPT就是这么训练出来的。
在RLHF里,咱们先是用监督学习让模型有个基础,然后再用强化学习让模型更上一层楼。这就像是先教会孩子基本的对错,然后通过奖励好行为、纠正不好的行为来让他们学得更好。
那怎么收集人类反馈呢?就是让真人来看模型的回答,然后打分、排名,这些评价就成了奖励信号。然后,咱们用这些信号来训练一个奖励模型,这个模型学会了人类的喜好之后,就能用来指导LLMs的训练。
为啥要这么麻烦用奖励模型呢?因为咱们不能实时地让真人来反馈,那样太慢了,得用模型来模拟这个过程。
这个近端策略优化,就是一种强化学习方法,它能让模型在训练的时候更加精准地适应人类的喜好。这样一来,咱们的模型不仅能回答问题,还能更懂人心,给出更贴近人类思维的答案。
总结
虽然把预训练的大型语言模型(LLMs)的所有层都微调一遍,是让它们适应新任务的最好办法,但咱们本文中列举的这些方法,像基于特征的方法、上下文学习,或者用那些参数少、效率高的微调技术,这样既能达到目的,又能省不少计算资源。
本文中提及了三种常用的方法:一个是用模型提取的特征来训练新模型;一个是只微调模型的输出层;还有一个是把模型所有层都微调一遍。这三种方法各有利弊,计算效率高的,可能效果就一般;效果好的,计算成本可能就高。
另外,还有像软提示调整、前缀调整和适配器方法这样的技术,它们都能让模型在训练的时候更省事儿,不用动那么多参数。还有RLHF,也就是通过人类反馈来做强化学习,这种方法能给监督微调提供一个替代方案,有时候能让模型表现得更好。
总的来说,预训练的LLMs越来越厉害,给咱们提供了各种新机会和新策略,让它们能更好地适应各种不同的任务和领域。随着这个领域的研究越来越深入,咱们可以期待将来会有更多改进和创新出现。