最后更新于
最后更新于
本文介绍XLNet的代码的Fine-tuning部分,需要首先阅读、和,读者阅读前需要了解XLNet的原理,不熟悉的读者请先阅读。
目录
我这里是使用16GB的CPU来Fine-Tuning XLNET-Large模型,因为只是为了阅读代码和调试,所以不太关注其训练速度。
最重要的列是sentence1、sentence2和score,分别表示输入的两个句子以及它们的相似度得分。因此这个任务的输入是两个句子,输出是一个0-5的得分,这是一个回归问题。
运行的代码为(如果读者的CPU/GPU没有16GB,那么可以把batch大小减少一些:
接下来我们根据代码的执行顺序来分析fine-tuning相关的代码。
下面是fine-tuning的主要代码,结构比较清晰,请参考注释。
label_id是这个训练数据的标签,对于分类任务来说,这是一个整数ID,对于STS-B这样的回归任务来说,它是一个浮点数,比如第一个训练数据的label_id是4.2,表示两个句子的相似程度(5为最相似,0最不相似)。
接下来Estimator的train方法会调用get_model_fn函数的model_fn来构造model function。
温故而知新,我们再来看一下这个类的构造函数的参数,读者可以对比一下fine-tuning节点参数不同的地方。
xlnet_config: XLNetConfig,XLNet模型结构的超参数,比如层数,head数量等等
run_config: RunConfig,运行时的超参数,包括dropout、初始范围等等。
input_ids: int32 Tensor,shape是[len, bsz], 输入token的ID
seg_ids: int32 Tensor,shape是[len, bsz], 输入的segment ID
input_mask: float32 Tensor,shape是[len, bsz], 输入的mask,0是真正的tokens而1是padding的
mems: list,每个元素是float32 Tensors,shape是[mem_len, bsz, d_model], 上一个batch的memory。fine-tuning为None
perm_mask: float32 Tensor,shape是[len, len, bsz]。
如果perm_mask[i, j, k] = 0,则batch k的第i个Token可以attend to j
如果perm_mask[i, j, k] = 1, 则batch k的第i个Token不可以attend to j
如果是None,则每个位置都可以attend to 所有其它位置(包括自己)。fine-tuning为None
target_mapping: float32 Tensor,shape是[num_predict, len, bsz]。fine-tuning为None
如果target_mapping[i, j, k] = 1,则batch k的第i个要预测的是第j个Token,这是一种one-hot表示
只是在pretraining的partial prediction时使用,finetuning时设置为None
inp_q: float32 Tensor,shape是[len, bsz]。fine-tuning为None
需要计算loss的(Mask的位置)为1,不需要的值为0,只在pretraining使用,finetuning时应为None
上面的构造函数核心的代码其实只有一行:
在fine-tuning阶段,没有了cache,因此mlen(memory的length)为0。
如果是pretraining,使用的是two stream的Attention,这是XLNet的核心创新点。但是对于finetuning来说,使用的是和BERT类似的普通的Self-Attention。它通过调用rel_multihead_attn而不是之前的two_stream_rel_attn。
这个函数实现标准的Transformer的Self-Attenion,没有Mask,因此每个词都可以Attend to 其它任何词。和BERT的区别是位置编码的差别,这里使用的是相对位置编码。
到此为止,fine-tuning的XLNet模型的构造就介绍完成了,下面我们返回model_fn函数,看loss的构造。
我们这里调用是传入的summary_type是”last”,也就是得到最后一个Token(CLS)最后一层的输出,我们认为CLS编码了整个输入的语义。这个函数的主要代码是调用modeling.summarize_sequence。
有了CLS最后的输出,构造回归的loss代码就非常简单了。
至此,fine-tuning的代码介绍完毕。
最后我们再来对比一下XLNet的pretraining和fine-tuning的区别。
pretraining是XLNet最大的创新点,它通过不同顺序的语言模型分解方法来学习各种上下文信息。
图的左上是Content流Attention的计算,假设排列为3→2→4→1$ 3\rightarrow 2\rightarrow 4\rightarrow 1 $,它对应P(X)=P(x3)P(x2|x3)P(x4|x3,x2)P(x1|x3,x2,x4)$ P(X) = P(x{3})P(x{2}|x{3}P(x{4}|x{3},x{2}P(x{1}|x{3},x{2},x{4}\right.\right.\right.\right.\right.\right. $这种概率分解方法。
Content Stream类似于Transformer的Decoder:编码第3个Token的时候只能参考它自己的内容(第3行的Mask让它只能参考它自己);编码第2个Token的时候参考第3和第2个Token;编码第4个Token可以参考第2、3和4个Token;而编码1的时候可以参考所有的Token。
因为排列方式时随机的,所以XLNet模型能够学习各种方式的概率分解,从而可以利用各种上下文组合来编码一个Token的语义。
而在fine-tuning的时候,我们只使用Content Stream,并且Mask为全为0,这样每个Token都可以attend to其它所有的Token。细心的读者可能会问:fine-tuning的时候只用Content Stream,这和BERT还有什么区别呢?从fine-tuning的角度来说,确实是和BERT一样的——用整个输入的所有Token来编码当前Token的语义。但是因为在Pretraining的时候我们做了Mask,因此某个Attention head学到的是根据第3和第4个词来预测第1个词,而另外一个Attention head可能学到的是根据第2和第5个词来预测第1个词。
XLNet的代码分析就介绍完了,感谢关注!
显示Disqus评论(需要科学上网,有Disqus的广告)
创建于: 2020-04-11 10:57:11
目录: default
标签: 无
注意:即使batch大小为1,用XLNet-Large模型进行Fine-tuning需要16GB的内存,因此很难在普通(8GB)的GPU上训练大的模型。如果真的需要Fine-tuning,建议参考,它使用了一些技巧来减少内存,比如使用16位的浮点数,减少最大序列长度等等。相信读者了解了原始的XLNet代码再去阅读这个修改也会比较容易。下表是16GB内存的GPU可以训练的模型的序列长度和batch大小对应关系。如果出现内存不够,那么可以使用XLNET-Base模型或者减少batch大小或者序列长度,但是batch大小变小会让训练速度变慢,而序列长度变短可能会影响模型的效果。
并且这里只使用STS-B任务来介绍fine-tuning的代码。STS-B的数据是GLUE的一部分,可以使用下载,读者也可以参考。STS-B的数据示例为:
这个类负责读取训练数据,读者如果希望用自己的训练数据,则可以参考这个类。这个类和BERT的Processor是基本类似的,读者也可以参考。
它的核心代码是_create_examples函数,作用就是读取前面介绍的STS-B数据文件,完整代码在。它最终返回的是一个list,list的每一个元素是InputExample对象,比如第一个数据为:
这个函数把InputExample变成InputFeatures对象,然后使用TFRecordWriter存到TFRecord文件里。这个函数的核心部分是调用convert_single_example函数把输入字符串进行WordPiece”分词”、转换成ID,处理Segment和padding等工作。它的代码和BERT的也非常类似,完整的代码在。
这个函数的输出是一个InputFeatures对象,其中input_ids是Token的ID,如果长度不够max_seq_length,则会在开头的位置padding 0,两个句子之间用[SEP]分开,最后一个Token是特殊的[CLS]。这和BERT是相反的,BERT的[CLS]在最开头,原因在部分介绍过了。input_mask的长度为max_seq_length,值为0表示这个位置是真实的Token,而1表示这是一个Padding的Token。segment_ids表示这个Token属于哪个Segment,总共有SEG_ID_A、SEG_ID_B、SEG_ID_CLS、SEG_ID_SEP和SEG_ID_PAD五个值,分布表示Token属于第一个句子、第二个句子、特殊的CLS、特殊的SEP和padding。
这个函数定义读取TFRecord文件的operation,得到Dataset对象。然后构造Estimator需要的input function。关于Estimator的更多内容,读者可以参考。也可以参考作者的《深度学习理论与实战:基础篇》的第六章,里面详细的介绍了Tensorflow的基础知识。
XLNetModel类在的Pretraining阶段介绍过这个函数了,我们这里对比fine-tuning和pretraining不同的地方。
其余的代码在第二部分基本已经介绍过了,读者可以从开始阅读。下面我只介绍fine-tuning和pretraining不同的地方。
图:Two Stream排列模型的计算过程
Query Stream根据上下文和位置来预测当前Token:首先预测第3个位置的Token(没有任何Token可以参考);然后根据第3个位置的Token预测第2个位置的Token;再根据第3和第2个位置的Token预测第一个位置的Token;最后根据2、3和4个位置的Token预测第1个位置的Token。当然,在实际的pretraining时,前面的预测第3个位置的Token没有太大意义,因为信息量太少,所有一般只让模型预测后面的Token(比如只预测第4和第1个Token)。在前面的代码我们也可以看到,实际的Mask和上图是有一些区别的,实际的Mask我们让第3和第2个Token都可以看到它们自己(第3和第2个Token),这样让它们把第3和第2个Token的语义都编码出来。具体代码可以参考。
原网址:
System
Seq Length
Max Batch Size
XLNet-Base
64
120
…
128
56
…
256
24
…
512
8
XLNet-Large
64
16
…
128
8
…
256
2
…
512
1