Что означает параметр preserve_graph в методе Variable backward()?

Я перехожу к учебному пособию по переносу нейронной передачи, и я смущен об использовании retain_variable (устаревший, теперь называемый retain_graph). Пример кода показывает:

class ContentLoss(nn.Module):

    def __init__(self, target, weight):
        super(ContentLoss, self).__init__()
        self.target = target.detach() * weight
        self.weight = weight
        self.criterion = nn.MSELoss()

    def forward(self, input):
        self.loss = self.criterion(input * self.weight, self.target)
        self.output = input
        return self.output

    def backward(self, retain_variables=True):
        #Why is retain_variables True??
        self.loss.backward(retain_variables=retain_variables)
        return self.loss

Из документации

preserve_graph (bool, optional) - Если False, граф, используемый для вычисления града, будет освобожден. Обратите внимание, что почти во всех случаях установка этого параметра в True не требуется и часто может работать более эффективно. Значение по умолчанию для create_graph.

Поэтому, установив retain_graph= True, мы не освобождаем память, выделенную для графика на обратном проходе. В чем преимущество сохранения этой памяти, зачем она нам нужна?

Ответ 1

@cleros довольно подробно говорит об использовании retain_graph=True. По сути, он сохранит любую необходимую информацию для вычисления определенной переменной, чтобы мы могли сделать обратный переход на нее.

Иллюстративный пример

enter image description here

Предположим, что мы имеем приведенный выше график вычислений. Переменная d и e - это выход, а a - вход. Например,

import torch
from torch.autograd import Variable
a = Variable(torch.rand(1, 4), requires_grad=True)
b = a**2
c = b*2
d = c.mean()
e = c.sum()

когда мы делаем d.backward(), это нормально. После этого вычисления часть графика, вычисляющая d, по умолчанию будет освобождена для сохранения памяти. Поэтому, если мы делаем e.backward(), появится сообщение об ошибке. Чтобы сделать e.backward(), мы должны установить параметр retain_graph True в d.backward(), т. d.backward()

d.backward(retain_graph=True)

Пока вы используете retain_graph=True в своем обратном методе, вы можете сделать обратный в любое время:

d.backward(retain_graph=True) # fine
e.backward(retain_graph=True) # fine
d.backward() # also fine
e.backward() # error will occur!

Более полезную дискуссию можно найти здесь.

Реальный случай использования

Сейчас реальный прецедент - это многозадачное обучение, в котором у вас много потерь, которые могут быть на разных уровнях. Предположим, что у вас есть 2 поражения: loss1 и loss2 и они находятся в разных слоях. Для того, чтобы отменить градиент loss1 и loss2 по отношению к узнаваемому весу вашей сети независимо. Вы должны использовать retain_graph=True в backward() методе в первом обратном распространении.

# suppose you first back-propagate loss1, then loss2 (you can also do the reverse)
loss1.backward(retain_graph=True)
loss2.backward() # now the graph is freed, and next process of batch gradient descent is ready
optimizer.step() # update the network parameters

Ответ 2

Это очень полезная функция, когда у вас более одного выхода сети. Здесь приведен полный пример: представьте, что вы хотите создать некоторую случайную сверточную сеть, в которой вы можете задать два вопроса: содержит ли входное изображение кошку, и содержит ли изображение автомобиль?

Один из способов сделать это состоит в том, чтобы иметь сеть, которая разделяет сверточные слои, но имеет следующие два параллельных уровня классификации (простите мой ужасный график ASCII, но это должно быть три конвейера, за которыми следуют три полностью связанных слоя, один для кошек и один для автомобилей):

                    -- FC - FC - FC - cat?
Conv - Conv - Conv -|
                    -- FC - FC - FC - car?

Учитывая картину, в которой мы хотим задействовать оба ветки, при обучении сети, мы можем сделать это несколькими способами. Сначала (что, вероятно, было бы лучше всего здесь, иллюстрируя, как плохо выглядит пример), мы просто вычисляем потерю на обеих оценках и суммируем убытки, а затем возвращаем обратно.

Однако есть еще один сценарий, в котором мы хотим сделать это последовательно. Сначала мы хотим backprop через одну ветку, а затем через другую (раньше у меня был этот прецедент, поэтому он не полностью составлен). В этом случае запуск .backward() на одном графике также уничтожит любую информацию о градиенте в сверточных слоях, а вторая ветвь сверточных вычислений (так как они являются единственными, разделяемыми с другой ветвью) больше не будет содержать график! Это означает, что, когда мы пытаемся вернуться через вторую ветку, Pytorch выдает ошибку, так как не может найти граф, соединяющий вход с выходом! В этих случаях мы можем решить проблему, просто сохранив график на первом обратном проходе. Затем граф не будет потребляться, но будет потребляться только первым проходом назад, который не требует его сохранения.

EDIT: если вы сохраните график во всех обратных проходах, неявные определения графов, привязанные к выходным переменным, никогда не будут освобождены. Может быть, здесь тоже есть возможность, но я не могу думать об этом. Таким образом, в общем, вы должны убедиться, что последний обратный проход освобождает память, не сохраняя информацию о графике.

Что касается того, что происходит для нескольких обратных проходов: как вы уже догадались, pytorch накапливает градиенты, добавляя их на место (к свойству переменной/параметров .grad). Это может быть очень полезно, так как это означает, что цикл цикла и обработка его один раз за раз, аккумулируя градиенты в конце, будет делать тот же шаг оптимизации, что и полное пакетное обновление (которое суммирует все градиенты как Что ж). Хотя полностью упакованное обновление можно распараллелить больше и, как правило, предпочтительнее, есть случаи, когда пакетное вычисление либо очень, очень сложно реализовать, либо просто невозможно. Однако, используя это накопление, мы все еще можем полагаться на некоторые из хороших стабилизирующих свойств, которые приносит пакет. (Если нет при увеличении производительности)