Мне было интересно, как справиться с операциями на месте в PyTorch. Насколько я помню, использование автограда на месте всегда было проблематичным.
И на самом деле я удивлен, что приведенный ниже код работает, хотя я его не тестировал, я считаю, что этот код вызвал бы ошибку в версии 0.3.1
.
В основном я хочу, чтобы сделать определенную позицию тензорного вектора на определенное значение, например:
my_tensor[i] = 42
Рабочий пример кода:
# test parameter a
a = torch.rand((2), requires_grad=True)
print('a ', a)
b = torch.rand(2)
# calculation
c = a + b
# performing in-place operation
c[0] = 0
print('c ', c)
s = torch.sum(c)
print('s ', s)
# calling backward()
s.backward()
# optimizer step
optim = torch.optim.Adam(params=[a], lr=0.5)
optim.step()
# changed parameter a
print('changed a', a)
Выход:
a tensor([0.2441, 0.2589], requires_grad=True)
c tensor([0.0000, 1.1511], grad_fn=<CopySlices>)
s tensor(1.1511, grad_fn=<SumBackward0>)
changed a tensor([ 0.2441, -0.2411], requires_grad=True)
Так очевидно в версии 0.4.1
. это работает нормально, без предупреждений или ошибок.
Ссылка на эту статью в документации: автоград-механик
Supporting in-place operations in autograd is a hard matter, and we discourage their use in most cases. Autograd’s aggressive buffer freeing and reuse makes it very efficient and there are very few occasions when in-place operations actually lower memory usage by any significant amount. Unless you’re operating under heavy memory pressure, you might never need to use them.
Но хоть и работает, Использование операций на месте в большинстве случаев не рекомендуется.
Итак, мои вопросы:
Как много влияет на производительность при использовании операций на месте?
Как мне обойтись с использованием операций на месте в таких случаях, когда я хочу установить для одного элемента тензора определенное значение?
Заранее спасибо!
Я не уверен, насколько операция на месте влияет на производительность, но могу ответить на второй вопрос. Вы можете использовать маску вместо операций на месте.
a = torch.rand((2), requires_grad=True)
print('a ', a)
b = torch.rand(2)
# calculation
c = a + b
# performing in-place operation
mask = np.zeros(2)
mask[1] =1
mask = torch.tensor(mask)
c = c*mask
...
Для вашего второго запроса, когда вы выполняете c[i] = i
или аналогичные операции, обычно вызывается __setitem__
. Чтобы выполнить эту операцию на месте, вы можете попробовать вызвать функцию __setitem__
(если она выполняет операцию c[i] = i
.
Возможно, это не прямой ответ на ваш вопрос, а просто для информации.
Операции на месте работают для тензоров нелистовой в вычислительном графе.
Тензоры листьев - это тензоры, которые являются «концами» вычислительного графа. Официально (из is_leaf
атрибут здесь),
For Tensors that have requires_grad which is True, they will be leaf Tensors if they were created by the user. This means that they are not the result of an operation and so grad_fn is None.
Пример, который работает без ошибок:
a = torch.tensor([3.,2.,7.], requires_grad=True)
print(a) # tensor([3., 2., 7.], requires_grad=True)
b = a**2
print(b) # tensor([ 9., 4., 49.], grad_fn=<PowBackward0>)
b[1] = 0
print(b) # tensor([ 9., 0., 49.], grad_fn=<CopySlices>)
c = torch.sum(2*b)
print(c) # tensor(116., grad_fn=<SumBackward0>)
c.backward()
print(a.grad) # tensor([12., 0., 28.])
С другой стороны, операции на месте не работают для тензоров лист.
Пример, вызывающий ошибку:
a = torch.tensor([3.,2.,7.], requires_grad=True)
print(a) # tensor([3., 2., 7.], requires_grad=True)
a[1] = 0
print(a) # tensor([3., 0., 7.], grad_fn=<CopySlices>)
b = a**2
print(b) # tensor([ 9., 0., 49.], grad_fn=<PowBackward0>)
c = torch.sum(2*b)
print(c) # tensor(116., grad_fn=<SumBackward0>)
c.backward() # Error occurs at this line.
# RuntimeError: leaf variable has been moved into the graph interior
Я полагаю, что операция b[1]=0
в первом примере выше на самом деле не является операцией на месте. Я предполагаю, что он создает новый тензор с операцией CopySlices. «Старый b» перед операцией на месте может храниться внутри (просто его имя перезаписывается «новым b»). Нашел красивую фигурку здесь.
старый b --- (CopySlices) ----> новый b
С другой стороны, тензор a
- это листовой тензор. После операции CopySlices a[1]=0
он становится промежуточным тензором. Чтобы избежать такой сложной смеси между листовыми тензорами и промежуточными тензорами при обратном распространении, операции CopySlices над листовыми тензорами запрещено сосуществовать с обратным.
Это всего лишь мое личное мнение, поэтому обращайтесь к официальным документам.
Примечание:
Хотя операции на месте работают для промежуточных тензоров, будет безопасно использовать как можно больше клонирования и отсоединения, когда вы выполняете некоторые операции на месте, чтобы явно создать новый тензор, который не зависит от вычислительного графа.