python - 移植 theano function() 并更新到 Pytorch(负采样 RuntimeError: Expected hidden size)
问题描述
我正在尝试将代码从 Theano 移植到 PyTorch,坦率地说,我对这两个框架的实际工作方式了解非常有限,所以请多多包涵!我将不胜感激任何有助于我加深理解的帮助。
https://github.com/hidasib/GRU4Rec/blob/master/gru4rec.py#L614
是我要移植的代码。部分代码已经移植到 PyTorch,可以在这里找到:https ://github.com/hungthanhpham94/GRU4REC-pytorch/tree/master/lib
PyTorch 实现中缺少原始代码中存在的许多功能。我已经做了很多修改,但是在负采样方面遇到了障碍。
在原始代码中,定义了批次大小(默认 = 32),并且额外的负样本(默认 n_sample = 2048 每批次 afaik)存储在 GPU 内存中。
在 Theano 中:
P = theano.shared(pop.astype(theano.config.floatX), name='P')
self.ST = theano.shared(np.zeros((generate_length, self.n_sample), dtype='int64'))
self.STI = theano.shared(np.asarray(0, dtype='int64'))
X = mrng.uniform((generate_length*self.n_sample,))
updates_st = OrderedDict()
updates_st[self.ST] = gpu_searchsorted(P, X, dtype_int64=True).reshape((generate_length, self.n_sample))
updates_st[self.STI] = np.asarray(0, dtype='int64')
generate_samples = theano.function([], updates=updates_st)
generate_samples()
sample_pointer = 0
上面的块正在创建一个存储在 gpu 内存中的 idxs 数组。我在 DataLoader 类中将其实现为:
def generate_negatives(self):
P = torch.FloatTensor(self.pop)
ST = torch.LongTensor(np.zeros((self.generate_length, self.n_sample), dtype='int64'))
STI = torch.LongTensor(np.asarray(0, dtype='int64'))
X = torch.rand((self.generate_length * self.n_sample,))
return torch.searchsorted(P, X).reshape((self.generate_length, self.n_sample))
在 Theano 中,这里使用了负生成器:
while not finished:
........
else:
y = out_idx
if self.n_sample:
if sample_pointer == generate_length:
generate_samples()
sample_pointer = 0
sample_pointer += 1
reset = (start+i+1 == end-1)
cost = train_function(in_idx, y, len(iters), reset.reshape(len(reset), 1))
其中 train_function 定义为:
train_function = function(inputs=[X, Y, M, R], outputs=cost, updates=updates, allow_input_downcast=True, on_unused_input='ignore')
一个示例损失函数如下:
def bpr(self, yhat, M):
return T.cast(T.sum(-T.log(T.nnet.sigmoid(gpu_diag(yhat, keepdims=True)-yhat))), theano.config.floatX)
在 PyTorch 中,我尝试以相同的方式实现负生成器:
while not finished:
minlen = (end - start).min()
# Item indices(for embedding) for clicks where the first sessions start
idx_target = df.item_idx.values[start]
for i in range(minlen - 1):
# Build inputs & targets
idx_input = idx_target
idx_target = df.item_idx.values[start + i + 1]
if self.n_sample:
if sample_pointer == self.generate_length:
neg_samples = self.generate_negatives()
sample_pointer = 0
sample = neg_samples[sample_pointer]
sample_pointer += 1
# idx_target = np.hstack([idx_target, sample]) # like cpu version (doesn't work due to hidden size)
input = torch.LongTensor(idx_input)
target = torch.LongTensor(idx_target)
yield input, target, mask
上面的生成器用于 Trainer 类的 train_epoch 方法:
if self.n_sample:
dataloader = DataLoader(self.train_data, self.batch_size, self.n_sample, self.generate_length)
else:
dataloader = DataLoader(self.train_data, self.batch_size)
for ii, (input, target, mask) in enumerate(dataloader):
input = input.to(self.device)
target = target.to(self.device)
self.optim.zero_grad()
hidden = reset_hidden(hidden, mask).detach()
logit, hidden = self.model(input, hidden)
# output sampling
logit_sampled = logit[:, target.view(-1)]
loss = self.loss_func(logit_sampled)
losses.append(loss.item())
loss.backward()
self.optim.step()
相同的损失函数定义为:
class BPRLoss(nn.Module):
def __init__(self):
super(BPRLoss, self).__init__()
def forward(self, logit):
diff = logit.diag().view(-1, 1).expand_as(logit) - logit
loss = -torch.mean(F.logsigmoid(diff))
return loss
据我了解,在 Theano 版本中,in_idx 和 y(分别为输入项 idxs、目标项 idxs)具有相同的形状(batch_size)、(batch_size)。生成一个矩阵,其中 diag(注意:两个损失函数中的 keepdims=True 或 expand_as())包含目标项目的分数,而其余元素被视为小批量内的负样本。既然这在不同的实现中是一致的,那么如何在 Theano 版本中额外的 2048 个负样本上计算损失呢?
在 Theano CPU 实现中(已弃用):
y = np.hstack([out_idx, sample])
GPU实现:
def model(self, X, H, M, R=None, Y=None, drop_p_hidden=0.0, drop_p_embed=0.0, predict=False):
sparams, full_params, sidxs = [], [], []
if (hasattr(self, 'ST')) and (Y is not None) and (not predict) and (self.n_sample > 0):
A = self.ST[self.STI]
Y = T.concatenate([Y, A], axis=0)
如果我们的批量大小为 32,n_sample 为 2048,使用上述逻辑(将样本连接到目标),我们将产生大小为 32 的不变输入,而我们的目标大小为 32 + 2048 = 2080。结果如下错误:
RuntimeError: 预期的隐藏大小 (3, 2080, 100),得到 [3, 32, 100]。
这种尺寸不匹配如何解决?
我尝试过重塑输入(复制输入 idxs)/目标(带有连接的负样本)然后循环遍历(但这不是并行化的,因此非常慢)。
我还尝试在调用 init_hidden() 时将形状更改为新的批量大小 (n_samples + batch_size) 和 idx_input = np.repeat(idx_input, (self.n_sample + self.batch_size) // self.batch_size),但是这个产生其他运行时错误、OOM 和RuntimeError: The expanded size of the tensor (9248) must match the existing size (544) at non-singleton dimension 0. Target sizes: [9248, 544]. Tensor sizes: [544, 1]
亲切的问候
解决方案
推荐阅读
- javascript - 使用 vuejs 时 for 循环出现问题
- git - 我的 Google AppEngine 部署失败。我怎样才能恢复?
- bash - 为什么这个脚本可以在机器上运行一个而不是另一个?macOS 卡塔利娜
- android - 导航抽屉 - 为片段添加标签
- r - 从多元法线加速嵌套绘制,其中每个绘制在 r 中具有不同的平均向量
- java - 创建通知通道(React Native)
- c# - 如何在 EF Core 中以一对多关系重命名列?
- javascript - 将所有对象字段转换为布尔值
- azure - Azure Devops 服务连接在远程网络上的 vnet 对等互连上失败,权限问题?
- solr - Ansible 随机欺骗我