美文网首页机器学习与数据挖掘
(九)再谈embedding——bert详解(实战)中

(九)再谈embedding——bert详解(实战)中

作者: 天生smile | 来源:发表于2018-12-08 16:05 被阅读3063次

        Google开源的BERT的确很良心,代码写得非常好,是一个不错的学习案例,这里我从实战的角度从预训练到下游任务实战做一个全面的梳理。原理部分的讲解请参考我上篇博客

这里简单说下环境:

    tensorflow 1.11.0(用1.4版本的朋友建议还是装个CUDA9.0做个升级吧,我想google之后的开源应该都是基于这个版本的,所以为了能看到原汁原味的代码,还是妥协下吧!)

    我也尝试过pytorch的版本,但毕竟不是官方的代码,从学习的角度建议还是用tensorflow的。

Part1:模型预训练

1.数据准备

官方给的数据形式是这样的:

英文数据

这里也贴一份中文样本数据:

段和段之间用空格隔开,如果你的语料不是这种格式,需要事先进行处理,英文的官方使用的是NLP的 spaCy工具包进行的句子切割,中文的话,根据标点符号切就行啦。

2.生成Vocab

    根据语料生成词典,这里注意加上以下字符,如果是中文就分个词

3.运行create_pretraining_data.py,关于这个代码,我在这做个梳理吧,虽然没啥原理性的东西,但万事开头难,NLP的数据处理永远是至关重要的一步。

这个程序是用来生成tfrecord的,另外论文中提到的所有数据处理过程都在这个程序中。

- 创建tokenizer,很多人也许会困惑这个啥,这是Google AI Language Team写的一个字符处理的工具,按照代码里的使用就行。

-逐行读入数据:

官方代码这里是这么处理每一行英文数据的,实际上可以简单理解为做了个分词操作吧。

接下来就是将数据转换成任务需要的形式了,也就是文章中的两个任务masked lm,next sentences prediction。

        - 对于一份数据,可以每次将masked 设定的位置都不一样,也就是可以做个数据扩充,代码中的dupe_factor就是将数据重复多次进行处理。

最终的数据的结果,我们可以打印几条看看:

至于处理规则这里就不讲了,论文里对如何做mask和生成next也讲的比较详细 。

3.模型预训练

这个部分主要梳理下模型预训练的代码,即run_pretraining.py。

        首先准备一个config文件夹,里面放bert_config.json文件,这个文件最好在刚才进行数据预处理的时候就生成以下,内容可以参考官方提供的预训练模型的内容。

- (1)设置run config

tf.contrib.learn.RunConfig用于管理Estimator运行的控制信息,代码中用的*.tpu.RunConfig,主要是还有些tpu的设置,这里主要设置好checkpoints_steps和output_dir就行。

- (2)建立模型model_fn_builder,主要是建立好里面的model_fn的回掉函数,里面的内容我后续会说明。

- (3)建立estimator API

- (4)建立input_fn_builder回掉函数,核心是里面的input_fn(params)回掉函数,params是固定参数,是一个词典,里面有batch size(创建estimator 传入)等参数。

上面这5步几乎是靠回调函数完成的,可读性并不是很好,我下面会具体说一下细节。

注:train的第一步就是调用input_fn,读取record,并产生一个batch的数据。

然后就进到model_fn中创建模型,传入的fearure数据如下:

接下来就是创建模型:

模型里面的内容如下:

- embedding:word,position,token type embedding(将这三个embedding相加),得到最后的embedding_output

- encoder : 

        - 根据input创建mask  函数:create_attention_mask_from_input_mask

        - transformer_model 这个部分应该算整个代码的核心了,代码并不难,主要看看是不是论文里讲的那样:

masked忽略

               - self-attention

                - 残差+layer-norm(黄色框)

这里比论文里多了一层线性层(红色框)

                - Feed forward layer(intermediate)

                 - 残差+layer-norm

- 计算loss

先来看第一个get_masked_lm_output

    - get_masked_lm_output中的gather_indexes函数就根据positions(masked的位置)从transformer的输出层里把相应的step给挑出来,例如这个句子masked了20个词,那么输出的唯独就是[64,20,96],再接着做一个非线性变化+layer_norm(貌似论文里这块没有讲)

接下来就是全连接层了,这块做法和CBOW一样,直接乘最开始初始化的embedding矩阵:

最后的输出的概率值(64*20,749):

注意这里20是指mask的最大个数,不一定20个词全mask了,所以要配合label_weights一起算

再看next_sentence_example_loss

这里注意BERT做句子层面的分类,都是用的0step的[CLS]标签,这里的get_pooled_output是0_step隐藏层接了一个非线性变化的结果:

句子层面的分类比较简单,借一个线性层就行啦。

最后把两个部分的loss加起来就行了

至此BERT的预训练就梳理到这里。

相关文章

网友评论

    本文标题:(九)再谈embedding——bert详解(实战)中

    本文链接:https://www.haomeiwen.com/subject/cqcxhqtx.html