说明:本页所有链接均正确无误,如果发生404或者反应缓慢,是由于众所周知的原因,自行解决。

本章将用transformers的TFBertModel来实现文本分类。

首先介绍transformers(github),它是一个“State-of-the-art Natural Language Processing for PyTorch and TensorFlow 2.0 ”,也就是说它整合了目前自然语言处理方向的各种模型(不乏Bert、GPT、GET-2、XLNet以及各种网络衍生版本,详见这里),而且支持tf2(即使官方文档宣称支持TensorFlow 2.0以及上,但是由于文档更新的滞后,新版本的transformers需要TensorFlow 2.3及以上才可以正确运行,pytorch暂时没有做测试)和pytorch。

下面介绍TFBertModel。

TFBertModel

文档

这个类是最基本的Bert模型,先简单看看文档,稍后我们将进行源码分析。

TFBertModel 继承自TFPreTrainedModel,同时继承了tf.keras.Model。它支持多种输入,包括:

  • model(inputs_ids)
  • model([input_ids, attention_mask])
  • model([input_ids, attention_mask, token_type_ids])
  • model({"input_ids": input_ids, "token_type_ids": token_type_ids})
  • ……

可以看见对于输入的限制很松,其实它还支持很多字段

    def call(
        self,
        inputs,
        attention_mask=None,
        token_type_ids=None,
        position_ids=None,
        head_mask=None,
        inputs_embeds=None,
        output_attentions=None,
        output_hidden_states=None,
        return_dict=None,
        training=False,
    ):

最终返回一个类似于字典的数据结构,包括Bert线性层的输出、最后一层隐藏层(Bert线性层的输入)以及所有的隐藏层状态(optional)、所有的注意力参数(optional)。

详细文档在这里

简单看过文档后我们先看代码:

数据处理

首先是数据处理,transformers提供了tokenizer来进行text->token。

tokenizer提供了encoder_plus来编码(还有 encoder甚至构造方法来进行编码,但是只讲一个最全的)

可以看到输入的参数有很多,具体参数功能详见文档。

我们使用可以直接

inputs = tokenizer.encode_plus('你好transformers', max_length=50, padding='max_length')

我们就可以发现返回了token、attention mask等参数,而这些参数可以直接输入到model.call中。

其他数据处理代码就不作讲解了,可以去查看源码,源码在后面会给出github链接。

值得一提的是,目前encode_plus好像不支持批量获取token,因此需要一次一次获取然后转换成ndarry。

如果要使用batch那么观察前面提到的model.call()的格式,并没有跟batch_size有关的参数,因此要在数据处理部分处理,将所有的三种输入(神经网络输入)分别取出。

后更:找到了encode_plus_batch支持批处理,但是目前还不清楚返回数据格式需不需要处理。

模型编写

class MyTFBert(tf.keras.Model):
    def __init__(self):
        super(MyTFBert, self).__init__()
        self.bert = TFBertModel.from_pretrained('bert-base-chinese', return_dict=True)
        self.dropout = tf.keras.layers.Dropout(0.1)
        self.dense = tf.keras.layers.Dense(10, activation='softmax')
        self.dense2 = tf.keras.layers.Dense(768, activation='tanh')
    def call(self, inputs):
        idx, attn, ids = inputs
        hidden = self.bert(idx, attention_mask=attn, token_type_ids=ids, training=False)
        temp = hidden[1]
        temp = self.dropout(temp, training=True)
        out = self.dense(temp)
        return out

这个就比较简单了,值得一提的就是bert的返回,上面提到,返回了三个,而我们需要的是第二个,也就是pooler_output,那么last_hidden_state是什么呢?我们观察文档:

我们可以得出,last_hidden_state 和 pooler_output 只多了一个“Linear layer and a Tanh activation function”。也就是说,将Bert的输出放入一个FC。在后面我们也会分析源码和实际操作确定这个想法。

源码

我们首先看TFBertModel的源码。

我们看到,主要有embedding、encoder、pooler几个模型。

call中:

可以看到,encoder输出后,获取last_hidden_state然后pooler(也就是Dense,继续看源码)

我们继续看encoder的源码:

我们可以看到,是多层bert的叠加。

然后pooler:

只是一个Dense层,然后我们继续看call

可以看到,这里使用第一个token的embedding输出然后进行dense。

到这里我们就基本缕清了transformers的TFBertModel的背后流程。

我们也可以看到,在TFBertModel种pooler返回的pooler_output作为返回的poooler_output返回,因此我们也就确定了last_hidden_state和pooler_output的关系,只是TFBertPooler的区别,就是取第一个token的embedding然后进行dense操作。

完整源码


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注