Метод Ньютона: дорого и быстро

На прошлом семинаре...

  1. Методы спуска
  2. Направление убывания
  3. Градиентный метод
  4. Правила выбора шага
  5. Теоремы сходимости
  6. Эксперименты

Недостатки градиентного спуска

  • Линейная скорость сходимости
  • Зависимость от числа обусловленности гессиана

Можно ли их одновременно преодолеть?

Идея метода Ньютона

Рассмотрим задачу

$$ \min\limits_{x\ \in \mathbb{R}^n} f(x). $$
  • Градиентный спуск $\equiv$ линейная аппроксимация $f$
  • Метод Ньютона $\equiv$ квадратичная аппроксимация $f$:
$$ f(x + h) \approx f(x) + \langle f'(x), h \rangle + \frac{1}{2}h^{\top}f''(x)h \to \min_{h} $$

Из необходимого условия минимума:

$$ f'(x) + f''(x) h = 0, \qquad h^* = -(f''(x))^{-1} f'(x) $$

Является ли найденное направление направлением убывания?

Проверим знак скалярного произведения $\langle f'(x), h^* \rangle$.

$$ \langle f'(x), h^* \rangle = -(f')^{\top}(x) (f''(x))^{-1} f'(x) < 0 \Leftarrow f''(x) \succ 0 $$

Вопрос: а что если при некотором $k^*$ гессиан станет неопределён?

Метод Ньютона

  • Классический метод Ньютона: $\alpha_k \equiv 1$
  • Демпфированный метод Ньютона: $\alpha_k$ выбирается на каждой итерации по заданному правилу
def NewtonMethod(f, x0, epsilon, **kwargs):

    x = x0

    while True:

        h = ComputeNewtonStep(x, f, **kwargs)

        if StopCriterion(x, f, h, **kwargs) < epsilon:

            break

        alpha = SelectStepSize(x, h, f, **kwargs)

        x = x + alpha * h

    return x

Теорема сходимости (Ю. Е. Нестеров Введение в выпуклую оптимизацию, $\S$ 1.2)

Теорема. Пусть функция $f(x)$

  • дважды дифференцируема и её гессиан удовлетворяет условию Липшица с константой $M$
  • существует точка локального минимума с положительно определённым гессианом
$$ f''(x^*) \succeq l\mathbf{I}, \; l > 0 $$
  • начальная точка $x_0$ достаточно близка к точке минимума, в частности
$$ \|x_0 - x^*\|_2 \leq \frac{2l}{3M} $$

Тогда метод Ньютона сходится квадратично:

$$ \|x_{k+1} - x^* \|_2 \leq \dfrac{M\|x_k - x^*\|^2_2}{2 (l - M\|x_k - x^*\|_2)} $$

Пример

Применим метод Ньютона для поиска корня следующей функции

$$ \varphi(t) = \dfrac{t}{\sqrt{1+t^2}} $$

и определим область сходимости.

Аффинная инвариантность

Рассмотрим функцию $f(x)$ и невырожденное преобразование с матрицей $A$.

Выясним, как изменится шаг метода Ньютона после преобразования $A$.

Пусть $x = Ay$ и $g(y) = f(Ay)$. Тогда

$$ g(y + u) \approx g(y) + \langle g'(y), u \rangle + \frac{1}{2} u^{\top} g''(y) u \to \min_{u} $$

и

$$ u^* = -(g''(y))^{-1} g'(y) \qquad y_{k+1} = y_k - (g''(y_k))^{-1} g'(y_k) $$

или

\begin{align*} y_{k+1} & = y_k - (A^{\top}f''(Ay_k)A)^{-1} A^{\top}f'(Ay_k)\\ & = y_k - A^{-1}(f''(Ay_k))^{-1}f'(Ay_k) \end{align*}

Таким образом,

$$ Ay_{k+1} = Ay_k - (f''(Ay_k))^{-1}f'(Ay_k) \quad x_{k+1} = x_k - (f''(x_k))^{-1}f'(x_k) $$

Следовательно, направление метода Ньютона преобразуется при

линейном преобразовани так же, как и координаты!

Метод Ньютона с модификацией гессиана

  • Как быть с возможной неположительной определённостью гессиана на некоторой итерации?
  • Если $f''(x)$ неположительно определён, использовать положительно определённую матрицу $f''(x) + \Delta E$
  • Матрицу $\Delta E$ можно выбирать различными способами исходя из следующей задачи
$$ \Delta E = \arg\min \|\Delta E\|, \quad \text{s.t. } f''(x) + \Delta E \succ 0 $$

Какие нормы можно использовать?

  • $\|\cdot\|_2$: $\Delta E = \tau I$, где $\tau = \max(0, \delta - \lambda_{\min}(f''(x)))$, где $\delta > 0$ - заданная оценка снизу минимального собственного значения матрицы $f''(x) + \Delta E$
  • Чему равно $\Delta E$ при использовании $\|\cdot\|_F$?

Проблема:

  • Поскольку оценку $\lambda(f''(x))$ обычно сложно вычислять на каждой итерации, возможно модифицировать процедуру вычисления разложения Холецкого матрицы $f''(x)$ так чтобы в итоге получилось разложение Холецкого для матрицы $f''(x) + \Delta E$

Использование метода сопряжённых градиентов в методе Ньютона

  • В методе Ньютона надо решать линейную систему $H(x_k) h_k = -f'(x_k)$ для поиска направлению движения
  • Если функция сильно выпуклая, то $H(x_k) \in \mathbb{S}^n_{++}$ и для решения системы можно использовать метод сопряжённых градиентов. В этом случае метод называют неточный метод Ньютона.
  • Что нового это даёт
    • Явно хранить гессиан не нужно, достаточно умножать его на вектор
    • Можно регулировать точность решения системы и не решать её очень точно вдали от решения. Важно: неточное решение может не быть направлением убывания!
    • Сходимость будет только локально сверхлинейной при начале выбора шага с $\alpha_0 = 1$, как и в методе Ньютона

Вычислительная сложность и эксперименты

Узкие места метода Ньютона:

  • формирование и хранение гессиана
  • решение систем линейных уравнений
$$ f''(x_k)h = -f'(x_k) $$

Сравнение с градиентным спуском

Вспомним задачу нахождения аналитического центра системы неравенств $Ax \leq 1$ при условии $|x_i| \leq 1$

$$ f(x) = - \sum_{i=1}^m \log(1 - a_i^{\top}x) - \sum\limits_{i = 1}^n \log (1 - x^2_i) \to \min_x $$$$ f'(x) - ? \quad f''(x) - ? $$


In [1]:
import numpy as np

USE_COLAB = False
if USE_COLAB:
    !pip install git+https://github.com/amkatrutsa/liboptpy
        
import liboptpy.unconstr_solvers as methods
import liboptpy.step_size as ss

n = 1000
m = 200
x0 = np.zeros((n,))
A = np.random.rand(n, m) * 10

Точное решение с помощью CVXPy


In [2]:
import cvxpy as cvx
x = cvx.Variable((n, 1))

obj = cvx.Minimize(cvx.sum(-cvx.log(1 - A.T * x)) - 
                   cvx.sum(cvx.log(1 - cvx.square(x))))
prob = cvx.Problem(obj)
prob.solve(solver="SCS", verbose=True, max_iters=1000)
print("Optimal value =", prob.value)


----------------------------------------------------------------------------
	SCS v1.2.6 - Splitting Conic Solver
	(c) Brendan O'Donoghue, Stanford University, 2012-2016
----------------------------------------------------------------------------
Lin-sys: sparse-indirect, nnz in A = 205200, CG tol ~ 1/iter^(2.00)
eps = 1.00e-03, alpha = 1.50, max_iters = 1000, normalize = 1, scale = 1.00
Variables n = 3200, constraints m = 6600
Cones:	soc vars: 3000, soc blks: 1000
	exp vars: 3600, dual exp vars: 0
Setup time: 1.00e-02s
----------------------------------------------------------------------------
 Iter | pri res | dua res | rel gap | pri obj | dua obj | kap/tau | time (s)
----------------------------------------------------------------------------
     0|      inf       inf       nan      -inf      -inf       inf  2.84e-02 
   100| 1.63e+01  3.34e+00  9.79e-03 -8.85e+03 -8.68e+03  1.82e-13  1.23e+00 
   200| 1.46e+00  9.74e-01  1.70e-03 -1.94e+03 -1.94e+03  3.26e-13  2.62e+00 
   300| 4.56e-01  5.38e-01  2.18e-04 -1.60e+03 -1.60e+03  3.45e-13  3.82e+00 
   400| 2.22e-01  2.79e-01  1.05e-04 -1.47e+03 -1.47e+03  3.46e-13  4.97e+00 
   500| 1.31e-01  1.31e-01  1.61e-04 -1.42e+03 -1.42e+03  3.44e-13  6.27e+00 
   600| 7.66e-02  4.88e-02  1.25e-04 -1.39e+03 -1.39e+03  3.41e-13  7.63e+00 
   700| 3.87e-02  1.02e-02  6.85e-05 -1.37e+03 -1.37e+03  3.39e-13  8.94e+00 
   800| 1.64e-02  3.08e-03  2.68e-05 -1.37e+03 -1.37e+03  3.38e-13  1.02e+01 
   900| 7.63e-03  4.84e-03  5.63e-06 -1.37e+03 -1.37e+03  3.38e-13  1.16e+01 
  1000| 6.09e-03  3.18e-03  1.65e-06 -1.37e+03 -1.37e+03  3.38e-13  1.29e+01 
----------------------------------------------------------------------------
Status: Solved/Inaccurate
Hit max_iters, solution may be inaccurate
Timing: Solve time: 1.29e+01s
	Lin-sys: avg # CG iterations: 3.41, avg solve time: 4.59e-03s
	Cones: avg projection time: 8.14e-03s
----------------------------------------------------------------------------
Error metrics:
dist(s, K) = 3.7927e-03, dist(y, K*) = 1.1102e-16, s'y/|s||y| = 4.6512e-11
|Ax + s - b|_2 / (1 + |b|_2) = 6.0889e-03
|A'y + c|_2 / (1 + |c|_2) = 3.1846e-03
|c'x + b'y| / (1 + |c'x| + |b'y|) = 1.6486e-06
----------------------------------------------------------------------------
c'x = -1368.0090, -b'y = -1368.0045
============================================================================
Optimal value = -1368.0090171481181

Вспомогательные функции


In [3]:
f = lambda x: -np.sum(np.log(1 - A.T.dot(x))) - np.sum(np.log(1 - x*x))
grad_f = lambda x: np.sum(A.dot(np.diagflat(1 / (1 - A.T.dot(x)))), axis=1) + 2 * x / (1 - np.power(x, 2))
hess_f = lambda x: (A.dot(np.diagflat(1 / (1 - A.T.dot(x))**2))).dot(A.T) + np.diagflat(2 * (1 + x**2) / (1 - x**2)**2)

Реализация метода Ньютона


In [4]:
def Newton(f, gradf, hessf, x0, epsilon, num_iter, line_search, 
                    disp=False, callback=None, **kwargs):
    x = x0.copy()
    iteration = 0
    opt_arg = {"f": f, "grad_f": gradf}
    for key in kwargs:
        opt_arg[key] = kwargs[key]
    while True:
        gradient = gradf(x)
        hess = hessf(x)
        h = np.linalg.solve(hess, -gradient)
        alpha = line_search(x, h, **opt_arg)
        x = x + alpha * h
        if callback is not None:
            callback(x)
        iteration += 1
        if disp:
            print("Current function val =", f(x))
            print("Current gradient norm = ", np.linalg.norm(gradf(x)))
        if np.linalg.norm(gradf(x)) < epsilon:
            break
        if iteration >= num_iter:
            break
    res = {"x": x, "num_iter": iteration, "tol": np.linalg.norm(gradf(x))}
    return res

Сравнение с градиентным спуском


In [5]:
newton = methods.so.NewtonMethod(f, grad_f, hess_f, ss.Backtracking("Armijo", rho=0.9, beta=0.1, init_alpha=1.))
x_newton = newton.solve(x0, tol=1e-6, max_iter=50, disp=True)

gd = methods.fo.GradientDescent(f, grad_f, ss.Backtracking("Armijo", rho=0.9, beta=0.1, init_alpha=1.))
x_gd = gd.solve(x0, tol=1e-6, max_iter=50, disp=True)


Required tolerance achieved!
Convergence in 14 iterations
Function value = -1368.6652957529736
Norm of gradient = 7.596291750182202e-10
Required tolerance achieved!
Convergence in 47 iterations
Function value = -1368.6652957529739
Norm of gradient = 3.0014120117562214e-09
/Users/alex/anaconda3/envs/cvxpy/lib/python3.6/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in log
  """Entry point for launching an IPython kernel.

In [6]:
%matplotlib inline
import matplotlib.pyplot as plt

if not USE_COLAB:
    plt.rc("text", usetex=True)
    
plt.figure(figsize=(12, 8))
# Newton
plt.semilogy([np.linalg.norm(grad_f(x)) for x in newton.get_convergence()], label="$\| f'(x_k) \|^{N}_2$")
# Gradient
plt.semilogy([np.linalg.norm(grad_f(x)) for x in gd.get_convergence()], label="$\| f'(x_k) \|^{G}_2$")
plt.xlabel(r"Number of iterations, $k$", fontsize=26)
plt.ylabel(r"Convergence rate", fontsize=26)
plt.xticks(fontsize = 24)
plt.yticks(fontsize = 24)
plt.legend(loc="best", fontsize=24)


Out[6]:
<matplotlib.legend.Legend at 0xd2456fe80>

Сравнение времени работы


In [7]:
%timeit newton.solve(x0, tol=1e-6, max_iter=50)
%timeit gd.solve(x0, tol=1e-6, max_iter=50)


612 ms ± 138 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
/Users/alex/anaconda3/envs/cvxpy/lib/python3.6/site-packages/ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in log
  """Entry point for launching an IPython kernel.
256 ms ± 41.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  • Метод Ньютона даёт очень точное решение за длительное время
  • Градиентный спуска даёт не очень точное решение, но гораздо быстрее
  • Часто бывает, что очень точное решение не требуется, поэтому градиентный спуск может быть предпочтительнее

Pro & Contra

Pro

  • квадратичная сходимость вблизи решения
  • высокая точность полученного решения
  • аффинная инвариантность
  • параметры слабо влияют на скорость сходимости

Contra

  • необходимо хранить гессиан на каждой итерации: $O(n^2)$ памяти
  • необходимо решать линейные системы: $O(n^3)$ операций
  • гессиан может оказаться вырожден
  • гессиан может не быть положительно определён $\to$ направление $-(f''(x))^{-1}f'(x)$ может не быть направлением убывания

Сравнение с градиентным методом (Б.Т. Поляк Введение в оптимизацию, гл. 3, $\S$ 1 )

Метод Скорость сходимости Сложность Аффинная инвариантность Требования к $f(x)$
Градиентный спуск Глобально линейная $O(n) + $ определение шага Нет Дифференцируема; градиент липшицев
Метод Ньютона Локально квадратичная $O(n^3) + $ определение шага Да Дважды диференцируема; гессиан липшицев, положительно определён

Что дальше?

  • Сложность: как избавиться от решения систем линейных уравнений и хранения гессиана?
  • Сходимость: как совместить локально квадратичную и глобально линейную скорости? Желательно получить глобально квадратичную сходимость!
  • Требования к $f(x)$ необходимо минимизировать
  • Квазиньютоновские методы частично решают эти проблемы

Резюме

  1. Метод Ньютона
  2. Теоремы сходимости
  3. Сравнение с градиентным спуском
  4. Эксперименты