как рассчитать потери по нескольким изображениям, а затем обратно распространить средние потери и обновить вес сети

Я выполняю задачу, в которой размер пакета равен 1, т.е. каждая партия содержит только 1 изображение. Поэтому мне приходится выполнять группировку вручную: когда количество накопленных потерь достигает числа, усредните потери, а затем выполните обратное распространение. Мой исходный код:

real_batchsize = 200

for epoch in range(1, 5):
    net.train()

    total_loss = Variable(torch.zeros(1).cuda(), requires_grad=True)

    iter_count = 0
    for batch_idx, (input, target) in enumerate(train_loader):

        input, target = Variable(input.cuda()), Variable(target.cuda())
        output = net(input)

        loss = F.nll_loss(output, target)

        total_loss = total_loss + loss

        if batch_idx % real_batchsize == 0:
            iter_count += 1

            ave_loss = total_loss/real_batchsize
            ave_loss.backward()
            optimizer.step()

            if iter_count % 10 == 0:
                print("Epoch:{}, iteration:{}, loss:{}".format(epoch,
                                                           iter_count,
                                                           ave_loss.data[0]))
            total_loss.data.zero_() 
            optimizer.zero_grad()

Этот код выдаст сообщение об ошибке

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

Я пробовал следующий способ,

Первый способ (не удалось)

Я прочитал сообщение об этом сообщении об ошибке, но не могу его полностью понять. Измените ave_loss.backward() на ave_loss.backward(retain_graph=True), чтобы предотвратить появление сообщения об ошибке, но потери не улучшаются и вскоре становятся nan.

Второй способ (не удалось)

Я также пробовал изменить total_loss = total_loss + loss.data[0], это тоже предотвратит появление сообщения об ошибке. Но проигрыши всегда одни и те же. Значит, должно быть что-то не так.

Третий путь (успех)

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

Мой вопрос

Что означает сообщение об ошибке в моем случае? Кроме того, почему не работают первый и второй способ? Как правильно написать код, чтобы мы могли передавать градиент каждые real_batchsize изображений и обновлять градиент один раз, чтобы ускорить обучение? Я знаю, что мой код почти правильный, но я просто не знаю, как его изменить.


person jdhao    schedule 05.11.2017    source источник


Ответы (1)


Проблема, с которой вы столкнулись, связана с тем, как PyTorch накапливает градиенты за разные проходы. (см. здесь для другого сообщения по аналогичному вопросу) Итак, давайте посмотрим, что происходит, когда у вас есть код следующей формы:

loss_total = Variable(torch.zeros(1).cuda(), requires_grad=True)
for l in (loss_func(x1,y1), loss_func(x2, y2), loss_func(x3, y3), loss_func(x4, y4)):
    loss_total = loss_total + l
    loss_total.backward()

Здесь мы выполняем обратный проход, когда loss_total имеет следующие значения на разных итерациях:

total_loss = loss(x1, y1)
total_loss = loss(x1, y1) + loss(x2, y2)
total_loss = loss(x1, y1) + loss(x2, y2) + loss(x3, y3)
total_loss = loss(x1, y1) + loss(x2, y2) + loss(x3, y3) + loss(x4, y4)

поэтому, когда вы каждый раз вызываете .backward() на total_loss, вы фактически вызываете .backward() на loss(x1, y1) четыре раза! (и на loss(x2, y2) три раза и т. д.).

Объедините это с тем, что обсуждается в другом посте, а именно, что для оптимизации использования памяти PyTorch освободит граф, прикрепленный к переменной при вызове .backward() (и тем самым уничтожит градиенты, соединяющие x1 с y1, x2 с y2 и т. Д.), Вы можете посмотрите, что означает сообщение об ошибке - вы несколько раз пытаетесь выполнить обратные проходы через потерю, но нижележащий граф освободился после первого прохода. (если, конечно, retain_graph=True не указать)

Что касается конкретных вариантов, которые вы пробовали: Первый способ: здесь вы будете накапливать (то есть суммировать - снова, см. Другой пост) градиенты навсегда, с их (потенциально) в сумме до inf. Второй способ: здесь вы конвертируете loss в тензор, выполняя loss.data, удаляя оболочку Variable и тем самым удаляя информацию о градиенте (поскольку только переменные содержат градиенты). Третий способ: здесь вы делаете только один проход через каждый xk, yk кортеж, так как вы сразу делаете шаг обратного распространения, полностью избегая вышеуказанной проблемы.

РЕШЕНИЕ: Я не тестировал его, но из того, что я понял, решение должно быть довольно простым: создать новый объект total_loss в начале каждого пакета, затем суммировать все потери в этом объекте, а затем выполнить один последний шаг обратного распространения в конце.

person cleros    schedule 07.11.2017
comment
после каждого обновления параметра создание новой переменной total_loss решает эту проблему. - person jdhao; 08.11.2017
comment
отличный! не могли бы вы тогда принять ответ? Спасибо! - person cleros; 08.11.2017