0 前言
阅读经典论文是想深入任何领域都必须经历的过程,
接下来让我们看看openai的经典之作GPT2。下述内容包含很多个人观点可能存在有问题的地方,欢迎一起讨论。
文本问答、机器翻译、阅读理解等自然语言处理任务,通常是通过在特定任务的数据集上进行监督学习来实现的。本文通过将语言模型放在WebText数据集上进行训练,证明实际上语言模型并不需要明确的监督学习就可以学到这些特定任务的知识。我们给模型一篇文章,然后问它几个关于这篇文章的问题。在 CoQA 数据集上生成的答案达到了 55 F1 的分数【重要的是训练过程中模型从未见过CoQA 数据集中的数据,但是它的表现达到或超过了 4 个基线系统中的 3 个的性能。】
只有模型足够大才能实现零样本任务迁移,同时对于参数量的增加其性能会以对数线型增长。
我们的模型GPT2,拥有15亿参数模型(这个模型在2019年的时候是非常巨大的),它在WebText数据集上进行训练,这个模型通过零样本迁移在8个数据集中的7个都达到了最先进的水平,但是它在训练数据集上却是欠拟合的状态。
这实际上在当时是振奋人心的,那假如我们有一个足够大的模型是不是就可以学到它想学的一切?
并且实际上语言模型并不需要对特定任务在特定的数据集上进行训练,只需要在一个巨大的自然而然产生的文本上进行训练就可以得到一个通用的语言模型。
CoQA dataset : 一个知名的、用于测试对话式问答能力的公开数据集。使用它作为基准,意味着结果具有可比性和权威性。
F1 Score : 一个综合评估指标(最高为100),同时考虑了答案的精确度 和召回率。在阅读理解任务中,它用于衡量模型生成的答案与标准答案的匹配程度。
1 整体叙述
首先,我们来理一下他们(openai)在干嘛~
回忆一下之前的gpt1提出了一个生成式预训练模型,预训练部分用的是ROCStories 语料库(这个数据库实际上是由5句话组成的大量常识故事组成。每一段话之间前后有逻辑。)
这其实像一个模糊的想法的验证,idea是transformer这种框架能不能通过无监督学习学习到一种文字特征提取的通用模型,这个模型能在特定的任务中做微调来继续使用。如果你按照我之前提供的代码走过一边会发现gpt1虽然学会了一点东西但不多总的来说喜欢胡说八道,尤其是在说的话越来越多之后。
那我们顺着这个思路继续往下走,这个模型确实能学到一些语言特征,只不过学的不多,如果我坚信我的模型结构上没有问题,那么可能想到的两个问题是:
- 模型是不是不够大(这里的不够大是指参数不过多)
- 这个数据集是不是不够多,并且它每个sample只有5句话,能学到的逻辑太有限了
在这里再次感慨天才们真的自信、敢想、敢做并且有能力去做,他们想做一个通用的语言模型,并且认为这是可能的。
- 模型上:所以他们又一次堆叠了transformer架构,这次把模型参数堆叠到了15亿(GPT2),这在2019年是一个及其庞大的模型。
- 数据上:同时他们创建了一个名为Web Text的数据集,该数据集包含了数百万网页的内容。
那么,现在万事俱备只欠东风,即首先按照思路在庞大的数据集上训练好一个包含很多参数的模型,验证模型可以实现零样本任务迁移【这说明预言模型具有通用性】。和现有的监督学习结果做对比【这说明模型不光通用,并且结果更好】。模型在现在的数据集上还是欠拟合的状态【这说明如果算力足够大,模型还可以更大,展望一下它或许会更好】。
某种程度上来说openai预言了现在的大模型。
2 核心理念
2.1 对语言模型进行数学建模
实际上所有和数学相关的问题也都是这么做的,如果你还能想起初高中做数学题时的过程,你会发现你的老师让你仔细审题,找出给明的条件及要求解的变量,分析他们之间的关系,建立数学模型,求解。
以小见大,实际上几乎所有问题都是这样的流程,那么我们接下来先搞清楚,我们有什么以及我们要什么?
我们有什么:一个大型的语料库,大白话就是我有很多很多语句。接下来我要一般化的把它拆成变量的形式。我们把一段一段话拆开变成 ( x 1 , x 2 , x 3 , x 4 , . . . , x n ) (x_1,x_2,x_3,x_4,...,x_n) (x1,x2,x3,x4,...,xn),其中每个 x i x_i xi都是一句话,继续拆解 x i = ( s 1 , s 2 , . . . , s n ) x_i=(s_1,s_2,...,s_n) xi=(s1,s2,...,sn)你可以暂时把它理解为一个个单词,但实际上它是一个个token。
我们想要什么:我们希望模型学会说话,在面对不同的情况它知道说什么,那么实际上就是想知道 p ( x ) p(x) p(x)即 p ( s 1 , s 2 , . . . , s n ) p(s_1,s_2,...,s_n) p(s1,s2,...,sn)这样的联合概率分布,结下来的问题就是我怎么估计这个概率分布?学过概率论的话,这样的联合概率分布实际上可以分解为一个个条件概率分布的乘积:
p ( x ) = p ( s 1 , s 2 , . . . , s n ) = Π i = 1 n p ( s i ∣ s 1 , s 2 , . . . , s i − 1 ) p(x)=p(s_1,s_2,...,s_n)=\Pi^n_{i=1}p(s_i|s_1,s_2,...,s_{i-1}) p(x)=p(s1,s2,...,sn)=Πi=1np(si∣s1,s2,...,si−1)
p ( s i ∣ s 1 , s 2 , . . . , s i − 1 ) p(s_i|s_1,s_2,...,s_{i-1}) p(si∣s1,s2,...,si−1)这样的条件概率是不是很眼熟,回忆一下transformer的解码器部分在做什么?
假设输入是 ( s 1 , s 2 , s 3 , s 4 ) (s_1,s_2,s_3,s_4) (s1,s2,s3,s4),那么解码器的输出实际上是 ( p ( s 1 ∣ < b o s > ) , p ( s 2 ∣ s 1 ) , p ( s 3 ∣ s 1 , s 2 ) , p ( s 4 ∣ s 1 , s 2 , s 3 ) ) (p(s1 | <bos>),p(s2 | s1),p(s3 | s1,s2),p(s4 | s1,s2,s3)) (p(s1∣<bos>),p(s2∣s1),p(s3∣s1,s2),p(s4∣s1,s2,s3)),忘记的小伙伴可以回头看前面的内容,那loss是什么呢, L = − ∑ i = 1 n l o g p θ ( s i ∣ s < i ) L=−∑_{i=1}^nlog p_θ(si∣s<i) L=−∑i=1nlogpθ(si∣s<i)
问题建模好之后,接下来的问题就是怎么处理不同任务。
2.2 任务处理
任务条件化的两条经典技术路径:
- 架构层面的任务条件化:为不同任务定制专属模块
- 核心思想:在模型硬件结构上做文章,为不同任务设计不同的子网络(编码器/解码器),并通过某种即值进行切换或组合。
- 典型代表:Kaiser et al.,2017(One Model To Learn Them All)中提出的"任务特定编码器/解码器"[这个后面有机会也会出一篇]
- 工作原理:模型会共享一个核心主干网络,学习通用特征;同时针对每个任务T,都有一个轻量级的、与之绑定的任务特定适配模块(如一个小型的编码器或解码器);当处理任务T时,输入先经过共享主干,然后被路由到任务T的适配模块进行处理,最后产生输出。
- 算法层面的任务条件化:在"学习过程"中动态适应
- 核心思想:模型结构是单一的固定的。任务条件化是通过动态调整模型参数本身来实现。而不是切换模块,是"弯曲"参数。
- 典型代表:Finn et al.,2017 提出的 MAML :Model-agnostic meta-learning for fast adaptation of deep networks
- 工作原理:它的更新方式实际上有点像强化学习。
- 内循环 :对于一个新任务
T_i,模型从一组"元参数"θ开始。用该任务的少量数据对θ进行几步梯度更新 ,得到针对该任务优化后的参数θ_i'。这个过程就是"快速适应"。 - 外循环 :评估
θ_i'在该任务上的表现(类比价值函数)。然后,关键的一步来了:根据这个表现,去更新最初的"元参数"θ。目标是让θ具备这样一种属性------从它出发,对任何新任务只需内循环的几步更新就能达到好性能
- 内循环 :对于一个新任务
对于上面两中方法而言,对于这类多任务的问题建模实际上是下面这样的
tex
任务:[翻译英文到中文]
输入:Thank you.
输出:谢谢你。
而现在的通用大模型,对于多任务的问题建模是下面这样的
tex
输入:将 Thank you 翻译成中文.
输出:谢谢你。
3 训练数据集
事实上在WebText之前就已经有人收集了大量的网络文本数据,但是质量不高,其中存在一些不和逻辑的数据,会大大影响模型的学习。
实际上WebText数据集强调的就是质量,那这些质量高的文本或对话从哪里来呢?
以Reddit这一社交媒体平台为起点,爬取了所有获得至少3个"点赞"的外链内容。
4 输入
一个通用的语言模型应该具备输出人以字符串概率的能力。现有的大语言模型包含字母小写化、词元化、处理未登录词等预处理步骤,这些步骤限制了模型可处理的字符串范围。管字节级模型在理论上更"通用"、"优雅",但在当时的大规模数据集上,其实际性能却不如传统的词级模型。作者在WebText上也观测到了相同的现象。
预处理具体带来的限制案例:
- 将所有文本统一转换为小写字母,导致它无法知道"Apple"(苹果公司)和"apple"(水果)的区别。
- 词元化,会将文本拆分成更小的单元(词元),例如子词(如 "un+able")。模型处理的不再是原始的字符序列,而是这些词元。如果某个字符串无法被词表完美地分割或表示,模型就可能无法处理它。
- 未登录词处理,设定一个固定的词表。遇到词表中没有的词元(如罕见词、拼写错误、特殊符号组合)时,用一个特殊的标记(如
<UNK>)来代替。导致任何被替换为<UNK>的字符串,其原本的形态和信息对模型来说都丢失了。
举一个例子来说明文章是怎么做的:
假设现在我们有一句话:
"The dog! chased a quokka."
- UTF-8 bytes转成 byte-level 序列:
b'T', b'h', b'e', b' ', b'd', b'o', b'g', b'!', b' ', b'c', ... - 初始化 byte vocab:vocab size = 256,每个 byte 是一个 token
- BPE 合并:
- 高频序列 :
"the "→ 一个 token"dog"→ 一个 token
- 低频序列 :
"quokka"→ 每个 byte 单独 token
- 防止跨类别合并 :
"dog!"不会变成"dog!"作为 token"chased a"中空格可以 merge →"chased "+"a"
- 高频序列 :
- 生成 token 序列:
["The ", "dog", "!", " ", "chased ", "a", " ", "q", "u", "o", "k", "k", "a", "."]
5 模型
模型只是在OpenAI GPT model (Radford et al., 2018)的基础上做了一点小小的改动,
- Layer Normalization
- Pre-norm:每个子块输入先做 Layer Norm
- Final norm:在最后 self-attention block 后增加额外 Layer Norm
- 效果:训练更稳定,尤其是深层网络。
- Residual Layer 权重缩放
- 为防止残差路径累加导致数值过大,初始化时对每层残差权重乘 1/√N,N = 残差层数
- 效果:防止梯度爆炸,深层训练更稳。
- 上下文和 batch
- context size:从 512 → 1024 tokens → 捕获更长上下文
- batch size:512 → 梯度更平滑,训练更稳定