BERT中的Tokenizer说明

BERT中的Tokenizer说明

预训练BERT的Tokenizer有着强大的embedding的表征能力,基于BERT的Tokenizer的特征矩阵可以进行下游任务,包括文本分类,命名实体识别,关系抽取,阅读理解,无监督聚类等。由于最近的工作涉及到了Tokenizer,利用hugging face的transformers学习了Tokenizer,整理了这篇博客,如有理解表达不当,欢迎大家指正。

Tokenizer

加载预训练BERT的Tokenizer,这里采用的bert-base-chinese预训练模型,代码如下:

from transformers import BertTokenizerFast

tokenizer = BertTokenizerFast.from_pretrained(
    "D:/Spyder/pretrain_model/transformers_torch_tf/bert_base_chinese/",
    add_special_tokens=False, # 不添加CLS,SEP
    do_lower_case=True)  # 区分大小写字母,

注意:vocab.txt中英文是小写,设置为do_lower_case=True,如果do_lower_case=False,中文中英文会变成[UNK]

对文本进行tokenizer

对文本进行tokenizer,得到文本的tokens,token_span,token_ids,token_mask等

text = "30%~50%患儿血清IgA浓度升高;HSP急性期血循环中表面IgA阳性的B淋巴细胞数、IgA类免疫复合物或冷球蛋白均增高;"
tokens = tokenizer.tokenize(text)
token_span = tokenizer.encode_plus(text, return_offsets_mapping=True, add_special_tokens=False)["offset_mapping"]

tokens的输出的结果为

tokens:  ['30', '%', '~5', '##0', '%', '患', '儿', '血', '清', '[UNK]', '浓', '度', '升', '高', ';', '[UNK]', '急', '性', '期', '血', '循', '环', '中', '表', '面', '[UNK]', '阳', '性', '的', '[UNK]', '淋', '巴', '细', '胞', '数', '、', '[UNK]', '类', '免', '疫', '复', '合', '物', '或', '冷', '球', '蛋', '白', '均', '增', '高']

token_span的输出结果为

token_span:  [(0, 2), (2, 3), (3, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (31, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), (41, 42), (42, 43), (43, 44), (44, 47), (47, 48), (48, 49), (49, 50), (50, 51), (51, 52), (52, 53), (53, 54), (54, 55), (55, 56), (56, 57), (57, 58), (58, 59), (59, 60), (60, 61)]

tokens的30对应原文中的text的索引位置为[0,1]

通常在实际任务中,需要将文本中字与tokenizer的编码对应起来会使用到token_span,有很多时候,文本中的文字的位置index与tokenizer中的tokens的index不是一一对应的,一个字符经过tokenizer后得到多个字符,下面通过两个代码说明tokens和文本的字符建议一一对应的关系:
方法1

# 获取文本的长度
char_num = None
for tok_ind in range(len(token_span) - 1, -1, -1):
    if token_span[tok_ind][1] != 0:
        char_num = token_span[tok_ind][1]
        break
# 建立文本与tokens之间的对应关系
char2tok_span = [[-1, -1] for _ in range(char_num)] # [-1, -1] is whitespace
for tok_ind, char_sp in enumerate(token_span):
    for char_ind in range(char_sp[0], char_sp[1]):
        tok_sp = char2tok_span[char_ind]
        # 因为char to tok 也可能出现1对多的情况,比如韩文。所以char_span的pos1以第一个tok_ind为准,pos2以最后一个tok_ind为准
        if tok_sp[0] == -1:
            tok_sp[0] = tok_ind
        tok_sp[1] = tok_ind + 1

方法2

context_tokens = tokenizer.encode(context, add_special_tokens=False)
tokens = query_context_tokens.ids
type_ids = query_context_tokens.type_ids
offsets = query_context_tokens.offsets

origin_offset2token_idx_start = {}
origin_offset2token_idx_end = {}
for token_idx in range(len(tokens)):
    # skip query tokens
    if type_ids[token_idx] == 0:
        continue
    token_start, token_end = offsets[token_idx]
    # skip [CLS] or [SEP]
    if token_start == token_end == 0:
      	continue
    origin_offset2token_idx_start[token_start] = token_idx
    origin_offset2token_idx_end[token_end] = token_idx
start_positions = [1, 3, 5, 6]    
new_positions = [origin_offset2token_idx_start[start] for start in start_positions]

方法2的处理方式只争对纯中文,如果文本中包含中文,英文,数字,将文本的字符与tokens对应出现偏差,因为tokenizer的结果可能不是一个字符。而方法1可以避免出现上述偏差的现象。

basic tokenizer

basic tokenzier和上文使用的BertTokenizerFast不一样,如果中文文本中含有英文,basic tokenizer 会将英文识别为单词,BertTokenizerFast会将英文识别为英文单词本身,或者##xxx之类,详细看下面的例子
(1)basic tokenizer

from transformers import BasicTokenizer
basic_tokenizer = BasicTokenizer(do_lower_case=True)
text = "临时用电“三省”fighting服务相关说明"
basic_token1 = basic_tokenizer.tokenize(text)
print("basic token2: ", basic_token1)
text = "掌上营业厅2019APP充值卡APP充值"
basic_token2 = basic_tokenizer.tokenize(text)
print("basic token2: ", basic_token2)

tokens的结果为

basic token1:  ['临', '时', '用', '电', '“', '三', '省', '”', 'fighting', '服', '务', '相', '关', '说', '明']
basic token2:  ['掌', '上', '电', '力', '2019app', '充', '值', '卡', 'app', '充', '值']

(2)tokenzier
这里使用transformers中的BertTokenizerFast,

from transformers import BertTokenizerFast
tokenizer = BertTokenizerFast.from_pretrained(bert_path,
                                              add_special_tokens=False,
                                              do_lower_case=True)
text = "临时用电“三省”fighting服务相关说明"
tokens1 = tokenizer.tokenize(text)
text = "掌上营业厅2019APP充值卡APP充值"
token2 = tokenizer.tokenize(text)
print("tokens2:", tokens2)

tokens的结果为

tokens1:  ['临', '时', '用', '电', '[UNK]', '三', '省', '[UNK]', 'fi', '##ght', '##ing', '服', '务', '相', '关', '说', '明']
tokens2:  ['掌', '上', '电', '力', '2019', '##app', '充', '值', '卡', 'app', '充', '值']
添加额外的tokens

transformers中的tokenizer可以添加vocab.txt不包含的词,从而实现了对特殊指定的词进行tokens编码。详细看代码

from transformers import BertTokenizer

pretrain_model_path = "D:/Spyder/pretrain_model/transformers_torch_tf/bert-base-chinese/"
tokenizer = BertTokenizer.from_pretrained(pretrain_model_path)
# 注意这里对   的tokenizer的结果
text = "舅父赵叔孺亦为闽中大收藏家,林寿图从小耳濡目染,对书画颇有灵性。"
ori_tokens = tokenizer.tokenize(text)
print("ori_tokens: ", ori_tokens)

ori_tokens 结果为:

ori_tokens:  ['舅', '父', '<', 'e1', '>', '赵', '叔', '孺', '<', '/', 'e1', '>', '亦', '为', '闽', '中', '大', '收', '藏', '家', ',', '<', 'e2', '>', '林', '寿', '图', '<', '/', 'e2', '>', '从', '小', '耳', '濡', '目', '染', ',', '对', '书', '画', '颇', '有', '灵', '性', '。']

在tokenizer 中添加额外特殊的tokens, 例如添加 < e 1 > <e1>, < / e 1 > </e1>, < e 2 > <e2>, < / e 2 > </e2>

add_special_tokens = ["", "", "", ""]
tokenizer.add_special_tokens({"additional_special_tokens": add_special_tokens})
special_tokens_text = "   "
special_tokens = tokenizer.tokenize(special_tokens_text)
print("special tokens: ", special_tokens)
special_input_ids = tokenizer(special_tokens_text, max_length=64, truncation=True,
                              add_special_tokens=False).input_ids
print("special tokens input ids: ", special_input_ids)

输出结果如下:

special tokens:  ['', '', '', '']
special tokens input ids:  [21128, 21129, 21130, 21131]

备注:给添加的 < e 1 > <e1>, < / e 1 > </e1>, < e 2 > <e2>, < / e 2 > </e2>进行了编码,这里使用的bert-base-chinese预训练模型vocab.txt的大小为21128,所以新添加的tokens编码从21128后面开始编码。
下面添加tokens 对text进行tokenizer

tokens = tokenizer.tokenize(text)
print("add special tokens: ", tokens)
print("add special tokens size: ", len(tokens))

输出结果为:

add special tokens:  ['舅', '父', '', '赵', '叔', '孺', '', '亦', '为', '闽', '中', '大', '收', '藏', '家', ',', '', '林', '寿', '图', '', '从', '小', '耳', '濡', '目', '染', ',', '对', '书', '画', '颇', '有', '灵', '性', '。']
add special tokens size:  36
batch encoder

tokenizer提供了batch encoder的api,同时多个句子进行编码,获取特征。

text_list = ["今天天气温度很高", "来一场大暴雨"]
features = tokenizer.batch_encode_plus(text_list, add_special_tokens=False,
                                       padding=True, truncation=True, max_length=16)
print("features: ", features["input_ids"])

结果如下:

input ids:  [[791, 1921, 1921, 3698, 3946, 2428, 2523, 7770], 
[3341, 671, 1767, 1920, 3274, 7433, 0, 0]]

你可能感兴趣的:(NLP,自然语言处理,人工智能)