使用 from_pretrained()
方法 创建 model and tokenizer
from transformers import AutoTokenizer, AutoModelForSequenceClassification
model_name = "distilbert-base-uncased-finetuned-sst-2-english"
pt_model = AutoModelForSequenceClassification.from_pretrained(model_name) # 创建 model
tokenizer = AutoTokenizer.from_pretrained(model_name) # 创建 Tokenizer
Tokenizer
主要用来 处理数据:
vocab
中的 indexvocab
),vocab 会在使用 from_pretrained 方法实例化它的时候自动下载,因为我们需要使用与模型预训练时相同的词汇表。inputs = tokenizer("We are very happy to show you the Transformers library.") #
tokenizer()
方法:返回一个 字典(dict),其中 key 是 string;value 为 list 类型。
>>> print(inputs)
{'input_ids': [101, 2057, 2024, 2200, 3407, 2000, 2265, 2017, 1996, 100, 19081, 3075, 1012, 102],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
也可以同时处理 多条句子:
## PYTORCH CODE
pt_batch = tokenizer(
["We are very happy to show you the Transformers library.", "We hope you don't hate it."],
padding=True,
truncation=True,
max_length=512,
return_tensors="pt"
)
## PYTORCH CODE
for key, value in pt_batch.items():
print(f"{key}: {value.numpy().tolist()}")
# input_ids: [[101, 2057, 2024, 2200, 3407, 2000, 2265, 2017, 1996, 100, 19081, 3075, 1012, 102], [101, 2057, 3246, 2017, 2123, 1005, 1056, 5223, 2009, 1012, 102, 0, 0, 0]]
# attention_mask: [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]]
更多使用 Tokenizer 的 细节
一旦您的输入被 Tokenizer
预处理,就可以将其直接输入到 model。
对于 PyTorch 模型,您需要通过 添加**
来解压字典
pt_outputs = pt_model(**pt_batch) # 添加`**`来解压字典
print(pt_outputs)
# SequenceClassifierOutput(loss=None, logits=tensor([[-4.0833, 4.3364],
# [ 0.0818, -0.0418]], grad_fn=), hidden_states=None, attentions=None)
所有 Transformers 模型的输出结果都未经过最终激活函数(如 SoftMax)激活,因为这个最终激活函数通常与损失融合。
下面使用 output 的logits
属性,进行激活,得到最终的 预测标签。
from torch import nn
pt_predictions = nn.functional.softmax(pt_outputs.logits, dim=-1) # 使用 softmax 对 output 进行 激活
print(pt_predictions)
# tensor([[2.2043e-04, 9.9978e-01],
# [5.3086e-01, 4.6914e-01]], grad_fn=)
一旦模型被 is fine-tuned
后,便可以保存 Tokenizer
。
tokenizer.save_pretrained(save_directory)
model.save_pretrained(save_directory)
from_pretrained()
方法来 加载保存后的 model,但是此时把 model_name 换成 保存模型的 “模型文件目录” save_directory
from transformers import AutoTokenizer, AutoModelForSequenceClassification
tokenizer = AutoTokenizer.from_pretrained(save_directory)
model = AutoModelForSequenceClassification.from_pretrained(save_directory)
也可以使用 torch 加载 tf 保存的 transformers 模型;反之,亦可以。详见
transformers 库中的每个 model 对应 3 个类型的 class:
Model class
:预训练模型(该库中大概有 30+ 个)。比如,BertModelConfiguration class
:用于存储建立 model 所需的所有参数。比如,BertConfig
Tokenizer class
以上模型 “微调”后,可以如下方法 “保存”、“加载”:
from_pretrained()
:加载“微调”后的 Model/Configuration/Tokenizersave_pretrained()
:保存“微调”后的 Model/Configuration/Tokenizerinput_ids
是 句子 sentence 中 tokens 在 词典 vocab 中对应的 index 序列。
input_ids
是 model 的 唯一 输入。
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']
“分词”的结果,要么是 词典 vocab 中的 word、要么是 词典vocab 中的 子词subword。
比如,“VRAM” 不在 词典中,它被分为 “V”, “RA” and “M”。为了表示它们是来自同一个 word,在 “RA” and “M” 前面加上 ##
前缀。
使用 tokenizer() 方法,得到的是一个 字典dict,而 model 的输入通常只需要
input_ids
即可。
inputs = tokenizer(sequence)
print('inputs: ', inputs) # [101, 1037, 16537, 19387, 2595, 2038, 2484, 18259, 1997, 27830, 8067, 11057, 102]
encoded_sequence = inputs['input_ids'] # model 输入只要
decoded_sequence = tokenizer.decode(encoded_sequence)
print(decoded_sequence) # [CLS] a titan rtx has 24gb of vramaaa [SEP],这就是 BERT 输入的格式
在进行 batch批处理 句子时,需要利用 padding
,将所有句子处理成 相同的长度。
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
sequence_a = "This is a short sequence."
sequence_b = "This is a rather long sequence. It is at least longer than the sequence A."
encoded_sequence_a = tokenizer(sequence_a)["input_ids"]
encoded_sequence_b = tokenizer(sequence_b)["input_ids"]
print('len_a = ', len(encoded_sequence_a), # 8
', len_b = ', len(encoded_sequence_b)) # 19
- sequence_a 中只有 6 个 token,而 encoded_sequence_a 长度却为 8,是因为在首尾多了 CLS 和 SEP.
- 在 BERT 模型中,CLS 在 vocab 中的 index 为 101,而 SEP 为 102.
padding
操作:padded_sequences = tokenizer([sequence_a, sequence_b], padding=True)
print(len(padded_sequences['input_ids'][0])) # sequence_a 经过 padding 后,len 也是 19(和sequence_b长度相同)
attention_mask
attention_mask
的意思是,只用关注 sequence 中未 padding 之前的 token(即,attention_mask 为 1 的 token),而不用关注 padding 的 toekn(即,为0的)
print(padded_sequences['attention_mask'])
# [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
当需要将 多个句子 拼接成 一个input_ids 时,需要使用 [CLS]
和 [SEP]
。
比如,
[CLS] SEQUENCE_A [SEP] SEQUENCE_B [SEP]
具体做法,仍然是使用 tokenizer()
方法,只不过需要注意的是:
列表 []
;padded_sequences = tokenizer([sequence_a, sequence_b], padding=True) # 这里是 列表[]
,
tokenizer(sequence_a, sequence_b) # 这里是逗号 ,
具体代码如下:
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
sequence_a = "HuggingFace is based in NYC"
sequence_b = "Where is HuggingFace based?"
encoded_dict = tokenizer(sequence_a, sequence_b) # 使用 ',' 逗号连接
decoded = tokenizer.decode(encoded_dict["input_ids"]) # 使用 [SEP] 拼接2个句子
# [CLS] this is a short sequence. [SEP] this is a rather long sequence. it is at least longer than the sequence a. [SEP]
其中,
[SEP]
标记上一个 sentence 的 end,以及下一个 sentence 的 start。
BERT 中还使用 token_type_ids
来标记,区分同一个 input_ids 中的不同 sentence。
transformer 库,常用功能:
这里以
Sequence Classification
为例,说明一下用法。