是什么
mask矩阵是什么?是一个由0和1组成的矩阵。一个例子是,在自然语言处理(NLP)中,句子的长度是不等长的,但因为我们经常将句子组成mini-batch用以训练,因此那些长度较短的句子都会在句尾进行填充0,也即padding的操作。一个mask矩阵即用以指示哪些是真正的数据,哪些是padding。如:
图片来源:Theano:LSTM源码解析
其中mask矩阵中1代表真实数据;0代表padding数据。
为什么
为什么要使用mask矩阵?使用mask矩阵是为了让那些被mask掉的tensor不会被更新。考虑一个tensor T的size(a,b),同样大小的mask矩阵M,相乘后,在反向回传的时候在T对应mask为0的地方,0的梯度仍为0。因此不会被更新。
怎么做
接下来介绍几种(可能不全)使用mask的场景。
对输入进行mask
考虑NLP中常见的句子不等长的情况。设我们的输入的batch I:(batch_size,max_seqlen),我们在过一层Embedding层之前,
在过了一层Embedding层,则有 E:(batch_size,max_seqlen,embed_dim),如果我们希望Embedding是更新的(比如我们的Embedding是随机初始化的,那当然Embedding需要更新),但我们又不希望padding更新。
一种方法即令E与M相乘。其中M是mask矩阵(batch_size,max_seqlen,1) (1是因为要broadcast),这样在Embedding更新梯度时,因为mask矩阵的关系,padding位置上的梯度就是0。
当然在Pytorch中还可以直接显式地写:
1 | self.embedding = nn.Embedding(vocab_size, embed_dim,padding_idx=0) |
而此时应当将padding显式添加到词典的第一个。
对模型中间进行mask
一个很经典的场景就是dropout。
对于参数矩阵W:(h,w),同样大小的mask矩阵M,在前向传播时令W’=W*M,则在反向传播时,M中为0的部分不被更新。
当然,我们可以直接调用PyTorch中的包nn.Dropout()
1 | m = nn.Dropout(p=0.2) |
对loss进行mask
考虑NLP中的language model,每个词都需要预测下一个词,在一个batch中句子总是有长有短,对于一个短句,此时在计算loss的时候,会出现这样的场景:<pad>
词要预测下一个<pad>
词。举个例子:三个句子[a,b,c,d],[e,f,g],[h,i],在组成batch后,会变成
X:
a | b | c | d |
e | f | g | <pad> |
h | i | <pad> | <pad> |
Y:
b | c | d | <pad> |
f | g | <eos> | <pad> |
i | <eos> | <pad> | <pad> |
X是输入,Y是预测。那么从第三行可以看出,<pad>
在预测下一个<pad>
。这显然是有问题的。
一种解决方案就是使用mask矩阵,在loss的计算时,将那些本不应该计算的mask掉,使得其loss为0,这样就不会反向回传了。
具体实践:在PyTorch中,以CrossEntropy为例:
1 | class torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, |
如果reduction=None
则会返回一个与输入同样大小的矩阵。在与mask矩阵相乘后,再对新矩阵进行mean操作。
在PyTorch实践上还可以可以这么写:
1 | masked_outputs = torch.masked_select(dec_outputs, mask) |
另一种更为简单的解决方案是,直接在CrossEntropy中设ignore_index=0
,这样,在计算loss的时候,发现target=0时,会自动不对其进行loss的计算。其本质和mask矩阵是一致的。
总结
mask矩阵可以用在任何地方,只要希望与之相乘的tensor相对应的地方不更新就可以进行mask操作。