使用和微调预训练transformers
使用和微调预训练的大型语言模型(LLMs),常见的方法可以归结为三种:基于特征的方法、上下文提示,以及更新模型参数的子集。

大多数预训练的LLMs或语言转换器,其实不用额外微调就能直接上手。比如,你可以用基于特征的方法:把预训练模型生成的嵌入当作特征,拿去训练一个新的下游模型,比如一个简单的线性分类器。另一种思路,是在输入里直接塞进新任务的例子——相当于把期望的结果直接展示给模型看,不需要对它做任何更新或学习。这个概念,业内通常叫“提示”(prompting)。当然,最后一种办法,就是微调所有参数,或者只微调一小部分,来达成你想要的效果。
01、用Transformers做分类任务
先从最传统的方式聊起,看看怎么利用预训练的Transformers。主要分三条路:提取特征嵌入来训练另一个模型、微调输出层,以及微调所有层。
基于特征的方法
所谓基于特征的方法,就是把预训练模型加载进来,然后“冻结”它——也就是说,我们不会动它任何一个参数。这个模型,本质上就成了一个现成的特征提取器。我们只需要在这批嵌入之上,训练一个下游模型就行。这个下游模型可以是随你喜欢的(随机森林、XGBoost都行),不过实践下来,线性分类器的表现通常是最稳的。
原因也不难理解:像BERT、GPT、Llama、Mistral这类预训练Transformer,本身已经从输入数据里榨出了高质量、信息量巨大的特征。这些嵌入,往往已经把复杂的关系和模式捕捉得很到位了,所以线性分类器稍微一划拉,就能把数据分得明明白白。
另外,逻辑回归、支持向量机这类线性模型,通常自带很强的正则化能力。这在高维特征空间里特别重要——能有效防止过拟合。而且,这种方法在计算上也是最省事的,因为根本不需要更新Transformer。更妙的是,如果你要跑多个训练周期,可以事先把训练数据集的嵌入一次性算好存起来(反正它们不会变),后面直接用就行。
图1展示的是LLMs通常的“出生”路线:先在通用文本语料上预训练,然后针对下游任务(比如德语翻译成英语)微调一下。
微调
微调预训练LLMs的传统路子,无非两种:只更新输出层(咱们叫它微调I),或者更新所有层(微调II)。
微调I跟前边说的基于特征的方法有点像,区别在于它是在LLM本身上再加一层或多层输出层。模型的主干部分依然冻结不动,我们只去更新新加的那些层。因为不需要在整个网络里反向传播,所以它在吞吐量和内存占用上还算经济。
微调II就彻底多了:同样先加载模型并加上输出层,但它的反向传播会贯穿所有层——也就是说,整个模型都要跟着更新。这无疑是最耗资源的一种方法。但话说回来,虽然计算成本最高,它通常也能带来最好的建模或预测性能。尤其是处理那些非常专业的领域数据集时,这个优势就更明显了。
图2把上面三种方法做了一个总结。顺带提一句训练效率的“经验法则”:微调II涉及更多层和参数,反向传播成本自然最高。微调I次之。最简单的,还是基于特征的方法。
02、上下文学习、索引与提示调整
像GPT-2、GPT-3这样的LLMs,把“上下文学习”这个概念推到了台前。在这个语境下,它通常被叫作零样本或少样本学习,如图3所示。
上下文学习的核心思路,就是在输入或提示里,给模型提供任务的上下文或示例,让它自己推断出该怎么做,然后生成合适的回答。这招充分利用了模型在预训练阶段从海量数据里学到的本事——因为那些数据本就覆盖了五花八门的任务和背景。
顺便提一嘴,少样本学习的定义,通常就归在上下文学习方法这个门类下。
举个例子,假如你想拿GPT-3来做德语-英语的少样本翻译。操作很简单:给它几个例子,帮它理解任务就行——
将以下德语句子翻译成英语:
例1:
德语:“Ich liebe Pfannkuchen.”
英语:“I love pancakes.”
例2:
德语:“Das Wetter ist heute schön.”
英语:“The weather is nice today.”
翻译这句话:
德语:“Wo ist die nächste Bushaltestelle?”
一般来说,上下文学习在某些任务或特定数据集上的表现,是不如微调的。因为它完全依赖预训练模型“硬扛”——不针对手头任务调整任何参数,只能靠训练时积累的泛化能力。
但它的优势也恰好在这里:当微调所需的标注数据很少或者压根没有的时候,它尤其管用。而且,如果你不方便直接访问模型,只能通过UI或API跟它打交道,那上下文学习就给了你一个快速试错的方式。
跟它相关的,还有一个概念叫“硬提示调整”。前面说的微调方法,是通过更新模型参数来适应任务;而硬提示调整,目标是优化提示本身——模型参数不动,但靠调整提示来提升效果。依然拿德语-英语翻译来举例,为了优化提示,我们可以试几种不同的模板:
- “将德语‘{german_sentence}’翻译成英语:{english_translation}”
- “德语:‘{german_sentence}’ | 英语:{english_translation}”
- “从德语到英语:‘{german_sentence}’ -> {english_translation}”
提示调整是参数微调的一种“平替”,资源消耗小得多。但它的表现通常不及完整的模型微调——毕竟它不动模型的参数,也就限制了适应任务细节的能力。而且,提示调整有时需要人工参与来比较不同提示的优劣,或者用其他类似的法子来帮忙。这也是“硬提示”这个名字的由来——因为输入标记本身是不可微的。当然,也有人提出可以用另一个LLM来干“自动提示生成和评估”的活儿。
除了纯上下文学习,另一种选择是LLM索引,如图4所示。
在LLMs的语境里,我们可以把索引看作一种变通方案——它让LLMs能跟已有的数据源对接起来。在图4中,索引模块先把文档或网站拆成小段,然后把这些小段嵌入成向量,存进向量数据库。等到用户提交查询时,索引模块会计算查询嵌入跟库里每个向量的相似度,最终挑出最相似的前k个嵌入,拼成回答。
LLM索引,其实是一个“框架”或“流程”的总称,专门用来把LLMs跟外部数据源连起来。它最具体的例子,就是检索增强生成(RAG)。
03、参数高效微调
最近几年,冒出了不少更高效的调法,专门用来让预训练Transformer适配新的目标任务。这些方法一般被叫作“参数高效微调”。目前最流行的几种,按时间线总结在图5里了。
前面讲的是“硬提示”,那相比之下,“软提示”策略就不同了——它优化的是提示的嵌入版本。硬提示改的是离散的输入标记,而软提示用的是可训练的参数张量。
软提示调整
软提示调整的思路很直接:在嵌入的查询标记前面,加上一个可训练的参数张量(也就是“软提示”)。然后,通过梯度下降来调整这个前置的张量,让它在目标数据集上的表现越来越好。用类似Python的伪代码来说,就是:
x = EmbeddingLayer(input_ids)
x = concatenate([soft_prompt_tensor, x], dim=seq_len)
output = model(x)
这里的soft_prompt_tensor,跟嵌入层输出的特征维度是相同的。所以,改过之后的输入矩阵会多出几行——相当于原始输入序列被“拉长”了几个额外的标记。
前缀调整
另一种很火的提示调整方法,叫“前缀调整”。它跟软提示调整有点像,但区别在于:前缀调整把可训练的张量(软提示)加到每一个Transformer块里,而不只是嵌入的输入层。这么做,训练会更稳定一些。它的实现伪代码如下:
def transformer_block_with_prefix(x):
soft_prompt = FullyConnectedLayers(soft_prompt) # Prefix
x = concatenate([soft_prompt, x], dim=seq_len) # Prefix
x = SelfAttention(x)
x = LayerNorm(x + residual)
residual = x
x = FullyConnectedLayers(x)
x = LayerNorm(x + residual)
return x
目录1:修改为前缀调整的Transformer块
这段代码可以分为三个部分:先将软提示通过全连接层处理一下;然后把处理后的软提示跟主输入x拼在一起(拼接维度是序列长度维度);最后的那些行,就是Transformer块的标准操作了——自注意力、层归一化、残差连接,以及前馈神经网络层。
图6展示了普通Transformer块和加前缀调整的Transformer块之间的区别。
举个例子,如果A=25,B=50,那ΔW的尺寸就是25×50=1250。如果h=5,那么WA有125个参数,WB有250个参数,两个矩阵合在一起也才375个参数。
学完权重更新矩阵后,全连接层的矩阵乘法就可以写成:
def lora_forward_matmul(x):
h = x . W # Regular matrix multiplication
h += x . (W_A . W_B) * scalar
return h
目录2:使用LoRA的矩阵乘法
在这个公式里,scalar是一个缩放因子,用来调整组合结果的大小——也就是原始模型输出加上低秩调整的那部分。这起到了平衡预训练模型知识和新任务特定调整之间关系的作用。根据原始论文的说法,用LoRA的模型在好几个任务特定基准测试上,表现都比之前的适配器方法稍好一些。甚至有时候,LoRA的效果还能超过前面说的微调II。
04、通过人类反馈进行强化学习
前面聊的都是怎么通过微调,让LLMs的“建模性能”变得更好。现在,咱们换个方向——怎么通过微调,让LLMs跟“人类偏好”更合拍?
传统的方法是监督学习——带着标签的目标数据直接上。比如用微调II,拿一个带情感标签(积极、中性、消极)的文本数据集,把预训练LLM调成情感分类器。
监督微调是训练LLM的基础步骤。但更进阶一步,是使用“人类反馈的强化学习”(RLHF)。它能进一步提升模型跟人类偏好的一致性。ChatGPT及其前身InstructGPT,就是两个典型的例子——它们都是用了RLHF来微调预训练的GPT-3。
在RLHF里,预训练模型是监督学习和强化学习的结合体。这种方法最初由ChatGPT模型推广开来,而ChatGPT又基于InstructGPT。做法是:让人类对模型的不同输出进行排名或打分,收集反馈,作为“奖励信号”。这些奖励标签可以用来训练一个“奖励模型”,然后拿这个奖励模型去引导LLMs适应人类的偏好。奖励模型本身是通过监督学习学出来的——通常也用预训练LLM做底子。之后,再拿它通过一轮额外的微调(用的是强化学习的一种变体,叫近端策略优化),来把预训练LLM调整得更符合人类口味。
为什么不直接用人类反馈来训练预训练模型,非要兜个圈子用奖励模型?原因很简单:实时拉人类进来做裁判,太慢了,是个节奏瓶颈。
05、调整预训练语言模型
虽然微调预训练LLMs的所有层,仍然是适应新目标任务的“黄金标准”,但前面这些替代方案,各有各的妙用。基于特征的方法、上下文学习、参数高效微调……它们能在不同的任务和数据限制下,帮你用最少的计算成本和标注成本,把预训练LLMs的最大价值榨出来。
写在最后
我们还聊了人类反馈的强化学习(RLHF)是怎么跟监督学习联手,进一步提升预训练LLMs性能的。每种方法都有自己的权衡和适用场景,所以选哪个,关键还是看任务的性质、你手头的资源,以及对性能的要求有多高。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名