查看原文
其他

NLP中的Mask全解

海晨威 PaperWeekly 2022-03-17


©PaperWeekly 原创 · 作者|海晨威

学校|同济大学硕士生

研究方向|自然语言处理


Mask 在 NLP 中是一个很常规的操作,也有多种应用的场景和形式,下面尝试从以下几个方面去全解(用了夸张的修辞手法)Mask,并尽可能地辅以图片说明和代码解释: 

1. Mask 的作用 

  • 处理非定长序列

    • RNN 中的 Mask 

    • Attention 中 Mask 

2. 防止标签泄露 

  • Transformer 中的 Mask 

  • BERT 中的 Mask 

  • XLNet 中的 Mask


Mask的作用

对于 NLP 中 mask 的作用,先上结论: 

1. padding mask:处理非定长序列,区分 padding 和非 padding 部分,如在 RNN 等模型和 Attention 机制中的应用等;

2. sequence mask:防止标签泄露,如:Transformer decoder 中的 mask 矩阵,BERT 中的 [Mask] 位,XLNet 中的 mask 矩阵等。

PS:padding mask 和 sequence mask非官方命名。

处理非定长序列 

在 NLP 中,文本一般是不定长的,所以在进行 batch 训练之前,要先进行长度的统一,过长的句子可以通过 truncating 截断到固定的长度,过短的句子可以通过 padding 增加到固定的长度,但是 padding 对应的字符只是为了统一长度,并没有实际的价值,因此希望在之后的计算中屏蔽它们,这时候就需要 Mask。


上图(图片参考 [1])为中文场景下,一个 batch=5 的,以字为单位的输入矩阵(也可以在分词后以词为单位)和 mask 矩阵,左图已经将文本 padding 到统一长度了,右图中的 1 表示有效字,0 代表无效字。

RNN中的Mask 

对于 RNN 等模型,本身是可以直接处理不定长数据的,因此它不需要提前告知  sequence length,如下是 PyTorch 下的 LSTM 定义:

nn.LSTM(input_size, hidden_size, *args, **kwargs)

最终,R-MeN 将这 3 个编码向量反馈给基于 CNN 的解码器,返回一个三元组的得分。
但是在实践中,为了 batch 训练,一般会把不定长的序列 padding 到相同长度,再用 mask 去区分非 padding 部分和 padding 部分。 
区分的目的是使得 RNN 只作用到它实际长度的句子,而不会处理无用的 padding 部分,这样 RNN 的输出和隐状态都会是对应句子实际的最后一位。另外,对于 token 级别的任务,也可以通过 mask 去忽略 padding 部分对应的 loss。 
不过,在 PyTorch 中,对 mask 的具体实现形式不是 mask 矩阵,而是通过一个句子长度列表来实现的,但本质一样。实现如下,sentence_lens 表示的是这个 batch 中每一个句子的实际长度(参考 [2])。
embed_input_x_packed = pack_padded_sequence(embed_input_x, sentence_lens, batch_first=True)
encoder_outputs_packed, (h_last, c_last) = self.lstm(embed_input_x_packed)
encoder_outputs, _ = pad_packed_sequence(encoder_outputs_packed, batch_first=True)
btw,在 PyTorch 的 Embedding 和 Loss 中也有对 padding 值的设置:
# padding_idx (int, optional): If given, pads the output with the embedding vector at 
# `padding_idx` (initialized to zeros) whenever it encounters the index.
embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)

# ignore_index (int, optional): Specifies a target value that is ignored
# and does not contribute to the input gradient.
criterion = nn.CrossEntropyLoss(ignore_index=0)
Attention中Mask 
在 Attention 机制中,同样需要忽略 padding 部分的影响,这里以 transformer encoder 中的 self-attention 为例: 
self-attention 中,Q 和 K 在点积之后,需要先经过 mask 再进行 softmax,因此,对于要屏蔽的部分,mask 之后的输出需要为负无穷,这样 softmax 之后输出才为 0。

▲ The self-attention calculation in matrix form (图片参考 [3])


def attention(query, key, value, mask=None, dropout=None):
    "Compute 'Scaled Dot Product Attention'"
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-2-1)) \
             / math.sqrt(d_k)
    if mask is not None:
        scores = scores.masked_fill(mask == 0-1e9# mask步骤,用 -1e9 代表负无穷
    p_attn = F.softmax(scores, dim = -1)
    if dropout is not None:
        p_attn = dropout(p_attn)
    return torch.matmul(p_attn, value), p_attn

▲ 代码参考 [4]

PS:上述两个参考都是非常好的 transformer 介绍文章,参考 1,图片与文字相得益彰,参考 2,代码与讲解相辅相成。


防止标签泄露

在语言模型中,常常需要从上一个词预测下一个词,但如果要在 LM 中应用 self attention 或者是同时使用上下文的信息,要想不泄露要预测的标签信息,就需要 mask 来“遮盖”它。不同的 mask 方式,也对应了一篇篇的 paper,这里选取典型的几个。 

Transformer中的Mask 

Transformer 是包括 Encoder和 Decoder的,Encoder中 self-attention 的 padding mask 如上,而 Decoder 还需要防止标签泄露,即在 t 时刻不能看到 t 时刻之后的信息,因此在上述 padding mask 的基础上,还要加上 sequence mask。 

sequence mask 一般是通过生成一个上三角矩阵来实现的,上三角区域对应要 mask 的部分。 

在 Transformer 的 Decoder 中,先不考虑 padding mask,一个包括四个词的句子 [A,B,C,D] 在计算了相似度 scores 之后,得到下面第一幅图,将 scores 的上三角区域 mask 掉,即替换为负无穷,再做 softmax 得到第三幅图。这样,比如输入 B 在 self-attention 之后,也只和 A,B 有关,而与后序信息无关。 

因为在 softmax 之后的加权平均中: B‘ = 0.48*A+0.52*B,而 C,D 对 B' 不做贡献。


▲ 图片参考 [5]


实际应用中,Decoder 需要结合 padding mask 和 sequence mask,下面在 PyTorch 框架下以一个很简化的例子展示 Transformer 中的两种 Mask。


import torch

def padding_mask(seq, pad_idx):
    return (seq != pad_idx).unsqueeze(-2)   # [B, 1, L]

def sequence_mask(seq):
    batch_size, seq_len = seq.size()
    mask = 1- 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

def test():
    # 以最简化的形式测试Transformer的两种mask
    seq = torch.LongTensor([[1,2,0]]) # batch_size=1, seq_len=3,padding_idx=0
    embedding = torch.nn.Embedding(num_embeddings=3, embedding_dim=10, padding_idx=0)
    query, key = embedding(seq), embedding(seq)
    scores = torch.matmul(query, key.transpose(-2-1))

    mask_p = padding_mask(seq, 0)
    mask_s = sequence_mask(seq)
    mask_decoder = mask_p & mask_s # 结合 padding mask 和 sequence mask

    scores_encoder = scores.masked_fill(mask_p==0-1e9# 对于scores,在mask==0的位置填充
    scores_decoder = scores.masked_fill(mask_decoder==0-1e9)

test()


对应的各 mask 值为:


# mask_p
[[[1 1 0]]]
# mask_s
[[[1 0 0]
  [1 1 0]
  [1 1 1]]]
# mask_decoder
[[[1 0 0]
  [1 1 0]
  [1 1 0]]]


可以看到 mask_decoder 的第三列为 0 ,对应 padding mask,上三角为 0,对应 sequence mask。 

BERT中的Mask 

BERT 实际上是 Transformer 的 Encoder,为了在语言模型的训练中,使用上下文信息又不泄露标签信息,采用了 Masked LM,简单来说就是随机的选择序列的部分 token 用 [Mask] 标记代替。这波 Mask 操作,思想很直接,实现很简单,效果很惊人。


BERT 之后,也有不少在 Mask 的范围和形式上做文章的,比如:ERNIE,但大同小异,不多赘述。 

而 XLNet 的 Mask 操作非常的巧(nan)妙(dong),如下。 

XLNet中的Mask 

XLNet 通过 Permutation Language Modeling 实现了不在输入中加 [Mask],同样可以利用上下文信息,关键的操作就是下面所示的 Attention Mask 机制。


但并不是那么好理解,要理解 XLNet 中的 Mask,一定要先看张俊林老师的:XLNet:运行机制及和 Bert 的异同比较 [6],再来看下面的内容。上图也是引自该文,这里再引用一句我认为非常关键的一段话: 

在 Transformer 内部,通过 Attention 掩码,从 X 的输入单词里面,也就是 Ti 的上文和下文单词中,随机选择 i-1 个,放到 Ti 的上文位置中,把其它单词的输入通过 Attention 掩码隐藏掉,于是就能够达成我们期望的目标(当然这个所谓放到 Ti 的上文位置,只是一种形象的说法,其实在内部,就是通过 Attention Mask,把其它没有被选到的单词 Mask 掉,不让它们在预测单词 Ti 的时候发生作用,如此而已。看着就类似于把这些被选中的单词放到了上文 Context_before 的位置了)。 

对于排列序列:3->2->4->1,通过 Attention Mask,在 self-attention 的加权平均计算中,以上图中的  为例: 

self-attention 计算之后 Content stream 中的 ,其中  表示第 2 个词对应的向量,其他同理。这样在  中就看到了它的下文 ,就好像是把  放到了它的上文位置一样,但实际顺序并没有改变。 

对序列进行排列的目的是为了生成这个 Attention Mask,再加上双流注意力去解决预测歧义的问题,可以说就是 Permutation Language Modeling 的全部了。 

到这里,就是本文全部的 Mask,但这肯定不是 NLP 中 Mask 的全部,但希望能帮助你去更好地理解 Mask。

参考文献

[1] https://www.cnblogs.com/neopenx/p/4806006.html
[2] https://zhuanlan.zhihu.com/p/34418001
[3] https://jalammar.github.io/illustrated-transformer/
[4] http://nlp.seas.harvard.edu/2018/04/03/attention.html#attention
[5] https://baijiahao.baidu.com/s?id=1652093322137148754&wfr=spider&for=pc
[6] https://zhuanlan.zhihu.com/p/70257427



点击以下标题查看更多往期内容: 



#投 稿 通 道#

 让你的论文被更多人看到 



如何才能让更多的优质内容以更短路径到达读者群体,缩短读者寻找优质内容的成本呢?答案就是:你不认识的人。


总有一些你不认识的人,知道你想知道的东西。PaperWeekly 或许可以成为一座桥梁,促使不同背景、不同方向的学者和学术灵感相互碰撞,迸发出更多的可能性。 


PaperWeekly 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是最新论文解读,也可以是学习心得技术干货。我们的目的只有一个,让知识真正流动起来。


📝 来稿标准:

• 稿件确系个人原创作品,来稿需注明作者个人信息(姓名+学校/工作单位+学历/职位+研究方向) 

• 如果文章并非首发,请在投稿时提醒并附上所有已发布链接 

• PaperWeekly 默认每篇文章都是首发,均会添加“原创”标志


📬 投稿邮箱:

• 投稿邮箱:hr@paperweekly.site 

• 所有文章配图,请单独在附件中发送 

• 请留下即时联系方式(微信或手机),以便我们在编辑发布时和作者沟通



🔍


现在,在「知乎」也能找到我们了

进入知乎首页搜索「PaperWeekly」

点击「关注」订阅我们的专栏吧



关于PaperWeekly


PaperWeekly 是一个推荐、解读、讨论、报道人工智能前沿论文成果的学术平台。如果你研究或从事 AI 领域,欢迎在公众号后台点击「交流群」,小助手将把你带入 PaperWeekly 的交流群里。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存