Pytorch - вывод всех изображений и обратного распространения пакета за пакетом - PullRequest
0 голосов
/ 19 декабря 2018

У меня есть специальный сценарий использования, который я должен разделить вывод и обратное распространение: я должен сделать вывод всех изображений и среза выходов в партии, а затемпутем обратного размножения партиями.Мне не нужно обновлять wegiths моих сетей.

Я изменил фрагменты cifar10_tutorial на следующие, чтобы смоделировать мою проблему: j - это переменная дляпредставляет индекс, который возвращается по моей собственной логике, и я хочу градиент некоторых переменных.

for epoch in range(2):  # loop over the dataset multiple times

    for i, data in enumerate(trainloader, 0):
        # get the inputs
        inputs, labels = data
        inputs.requires_grad = True

        # zero the parameter gradients
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)

        for j in range(4): # j is given by external logic in my own case

            loss = criterion(outputs[j, :].unsqueeze(0), labels[j].unsqueeze(0))

            loss.backward()

            print(inputs.grad.data[j, :]) # what I really want

Я получил следующие ошибки:

RuntimeError: Попытка возвратачерез график во второй раз, но буферы уже были освобождены.Укажите retain_graph = True при обратном вызове в первый раз.

Мои вопросы:

  1. Насколько я понимаю, проблема возникает из-за первого обратного распространенияназад все outputs и outputs[1,:].unsqueeze(0) были освобождены, поэтому вторая обратная передача не удалась.Я прав?

  2. В моем случае, если я установлю retain_graph=True, будут ли коды выполняться медленнее и медленнее в соответствии с этим сообщением ?

  3. Есть ли лучший способ достичь моей цели?

1 Ответ

0 голосов
/ 19 декабря 2018
  1. Да, вы правы.Когда вы уже выполните обратное распространение через outputs в первый раз (первая итерация), буферы будут освобождены и в следующий раз произойдет сбой (следующая итерация вашего цикла) , потому что тогда необходимы данные для этоговычисления уже удалены.

  2. Да, график становится все больше и больше, поэтому он может быть медленнее в зависимости от использования GPU (или CPU) и вашей сети.Я использовал это один раз, и это было намного медленнее, однако это сильно зависит от архитектуры вашей сети.Но вам, безусловно, потребуется больше памяти с retain_graph=True, чем без.

  3. В зависимости от вашей формы 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, по умолчанию «среднее», что, вероятно, приводит к несколько иным результатам.

...