深入理解padding_idx(nn.Embedding、nn.Embedding.from_pretrained)

这个参数出现在一些地方,例如:
nn.Embedding、nn.Embedding.from_pretrained。

import torch
import torch.nn as nn
import torch.optim as optim

参数含义

如下:

padding_idx (int, optional) – If specified, the entries at padding_idx do not contribute to the gradient; therefore, the embedding vector at padding_idx is not updated during training, i.e. it remains as a fixed “pad”. For a newly constructed Embedding, the embedding vector at padding_idx will default to all zeros, but can be updated to another value to be used as the padding vector.

可以看到,这个是用来指定embedding的某行是填充值的参数,指定之后,这一行将不会被更新,并且默认pytorch构造的时候会把这一行初始化为0,并且由于不被更新,所以一直为0。难点在于最后一句,不是说不会被更新吗?怎么又说

but can be updated to another value to be used as the padding vector.

这里我们对这些疑惑分别举例子解答。1.默认初始化为0,且训练时不会被更新的验证:

class cnn(nn.Module):
    def __init__(self,a):
        super(cnn,self).__init__()
        self.emb=nn.Embedding(2,3,padding_idx=0)
    def forward(self,x):
        x=self.emb(x)
        return x.sum()
model=cnn(a)
print(model.emb.weight)

结果如下,默认0行初始化为0.

Parameter containing:
tensor([[0.0000, 0.0000, 0.0000],
[0.2302, 0.0929, 0.2190]], requires_grad=True)

默认不会更新,一直是0,验证如下:

optimizer=optim.Adam(model.parameters(),lr=0.001)
epoch=1
x=torch.tensor([0,1],dtype=torch.int64)
for i in range(epoch):
    optimizer.zero_grad()
    loss=model(x)
    loss.backward()
    optimizer.step()
print(model.emb.weight)

Parameter containing:
tensor([[0.0000, 0.0000, 0.0000],
[0.2292, 0.0919, 0.2180]], requires_grad=True)

可以看到,第1行的值变小了,更新了,但是0行还是没有更新。

问题2,为什么又说这个可以被更新?首先要吐槽的是,这句话确实不好理解,而且个人觉得是作者没有表述好这句话,导致了大家理解困难。其实这个can be updated,就是指赋值修改的意思。

model.emb.weight.data[0]=torch.ones(3)
model.emb.weight

Parameter containing:
tensor([[1.0000, 1.0000, 1.0000],
[0.2292, 0.0919, 0.2180]], requires_grad=True)

所以我还是想吐槽一下作者说这句话,我觉得是句废话,因为赋值无论哪一行都是可以的,这属于基本操作,根本不用告诉大家。

至于nn.Embedding.from_pretrained中的Padding_idx大部分含义都和这里的一样,只有一点不一样,那就是参数的值不是默认为0.0,而是固定为你传入的那个矩阵所在的行。

a=torch.rand(2,3)
print(a)

tensor([[0.0425, 0.3806, 0.1419],
[0.2301, 0.3789, 0.6342]])

nn.Embedding.from_pretrained(a,padding_idx=0)

此时就是a的0行为填充行。

[0.0425, 0.3806, 0.1419]

补充一点,其实上述写法是没有意义的,因为from_pretrained默认是不求导的,此时,0行1行都是固定的,那么你告诉模型padding_idx=0又有什么意义呢?下面场景才有意义。

nn.Embedding.from_pretrained(a,padding_idx=0,freeze=False)