Да, вы правы.Когда вы уже выполните обратное распространение через outputs
в первый раз (первая итерация), буферы будут освобождены и в следующий раз произойдет сбой (следующая итерация вашего цикла) , потому что тогда необходимы данные для этоговычисления уже удалены.
Да, график становится все больше и больше, поэтому он может быть медленнее в зависимости от использования GPU (или CPU) и вашей сети.Я использовал это один раз, и это было намного медленнее, однако это сильно зависит от архитектуры вашей сети.Но вам, безусловно, потребуется больше памяти с retain_graph=True
, чем без.
В зависимости от вашей формы outputs
и labels
вы сможете рассчитать потери для всех ваших outputs
и labels
сразу:
criterion(outputs, labels)
Вы должны пропустить цикл j
- , тогда это также сделает ваш код быстрее.Возможно, вам нужно изменить (соответственно view
) ваши данные, но это должно работать нормально.
Если вы по какой-то причине не можете этого сделать, вы можете вручную суммировать потери на тензор и вызвать backward
после петли.Это тоже должно работать нормально, но медленнее, чем решение выше.
Итак, чем ваш код будет выглядеть так:
# init loss tensor
loss = torch.tensor(0.0) # move to GPU if you're using one
for j in range(4):
# summing up your loss for every j
loss += criterion(outputs[j, :].unsqueeze(0), labels[j].unsqueeze(0))
# ...
# calling backward on the summed loss - getting gradients
loss.backward()
# as you call backward now only once on the outputs
# you shouldn't get any error and you don't have to use retain_graph=True
Редактировать:
Накопление убытков и обратный вызов позже полностью эквивалентны, вот небольшой пример с и без накопления убытков:
Сначала создаем некоторые данные data
:
# w in this case will represent a very simple model
# I leave out the CE and just use w to map the output to a scalar value
w = torch.nn.Linear(4, 1)
data = [torch.rand(1, 4) for j in range(4)]
data
выглядит так:
[tensor([[0.4593, 0.3410, 0.1009, 0.9787]]),
tensor([[0.1128, 0.0678, 0.9341, 0.3584]]),
tensor([[0.7076, 0.9282, 0.0573, 0.6657]]),
tensor([[0.0960, 0.1055, 0.6877, 0.0406]])]
Давайте сначала сделаем так, как вы делаете, вызывая в обратном направлении для каждой итерации j
отдельно:
# code for directly applying backward
# zero the weights layer w
w.zero_grad()
for j, inp in enumerate(data):
# activate grad flag
inp.requires_grad = True
# remove / zero previous gradients for inputs
inp.grad = None
# apply model (only consists of one layer in our case)
loss = w(inp)
# calling backward on every output separately
loss.backward()
# print out grad
print('Input:', inp)
print('Grad:', inp.grad)
print()
print('w.weight.grad:', w.weight.grad)
Вот распечатка с каждымвход и соответствующий градиент и градиенты для модели соотв.layer w
в нашем упрощенном случае:
Input: tensor([[0.4593, 0.3410, 0.1009, 0.9787]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.1128, 0.0678, 0.9341, 0.3584]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.7076, 0.9282, 0.0573, 0.6657]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.0960, 0.1055, 0.6877, 0.0406]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
w.weight.grad: tensor([[1.3757, 1.4424, 1.7801, 2.0434]])
Теперь вместо обратного вызова один раз для каждой итерации j мы накапливаем значения и вызываем backward
для суммы и сравниваем результаты.:
# init tensor for accumulation
loss = torch.tensor(0.0)
# zero layer gradients
w.zero_grad()
for j, inp in enumerate(data):
# activate grad flag
inp.requires_grad = True
# remove / zero previous gradients for inputs
inp.grad = None
# apply model (only consists of one layer in our case)
# accumulating values instead of calling backward
loss += w(inp).squeeze()
# calling backward on the sum
loss.backward()
# printing out gradients
for j, inp in enumerate(data):
print('Input:', inp)
print('Grad:', inp.grad)
print()
print('w.grad:', w.weight.grad)
Давайте посмотрим на результаты:
Input: tensor([[0.4593, 0.3410, 0.1009, 0.9787]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.1128, 0.0678, 0.9341, 0.3584]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.7076, 0.9282, 0.0573, 0.6657]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.0960, 0.1055, 0.6877, 0.0406]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
w.grad: tensor([[1.3757, 1.4424, 1.7801, 2.0434]])
При сравнении результатов мы видим, что оба они одинаковы.
Это очень простой пример, но тем не менее мы можем видеть, что вызов backward()
для каждого отдельного тензора и суммирование тензоров, а затем вызов backward()
эквивалентны с точки зрения результирующих градиентов как для входных данных, так и для весов.
Когда вы используете CE для всех j одновременно, как описано в 3. , вы можете использовать флаг reduction='sum'
для архивациито же самое, что и выше при суммировании значений CE, по умолчанию «среднее», что, вероятно, приводит к несколько иным результатам.