首页 > 解决方案 > 移植 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]

亲切的问候

标签: pythonpytorchtheano

解决方案


推荐阅读