Шаг обновления в реализации PyTorch метода Ньютона - PullRequest
0 голосов
/ 22 января 2019

Я пытаюсь понять, как работает PyTorch, реализовав метод Ньютона для решения x = cos (x) .Вот версия, которая работает:

x =  Variable(DoubleTensor([1]), requires_grad=True)

for i in range(5):
    y = x - torch.cos(x)
    y.backward()
    x = Variable(x.data - y.data/x.grad.data, requires_grad=True)

print(x.data) # tensor([0.7390851332151607], dtype=torch.float64) (correct)

Этот код кажется мне не элегантным (неэффективным?), Поскольку он воссоздает весь вычислительный граф на каждом этапе цикла for (верно?).Я пытался избежать этого, просто обновляя данные, хранящиеся в каждой из переменных, вместо того, чтобы воссоздавать их:

x =  Variable(DoubleTensor([1]), requires_grad=True)
y = x - torch.cos(x)
y.backward(retain_graph=True)

for i in range(5):
    x.data = x.data - y.data/x.grad.data
    y.data = x.data - torch.cos(x.data)
    y.backward(retain_graph=True)

print(x.data) # tensor([0.7417889255761136], dtype=torch.float64) (wrong)

Похоже, с DoubleTensor s я несу достаточно цифр точности, чтобы исключитьошибка округления.Так откуда же возникла ошибка?

Возможно, связано: Приведенный выше фрагмент разбивается без флага retain_graph=True, установленного на каждом шаге, если цикл for.Я получаю сообщение об ошибке, если опускаю его в цикле - но сохраняю его в строке 3 -: RuntimeError: Попытка выполнить обратный просмотр графика во второй раз, но буферы уже освобождены.Укажите retain_graph = True при обратном вызове в первый раз . Это похоже на доказательство того, что я что-то неправильно понимаю ...

1 Ответ

0 голосов
/ 23 января 2019

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

# initial guess
guess = torch.tensor([1], dtype=torch.float64, requires_grad = True) 

# function to optimize
def my_func(x): 
    return x - torch.cos(x)

def newton(func, guess, runs=5): 
    for _ in range(runs): 
        # evaluate our function with current value of `guess`
        value = my_func(guess)
        value.backward()
        # update our `guess` based on the gradient
        guess.data -= (value / guess.grad).data
        # zero out current gradient to hold new gradients in next iteration 
        guess.grad.data.zero_() 
    return guess.data # return our final `guess` after 5 updates

# call starts
result = newton(my_func, guess)

# output of `result`
tensor([0.7391], dtype=torch.float64)

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

...