「Huggingface🤗NLP笔记系列-第5集」 最近跟着Huggingface上的NLP tutorial走了一遍,惊叹居然有如此好的讲解Transformers系列的NLP教程,于是决定记录一下学习的过程,分享我的笔记,可以算是官方教程的精简+注解版。但最推荐的,还是直接跟着官方教程来一遍,真是一种享受。

  • 官方教程网址:https://huggingface.co/course/chapter1
  • 本期内容对应网址:https://huggingface.co/course/chapter2/5?fw=pt
  • 本系列笔记的GitHub: https://github.com/beyondguo/Learn_PyTorch/tree/master/HuggingfaceNLP

# attention_mask在处理多个序列时的作用

现在我们训练和预测基本都是批量化处理的,而前面展示的例子很多都是单条数据。单条数据跟多条数据有一些需要注意的地方。

# 处理单个序列

我们首先加载一个在情感分类上微调过的模型,来进行我们的实验(注意,这里我们就不能能使用AutoModel,而应该使用AutoModelFor*这种带Head的model)。

from pprint import pprint as print  # 这个pprint能让打印的格式更好看一点
from transformers import AutoModelForSequenceClassification, AutoTokenizer
checkpoint = 'distilbert-base-uncased-finetuned-sst-2-english'
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
1
2
3
4
5

对一个句子,使用tokenizer进行处理:

s = 'Today is a nice day!'
inputs = tokenizer(s, return_tensors='pt')
print(inputs)
1
2
3
{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1]]),
 'input_ids': tensor([[ 101, 2651, 2003, 1037, 3835, 2154,  999,  102]])}
1
2

可以看到,这里的inputs包含了两个部分:input_idsattention_mask.

模型可以直接接受input_ids

model(inputs.input_ids).logits
1

输出:

tensor([[-4.3232,  4.6906]], grad_fn=<AddmmBackward>)
1

也可以通过**inputs同时接受inputs所有的属性:

model(**inputs).logits
1

输出:

tensor([[-4.3232,  4.6906]], grad_fn=<AddmmBackward>)

上面两种方式的结果是一样的

# 但是当我们需要同时处理多个序列时,情况就有变了!

ss = ['Today is a nice day!',
      'But what about tomorrow? Im not sure.']
inputs = tokenizer(ss, padding=True, return_tensors='pt')
print(inputs)
1
2
3
4

输出:

{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]),
 'input_ids': tensor([[  101,  2651,  2003,  1037,  3835,  2154,   999,   102,     0,     0,
             0],
        [  101,  2021,  2054,  2055,  4826,  1029, 10047,  2025,  2469,  1012,
           102]])}
1
2
3
4
5
6

然后,我们试着直接把这里的input_ids喂给模型

model(inputs.input_ids).logits  # 第一个句子原本的logits是 [-4.3232,  4.6906]
1

输出:

tensor([[-4.1957,  4.5675],
        [ 3.9803, -3.2120]], grad_fn=<AddmmBackward>)
1
2

发现,第一个句子的logits变了

这是因为在padding之后,第一个句子的encoding变了,多了很多0, 而self-attention会attend到所有的index的值,因此结果就变了

这时,就需要我们不仅仅是传入input_ids,还需要给出attention_mask,这样模型就会在attention的时候,不去attend被mask掉的部分。

因此,在处理多个序列的时候,正确的做法是直接把tokenizer处理好的结果,整个输入到模型中,即直接**inputs。 通过**inputs,我们实际上就把attention_mask也传进去了:

model(**inputs).logits
1

输出:

tensor([[-4.3232,  4.6906],
        [ 3.9803, -3.2120]], grad_fn=<AddmmBackward>)
1
2

现在第一个句子的结果,就跟前面单条处理时的一样了。