PyTorch, похоже, не выполняет правильную оптимизацию

#pytorch

#pytorch

Вопрос:

Я разместил этот вопрос на сайте Data Science StackExchange, поскольку StackOverflow не поддерживает LaTeX. Ссылка на это здесь, потому что этот сайт, вероятно, более уместен.

Вопрос с правильно отображаемым LaTeX здесь: https://datascience.stackexchange.com/questions/48062/pytorch-does-not-seem-to-be-optimizing-correctly

Идея в том, что я рассматриваю суммы синусоидальных волн с разными фазами. Волны отбираются с некоторой частотой дискретизации s в интервале [0, 2pi]. Мне нужно выбрать фазы таким образом, чтобы сумма волн в любой точке выборки была минимизирована.

Ниже приведен код Python. Оптимизация, похоже, вычисляется неправильно.

 import numpy as np
import torch

def phaseOptimize(n, s = 48000, nsteps = 1000):
    learning_rate = 1e-3

    theta = torch.zeros([n, 1], requires_grad=True)
    l = torch.linspace(0, 2 * np.pi, s)
    t = torch.stack([l] * n)
    T = t   theta

    for jj in range(nsteps):
        loss = T.sin().sum(0).pow(2).sum() / s
        loss.backward()
        theta.data -= learning_rate * theta.grad.data

    print('Optimal theta: nn', theta.data)
    print('nnMaximum value:', T.sin().sum(0).abs().max().item())
  

Ниже приведен пример вывода.

 phaseOptimize(5, nsteps=100)


Optimal theta: 

 tensor([[1.2812e-07],
        [1.2812e-07],
        [1.2812e-07],
        [1.2812e-07],
        [1.2812e-07]], requires_grad=True)


Maximum value: 5.0
  

Я предполагаю, что это как-то связано с трансляцией в

 T = t   theta
  

и / или способ, которым я вычисляю функцию потерь.

Один из способов убедиться, что оптимизация неверна, — это просто оценить функцию потерь при случайных значениях для массива $ theta_1, dots, theta_n $, скажем, равномерно распределенных в $ [0, 2 pi] $. Максимальное значение в этом случае почти всегда намного ниже максимального значения, сообщаемого phaseOptimize() . На самом деле гораздо проще рассмотреть случай с $ n = 2 $ и просто вычислить при $ theta_1 = 0 $ и $ theta_2 = pi $. В этом случае мы получаем:

 phaseOptimize(2, nsteps=100)

Optimal theta: 

 tensor([[2.8599e-08],
        [2.8599e-08]])


Maximum value: 2.0
  

С другой стороны,

 theta = torch.FloatTensor([[0], [np.pi]])
l = torch.linspace(0, 2 * np.pi, 48000)
t = torch.stack([l] * 2)
T = t   theta

T.sin().sum(0).abs().max().item()
  

выдает

 3.2782554626464844e-07
  

Ответ №1:

Вы должны перемещать вычисления T внутри цикла, иначе они всегда будут иметь одно и то же постоянное значение, следовательно, постоянные потери.

Другое дело — инициализировать theta разные значения в индексах, в противном случае из-за симметричной природы проблемы градиент одинаков для каждого индекса.

Другое дело, что вам нужно обнулить градиент, потому что backward они просто накапливаются.

Кажется, это работает:

 def phaseOptimize(n, s = 48000, nsteps = 1000):
    learning_rate = 1e-1

    theta = torch.zeros([n, 1], requires_grad=True)
    theta.data[0][0] = 1
    l = torch.linspace(0, 2 * np.pi, s)
    t = torch.stack([l] * n)

    for jj in range(nsteps):
        T = t   theta
        loss = T.sin().sum(0).pow(2).sum() / s
        loss.backward()
        theta.data -= learning_rate * theta.grad.data
        theta.grad.zero_()
  

Ответ №2:

Вас кусают как PyTorch, так и math. Во-первых, вам нужно

  1. Обнуляйте градиент, задавая значение theta.grad = None перед каждым backward шагом. В противном случае градиенты накапливаются вместо перезаписи предыдущих
  2. Вам нужно пересчитывать T на каждом шаге. PyTorch не является символьным, в отличие от TensorFlow и T = t theta означает «T равно сумме текущего t и current theta «, а не «T равно сумме t и theta , какими бы ни были их значения в любое время в будущем».

С этими исправлениями вы получаете следующий код:

 def phaseOptimize(n, s = 48000, nsteps = 1000):
    learning_rate = 1e-3

    theta = torch.zeros(n, 1, requires_grad=True)
    l = torch.linspace(0, 2 * np.pi, s)
    t = torch.stack([l] * n)
    T = t   theta

    for jj in range(nsteps):
        T = t   theta
        loss = T.sin().sum(0).pow(2).sum() / s
        theta.grad = None
        loss.backward()
        theta.data -= learning_rate * theta.grad.data

    T = t   theta

    print('Optimal theta: nn', theta.data)
    print('nnMaximum value:', T.sin().sum(0).abs().max().item())
  

который все равно будет работать не так, как вы ожидаете, из-за математики.

Легко видеть, что минимум вашей функции потерь — это когда theta они также равномерно распределены по [0, 2pi) . Проблема в том, что вы инициализируете свои параметры как torch.zeros , что приводит к тому, что все эти значения равны (это полярная противоположность equispaced!). Поскольку ваша функция потерь симметрична относительно перестановок theta , вычисленные градиенты равны, и алгоритм градиентного спуска никогда не сможет их «дифференцировать». В более математических терминах вам не повезло инициализировать ваш алгоритм точно в седловой точке, поэтому он не может продолжаться. Если вы добавите какой-либо шум, он будет сходиться. Например, с

 theta = torch.zeros(n, 1)   0.001 * torch.randn(n, 1)
theta.requires_grad_(True)