【发布时间】:2021-02-27 22:44:06
【问题描述】:
我(认为)我了解嵌入背后的基本原理:它们是快速执行操作 (one hot encoded vector) * matrix 的快捷方式,而 实际上 不执行该操作(利用此操作等效的事实索引到矩阵中),同时仍然保持与实际执行该操作相同的梯度。
我知道下面的例子:
e = Embedding(3, 2)
n = e(torch.LongTensor([0, 2]))
n 将是一个形状为 2, 2 的张量。
但是,我们也可以这样做:
p = nn.Parameter(torch.zeros([3, 2]).normal_(0, 0.01))
p[tensor([0, 2])]
并在没有嵌入的情况下获得相同的结果。
这本身不会令人困惑,因为在第一个示例中,n 有一个名为 EmbeddingBackward 的 grad_fn,而在第二个示例中,p 有一个名为 IndexBackward 的 grad_fn,即我们所期望的,因为我们知道嵌入模拟了不同的导数。
令人困惑的部分是他们使用嵌入来计算电影推荐的 fastbook 的第 8 章。但是,他们在没有嵌入的情况下以基本相同的方式做到这一点,并且模型仍然有效。我希望没有嵌入的版本会失败,因为导数不正确。
嵌入版本:
class DotProductBias(Module):
def __init__(self, n_users, n_movies, n_factors, y_range=(0,5.5)):
self.user_factors = Embedding(n_users, n_factors)
self.user_bias = Embedding(n_users, 1)
self.movie_factors = Embedding(n_movies, n_factors)
self.movie_bias = Embedding(n_movies, 1)
self.y_range = y_range
def forward(self, x):
users = self.user_factors(x[:,0])
movies = self.movie_factors(x[:,1])
res = (users * movies).sum(dim=1, keepdim=True)
res += self.user_bias(x[:,0]) + self.movie_bias(x[:,1])
return sigmoid_range(res, *self.y_range)
没有的版本:
def create_params(size):
return nn.Parameter(torch.zeros(*size).normal_(0, 0.01))
class DotProductBias(Module):
def __init__(self, n_users, n_movies, n_factors, y_range=(0,5.5)):
self.user_factors = create_params([n_users, n_factors])
self.user_bias = create_params([n_users])
self.movie_factors = create_params([n_movies, n_factors])
self.movie_bias = create_params([n_movies])
self.y_range = y_range
def forward(self, x):
users = self.user_factors[x[:,0]]
movies = self.movie_factors[x[:,1]]
res = (users*movies).sum(dim=1)
res += self.user_bias[x[:,0]] + self.movie_bias[x[:,1]]
return sigmoid_range(res, *self.y_range)
有人知道怎么回事吗?
【问题讨论】:
-
nn.Parameter用于为自定义操作创建权重。nn.Embedding在后台创建一个查找表(如字典),并为您词汇表中曾经通过嵌入层的每个标记存储一个自定义的随机嵌入。 -
Embedding包装了一些额外的功能,例如标准化、为使用静态向量填充的索引保留索引、稀疏索引和梯度缩放。原生索引运算符也有点混乱,更容易出错。