PyTorch Gradient не работает после добавления в ("cuda: 0") - PullRequest
1 голос
/ 03 августа 2020

Я собираюсь дать контекстно-свободный набор кода. Код работает до добавления «к (устройству)».

def get_input_layer(word_idx) :
x = torch.zeros(vocabulary_size).float().to(device)
x[word_idx] = 1.0
return x

embedding_dims = 5
device = torch.device("cuda:0")
W1 = Variable(torch.randn(embedding_dims, vocabulary_size).float(), requires_grad=True).to(device)
W2 = Variable(torch.randn(vocabulary_size, embedding_dims).float(), requires_grad=True).to(device)
num_epochs = 100
learning_rate = 0.001

x = Variable(get_input_layer(data)).float().to(device)
y_true = Variable(torch.from_numpy(np.array([target])).long()).to(device)
z1 = torch.matmul(W1, x).to(device)
z2 = torch.matmul(W2, z1).to(device)

log_softmax = F.log_softmax(z2, dim=0).to(device)

loss = F.nll_loss(log_softmax.view(1,-1), y_true).to(device)
loss_val += loss.data
loss.backward().to(device)

## Optimize values. This is done by hand rather than using the optimizer function
W1.data -= learning_rate * W1.grad.data
W2.data -= learning_rate * W2.grad.data

Я получаю

Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'data'

Что срабатывает именно в строке

W1.data -= learning_rate * W1.grad.data

Проверяю, это подтверждается, потому что W1.grad по какой-то причине имеет значение None.

И этот цикл повторяется после очистки градиентов. Это отлично работает, если я удалю все .to (устройство). Что я делаю неправильно, пытаясь запустить это на моем GPU?

Спасибо за ваше время.

1 Ответ

4 голосов
/ 03 августа 2020

Это происходит потому, что .to возвращает новый тензор, не являющийся листовым. Вы должны установить requires_grad после перехода на желаемое устройство. Кроме того, интерфейс Variable устарел уже давно, еще до появления pytorch 1.0. Он ничего не делает (за исключением того, что в этом случае действует как слишком сложный способ установки requires_grad).

Рассмотрим

W1 = Variable(torch.randn(embedding_dims, vocabulary_size).float(), requires_grad=True).to(device)

Проблема в том, что есть два разных тензора . Разбив его, мы могли бы переписать то, что вы делаете, как

W1a = Variable(torch.randn(embedding_dims, vocabulary_size).float(), requires_grad=True)
W1 = W1a.to(device)

Обратите внимание, что W1a требует градиента, но W1 происходит от W1a, поэтому он не считается листовым тензором, поэтому атрибут .grad для W1a будет обновлен, а W1 - нет. В вашем коде у вас больше нет прямой ссылки на W1a, поэтому у вас не будет доступа к градиентам.

Вместо этого вы можете сделать

W1 = torch.randn(embedding_dims, vocabulary_size).float().to(device)
W1.required_grad_(True)

, который правильно установит W1 быть листовым тензором после передачи на другое устройство.

Обратите внимание, что для вашего случая c мы также могли бы просто использовать аргументы device, dtype и requires_grad для torch.randn и просто сделать

W1 = torch.randn(embedding_dims, vocabulary_size, dtype=torch.float, device=device, requires_grad=True)

Большинство Функции pytorch, которые инициализируют новые тензоры, поддерживают эти три аргумента, которые могут помочь избежать проблем, с которыми вы столкнулись.

Чтобы ответить на дополнительный вопрос OP в комментариях:

Есть ли хорошее место, которое я бы нашел в документации?

AFAIK, документация не решает конкретно эту проблему. Это своего рода комбинация того, как работают переменные в python и как работает механика автограда в pytorch.

Предполагая, что вы хорошо разбираетесь в переменных в python, вы можете прийти к выводам из этого ответа самостоятельно, при первом чтении Tensor.is_leaf, в частности

, они будут листовыми тензорами, если они были созданы пользователем. Это означает, что они не являются результатом операции , поэтому grad_fn равно Нет.

И, кроме того, документация для Tensor.to, в которой говорится

Если тензор self уже имеет правильные torch.dtype и torch.device, то возвращается self. В противном случае возвращаемый тензор является копией self с желаемыми torch.dtype и torch.device.

Поскольку Tensor.to возвращает копию, а копия операции, то из документации должно быть ясно, что тензор W1 в исходном коде не является тензорным листом.

...