Wordpiece可以将词转化为多个词片段,比如:Nanostructured 被切分为 ['Nan', '##ost', '##ru', '##cture', '##d'],相比直接使用空格进行分词表示的方式,通过种排列组合的方式组成更多的单词,可以把词的本身的意思和时态分开,有效的减少了词表的数量[1]。被切分出来的词叫做Subword,十分形象。
Transformers包中自带了tokenizer方法可以帮助我们实现Wordpiece,但是被切的词汇会改变原有的句子长度,在做序列标注的时候,经过token转化的句子长度无法和标签长度进行对应,此时一种解决方法是记录其词首的位置,用于后期的标注。
本文为其记录句首词的小trick,常见于序列标注任务中。使用numpy的累加方法cumsum来获取其词首位置。
import numpy as np
from transformers import BertTokenizer
bert_class = 'pretrained_model/bert-base-cased' # 提前下好的预训练模型的位置
tokenizer = BertTokenizer.from_pretrained(bert_class, do_lower_case=False)
sentences = [] # 存储结果的列表
line = 'Nanostructured Pt-alloy electrocatalysts for PEM fuel cell oxygen reduction reaction' 样例句子
tokens = line.strip().split(' ') # 按照空格进行分词
subwords = list(map(tokenizer.tokenize, tokens))
"""
[['Nan', '##ost', '##ru', '##cture', '##d'], ['P', '##t', '-', 'alloy'], ['electro', '##cat', '##aly', '##sts'], ['for'], ['P', '##EM'], ['fuel'], ['cell'], ['oxygen'], ['reduction'], ['reaction']]
"""
subword_lengths = list(map(len, subwords))
# [5, 4, 4, 1, 2, 1, 1, 1, 1, 1]
subwords = ['[CLS]'] + [item for indices in subwords for item in indices]
# ['[CLS]', 'Nan', '##ost', '##ru', '##cture', '##d', 'P', '##t', '-', 'alloy', 'electro', '##cat', '##aly', '##sts', ...]
# cls直接加到句首,并且列表
token_start_idxs = 1 + np.cumsum([0] + subword_lengths[:-1])
# 基于cumsum方法对长度进行累加,获取词首index,整体+1,相当于加入了cls标记占位的影响
sentences.append((tokenizer.convert_tokens_to_ids(subwords),token_start_idxs))
# 存入结果,这里可以携程循环用于data loader中
参考: