0%

随着各大语言模型的开源,很多研究在没有计算资源的情况下,唯一可想的办法就是在各大开源模型上利用垂直领域的数据集来finetune开源大模型,充分利用pretrained模型的能力,而又能让它解决特定的任务。 斯坦福的羊驼alpaca模型的发布掀起了instruction tuning的一阵狂潮,国内很多工作也在模仿Stanford这个工作方法做自己领域的大模型,其实在接触这些工作的时候我一直有一个疑问,就是什么时候我们该finetune?手里有多少数据的时候你可以finetune。

如果我们要开展微调,数据以及如何组织数据形式和微调的方式是两个主要问题。

微调的方式

现有的LLM规模太大,因此完全微调模型已并非易事。因此无论学界还是业界都需要一种高效的方法在下游任务数据上训练,这就为参数高效微调(Parameter-efficient fine-tuning,PEFT)带来了研究空间。PEFT的目的是只训练一小部分参数(可以是大模型自身的,也可以是额外引入的)就能提升模型在下游任务的效果。

Scaling Down to Scale Up: A Guide to Parameter-Efficient Fine-Tuning 一文总结了现有peft的主要方法,并做了分类:

PEFT methods分类

可以看到lora也就是我们现在经常用到的微调手段被分配到了reparametrization-based里面。并且在additive这个分类里,又分了两个子类:adapter-like和soft prompts。对于每一个分类的解释可以看paper或者参考这篇知乎博客

我这里只做简单的对比:

  • prefix tuning,prompt tuning根本就是为了解决人工为每一个任务构造prompt太过于随机性,而且构造的模板有时候是离散化的;另外一个痛点就是如果对模型在下游任务上进行全参数调整,每一个任务得保存一份参数副本,对存储和训练都是考验。所以这一类方法就是让模型通过学习来自动寻找最合适的prompt,称之为soft prompt.LLM那一部分的参数不做调整,只调整添加的这一部分参数。之后清华大学提出的ptuning和ptuning v2版本,可以简单的将P-Tuning认为是针对Prompt Tuning的改进,P-Tuning v2认为是针对Prefix Tuning的改进。
  • lora这一类方法(AdaLoRA,Qlora)基于的理论是:将LLM在下游任务进行微调时发现改变的参数都是哪些低秩矩阵,所以这些方法重构了一种低秩矩阵运算,这些矩阵里面的参数就可以代表下游任务的知识,这样将LLM的预训练参数和这部分参数进行合并之后就可以适配下游任务了。

微调实践Applications

这一章节主要介绍一些值得关注的微调实践工作,可以给我们在实际工作中提供一些微调的思路,比如看看别人数据集是如何组织的,有多少的数据量,针对什么任务有了performance的提高,还有做evaluation是咋做的。

Stanford Alpaca

这是开创instruction tuning工作的鼻祖,而且斯坦福的代码写的很优秀,特别是用GPT3生成instruction-input-output那一部分,把代码通读一遍可以加深self-instruct方法的理解。而且斯坦福的这个数据集只有52k条,但是有意思的是它在组织这52k条数据的时候是需要充分保持task的多样性的,毕竟我们知道instruction tuning当时在 Finetuned Language Models Are Zero-Shot Learners 文中被提出的时候,其实是为了提高模型在unseen task上的zero-shot能力,它有一个重要的前提是finetune的task要多样,这个非常重要!

现在有一些国内的工作就是直接弄了一些数据就开始finetune,然后叫自己是instruction tuning,我觉得不太合理。

最近出了一个工作: LIMA: Less Is More for Alignment,在LLaMa-65B基础上用1000条instruction数据训练的模型,在43%的情况下,LIMA可以超过或者和GPT4平齐,这真的很厉害了,毕竟只用了1000条数据,而且作者也用斯坦福的方法复刻了52k微调llama-65B的大羊驼,发现还是LIMA优秀一点,作者猜测是因为数据集质量,这1000条数据是精心策划的。

Chatglm-6B p-tuning

基于chatglm-6B的微调项目超级多,chatglm有天然的中文优势,所以国内好多语言模型都是基于清华的这个语言模型做的工作。chatglm-6B给出的官方github repo中包含了p-tuning v2的代码, p tuning v2的原理就是将应该人工写的那一部分prompt用参数来学习,LLM预训练好的那一部分参数固定住,只更新添加的这部分参数。参考chatglm-6B自己给出的再ADGEN(广告生成的数据集)上finetuneg chatglm6B的代码: https://github.com/THUDM/ChatGLM-6B/tree/main/ptuning, 这部分代码的数据组织部分蛮有意思的,数据集长这样:

1
2
3
4
{
"content": "类型#上衣*版型#宽松*版型#显瘦*图案#线条*衣样式#衬衫*衣袖型#泡泡袖*衣款式#抽绳",
"summary": "这件衬衫的款式非常的宽松,利落的线条可以很好的隐藏身材上的小缺点,穿在身上有着很好的显瘦效果。领口装饰了一个可爱的抽绳,漂亮的绳结展现出了十足的个性,配合时尚的泡泡袖型,尽显女性甜美可爱的气息。"
}

也就是不像alpaca数据集有instruction了,只是一个映射关系,而且在main.py训练代码里也没有见到处理数据sample时要给每一个数据前加instruction,唯一加的就是在content前加了一个“问:”,在summary前加了一个“答:”。

Gorilla

这是一个微调LLaMa-7B让LLM实现用语言方式让LLM返回API调用代码的工作。

参考:

image-20230608133459274

产生数据集的方式跟alpaca如出一辙,利用了self-instruct方式,让GPT-4来产生instruction-API的训练数据。而且它分了两种模式来训练,一种是有retriever的模式,在instruction中添加了数据库里关于这个API的帮助信息,一种就是instruction-API的格式。因为repo里并没有开源这部分的训练数据,所以我们也只能看论文来猜测数据是长这样的,等作者公布了数据可以再补充这部分数据到底长什么样子。

我们在使用这个model进行推理时,输入给他的就是一串我呢本,告诉它你想获得一个怎么样的API,比如“I would like to identify the objects in an image”或者更模糊一点:“I am going to the zoo, and would like to track animals”。在zero-shot模式下这个instruction会直接给到gorilla,模型会给你返回一串API调用的代码,像这样:

image-20230608134439167

总体来说这个项目想法蛮有意思的,就是很多东西暂时还没开源,我们拭目以待吧,现在就用用就行。

让LLM适配specific的下游任务,两条线:1. 在prompt engineering上下功夫 2. Fine-tune LLM. 其实这两条线并不分家,中间也有一些技术是有overlap的。prompt engineering并不只是手动设计prompt让LLM返回更好的结果,使得其在下游任务中得以使用,一些研究并不想自己手动设计prompt,那就产生了很多自动产生prompt的方式。刘鹏飞博士的review文章Pre-train, Prompt, and Predict: A Systematic Survey of Prompting Methods in Natural Language Processing将这些技术统一到一个体系里来,分类方式也比较清晰:

characteristics of different tuning strategies

Full Fine-tune(Promptless Finetune)

Bert就是一个典型的应用,将模型在一个很大的语料库上pretrain之后,再在一些任务的数据集上对模型参数进行调整。注意这里模型的所有参数都会进行调整。

image-20230519142832464

对所有模型参数调整就带来很多问题:

  • 要维护每一个task上的模型,有一些模型的参数量都是亿级别的,这对存储是一个考验
  • finetune所有参数就需要数据集达到一定的数量级,这在特定领域不一定是可以达到的;如果没有很多数据,有可能finetune完之后还会引起perfomance的下降或者过拟合。
  • 计算资源的限制

More Efficient Ways of Tuning

或许有更合适的tuning方式,less overfitting and more efficient finetuning and inference

Prefix-Tuning

Prefix-Tuning: Optimizing Continuous Prompts for Generation

prefix Tuning

一开始理解prefix tuning其实是从“如果不调整所有参数,那么是不是可以调整部分参数”来思考这个模型的。但是看了原文之后会发现作者的思考路径其实有点不太一样。paper中说

Prefix-tuning draws inspiration from prompting, allowing subsequent tokens to attend to this prefix as if it were “virtual tokens”

lilian的博客对于这个也解释的蛮有意思:

Prompt is a sequence of prefix tokens that increase the probability of getting desired output given input. Therefore we can treat them as trainable parameters and optimize them directly on the embedding space via gradient descent, such as AutoPrompt (Shin et al., 2020, Prefix-Tuning (Li & Liang (2021)), P-tuning (Liu et al. 2021) and Prompt-Tuning (Lester et al. 2021). This section in my “Controllable Neural Text Generation” post has a good coverage of them. The trend from AutoPrompt to Prompt-Tuning is that the setup gets gradually simplified.

也就是既然我们发现in-context learning是可以促进大语言模型解决特定问题(因为我们让LLM以更高的概率输出我们想要的结果了),那么是不是可以可以把这一部分信息编码进模型参数里,从而在特定地数据集上单独训练这些参数。

所以研究者也想了一些办法如何以最小的成本为特定的任务增加一些参数,fix住预训练模型的大部分参数,而去finetune给每一个任务增加的那一部分参数。其中adapter-tuning就是一种。

QA是什么?

直观上理解,QA就是用户给出一个question,系统(模型)给出一个answer。它和reading comprehension还是不一样的,在paper中对于这两个问题做了归纳:

Comparison of assumptions

reading comprehension问题,我们的任务是从一个paragraph中找出answer:comprehend a passage of text and answer questions about its content (P,Q)-> A,也就是我们事先不仅有question,还有一段paragraph,我们只需要找出answer在这个paragrapg中的位置(span),但是对于open-domain的QA来说,我们事先是不知道anwer在哪儿的,比如上图中unsupervised QA。那么对于这一类open-domaiin的问题如何解决?最常见的就是retriver-reader架构,就是我们先从一大堆语料库中挑选出我们的paragraph,然后对于这一些paragraph,我们用reading comprehension的技术再来定位answer在哪里。上面这篇paper就是应用的这种思想,提出了ORQA,这是19年的文章,想法很典型。

ORQA

上文提到的斯坦福的数据集SQuAD是使用的最广泛的reading comprehension数据集,它的组成形式是(passage, question,answer),每一个answer都是passgae里面的一个segment。

SQuAD example

这就有它的局限性,因为有一些问题的回答是不会在passage中找到的,所以它不能用于open-domain的task,它完全是一个监督性任务的数据集,目前在这个数据集上最好的模型的表现已经超越了人类,可以说是"almost solved"。

Reading Comprehension

首先我们可以先从比较简单的问题开始解决,reading comprehension可以说是QA的一个子问题。第一步搞明白问题定义:

problem formulation

2016年-2018年,主要用于解决RC的方法是LSTM+attention的模型架构,这些模型主要有15年的attentive reader、16,17年的stanford attentive reader、17年的match-LSTM、17年的BiDAF、17年的dynamic coattention network、17年的DrQA、17年的R-Net、17年的ReadoNet。2019年开始,主要是18年Bert出来之后,大家普遍开始采用finetune BERT来解决这个问题,值得一提的是在BERT的原文paper中下游任务也使用了SQuAD来做了实验,作者用于预测start和end的方式也设计的很精巧。

stanford attentive reader

这篇文章在logistic regression的基础上(SQuAD数据集)有了巨大的进步,算是18年之前lstm+attention架构的集大成者。思想很简单,但里面很多细节,比如在编码passage的时候用了好多来源的vector进行拼接,然后再输入斤lstm中。但整体架构和机器翻译领域的发展历程是一样的,在transformer没有出现之前,大家都在lstm上做了很多创新,attention加入计算是其中一种,DrQA将多个来源的向量拼接也是一种,感兴趣的可以读一下原文:Reading Wikipedia to Answer Open-Domain Questions。作者是cs224n的QA这门课的讲师,也是一位华人小姐姐。其实我在看cs224n QA这一讲的ppt时感觉斯坦福的这门课着重讲解了stanford attentive reader这一系列的模型,不知道有没有私心。

DrQA

这之后还出现了bidirectional attention的,例如BiDAF,就是不仅计算了query2Context的attention weights,也计算了Context2Query的attention weights,将这两者和context原来的lstm值再输入进一个双层LSTM进行start,end的预测(很复杂就是了),虽然也不知道为啥这么做就会有一个不错的performance,模型架构越来越复杂,卷死了。

但不管怎么说,在transformer被大家普遍使用之前,rnn+attention的这种模式是最好的解决办法了,俗称SOAT。

Bert用作reading comprehension

自从18年开始,在reading comprehension这个任务上,谷歌出了bert,成就了新的历史,一下子F1从79.4(DrQA)升级到了91.8,人类在SQuAD上的表现才91.2,所以算是超越人类表现了。

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 这篇文章的4.2节详细介绍了预训练完成之后的bert如何在SQuAD这个下游数据集上进行finetune的。

Bert QA

上面这幅图更好的解释了Bert在finetune的时候如何做RC这个任务的。首先我们将question和paragraph拼接在一起,中间使用[SEP]连接。然后将这一串字符串整个输入进预训练好的Bert。对应的bert输出了同样长度的一连串向量,这时我们只取paragrapgh那一部分对应的向量们。第二步加了两个独特的向量,这两个向量是下游任务新增的,需要通过finetune时更新参数,分别是start S和end E向量。为了得到paragrapgh中每一个token它时span-start的概率,采用如下公式:

image-20230508171252656

同样对于每一个token成为泡影span-end的概率,也对E采用同样的计算方式。到这一步我们就得到了paragrapgh中每一个token它们成为span-start和span-end的概率。第三步如何通过这些概率值得出我们的answer所在的span?

作者的做法是对每一个candidate span都计算一个分数: \[ S*T_i+E*T_j \] 选取得分最大的那个span作为预测值。计算loss时有一点不太一样,刚刚说的是predict,loss的计算是用的负log \[ L = - logP_(start^s) - logP_(end^e) \] 这里可能会有点迷惑。其实训练数据拿来时,对于paragraph中每一个token我们都会有一个label表示它是否是start还是end,是就是1,不是就是0.那么我们在做预测的时候,上面的第三步骤已经得到了每一个token的成为start还是end的概率,那么这时候我们就能用交叉墒来计算loss了。注意这里我们用的是softmax,所以我们应该是计算softmax的loss。另外这里只会计算到真正的start和end的那两个token上的loss,因为其他token的groudtruth label都是0。

SpanBERT

这篇是在Bert基础上改进的,主要改进点在于improve了Bert在pretrain时候的两个task:1. mask 2. next sentence prediction。同样是chen的文章:(Joshi & Chen et al., 2020): SpanBERT: Improving Pre-training by Representing and Predicting Spans

To finish

SpanBert在谷歌的bert基础上又将performance提高了许多。

open-domain question answering

这个问题在Speech and language processing的第三版的14章节进行了详细介绍,它也可以叫做是information-retrieval(IR) based QA。它首先要做的是从很大的语料库中搜索出相关的passage,然后第二步运动reading comprehension的算法从这些passage中找出answer(spans of text)。从一堆语料库里找到相关的passage,这个过程称之为information retrival。

IR能想到的最最简单的做法就是将query和语料库中的每一个passage都编码成一个向量,然后计算这些向量之间的相似度,也就是score,分数越高的就是跟query越相似,那么就可以得出语料库中和query最相关的那些passage了。那么将query和passage们编码成向量有很多种方法,最简单的方法就是TFIDF,还有tfidf的变种BM25。这些方法现在已经不再采用,更多的是用Bert,俗称dense vectors,这是和tfidf这种稀疏向量相对应的叫法。

具体做法是:

ORQA

分别用两个不同的bert分别编码query和passage们,然后将query的向量[CLS]和passage的向量[CLS]点积,这个点积的结果就作为query和passage的相似度得分。上面这张图片是将retriver和reader一起训练的,当然也可以单独用query和answer训练retriver

用生成模型来做QA

由于LLM的兴起,大家开始发现用生成模型来做QA更能回答复杂的问题。比如现在的GPT系列模型。它完全摈弃了抽取信息和从passage中寻找answer所在位置的环节,有点黑科技,就好像模型将所有的知识都记到脑子里去了。但也带来了新的问题,用户没有办法知道模型是从哪里找到的答案。