#python #pytorch #theano
Вопрос:
Я пытаюсь перенести код с 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.
В Теано:
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
Вышеупомянутый блок создает массив идентификаторов, хранящихся в памяти графического процессора. Который я реализовал в классе 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))
В то же время здесь используется отрицательный генератор:
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))
where the train_function is defined as:
train_function = function(inputs=[X, Y, M, R], outputs=cost, updates=updates, allow_input_downcast=True, on_unused_input=’ignore’)
and an example loss function is as follows:
def bpr(self, yhat, M):
return T.cast(T.sum(-T.log(T.nnet.sigmoid(gpu_diag(yhat, keepdims=True)-yhat))), theano.config.floatX)
In PyTorch, I’ve attempted to implement the negative generator in the same way:
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 amp; 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
The above generator is used in train_epoch method in Trainer class:
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()
The same loss function is defined as:
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
from my understanding, in the Theano version, in_idx and y (input item idxs, target item idxs respectively), are of the same shape (batch_size), (batch_size). A matrix is produced where the diag (note: keepdims=True or expand_as() in both loss functions) comprises the scores for target items while remaining elements are treated as intra-mini-batch negative samples. Since this is consistent across implementations, how then is the loss calculated on the additional 2048 negative samples in the Theano version?
В реализации процессора Theano (которая устарела):
y = np.hstack([out_idx, sample])
Реализация графического процессора:
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. В результате возникает следующая ошибка:
Ошибка времени выполнения: Ожидаемый скрытый размер (3, 2080, 100), получен [3, 32, 100].
Как можно устранить это несоответствие размеров?
Я попытался изменить ввод (скопировать входные идентификаторы)/ цель (с объединенными отрицательными образцами), а затем выполнить цикл (но это не распараллеливается и, следовательно, чрезвычайно медленно).
Я также попытался изменить форму на новый размер пакета (n_samples batch_size) и с помощью idx_input = np.repeat(idx_input, (self.n_sample self.batch_size) // self.batch_size) при вызове init_hidden(), однако это приводит к другим ошибкам во время выполнения, 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]
С уважением
Комментарии:
1. Для ясности, вы пытаетесь подогнать мини-партию под размер, который меньше заданного размера партии? Я думаю, что реализация PyTorch может пропустить много образцов позже в процессе обучения.