文章目录
本文为 https://space.bilibili.com/21060026/channel/collectiondetail?sid=1357748的视频学习笔记
项目地址为:https://github.com/zyds/transformers-code
一、Tokenizer 简介
在过去,自然语言预训练模型出来前,做NLP,数据预处理是比较麻烦的,一般有以下几个步骤。
- step1 分词: 使用分词器对文本数据进行分词(字、字词)
- step2 构建词典:根据数据集分词的结果,构建词典映射(这一步并不绝对,如果采用预训练词向量,词典映射要根据词向量文件进行处理)
- step3 数据转换:根据构建好的词典,将分词后处理的数据做映射,将文本序列转换为数字序列
- step4 数据填充与截断: 在以Batch输入到模型的方式中,需要对过短的数据进行填充,过长的数据进行截断,保证数据长度符合模型能接受的范围,同时batch内的数据维度大小一致。
现在,Transformers中的Tokenizer, 可以完成上面的所有操作。 再也不需要安装各种库,来回找各种词典啦。
二、Tokenizer基本使用
(不同的模型有不同的tokenizer,AutoTokenizer可以自动适配 )
# 先从transformers 导入 AutoTokenizer
from transformers import AutoTokenizer
sen = "弱小的我也有大梦想!"
2.1 加载保存
# 从HuggingFace加载,输入模型名称,即可加载对于的分词器
tokenizer = AutoTokenizer.from_pretrained("../../models/models_roberta-base-finetuned-dianping-chinese")
tokenizer
# tokenizer 保存到本地
tokenizer.save_pretrained("./roberta_tokenizer")
输出
# 从本地加载tokenizer
tokenizer = AutoTokenizer.from_pretrained("./roberta_tokenizer/")
tokenizer
2.2 句子分词
借助 tokenizer.tokenize
方法,将中文拆分成一个一个字
tokens = tokenizer.tokenize(sen)
tokens
输出
2.3 查看词典
tokenizer.vocab # 查看词典
tokenizer.vocab_size # 查看词典的大小
有些是一个字,有些有#号。 #号指做一些子词,如把一个完整的词分成字词,让词表更小。
输出如下:
2.4 索引转换
# 将词序列转换为id序列
ids = tokenizer.convert_tokens_to_ids(tokens)
# 将id序列转换为token序列
tokens = tokenizer.convert_ids_to_tokens(ids)
# 将token序列转换为string
str_sen = tokenizer.convert_tokens_to_string(tokens)
以上方法还是需要分词,略微麻烦,但tranformers还有 更便捷的实现方式。
# 将字符串转换为id序列,又称之为编码
# add_special_tokens 指特殊的标志符,和模型有关,如Bert开始有 CLS , 末尾有SEP
ids = tokenizer.encode(sen, add_special_tokens=True) # 设置为False则没有这些
ids
输出如下:前面多了101,后面多了102. 根具体的模型有关
# 将id序列转换为字符串,又称之为解码
str_sen = tokenizer.decode(ids, skip_special_tokens=False)
str_sen
2.5 填充与截断
# 填充
ids = tokenizer.encode(sen, padding="max_length", max_length=15)
ids
输出多了三个0
# 截断
ids = tokenizer.encode(sen, max_length=5, truncation=True)
ids
截断需要注意,是包括开始、终止符号的。
2.6 其他输入
我们的模型在进入transormer模型,有一些特殊的事项,当句子出现填充,要告诉模型哪些是填充,这时候需要 attention mask,如果自己写。
如像Bert模型,会有token type id,来区别两个句子,到底哪个句子是第一个句子。
ids = tokenizer.encode(sen, padding="max_length", max_length=15)
attention_mask = [1 if idx != 0 else 0 for idx in ids]
token_type_ids = [0] * len(ids)
ids, attention_mask, token_type_ids
transormers便捷方式调用:
inputs = tokenizer.encode_plus(sen, padding="max_length", max_length=15)
inputs
返回结果是一个dict是,包括的input_idx,token_type_ids,attention_mask
tokenizer.encode_plus
与tokenizer(input)
是一样的效果。
大部分情况,都是直接用tokenizer
的方法。
inputs = tokenizer(sen, padding="max_length", max_length=15)
三、Fast/Slow Tokenizer
3.1 FastTokenizer
- 基于Rust实现,速度蒯
- offsets_mapping, word_ids
有一个参数use_fast=False
来控制。
fast_tokenizer = AutoTokenizer.from_pretrained("../../models/models_roberta-base-finetuned-dianping-chinese")
FastTokenizer 可以返回 offsets_mapping和word_ids。 但SlowTokenizer 会报错
sen = "弱小的我也有大Dreaming!"
inputs = fast_tokenizer(sen, return_offsets_mapping=True)
inputs
输出看offet_mapping.
将word_id可以和offer_mapping对应起来。 在77这个位置,token产生了两个(7-12)和(12-15)
inputs.word_ids()
如这里的dreaming拆分成了两个。如第7个 Dreaming 拆成了两个7、
word_ids()对命名实体识别有帮助,offer_mapping更多在QA上。
3.2 SlowTokenizer
- 基于python实现,速度慢。
- 不支持 offsets_mapping和word_ids
slow_tokenizer = AutoTokenizer.from_pretrained("../../models/models_roberta-base-finetuned-dianping-chinese", use_fast=False)
四、特殊Tokenizer的加载
from transformers import AutoTokenizer
# 当想加载个人实现的分词器,需要trust_remote_code,配置文件里有配置的分词代码.py文件。
tokenizer = AutoTokenizer.from_pretrained("Skywork/Skywork-13B-base", trust_remote_code=True)
tokenizer.save_pretrained("skywork_tokenizer")
tokenizer = AutoTokenizer.from_pretrained("skywork_tokenizer", trust_remote_code=True)
tokenizer.decode(tokenizer.encode(sen))
如chatglm3-6b,除了模型文件外,还针对分词器写了tokenization_chatglm.py
文件。
当我们想加载这个分词器时,就必须 trust_remote_code=True