如何利用 BERT 提取句向量.

BERT 在 NLP 方向中是一个十分具有里程碑的模型,那么如何通过 BERT 提取一个句子的句向量呢?

网络上的资料还是很多的,由于比赛要求只使用 Pytorch 框架,所以很多基于 TF 的教程和库就没办法用了。在查询了部分资料后,我终于总结出了一个提取的方法:

这一篇总结我会尽力写得通俗易懂,一读就明白。

算法解释

    import torch
    from pytorch_pretrained_bert import BertTokenizer, BertModel, BertForMaskedLM

导入 torchpytorch_pretrained_bert 库,站在巨人的肩膀上,直接缩短大量的训练时间。

    tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')  # 导入预训练模型

    tokenized_text = tokenizer.tokenize(text) # tokenize 输入的文本
    indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)  # 向量化

这一部分的代码主要是将输入的文本转化为 tokenized 的文本,然后再把该文本给向量化

问题来了,什么是 tokenized 和向量化?

tokenized

标记化,把输入的句子进行标记划的操作,举个小例子:

    tokenized_text = tokenizer.tokenize('Hello world !')
    >>> ['hello', 'world', '!']

大家可能注意到了,为什么字母全部为小写了?这是因为我们使用的模型 bert_base_uncased 是不分大小写的,所以输出的 tokenized_text 会全由小写表示。

其实 BERT 还支持输入两个句子,但是需要放在同一个字符串中,并通过一个标记来表示一个句子的开始和结束,再举个小例子:

    tokenized_text = tokenizer.tokenize('[CLS] Hello world! [SEP] I\'m Xiao Ming. [SEP]')
    >>> ['[CLS]', 'hello', 'world', '!', '[SEP]', 'i', "'", 'm', 'xiao', 'ming', '.', '[SEP]']

其中的 [CLS] 是句子的开始符,[SEP] 是句子的结束符,同时也可以作为下一个句子的开始符,所以第二句的 [CLS] 就可以省去。

向量化

向量化便是把生成的 tokenized_text 变为矩阵向量表示,从而便于后续的网络运算,举个例子:

    tokenized_text = tokenizer.tokenize('[CLS] Hello world world [SEP] I\'m Xiao Ming. [SEP]')
    indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
    >>> [101, 7592, 2088, 2088, 102, 1045, 1005, 1049, 19523, 11861, 1012, 102]

通过这个例子可以发现,两个 world 生成的向量是相同的,即每一个单词或者标记都有对应的词向量,储存在一个预训练好的表中;

让我们继续

OK ,在解释完原理后,继续一通操作:

    segments_ids = [1] * len(tokenized_text)
    tokens_tensor = torch.tensor([indexed_tokens])
    segments_tensors = torch.tensor([segments_ids])

segments_ids 是用来标记分句的,用 0 和 1 分开一个 tokenized_text 中的两个句子——一个句子为 0,另一个句子为 1。因为我一次只输入一个句子,所以就全设为 1,长度与 tokenized_text 相同(不相同会报错,这个需要注意)。

而下面的 tokens_tensorsegments_tensors 是把生成出来的两个向量矩阵给张量化

    model = BertModel.from_pretrained('bert-base-uncased')

初始化预训练模型 bert-base-uncased

    tokens_tensor = tokens_tensor.cuda()
    segments_tensors = segments_tensors.cuda()
    model.cuda()

将矩阵和模型全部放入 cuda 中,即用 GPU 运算,加快矩阵运算的速度。

    encoded_layers, pooled_output= model(tokens_tensor, segments_tensors)

tokens_tensorsegments_tensors 放入模型前向传播,计算出最后的输出。其中 encoded_layers 为每一层产生的输出,pooled_output最后输出的句向量,而句向量为我们最终所需要的矩阵向量。

至此,提取句向量的工作已经完成了,接下来就需要输入下层网络王城后续工作了,代码放在了下面。

代码

    import torch
    from pytorch_pretrained_bert import BertTokenizer, BertModel, BertForMaskedLM

    def encode(text):
        tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

        tokenized_text = tokenizer.tokenize(text)
        indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
        print(indexed_tokens)

        segments_ids = [1] * len(tokenized_text)
        tokens_tensor = torch.tensor([indexed_tokens])
        segments_tensors = torch.tensor([segments_ids])

        model = BertModel.from_pretrained('bert-base-uncased')

        tokens_tensor = tokens_tensor.cuda()
        segments_tensors = segments_tensors.cuda()
        model.cuda()

        encoded_layers, pooled_output= model(tokens_tensor, segments_tensors)

        return pooled_output

    encode('[CLS] Hello world world [SEP] I\'m Xiao Ming. [SEP]')