分词器是我们在使用 transformers 时一个重要的核心组件,它的主要职责就是将输入的原始文本转换为模型需要的格式,例如:对输入的文本添加开始和结束符、填充、计算掩码、转换为索引等等。
1. Tokenizer 使用
一段输入的纯文本会经历如下的流程,最终变成模型要求的输入:
- 根据词表拆分输入内容
- 将拆分后的内容转换为数值表示
示例代码如下:
from transformers import BertTokenizer if __name__ == '__main__': # 1. 加载预训练模型分词器 tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') # 2. 对输入文本分词 inputs = '我是中国人' inputs = tokenizer.tokenize(inputs) # 输出: ['我', '是', '中', '国', '人'] print(inputs) # 3. 将分词后的内容转换为索引表示 inputs = tokenizer.convert_tokens_to_ids(inputs) print(inputs)
程序输出结果:
['我', '是', '中', '国', '人'] [2769, 3221, 704, 1744, 782]
上面的输出内容中,并没有给输入添加开始和结束标记,也没有构造出 token_type_ids 和 attention_mask,如果我们希望构造完整的输入内容,可以使用 encode、encode_plus、batch_encode_plus 方法,它们的区别如下:
- encode 会给输入添加开始和结束标记,并转换为索引表示;
- encode_plus 不仅仅完成 encode 的功能,还会构建 token_type_ids 和 attention_mask;
- batch_encode_plus 适合对批量的输入进行 encode_plus 操作。
示例代码如下:
from transformers import BertTokenizer if __name__ == '__main__': tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') inputs = '我是中国人' inputs = tokenizer.encode(inputs) print('encode:', inputs) inputs = tokenizer.encode_plus(inputs) print('encode_plus:', inputs) inputs = ['我是中国人', '我爱我的国家'] inputs = tokenizer.batch_encode_plus(inputs) print('batch_encode_plus:', inputs)
程序输出内容如下:
encode: [101, 2769, 3221, 704, 1744, 782, 102] encode_plus: {'input_ids': [101, 101, 2769, 3221, 704, 1744, 782, 102, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} batch_encode_plus: {'input_ids': [[101, 2769, 3221, 704, 1744, 782, 102], [101, 2769, 4263, 2769, 4638, 1744, 2157, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]]}
最后,简单一点的话,可以直接调用 tokenizer 对象的 __call__ 方法来完成转换,它会自动识别传入的是单个文本还是批量文本,并将其转化为模型需要的输出,示例代码如下:
from transformers import BertTokenizer if __name__ == '__main__': tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') inputs = '我是中国人' inputs = tokenizer(inputs) print('tokenizer:', inputs) inputs = ['我是中国人', '我爱我的国家'] inputs = tokenizer(inputs) print('tokenizer:', inputs)
程序输出内容如下:
tokenizer: {'input_ids': [101, 2769, 3221, 704, 1744, 782, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1]} tokenizer: {'input_ids': [[101, 2769, 3221, 704, 1744, 782, 102], [101, 2769, 4263, 2769, 4638, 1744, 2157, 102]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]]}
最后,如果我们想把 ids 转换为文本表示,可以使用 decode 方法,如下代码所示:
from transformers import BertTokenizer if __name__ == '__main__': tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') # 将某个 id 转换为 token print(tokenizer.convert_ids_to_tokens(2769)) # 将 ids 转换为 tokens inputs = [101, 2769, 3221, 704, 1744, 782, 102] # skip_special_tokens=True 将会去除特殊标记,例如开始和结尾的特殊标记 inputs = tokenizer.decode(inputs, skip_special_tokens=True) print(inputs, len(inputs))
程序输出结果如下:
我 我 是 中 国 人 9
注意:输出的结果带有空格,
2. Tokenizer 构建
我们可以预先构建自己一个 vocab file,然后构建包含自己词典的 Tokenizer,将并将其应用到后面的输入转换操作中。
# 1. 使用自己的词典文件 def test01(): tokenizer = BertTokenizer(vocab_file='vocab.txt') inputs = ['不畏鸿门传汉祚', '新居落成创业始'] outputs = tokenizer(inputs) print(outputs) # 存储 Tokenizer 对象 tokenizer.save_pretrained('my-vocab') # 2. 加载词典对象 def test02(): # 指定词典路径 tokenizer = BertTokenizer.from_pretrained('my-vocab') inputs = ['不畏鸿门传汉祚', '新居落成创业始'] outputs = tokenizer(inputs) print(outputs) # 存储 Tokenizer 对象 tokenizer.save_pretrained('my-vocab') if __name__ == '__main__': test01() test02()
Tokenizer 存储到磁盘上有 3 个文件,分别是:
- special_tokens_map.json 存储一些特殊的 token,例如:[UNK]、[SEP] 等
- tokenizer_config.json 存储 Tokenizer 的配置信息
- vocab.txt 词典文件
当我们下次使用的时候,直接使用 from_pretrained 从指定路径下加载这三个文件即可,不需要重新使用词典构建对象。程序输出结果:
{'input_ids': [[5, 21, 1836, 606, 113, 135, 330, 2685, 4], [5, 35, 373, 96, 68, 545, 122, 737, 4]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1]]} {'input_ids': [[5, 21, 1836, 606, 113, 135, 330, 2685, 4], [5, 35, 373, 96, 68, 545, 122, 737, 4]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1]]}