GPT 解读(论⽂+TensorFlow 实现)
GPT这篇论⽂,我还是在GPT-2出来了之后,被它能续写《红楼梦》这⼀事件⽽震惊,所以才统⼀看了⼀下这两篇论⽂。这俩都是OpenAI 出的,也是⽤pretrain+fintune的套路进⾏处理。
⽂章⽬录
⼀. GPT 原理
GPT的训练分为两个阶段:1)⽆监督预训练语⾔模型;2)各个任务的微调。
1. ⽆监督pretrain
这⼀步论⽂⾥⾯⽤的是Transformer的decoder作为LM。它的⽬的是优化如下的损失函数:
对于transformer的decoder,可以简写为如下的样⼦:
熟悉Transformer的读者应该都知道,这⾥就不再赘述,不熟悉的可以看笔者之前的2. 有监督finetune
以分类任务为例,在⽤前⾯的LM得到最后⼀个timestep的输出之后,可以⽤如下的⽅式去进⾏finetune:
可以优化如下的损失函数:那么最终的损失函数就可以是优化:
可以发现,这样在进⾏finetune的时候,唯⼀需要添加的就是这个参数,因此添加的新参数量很少。
L (U )=1logP (u ∣u ,...,u ;Θ)
i ∑i i −k i −1h =0UW +e W p
h =l transformer _block (h )∀i ∈l −1[1,n ]
P (u )=softmax (h W )
n e T
P (y ∣x ,...,x )=1m softmax (h W )
l m
y L (C )=2logP (y ∣x ,...,x )
x ,y ∑1m L (C )=3L (C )+2λ∗L (C )
1W y
3. 变换到其他任务
前⾯的finetune是针对分类任务的,那么同样也可以通过⼀些变换,应⽤到其他类型的任务上,见下图:
⽐如对于Entailment任务,可以将Premise和Hypothesis打包在⼀起,⽽后⼀起经过这个transformer,进⾏编码,然后当成分类任务来处理。
对于Similarity任务,因为不像是Entailment是有序的,所以应有两种句⼦拼接⽅式,分别是Text1+Text2和Text2+Text1,这样分别经过transformer得到最后⼀个编码结果,然后逐元素相加,再进⾏最后的Linear层进⾏分类。
对于Multiple Choice任务,则将Context(包括⽂章和问题)分别与多个answer进⾏拼接,然后分别送⼊transformer,得到各个choice 的向量表⽰,最后再分别经过各⾃的Linear得到分数,⽽后经过softmax计算概率。
⼆. 实验
1. ⽆监督pretrain
作者⾸先将这⾥⽤到的数据集与ELMo进⾏了对⽐,⽤了BooksCorpus作为数据集,也有⽤到ELMo的那个数据集,但指出ELMo在进⾏LM的训练过程中,将其切分成了句⼦,并且做了shuffle,所以句⼦普遍都⽐较短。但GPT这⾥,则⽤原始的连续长句⼦进⾏训练,最后的ppl⽐ELMo在该数据集上的要低很多。
在模型上,与transformer不同的是,使⽤了GELU作为激活函数,并使⽤了可学习的position embedding。
2. 有监督finetune
作者在很多数据集上进⾏了评估,如下:
下⾯是在各个数据集上的表现:
其实笔者感觉,在作者对⽐各个不同model的时候,还是挺机智的。因为作者的模型可能会对长句⼦处理得较好,但这⾥选择对⽐的model 可能侧重点不在长句⼦上,⽽这个任务可能是长句⼦的,所以⽐较起来,还是有⼀些优势的!这其实也给了我们写论⽂⼀些启发,可以⼀些不同寻常的切⼊点,说不定你就是SoTA了呢。。
3. ⼀些分析
1. finetune层数对结果的影响
见下图:
结论就是每⼀层都有有⽤的信息
2. ZSL的表现
对于⼀些完全没有见过的任务的评估,有助于分析为什么pretrain是有⽤的,⼀种解释就是:pretrain的这个model在学习LM的时候,也⾃然学到了要评估的这些任务所需要的信息来辅助建⽴语⾔模型,这也是GPT2的切⼊点和主推的思路,可见切⼊点也是⽐较清奇。
结果如下:
3.
⼀些ablation study
decoder
结论就是:1)⼤数据集,在finetune的时候使⽤LM的obj作为辅助obj的时候,效果⽐较明显,可能是⼤数据集如果⾃⼰finetune的话,前⾯LM会产⽣灾难性遗忘,但对于⼩数据,就不会这样;2)LSTM的效果在⼤部分情况下都⽐Transformer的要差;3)pretrain很重要!
三. TensorFlow 实现
通过阅读它的源码,发现openai没有给出⽆监督pretrain具体的训练代码,只给了模型结构及预训练好的参数。同时在finetune部分,也仅仅是放出了⼀个Story Cloze任务的源码。不过,从实⽤⾓度来考虑的话,这些内容已经完全⾜够了!这⾥,笔者将按照论⽂的思路,将源码⾥⾯的内容拆分为pretrain的模型部分和finetune的模型及训练部分这两块进⾏剖析。
1. ⽆监督pretrain
前⾯提到过,这⾥pretrain的模型主要是transformer的decoder,具体代码如下:# 1. input X = tf .reshape (X , [-1, n_ctx , 2])# 2. embedding we = tf .get_variable ("we", [n_vocab +n_special +n_ctx ,
n_embd ], initializer =tf .random_normal_initializer (stddev =0.02))we = dropout (we , embd_pdrop , train )h = embed (X , we )# 3. decoder of transformer for  layer in  range (n_layer ):    h = block (h , 'h%d'%layer , train =train , scale =True )    # 4. loss lm_h = tf .reshape (h [:, :-1], [-1, n_embd ])lm_logits = tf .matmul (lm_h , we , transpose_b =True )lm_losses = tf .nn .sparse_softmax_cross_entropy_with_logits (logits =lm_logits , labels =tf .reshape (X [:, 1:, 0], [-1]))lm_losses = tf .reshape (lm_losses , [shape_list (X )[0], shape_list (X )[1]-1])lm_losses = tf .reduce_sum (lm_losses *M [:, 1:], 1)/tf .reduce_sum (M [:, 1:], 1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这是模型的整体架构,与前⾯原理部分介绍的⼀样。分为如下⼏个部分:
1. ⾸先是输⼊,将其reshape 为[-1, n_ctx, 2],这⾥的-1是batch_size * sentence_num ,即先对所有的句⼦分别进⾏encode,n_ctx 是句⼦长度,2分别代表的是句⼦中词的id,以及位置的id。
2. 接着的embedding部分,这⾥的词表⼤⼩为n_vocab + n_special + n_ctx ,前⾯的n_vocab + n_special 是正常embed的词表⼤⼩,后⾯的n_ctx 实际上是为位置id准备的,前⾯提到过,在GPT⾥⾯,位置的embedding也是通过学习来实现的,⽽不是使⽤transformer ⾥⾯的sin函数的形式。
3. 紧接着就是模型的主体部分,也即transformer的decoder部分,这⾥的block后⾯会详细说。
4. 最后是loss部分,在pretrain阶段是LM的交叉熵损失,这⾥是将前⾯transformer的输出,再乘上⼀个变换层(⽤了tie的思想,将输⼊的embedding层参数直接⽤于这⾥的变换)。这⾥的M 表⽰的是长度上的mask。下⾯来看transformer部分中block 的具体实现⽅式:
可见,就是去掉了encoder-decoder attention部分的标准transformer的decoder形式。
其中,attention计算的⽅式为:def  block (x , scope , train =False , scale =False ):    with  tf .variable_scope (scope ):        nx = shape_list (x )[-1]        a = attn (x , 'attn', nx , n_head , train =train , scale =scale )        n = norm (x +a , 'ln_1')        m = mlp (n , 'mlp', nx *4, train =train )        h = norm (
n +m , 'ln_2')        return  h
1
2
3
4
5
6
7
8

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。