一:介绍
这几天看代码时,总是遇到transformers 模块使用,产生很多疑惑,故特别花些时间来研究一下。研究一番之后,惊叹于该模块的强大功能,故将学习所得分享一下。简单来说,huggingface transfomers 是一个强大的处理自然语言任务的工具包,集成了各种自然语言任务的预训练模型。该工具包支持pytorch 和 TensorFlow。可以说,当你遇到一些NLP任务时,最好先考虑能不能在该模块中找到相关模型,如果能找到,将节省非常多力气。
二:说明
本文并不是简单的将官方文档翻译,而是结合官方文档和自己的理解书写,穿插代码和文字说明。如有疑问,以官方文档为主。如有错误,欢迎指出。官方文档提供pytorch和tensorflow两种框架的学习代码,但我使用pytorch,故文中所有案例都是使用pytorch代码进行说明。本文使用pycharm 运行。
三:transfomers
(1):安装
最简单的方式,打开terminal 输入:
pip install transfomers
这里默认最新版,可以指定版本安装。其他花里胡哨的安装参考官方文档。
(2):使用方式一,pipeline(不推荐)
直接用任务关键字实例化pipeline,得到一个训练好的实例对象,然后直接使用即可,实用性不高。代码如下(来自官方,未运行):
from transformers import pipeline
classifier = pipeline('sentiment-analysis')
classifier('We are very happy to show you the 🤗 Transformers library.')
# [{'label': 'POSITIVE', 'score': 0.9998}]
(3):使用方式二(常用方式)
自然处理任务总体可以分为两步,第一步分词,将文本通过分词器(tokenizer)得到基础的组成单元(token),然后根据词汇表(vocal)为这些单元编号,这样就能将文本数据变成数据编码形式,输入到模型。 第二步,将相应的token输入到网络,进行任务训练,根据网络不同,实现任务不同。这里的使用bert模型为例说明。(需要了解一下bert的原理)
1:tokenzier.
官方案例如下(不推荐)
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
sequence = "A Titan RTX has 24GB of VRAM"
tokenized_sequence = tokenizer.tokenize(sequence)
print(tokenized_sequence)
# >>> ['A', 'Titan', 'R', '##T', '##X', 'has', '24', '##GB', 'of', 'V', '##RA', '##M']
这里,导入BertTokenizer类,通过from_pretrained方法,从官网下载训练好的数据,然后得到可以直接使用的tokenizer对象,后面是个分词效果的实例展示。根据我看的代码,并不推荐程序中这样使用,因为这种方式下载的数据保存位置是默认的,并不好找。实际使用者采用如下代码:
from transformers import BertTokenizer
model_name = 'bert-base-cased' # 下载的数据模型名称
cache_dir = './cache_dir' # 将数据保存到的本地位置
tokenizer = BertTokenizer.from_pretrained(model_name,cache_dir=cache_dir)
# 使用cache_dir 可以指定文件下载位置
sequence = "A Titan RTX has 24GB of VRAM" # 待分词的句子
tokenized_sequence = tokenizer.tokenize(sequence) # 分词
print(tokenized_sequence) # 输出
输出效果及目录
可见,分词不是按单词划分的,其中的 ## 表示一个单词内的字母。
仅仅是分词还是不够的,还需要为每个词编码,代码如下:
from transformers import BertTokenizer
from pprint import pprint
model_name = 'bert-base-cased' # 下载的数据模型名称
cache_dir = './cache_dir' # 将数据保存到的本地位置
tokenizer = BertTokenizer.from_pretrained(model_name,cache_dir=cache_dir)
# 使用cache_dir 可以指定文件下载位置
sequence = "A Titan RTX has 24GB of VRAM" # 待分词的句子
# tokenized_sequence = tokenizer.tokenize(sequence) # 调用tokenize方法进行分词
# print(tokenized_sequence) # 输出
inputs = tokenizer(sequence)
pprint(inputs)
结果如下:
可见,结果是个字典。其中input_ids好理解,即将分好的词(token )编码,这样这些编码数据就可以输入到网络模型中,但attention_mask和token_type_ids 如何理解?这个后面在介绍。
二:建立模型
得到input_ids,就得到了输入数据,那网络模型又如何搭建,代码如下:
from transformers import BertModel
model = BertModel.from_pretrained(model_name,cache_dir=cache_dir) # 注意这里加载的预训练模型名字要与上面一致,需要下载模型400M
# model = BertModel.from_pretrained('./pre_model')
pprint(model)
下载太慢,解决方式:manually download models · Issue #856 · huggingface/transformers · GitHub
从官网下载模型,本地替换文件。
文件结构:
代码:
from transformers import BertModel
#model = BertModel.from_pretrained(model_name,cache_dir=cache_dir) # 注意这里加载的预训练模型名字要与上面一致,需要下载模型400M
model = BertModel.from_pretrained('./pre_model')
pprint(model)
输出:
三:结合使用
完整代码:
from transformers import BertTokenizer,BertModel
from pprint import pprint
model_name = 'bert-base-cased' # 下载的数据模型名称
cache_dir = './cache_dir' # 将数据保存到的本地位置
tokenizer = BertTokenizer.from_pretrained(model_name,cache_dir=cache_dir)
# 使用cache_dir 可以指定文件下载位置
sequence = "A Titan RTX has 24GB of VRAM" # 待分词的句子
# tokenized_sequence = tokenizer.tokenize(sequence) # 调用tokenize方法进行分词
# print(tokenized_sequence) # 输出
inputs = tokenizer([sequence],return_tensors='pt')
# encoded_sequence = inputs["input_ids"]
# pprint(inputs)
from transformers import BertModel
#model = BertModel.from_pretrained(model_name,cache_dir=cache_dir) # 注意这里加载的预训练模型名字要与上面一致,需要下载模型400M
model = BertModel.from_pretrained('./pre_model')
out = model(**inputs)
pprint(out['last_hidden_state'])
print(out['last_hidden_state'].shape)
输出结果:
四:批处理
事实上,使用时并不会输入单个句子,而是会多个句子同时处理,这时候就会用到attention_mask。比如:
from transformers import BertTokenizer
model_name = 'bert-base-cased' # 下载的数据模型名称
cache_dir = './cache_dir' # 将数据保存到的本地位置
tokenizer = BertTokenizer.from_pretrained(model_name,cache_dir=cache_dir)
sequence_a = "This is a short sequence."
sequence_b = "This is a rather long sequence. It is at least longer than the sequence A."
prencoded_sequence_a = tokenizer(sequence_a)["input_ids"]
encoded_sequence_b = tokenizer(sequence_b)["input_ids"]
print(prencoded_sequence_a)
print(encoded_sequence_b)
那么这两个句子由于分的token大小不一,所以无法同时输入网络。解决的方法就是把所有句子的token大小统一,不够的补充,多余的删除。
from transformers import BertTokenizer,BertModel
from pprint import pprint
model_name = 'bert-base-cased' # 下载的数据模型名称
cache_dir = './cache_dir' # 将数据保存到的本地位置
tokenizer = BertTokenizer.from_pretrained(model_name,cache_dir=cache_dir)
sequence_a = "This is a short sequence."
sequence_b = "This is a rather long sequence. It is at least longer than the sequence A."
# prencoded_sequence_a = tokenizer(sequence_a)["input_ids"]
# encoded_sequence_b = tokenizer(sequence_b)["input_ids"]
# print(prencoded_sequence_a)
# print(encoded_sequence_b)
input_batch = tokenizer([sequence_a,sequence_b],padding=True,
truncation=True,max_length=30,return_tensors='pt')
for key,value in input_batch.items():
pprint(f"{key}:{value.numpy().tolist()}")
输出:
可见,这段程序将两个句子的input_ids统一,少的用0补充,多的删除。而attention_mask 为inputs_ids 提供刚刚标准化的信息,1表示该位置token参与网络的输入运算,0表示该位置token不存在,在输入网络时,应该忽略其信息。
完整代码:
from transformers import BertTokenizer,BertModel
from pprint import pprint
model_name = 'bert-base-cased' # 下载的数据模型名称
cache_dir = './cache_dir' # 将数据保存到的本地位置
tokenizer = BertTokenizer.from_pretrained(model_name,cache_dir=cache_dir)
sequence_a = "This is a short sequence."
sequence_b = "This is a rather long sequence. It is at least longer than the sequence A."
# prencoded_sequence_a = tokenizer(sequence_a)["input_ids"]
# encoded_sequence_b = tokenizer(sequence_b)["input_ids"]
# print(prencoded_sequence_a)
# print(encoded_sequence_b)
input_batch = tokenizer([sequence_a,sequence_b],padding=True,
truncation=True,max_length=30,return_tensors='pt')
# for key,value in input_batch.items():
# pprint(f"{key}:{value.numpy().tolist()}")
model = BertModel.from_pretrained('./pre_model')
out = model(**input_batch)
print(out['last_hidden_state'].shape)
结果:
token_type_ids 在一些特殊的任务中会问到,比如问答模型,要将问句和回答语句放在一起,为了辨别出,两种句子,会采用token_type_ids来标记。
四:参考资料
https://huggingface.co/transformers/glossary.html#attention-mask