白话 NLP,3 个因果告诉你 Mask 矩阵因何而产生?

一、为什么需要 Mask?

在此,先思考一个问题,为什么需要 mask?

在 NLP 中,一个最常见的问题便是输入序列长度不等,通常需要进行 PAD 操作,通常在较短的序列后面填充 0,虽然 RNN 等模型可以处理不定长输入,但在实践中,需要对 input 做 batchsize,转换成固定的 tensor。

PAD 案例:

如下是两句英文,先将文本转换成数字

s1 = 'He likes cats'
s2 = 'He does not like cats'
s = s1.split(' ') + s2.split(' ')
word_to_id = dict(zip(s, range(len(s))))
id_to_word = dict((k,v) for v,k in word_to_id.items())
# {'He': 3, 'likes': 1, 'cats': 7, 'does': 4, 'not': 5, 'like': 6}
# {3: 'He', 1: 'likes', 7: 'cats', 4: 'does', 5: 'not', 6: 'like'}
s1_vector = [word_to_id[x] for x in s1.split(' ')]
s2_vector = [word_to_id[x] for x in s2.split(' ')]
sentBatch = [s1_vector, s2_vector]
print(sentBatch)

对文本进行数字编码

[[3, 1, 7], [3, 4, 5, 6, 7]]

对如上两个 vector 进行 pad 处理。

from torch.nn.utils.rnn import pad_sequence
a = torch.tensor(s1_vector)
b = torch.tensor(s2_vector)
pad = pad_sequence([a, b])
print(pad)

PAD 结果

tensor([[3, 3],
        [1, 4],
        [7, 5],
        [0, 6],
        [0, 7]])

以句子 ”He likes cats“ 的 PAD 结果举例:[3, 1, 7, 0, 0],PAD 操作会引起以下几个问题。

1. mean-pooling 的问题

如上述案例所示,对于矩阵: s 1 = [ 3 , 1 , 7 ] s1 = [3, 1, 7] s1=[3,1,7]

对 s1 进行 mean-pooling: m e a n s 1 = ( 3 + 1 + 7 ) / 3 = 3.667 mean_{s1}=(3+1+7)/3=3.667 means1=(3+1+7)/3=3.667

进行 pad 之后: p a d s 1 = [ 3 , 1 , 7 , 0 , 0 ] pad_{s1}=[3,1,7,0,0] pads1=[3,1,7,0,0]

p a d s 1 pad_{s1} pads1 进行 mean-pooling: p a d s 1 = ( 3 + 1 + 7 + 0 + 0 ) / 10 = 1.1 pad_{s1}=(3+1+7+0+0)/10=1.1 pads1=(3+1+7+0+0)/10=1.1

对比 m e a n s 1 mean_{s1} means1 p a d s 1 pad_{s1} pads1 发现:pad 操作影响 mean-pooling。

2. max-pooling 的问题

对于矩阵 s1: s 1 = [ − 3 , − 1 , − 7 ] s1 = [-3, -1, -7] s1=[3,1,7],PAD 之后: p a d s 1 = [ − 3 , − 1 , − 7 , 0 , 0 ] pad_{s1}=[-3,-1,-7,0,0] pads1=[3,1,7,0,0]

分别对 s 1 s1 s1 p a d s 1 pad_{s1} pads1 进行 max-pooling: m a x s 1 = − 1 , m a x p a d s 1 = 0 max_{s1}=-1,max_{pad_{s1}}=0 maxs1=1,maxpads1=0

对比 m e a n s 1 mean_{s1} means1 p a d s 1 pad_{s1} pads1 发现:pad 操作影响 max-pooling。

3. attention 的问题

通常在 Attention 计算中最后一步是使用 softmax 进行归一化操作,将数值转换成概率。但如果直接对 PAD 之后的向量进行 softmax,那么 PAD 的部分也会分摊一部分概率,这就导致有意义的部分 (非 PAD 部分) 概率之和小于等于 1。

二、Mask 为解决 PAD 问题顺应而生

Mask 是相对于 PAD 而产生的技术,具备告诉模型一个向量有多长的功效。Mask 矩阵有如下特点:

  1. Mask 矩阵是与 PAD 之后的矩阵具有相同的 shape。
  2. mask 矩阵只有 1 和 0两个值,如果值为 1 表示 PAD 矩阵中该位置的值有意义,值为 0 则表示对应 PAD 矩阵中该位置的值无意义。

在第一部分中两个矩阵的 mask 矩阵如下所示:

mask_s1 = [1, 1, 1, 0, 0]
mask_s2 = [1, 1, 1, 1, 1]
mask = a.ne(torch.tensor(paddingIdx)).byte()
print(mask)
>>> tensor([[1, 1],
            [1, 1],
            [1, 1],
            [0, 1],
            [0, 1]], dtype=torch.uint8)
1. 解决 mean-pooling 问题

m e a n s 1 = s u m ( p a d s 1 ∗ m ) / s u m ( m ) mean_s1=sum(pad_{s1}*m)/sum(m) means1=sum(pads1m)/sum(m)

2. 解决 max-pooling 问题

在进行 max-pooling 时,只需要将 pad 的部分的值足够小即可,可以将 mask 矩阵中的值为 0 的位置替换的足够小 ( 如: 1 0 − 10 甚 至 负 无 穷 10^{-10} 甚至 负无穷 1010,则不会影响 max-pooling 计算。

m a x b = m a x ( p a d b − ( 1 − m ) ∗ 1 0 − 10 ) max_b=max(pad_b-(1-m)*10^{-10}) maxb=max(padb(1m)1010)

3. 解决 Attention 问题

该问题的解决方式跟 max-pooling 一样,就是将 pad 的部分足够小,使得 e x e^x ex 的值非常接近于 0,以至于忽略。

s o f t m a x ( x ) = s o f t m a x ( x − ( 1 − m ) ∗ 1 0 10 ) softmax(x)=softmax(x-(1-m)*10^{10}) softmax(x)=softmax(x(1m)1010)

二、常见的 Mask 有哪些?

有了之前的铺垫,你应该明白 Mask 因何而产生,有什么作用,而在 NLP 任务中,因为功能不同,Mask 也会不同。

常见的 Mask 有两种,Padding-mask,用于处理不定长输入,也即是上面讲的第一种,另一种则是 seqence-mask,为了防止未来信息不被泄露。接下来将详细讲解这两种 Mask。

padding mask - 处理输入不定长

在 NLP 中,一个常见的问题是输入序列长度不等,一般来说我们会对一个 batch 内的句子进行 PAD,通常值为 0。但在前面我们也讲过,PAD 为 0 会引起很多问题,影响最后的结果,因此,Mask 矩阵为解决 PAD 问题而产生。

举个例子:

case 1: I like cats.
case 2: He does not like cats.

假设默认的 seq_len 是5,一般会对 case 1 做 pad 处理,变成

[1, 1, 1, 0, 1]

在上述例子数字编码后,开始做 embedding,而 pad 也会有 embedding 向量,但 pad 本身没有实际意义,参与训练可能还是有害的。

因此,有必要维护一个 mask tensor 来记录哪些是真实的 value,上述例子的两个 mask 如下:

1 1 1 0 0
1 1 1 1 1

后续再梯度传播中,mask 起到了过滤的作用,在 pytorch 中,有参数可以设置:

nn.Embedding(vocab_size, embed_dim,padding_idx=0)
sequence mask - 防止未来信息泄露

在语言模型中,常常需要从上一个词预测下一个词,sequence mask 是为了使得 decoder 不能看见未来的信息。也就是对于一个序列,在 time_step 为 t 的时刻,我们的解码输出应该只能依赖于 t 时刻之前的输出,而不能依赖 t 之后的输出。因此我们需要想一个办法,把 t 之后的信息给隐藏起来。

那么具体怎么做呢?也很简单:产生一个上三角矩阵,上三角的值全为 1,下三角的值全为 0,对角线也是 0。把这个矩阵作用在每一个序列上,就可以达到我们的目的啦。

一个常见的 trick 就是生成一个 mask 对角矩阵,如[1]:

def sequence_mask(seq):
    batch_size, seq_len = seq.size()
    mask = torch.triu(torch.ones((seq_len, seq_len), dtype=torch.uint8),
                    diagonal=1)
    mask = mask.unsqueeze(0).expand(batch_size, -1, -1)  # [B, L, L]
    return mask

哈佛大学的文章The Annotated Transformer有一张效果图:

值得注意的是,本来 mask 只需要二维的矩阵即可,但是考虑到我们的输入序列都是批量的,所以我们要把原本二维的矩阵扩张成 3 维的张量。上面的代码可以看出,我们已经进行了处理。

如果觉得文章对您有帮助,欢迎点赞,转发,关注。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值