0%

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所在位置的环节,有点黑科技,就好像模型将所有的知识都记到脑子里去了。但也带来了新的问题,用户没有办法知道模型是从哪里找到的答案。

在Neural Language Generation的任务中,如何在每一个时间步产生一个token称为decoding method.最常见的decoding方法就是用softmax激活函数计算概率分布之后,将概率最大的那个token列为当前的预测值(most likely string)。这种argmax的方式在machine translation等non-open-ended的任务中表现还可以,但是如果是在纯粹的开放性的任务中,比如写一首诗歌。这种方式会造成重复性的输出,并且输出的句子有时候连续性也比较差。

这时候很多研究工作就对decoding strategic展开了研究,比如beam search,基本原理很简单,就是从取概率排名前k个token作为当前预测值。但是这还是会有一个问题,就是当我们概率分布均衡的时候,这个方法可以,但如果概率分布不均衡,也就是softmax计算出来的概率值只有几个token的概率比较大,其他概率都非常小,比如零点几,那这个时候其实我们不太需要考虑k个单词,只需要考虑概率比较大的那些tokens就够了。

Top-p(Nucleus Sampling)

所以这时候就有人提出了Top-p(nucleus) sampling的方法The Curious Case of Neural Text Degeneration

具体做法就是维护一个动态的k值,这个k值随着softmax的输出概率分布决定,

image-20230421134555223

作者的思路就是当概率分布比较flat的时候,我们应该把sample的池子定的大一点。但是当概率分布比较陡峭,也就是上图中的第三种情况时,我们就需要把这个sample的池子变得小一点。具体是如何操作的呢?

image-20230421134938176
image-20230421134952769

看公式可能会比较复杂,具体做法就是提前预设一个阈值p,然后对于某个时间步的概率分布P,我们寻找一个最小的top-p的一个token池子,这个池子里所有的token的概率值加起来要大于p,注意这里是找一个smallest set,也就是我们在找这个token池子时,从概率最高的token往下捋,一直到概率和大于p。找到这个池子之后,我们将原来经过softmax函数计算之后的概率分布按照上图中(3)的公式重新计算得出一个新的概率分布。最终我们得到的概率分布不属于我们之前找的token池子里的token的概率全部置为0,至于在这个token池子里的token的概率值会除以这个池子里所有token概率值的。然后我们从这个分布中去sample我们的预测token。

Sampling with Temperature

Temperature这个概念在GPT中也有,不同的温度值,你会得到不同的结果。温度值越低,它会对自己的输出结果更自信,而温度值越高,它会降低模型的确信值,也就会返回更多的结果给你。Blog对上面提到的The Curious Case of Neural Text Degeneration文章进行了解答,但我发现有一点和paper中不一样的是:

image-20230421144452222

temperature值t并不是取值是[0,1),stanford224n的课件以及博客里对t的取值是可以大于1的:

image-20230421144619190

在博客中我们可以看到作者对于t值大于1和小于1画出的图的区别:

image-20230421144711636

nlp领域很多新出现的名词或者火热的研究方向,没有一个统一的标准。我在接触这些新的概念的时候往往会很糊涂,需要找大量的文献来看,然后捋清楚模型或者技术路线的发展脉络。instructed LM,它是需要对pre-trained LLM进行finetune的,在这之前也有一种技术叫做prompt engineering,它是一种给大模型指令输入的手段,通过调整给大模型的输入,从而使得大模型能够返回更好的输出,解决我们的问题。也有更好的解释引用自blog

Prompt Engineering, also known as In-Context Prompting, refers to methods for how to communicate with LLM to steer its behavior for desired outcomes without updating the model weights. It is an empirical science and the effect of prompt engineering methods can vary a lot among models, thus requiring heavy experimentation and heuristics

prompt engineering得益于LLM拥有zero-shot learning和few-shot learning的两种prompt 模型的方法的发展。它更多的来源于经验。

prompt engineering领域也出现了非常多的文章,就正如blog里的观点一样,我同样觉得有一些文章只需要很少的文字就能讲明白它提出的方法是什么,但还是花了很多的篇幅,一个通用的benchmark才是我们需要的,现在有的只是一些零零碎碎的方法论。prompt engineering不是我的关注重点,它受制于很多因素的影响,比如如果你使用的是GPT-3模型来开展你的任务或者搭建你的application,你可能会因为输入过多的文字而超出limit,而且GPT可是按照字符数收费的,所以可能会比较贵。

那么除了使用prompt engineering的方式来让LLM输出能让我们满意的结果,另外一种方式是fine-tune整个LLM,直接让它在特定的数据集上调整参数(整体调整或者局部调整,比如Lora,prefix-tuning)或者使用增强学习训练一个打分模型,这也属于fine-tune的一个大分支。

2013年的综述文章A Survey of Large Language Models 在第五章介绍了详细的adaptation tuning of LLMs的方法,也就是我一个pretrain好的LLM,如何让它在不同的任务上得到更好的泛化能力,这时候就要tuning LLM。作者介绍其中有两种方法,一个是instruction Tuning,第二个是alignment tuning。后者就是利用增强学习让模型从人类的反馈中去改进自己生成的文本,InstructGPT采用了这种方法。第一种会稍微复杂一点,但原理很简单,就是创造一系列的instruction和问答对,让LLM在这些新instruction上重新finetune,loss为sequence-to-sequence的loss。

[My personal spicy take] 这里这篇综述我觉得写的不完整,有点误导读者。这篇综述第五章只介绍了adaptation tuning模型中的两种,但在instruction tuning出现之前,还有不少技术能够帮助我们“further adapt LLM according to specific goals”. 不仅如此,这篇综述也没有很好的解释instruction tuning为什么就能帮助我们在不同任务上有了performance的提高。所以我就想写一篇博客来记录如果我们拥有了一个pretrained的大模型,我们可以有什么样的做法来使得大模型在特定的任务上为我们所用。详见另一篇博客“Adaptation Tuning of LLMs”

在接触羊驼模型后,我一直有一个疑问,为什么instruction finetuned模型performance有了提高,或者说它在什么样的任务上有了提高?这个问题一直困扰我,直到我看到了google家的Finetuned Language Models Are Zero-Shot Learners.instruction tuning这种finetune方式的提出是为了improve zero-shot performance on unseen tasks,具体一点就是在一些任务上比如阅读归纳,question answering和语言推理上,研究者发现GPT3的zero-shot learning比few-shot 能力差很多,作者说一个潜在的原因是因为如果没有一些context给到模型的话,模型在面对跟pretrain时候数据相差很大的prompt时候会很困难,说直白点,就是没有例子给它参考了,就不会做题了。instruction tuning这种方式就提供了一种非常简单的方式,它在好多个task上finetune这个模型,这里每一个task的数据组织形式跟原来不一样了,现在被组织成了(instruction,[input],output)的形式。finetune完之后的模型在unseen task上做evaluation,研究者发现被instruction finetune之后的模型比原来的模型在同一任务上的zero-shot能力大大提升:

performance

instruction tuning

想要做到instruction tuning有两个前提条件:1. 你有一个pretrained的模型 2. 有很多instructions。首先第一个条件可以看看市面上有哪些模型是已经开源了,参考A Survey of Large Language Models3.1的整理,2023年斯坦福的羊驼模型是基于meta的LLaMA,所以目前github上出现了很多用LLaMA为LLM,在上面做instruction tuning工作的。

LLMs

那第一个问题解决了,起码我们有开源的LLM可以load到本地来使用,感谢facebook的开源。第二个问题如何产生很多的instructions,斯坦福的羊驼模型Alpaca采用的是下面文章介绍的方法,省时省力,花费上不超过600美金。当然也有其他的一些产生instruction的方法,详细可以参考A Survey of Large Language Models ,其中作者介绍了一系列可以从现有数据集生成instruction的方法,这些方法应该也是低成本快速产生instruction的方法。


Self-instruct: Aligning Language Model with Self Generated Instructions 这篇文章介绍了一种self generated instructions的方法,简单说就是让LLM自己生成人类的问题的答案,然后将这些instructions 重新来fine-tune我们的LLM。这样做的一个前提条件是:1. Large “instruction-tuned” language models (finetuned to respond to instructions) have demonstrated a remarkable ability to generalize zero-shot to new tasks. 2. 产生instruction data非常的耗时,原来都是采用Human written的方式。具体步骤是:

self-instruct whole picture

作者首先使用175个手工写的instructions作为seed set,利用这175个instructions用LLM再次生成更多的instructions,将这些instructions再次输入到LLM中我们就得到了很多input-output pair。这些input-output pair将会用来做instruction tuning. 作者使用的LLM是GPT-3. 最终得到了52k个instructions,以及82k个input-output pair。

Instruction generation

用bootstrap的方式,以人工产生的instruction为基础,用GPT来自己生成更多的"new and novel"instruction。


自Alpaca之后,国内的一些团队也仿照斯坦福的这种模型,做了一些自己的LLM,例如https://github.com/LC1332/Chinese-alpaca-lora,instruction来自用GPT翻译的斯坦福产生的52k的instruction的数据,它基于的模型aplaca-lora,lora的全称是Low-rank adaptation,作者说自己"reproducing the Stanford Alpaca results using low-rank adaptation (LoRA).",并且训练好的instructed model提供的文本质量可以和text-davinci-003(GPT-3)媲美。不太了解这个LoRA,有兴趣的可以读原文:https://arxiv.org/pdf/2106.09685.pdf。

看了Alpaca的blog,我发现斯坦福在evaluation阶段是将alpaca的结果和gpt3来进行比较的,由此也引发了我的思考,就是我们如何去衡量一个LLM的performance。刚上文的review的第七章很好的解答了我的疑惑,包括一系列的基本评测任务以及高级的评测任务。当然作者在7.3也给出了一些公开的全面的benchmarks,而且是用的比较多的,其中有MMLU,BIG-bench,HELM,这些benchmark内都包含了很多个任务,可以综合评测一个LLM的performance。

stanford alpaca

这是2023年斯坦福开源的一款基于meta的LLaMA的大语言模型,名字叫羊驼,只有7个billion的参数。属于instruction tuning的一个标杆。里面用了两个比较新的技术,第一个是上文提到的self-instruct,就是让GPT或者市面上的LLM在我们人工产生的种子instruction上去产生一系列更多的instruction,包括配套每一个instruction的input和output。斯坦福将这部分用GPT-3.5(text-davinci-003)产生的instruction数据慷慨开源,见github。不仅如此斯坦福还给出了产生这些instructions的代码,可谓是非常nice了,方便大家上手学习。

我比较关注用这些instructions数据如何finetune大模型LLaMA的过程,这里权当自己复现以及阅读斯坦福代码时候的记录。首先我本来是想在meta的LLaMA的7B开源模型上做实验,但发现想获取meta的weights需要提前申请,详细可参考huggingface的transformer页面。

斯坦福的代码仓库可以在github找到。

reference

  1. Self-instruct: Aligning Language Model with Self Generated Instructions
  2. Scaling Instruction-Finetuned Language Models
  3. Finetuned Language Models Are Zero-Shot Learners