I am working on the file "Spring2020/newton_quasi.ipynb".
The first task is:
Метод Ньютона даёт очень точное решение за длительное время
Градиентный спуска даёт не очень точное решение, но гораздо быстрее
Часто бывает, что очень точное решение не требуется, поэтому градиентный спуск может быть предпочтительнее

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)$ необходимо минимизировать
Квазиньютоновские методы частично решают эти проблемы

Как уменьшить сложность хранения и вычисления?

Сложность вычисления можно уменьшить с помощью

Квазиньютоновские методы, они же методы переменной метрики
Требуется хранение матрицы $n \times n$

Сложность вычисления и хранения можно уменьшить

квазиньютоновские методы с ограниченной памятью, например L-BFGS (Limited Broyden-Fletcher-Goldfarb-Shanno)
НЕ требуется хранить матрицу
вместо этого хранятся $k \ll n$ векторов из $\mathbb{R}^n$

Единообразный способ получения метода Ньютона и градиентного спуска

градиентный метод получен из аппроксимации первого...
Can you write Python code for it?

import numpy as np
import liboptpy.unconstr_solvers as methods
import liboptpy.step_size as ss
%matplotlib inline
import matplotlib.pyplot as plt
import scipy.optimize as scopt
plt.rc("text", usetex=True)

n = 3000
m = 100
x0 = np.zeros(n)
max_iter = 100
tol = 1e-5
A = np.random.rand(m, n) * 10

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

def bb_method(f, gradf, x0, tol=1e-6, maxiter=100, callback=None, alpha_type=1):
    it = 0
    x_prev = x0.copy()
    current_tol = np.linalg.norm(gradf(x_prev))
    alpha = 1e-4
    while current_tol > tol and it < maxiter:
        it += 1
        current_grad = gradf(x_prev)
        if it != 1:
            g = current_grad - prev_grad
            if alpha_type == 1:
                alpha = g.dot(s) / g.dot(g)
            elif alpha_type == 2:
                alpha = s.dot(s) / g.dot(s)
        if callback:
            callback(x_prev)
        x_next = x_prev - alpha * current_grad
        current_tol = np.linalg.norm(gradf(x_next))
        prev_grad = current_grad
        s = x_next - x_prev
        x_prev = x_next
    if callback:
        callback(x_prev)
    return x_next

method = {
    "BB 1": methods.fo.BarzilaiBorweinMethod(f, grad_f, init_alpha=1e-4, type=1),
    "BFGS": methods.fo.BFGS(f, grad_f),
    "DFP": methods.fo.DFP(f, grad_f),
    "LBFGS": methods.fo.LBFGS(f, grad_f),
}

for m in method:
    print("\t Method {}".format(m))
    _ = method[m].solve(x0=x0, tol=tol, max_iter=max_iter, disp=True)

print("\t Method BFGS Scipy")
scopt_conv = []
scopt_res = scopt.minimize(f, x0, method="BFGS", jac=grad_f, callback=lambda x: scopt_conv.append(x),
                           tol=tol, options={"maxiter": max_iter})
print("Result: {}".format(scopt_res.message))
if scopt_res.success:
    print("Convergence in {} iterations".format(scopt_res.nit))
print("Function value = {}".format(f(scopt_res.x)))

plt.figure(figsize=(8, 6))

for m in method:
   ...


I am working on the file "Spring2020/newton_quasi.ipynb".
The first task is:
Сравнение времени работы
Can you write Python code for it?

%timeit newton.solve(x0, tol=1e-6, max_iter=50)
%timeit gd.solve(x0, tol=1e-6, max_iter=50)


I am working on the file "Spring2020/newton_quasi.ipynb".
The first task is:
Сравнение с градиентным спуском
Can you write Python code for it?

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)

%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)


I am working on the file "Spring2020/newton_quasi.ipynb".
The first task is:
Точное решение с помощью CVXPy
Can you write Python code for it?

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)


I am working on the file "Spring2021/acc_grad.ipynb".
The first task is:
Время выполнения
Can you write Python code for it?

%timeit scopt.minimize(f, x0, method="CG", tol=tol, jac=grad_f, options={"maxiter": max_iter})
%timeit cg_fr.solve(x0, tol=tol, max_iter=max_iter)
%timeit cg_fr_rest.solve(x0, tol=tol, max_iter=max_iter)
%timeit gd.solve(x0, tol=tol, max_iter=max_iter)


I am working on the file "Spring2021/acc_grad.ipynb".
The first task is:
Реализация метода сопряжённых градиентов
Can you write Python code for it?

def ConjugateGradientQuadratic(x0, A, b, tol=1e-8, callback=None):
    x = x0
    r = A.dot(x0) - b
    p = -r
    while np.linalg.norm(r) > tol:
        alpha = r.dot(r) / p.dot(A.dot(p))
        x = x + alpha * p
        if callback is not None:
            callback(x)
        r_next = r + alpha * A.dot(p)
        beta = r_next.dot(r_next) / r.dot(r)
        p = -r_next + beta * p
        r = r_next
    return x

import liboptpy.unconstr_solvers as methods
import liboptpy.step_size as ss

max_iter = 70
print("\t CG quadratic")
cg_quad = methods.fo.ConjugateGradientQuad(A, b)
x_cg = cg_quad.solve(x0, tol=1e-7, max_iter=max_iter, disp=True)

print("\t Gradient Descent")
gd = methods.fo.GradientDescent(f, grad_f, ss.ExactLineSearch4Quad(A, b))
x_gd = gd.solve(x0, tol=1e-7, max_iter=max_iter, disp=True)

print("Condition number of A =", abs(max(eigs)) / abs(min(eigs)))


I am working on the file "Spring2021/acc_grad.ipynb".
The first task is:
Резюме

Сопряжённые направления
Метод сопряжённых градиентов
Сходимость
Эксперименты

Метод тяжёлого шарика (heavy-ball method)

Предложен в 1964 г. Б.Т. Поляком

Для квадратичной целевой функции зигзагообразное поведение градиентного спуска обусловлено неоднородностью направлений
Давайте учитывать предыдущие направления для поиска новой точки
Метод тяжёлого шарика

$$
x_{k+1} = x_k - \alpha_k f'(x_k) + {\color{red}{\beta_k(x_k - x_{k-1})}}
$$

Помимо параметра шага вдоль антиградиента $\alpha_k$ появился ещё один параметр $\beta_k$

Геометрическая интерпретация метода тяжёлого шарика
Картинка отсюда

Теорема сходимости
Пусть $f$ сильно выпукла с Липшицевым градиентом. Тогда для
$$
\alpha_k = \frac{4}{(\sqrt{L} + \sqrt{\mu})^2}
$$
и
$$
\beta_k = \max(|1 - \sqrt{\alpha_k L}|^2, |1 - \sqrt{\alpha_k \mu}|^2)
$$
справедлива следующая оценка сходимости
$$
\left\| \begin{bmatrix} x_{k+1} - x^ \ x_k - x^ \end{bmatrix} \right\|_2
\leq
\left( \frac{\sqrt{\kappa} - 1}{\sqrt{\kappa} + 1} \right)^k \left \|
\begin{bmatrix} x_1 - x^ \ x_0 - x^ \end{bmatrix}
\right \|_2
$$

Совпадает с оценкой снизу для методов первого порядка!
Оптимальные параметры $\alpha_k$ и $\beta_k$ определяются через неизвестные константы $L$ и $\mu$

Схема доказательства

Перепишем метод как

\begin{align}
\begin{bmatrix} x_{k+1}\ x_k \end{bmatrix} = \begin{bmatrix} (1 + \beta_k)I & -\beta_k I\ I & 0 \end{bmatrix} \begin{bmatrix} x_k\ x_{k-1} \end{bmatrix} + \begin{bmatrix} -\alpha_k f'(x_k)\ 0 \end{bmatrix}
\end{align}

Используем теорему из анализа

\begin{align}
\begin{bmatrix} x_{k+1} - x^\ x_k - x^ \end{bmatrix} = \underbrace{ \begin{bmatrix} (1 + \beta_k)I - \alpha_k \int_0^1 f''(x(\tau))d\tau & -\beta_k I\ I & 0 \end{bmatrix}}_{=A_k}\begin{bmatrix} x_k - x^\ x_{k-1} - x^\end{bmatrix},
\end{align}
где $x(\tau) = x_k + \tau(x^* - x_k) $
- В силу интегральной теоремы о среднем $A_k(x) = \int_0^1 f''(x(\tau))d\tau = f''(z)$, поэтому $L$ и $\mu$ ограничивают...
Can you write Python code for it?

import liboptpy.base_optimizer as base
import numpy as np
import liboptpy.unconstr_solvers.fo as fo
import liboptpy.step_size as ss
import matplotlib.pyplot as plt
%matplotlib inline

plt.rc("text", usetex=True)

class HeavyBall(base.LineSearchOptimizer):
    def __init__(self, f, grad, step_size, beta, **kwargs):
        super().__init__(f, grad, step_size, **kwargs)
        self._beta = beta

    def get_direction(self, x):
        self._current_grad = self._grad(x)
        return -self._current_grad

    def _f_update_x_next(self, x, alpha, h):
        if len(self.convergence) < 2:
            return x + alpha * h
        else:
            return x + alpha * h + self._beta * (x - self.convergence[-2])

    def get_stepsize(self):
        return self._step_size.get_stepsize(self._grad_mem[-1], self.convergence[-1], len(self.convergence))

np.random.seed(42)
n = 100
A = np.random.randn(n, n)
A = A.T.dot(A)
x_true = np.random.randn(n)
b = A.dot(x_true)
f = lambda x: 0.5 * x.dot(A.dot(x)) - b.dot(x)
grad = lambda x: A.dot(x) - b
A_eigvals = np.linalg.eigvalsh(A)
L = np.max(A_eigvals)
mu = np.min(A_eigvals)
print(L, mu)
print("Condition number = {}".format(L / mu))
alpha_opt = 4 / (np.sqrt(L) + np.sqrt(mu))**2
beta_opt = np.maximum((1 - np.sqrt(alpha_opt * L))**2,
                      (1 - np.sqrt(alpha_opt * mu))**2)
print(alpha_opt, beta_opt)
beta_test = 0.95

methods = {
    "GD fixed": fo.GradientDescent(f, grad, ss.ConstantStepSize(1 / L)),
    "GD Armijo": fo.GradientDescent(f, grad,
            ss.Backtracking("Armijo", rho=0.5, beta=0.1, init_alpha=1.)),
    r"HB, $\beta = {}$".format(beta_test): HeavyBall(f, grad, ss.ConstantStepSize(1 / L), beta=beta_test),
    "HB optimal": HeavyBall(f, grad, ss.ConstantStepSize(alpha_opt), beta = beta_opt),
    "CG": fo.ConjugateGradientQuad(A, b)
}
x0 = np.random.randn(n)
max_iter = 5000
tol = 1e-6

for m in methods:
    _ = methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)

figsize = (10, 8)
fontsize = 26
plt.figure(figsize=figsize)
for m in...


I am working on the file "Spring2021/acc_grad.ipynb".
The first task is:
Тестовая задача 2
$$
f(w) = \frac12 \|w\|2^2 + C \frac1m \sum{i=1}^m \log (1 + \exp(- y_i \langle x_i, w \rangle)) \to \min_w
$$
Can you write Python code for it?

n = 300
m = 1000

X, y = skldata.make_classification(n_classes=2, n_features=n, n_samples=m, n_informative=n//3)
C = 1

@jax.jit
def f(w):
    return jnp.linalg.norm(w)**2 / 2 +  C * jnp.mean(jnp.logaddexp(jnp.zeros(X.shape[0]), -y * (X @ w)))

# def grad(w):
#     denom = scspec.expit(-y * X.dot(w))
#     return w - C * X.T.dot(y * denom) / X.shape[0]

autograd_f = jax.jit(jax.grad(f))
x0 = jnp.zeros(n)
print("Initial function value = {}".format(f(x0)))
print("Initial gradient norm = {}".format(jnp.linalg.norm(grad_f(x0))))

alpha_test = 5e-3
beta_test = 0.9

methods = {
    r"GD, $\alpha_k = {}$".format(alpha_test): fo.GradientDescent(f, autograd_f, ss.ConstantStepSize(alpha_test)),
    "GD Armijo": fo.GradientDescent(f, autograd_f,
            ss.Backtracking("Armijo", rho=0.7, beta=0.1, init_alpha=1.)),
    r"HB, $\beta = {}$".format(beta_test): HeavyBall(f, autograd_f, ss.ConstantStepSize(alpha_test), beta=beta_test),

}
# x0 = np.random.rand(n)
# x0 = jnp.zeros(n)
x0 = jax.random.normal(jax.random.PRNGKey(0), (X.shape[1],))
max_iter = 400
tol = 1e-5

for m in methods:
    print(m)
    _ = methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)

scopt_cg_array = []
scopt_cg_callback = lambda x: callback(x, scopt_cg_array)
x = scopt.minimize(f, x0, tol=tol, method="CG", jac=autograd_f, callback=scopt_cg_callback, options={"maxiter": max_iter})
x = x.x

figsize = (10, 8)
fontsize = 26
plt.figure(figsize=figsize)
for m in methods:
    plt.semilogy([np.linalg.norm(autograd_f(x)) for x in methods[m].get_convergence()], label=m)
plt.semilogy([np.linalg.norm(autograd_f(x)) for x in scopt_cg_array], label="CG PR")
plt.legend(fontsize=fontsize, loc="best")
plt.xlabel("Number of iteration, $k$", fontsize=fontsize)
plt.ylabel(r"$\| f'(x_k)\|_2$", fontsize=fontsize)
plt.xticks(fontsize=fontsize)
_ = plt.yticks(fontsize=fontsize)

for m in methods:
    print(m)
    %timeit methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)


I am working on the file "Spring2021/acc_grad_vol2.ipynb".
The first task is:
Главное про ускоренный метод Нестерова

Теоретически оптимальная скорость сходимости для выпуклых и сильно выпуклых функций
Необходимо подбирать шаг
Волнообразное поведение может быть подавлено с помощью рестартов
Нелинейные композиции градиентов могут дать более быстрые на практике методы (см самое начало семинара)
Оригинальное доказательство сложно понять на интуитивном уровне, есть гораздо более простые подходы, будут рассказаны позднее

Адаптивный выбор константы $L$

В методе Нестерова шаг у градиента равен $\frac{1}{L}$, где $L$ - константа Липшица градиента
Однако она обычно неизвестна
НО! У нас есть условие на эту константу, а именно

$$
f(y) \leq f(x) + \langle f'(x), y - x \rangle + \frac{L}{2}\|x - y\|_2^2
$$

Аналогия с правилами Армихо и прочими такого типа
Более подробно про этот метод смотрите в книге лектора

```python
def backtracking_L(f, h, grad, x, L0, rho):
L = L0

fx = f(x)

gradx = grad(x)

while True:

    y = x - 1 / L * h

    if f(y) <= fx - 1 / L * gradx.dot(h) + 1 / (2 * L) * h.dot(h):

        break

    else:

        L = L * rho

return L

```
Эксперименты
Can you write Python code for it?

beta_test = 0.9
methods = {
    "GD Armijo": fo.GradientDescent(f, grad,
            ss.Backtracking("Armijo", rho=0.5, beta=0.1, init_alpha=1.)),
    r"HB, $\beta = {}$".format(beta_test): HeavyBall(f, grad, ss.ConstantStepSize(1 / L), beta=beta_test),
    "Nesterov": fo.AcceleratedGD(f, grad, ss.ConstantStepSize(1 / L)),
    "Nesterov adaptive": fo.AcceleratedGD(f, grad, ss.Backtracking(rule_type="Lipschitz", rho=0.5, init_alpha=1)),
}
x0 = np.zeros(n)
max_iter = 2000
tol = 1e-6

for m in methods:
    _ = methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)

figsize = (10, 8)
fontsize = 26
plt.figure(figsize=figsize)
for m in methods:
    plt.semilogy([np.linalg.norm(grad(x)) for x in methods[m].get_convergence()], label=m)
plt.legend(fontsize=fontsize, loc="best")
plt.xlabel("Number of iteration, $k$", fontsize=fontsize)
plt.ylabel(r"$\| f'(x_k)\|_2$", fontsize=fontsize)
plt.xticks(fontsize=fontsize)
_ = plt.yticks(fontsize=fontsize)

for m in methods:
    print(m)
    %timeit methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)


I am working on the file "Spring2021/acc_grad_vol2.ipynb".
The first task is:
Эксперимент на неквадратичной задаче
$$
f(w) = \frac12 \|w\|2^2 + C \frac1m \sum{i=1}^m \log (1 + \exp(- y_i \langle x_i, w \rangle)) \to \min_w
$$
Can you write Python code for it?

import jax
import jax.numpy as jnp
from jax.config import config
config.update("jax_enable_x64", True)
import sklearn.datasets as skldata
n = 300
m = 1000

X, y = skldata.make_classification(n_classes=2, n_features=n, n_samples=m, n_informative=n//3, random_state=42)
C = 1

@jax.jit
def f(w):
    return jnp.linalg.norm(w)**2 / 2 +  C * jnp.mean(jnp.logaddexp(jnp.zeros(X.shape[0]), -y * (X @ w)))

# def grad(w):
#     denom = scspec.expit(-y * X.dot(w))
#     return w - C * X.T.dot(y * denom) / X.shape[0]

autograd_f = jax.jit(jax.grad(f))
x0 = jnp.ones(n)
print("Initial function value = {}".format(f(x0)))
print("Initial gradient norm = {}".format(jnp.linalg.norm(autograd_f(x0))))

beta_test = 0.9
L_trial = (1 + C*100)
methods = {
    "GD Armijo": fo.GradientDescent(f, autograd_f,
            ss.Backtracking("Armijo", rho=0.5, beta=0.01, init_alpha=1.)),
    r"HB, $\beta = {}$".format(beta_test): HeavyBall(f, autograd_f, ss.ConstantStepSize(1 / L_trial), beta=beta_test),
    "Nesterov": fo.AcceleratedGD(f, autograd_f, ss.ConstantStepSize(1 / L_trial)),
    "Nesterov adaptive": fo.AcceleratedGD(f, autograd_f, ss.Backtracking(rule_type="Lipschitz", rho=0.5, init_alpha=1)),
}
max_iter = 2000
tol = 1e-6

for m in methods:
    _ = methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)

figsize = (10, 8)
fontsize = 26
plt.figure(figsize=figsize)
for m in methods:
    plt.semilogy([np.linalg.norm(autograd_f(x)) for x in methods[m].get_convergence()], label=m)
plt.legend(fontsize=fontsize, loc="best")
plt.xlabel("Number of iteration, $k$", fontsize=fontsize)
plt.ylabel(r"$\| f'(x_k)\|_2$", fontsize=fontsize)
plt.xticks(fontsize=fontsize)
_ = plt.yticks(fontsize=fontsize)

for m in methods:
    print(m)
    %timeit methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Backtracking
```python
def SelectStepSize(x, f, h, rho, alpha0, beta1, beta2):
# 0 < rho < 1

# alpha0 - initial guess of step size

# beta1 and beta2 - constants from conditions

alpha = alpha0

# Check violating sufficient decrease and curvature conditions

while (f(x - alpha * h) >= f(x) + beta1 * alpha grad_f(x_k).dot(h)) and

      (grad_f(x - alpha * h).dot(h) <= beta2 * grad_f(x_k).dot(h)):

    alpha *= rho

return alpha

```
Теоремы сходимости (Б.Т. Поляк Введение в оптимизацию, гл. 1, $\S$ 4;  гл. 3, $\S$ 1; Ю.Е. Нестеров Введение в выпуклую оптимизацию, $\S$ 2.2)
От общего к частному:
Теорема 1.
Пусть

$f(x)$ дифференцируема на $\mathbb{R}^n$,
градиент $f(x)$ удовлетворяет условию Липшица с константой $L$
$f(x)$ ограничена снизу
$\alpha = const$ и $0 < \alpha < \frac{2}{L}$

Тогда для градиентного метода выполнено:
$$
\lim\limits_{k \to \infty} f'(x_k) = 0,
$$
а функция монотонно убывает $f(x_{k+1}) < f(x_k)$.
Теорема 2. Пусть
- $f(x)$ дифференцируема на $\mathbb{R}^n$
- $f(x)$ выпукла
- $f'(x)$ удовлетворяет условию Липшица с константой $L$
- $\alpha = \dfrac{1}{L}$
Тогда
$$
f(x_k) - f^ \leq \dfrac{2L \| x_0 - x^\|^2_2}{k+4}
$$
Теорема 3.
Пусть
- $f(x)$ дважды дифференцируема и $\mu\mathbf{I} \preceq f''(x) \preceq L\mathbf{I}$ для всех $x$
- $\alpha = const$ и $0 < \alpha < \frac{2}{L}$
Тогда
$$
\| x_k - x^\|_2 \leq \|x_0 - x^\|_2 q^k, \qquad q = \max(|1 - \alpha l|, |1 - \alpha L|) < 1
$$
и минимальное $q^ = \dfrac{L - \mu}{L + \mu}$ при $\alpha^ = \dfrac{2}{L + \mu}$
От чего зависит $q^*$ и как это использовать?
Из Теоремы 3 имеем
$$
q^* = \dfrac{L - \mu}{L + \mu} = \dfrac{L/\mu - 1}{L/\mu + 1} = \dfrac{M - 1}{M + 1},
$$
где $M$ - оценка числа обусловленности $f''(x)$.
Вопрос: что такое число обусловленности матрицы?

При $M \gg 1$, $q^ \to 1 \Rightarrow$ оооочень медленная сходимости градиентного метода. Например при $M = 100$: $q^ \approx 0.98 $
При $M \simeq 1$, $q^ \to 0 \Rightarrow$ ускорение сходимости градиентного метода. Например при $M = 4$: $q^ = 0.6 $

Вопрос:...
Can you write Python code for it?

def GradientDescent(f, gradf, 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)
        alpha = line_search(x, -gradient, **opt_arg)
        x = x - alpha * gradient
        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


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Время работы
Can you write Python code for it?

%timeit binary_search(f, a, b, epsilon)
%timeit golden_search(f, a, b, epsilon)


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Выбор шага
Реализации различных способов выбора шага приведены тут
Зависимость от обусловленности матрицы $f''(x)$
Рассмотрим задачу
$$
\min f(x),
$$
где
$$ f(x) = x^{\top}Ax, \; A = \begin{bmatrix} 1 & 0\ 0 & \gamma \end{bmatrix} $$
$$
f'(x) = 2Ax
$$
Can you write Python code for it?

def my_f(x, A):
    return 0.5 * x.dot(A.dot(x))

def my_gradf(x, A):
    return A.dot(x)

plt.rc("text", usetex=True)

gammas = [0.1, 0.5, 1, 2, 3, 4, 5, 10, 20, 50, 100, 1000, 5000, 10000]
# gammas = [1]
num_iter_converg = []
for g in gammas:
    A = np.array([[1, 0],
                  [0, g]], dtype=np.float64)
    f = lambda x: my_f(x, A)
    gradf = lambda x: my_gradf(x, A)
#     x0 = np.random.rand(A.shape[0])
#     x0 = np.sort(x0)
#     x0 = x0[::-1]
    x0 = np.array([g, 1], dtype=np.float64)
#     print x0[1] / x0[0]
    gd = methods.fo.GradientDescent(f, gradf, ss.ExactLineSearch4Quad(A))
    x = gd.solve(x0, tol=1e-7, max_iter=100)
    num_iter_converg.append(len(gd.get_convergence()))

plt.figure(figsize=(8, 6))
plt.loglog(gammas, num_iter_converg)
plt.xticks(fontsize = 20)
plt.yticks(fontsize = 20)
plt.xlabel(r"$\gamma$", fontsize=20)
plt.ylabel(r"Number of iterations with $\varepsilon = 10^{-7}$", fontsize=20)


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Значение теорем сходимости (Б.Т. Поляк Введение в оптимизацию, гл. 1, $\S$ 6)

Что дают теоремы сходимости

класс задач, для которых можно рассчитывать на применимость метода (важно не завышать условия!)
выпуклость
гладкость

качественное поведение метода
существенно ли начальное приближение
по какому функционалу есть сходимость

оценку скорости сходимости
теоретическая оценка поведения метода без проведения экспериментов
определение факторов, которые влияют на сходимость (обусловленность, размерность, etc)
иногда заранее можно выбрать число итераций для достижения заданной точности

Что НЕ дают теоремы сходимости

сходимость метода ничего не говорит о целесообразности его применения
оценки сходимости зависят от неизвестных констант - неконструктивный характер
учёт ошибок округления и точности решения вспомогательных задач

Мораль: нужно проявлять разумную осторожность
и здравый смысл!
Классификация задач

Безусловная оптимизация
целевая функция липшицева
градиент целевой функции липшицев

Условная оптимизация
многогранник
множество простой структуры
общего вида

Классификация методов
Какой размер истории нужно хранить для обновления?

Одношаговые методы

$$
x_{k+1} = \Phi(x_k)
$$

Многошаговые методы

$$
x_{k+1} = \Phi(x_k, x_{k-1}, ...)
$$
Какой порядок поизводных нужно вычислить?

Методы нулевого порядка: оракул возвращает только значение функции $f(x)$

Методы первого порядка: оракул возвращает значение функции $f(x)$ и её градиент $f'(x)$

Методы второго порядка: оракул возвращает значение функции $f(x)$, её градиент $f'(x)$ и гессиан $f''(x)$.

Q: существуют ли методы более высокого порядка?
А: Implementable tensor methods in unconstrained convex optimization by Y. Nesterov, 2019
Одномерная минимизация
Определение. Функция $f(x)$ называется унимодальной на $[a, b]$, если существует такая точка $x^ \in [a, b]$, что
- $f(x_1) > f(x_2)$ для любых $a \leq x_1 < x_2 < x^$,
и
- $f(x_1) < f(x_2)$ для любых $x^* < x_1 < x_2 \leq b$.
Вопрос: какая геометрия унимодальных функций?
Метод...
Can you write Python code for it?

def binary_search(f, a, b, epsilon, callback=None):
    c = (a + b) / 2.0
    while abs(b - a) > epsilon:
#         Check left subsegment
        y = (a + c) / 2.0
        if f(y) <= f(c):
            b = c
            c = y
        else:
#         Check right subsegment
            z = (b + c) / 2.0
            if f(c) <= f(z):
                a = y
                b = z
            else:
                a = c
                c = z
        if callback is not None:
            callback(a, b)
    return c

def my_callback(a, b, left_bound, right_bound, approximation):
    left_bound.append(a)
    right_bound.append(b)
    approximation.append((a + b) / 2.0)

import numpy as np

left_boud_bs = []
right_bound_bs = []
approximation_bs = []

callback_bs = lambda a, b: my_callback(a, b,
            left_boud_bs, right_bound_bs, approximation_bs)

# Target unimodal function on given segment
f = lambda x: (x - 2) * x * (x + 2)**2 # np.power(x+2, 2)
# f = lambda x: -np.sin(x)
x_true = -2
# x_true = np.pi / 2.0
a = -3
b = -1.5
epsilon = 1e-8
x_opt = binary_search(f, a, b, epsilon, callback_bs)
print(np.abs(x_opt - x_true))
plt.figure(figsize=(10,6))
plt.plot(np.linspace(a,b), f(np.linspace(a,b)))
plt.title("Objective function", fontsize=28)
plt.xticks(fontsize = 28)
_ = plt.yticks(fontsize = 28)


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Метод золотого сечения
Can you write Python code for it?

left_boud_gs = []
right_bound_gs = []
approximation_gs = []

cb_gs = lambda a, b: my_callback(a, b, left_boud_gs, right_bound_gs, approximation_gs)
x_gs = golden_search(f, a, b, epsilon, cb_gs)

print(np.abs(x_opt - x_true))


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Метод золотого сечения
Идея:
делить отрезок $[a,b]$ не на две равные насти,
а в пропорции "золотого сечения".
Оценим скорость сходимости аналогично методу дихотомии:
$$
|x_{K+1} - x^*| \leq b_{K+1} - a_{K+1} = \left( \frac{1}{\tau} \right)^{N-1} (b - a) \approx 0.618^K(b-a),
$$
где $\tau = \frac{\sqrt{5} + 1}{2}$.

Константа геометрической прогрессии больше, чем у метода дихотомии
Количество вызовов функции меньше, чем у метода дихотомии
Can you write Python code for it?

def golden_search(f, a, b, tol=1e-5, callback=None):
    tau = (np.sqrt(5) + 1) / 2.0
    y = a + (b - a) / tau**2
    z = a + (b - a) / tau
    while b - a > tol:
        if f(y) <= f(z):
            b = z
            z = y
            y = a + (b - a) / tau**2
        else:
            a = y
            y = z
            z = a + (b - a) / tau
        if callback is not None:
            callback(a, b)
    return (a + b) / 2.0

left_boud_gs = []
right_bound_gs = []
approximation_gs = []

cb_gs = lambda a, b: my_callback(a, b, left_boud_gs, right_bound_gs, approximation_gs)
x_gs = golden_search(f, a, b, epsilon, cb_gs)

print(f(x_opt))
print(f(x_gs))
print(np.abs(x_opt - x_true))


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Подробнее про jax, его возможности и особенности можно посмотреть например тут
Can you write Python code for it?

gd = methods.fo.GradientDescent(f, grad_f_jax, ss.Backtracking("Armijo", rho=0.5, beta=0.1, init_alpha=1.))
x = gd.solve(x0, tol=1e-5, max_iter=100, disp=True)

x_conv = gd.get_convergence()
grad_conv = [jnp.linalg.norm(grad_f_jax(x)) for x in x_conv]
plt.figure(figsize=(8,6))
plt.semilogy(grad_conv, label=r"$\| f'(x_k) \|_2$")
plt.semilogy([np.linalg.norm(x - np.array(x_k)) for x_k in x_conv], label=r"$\|x_k - x^*\|_2$")
plt.semilogy([np.linalg.norm(prob.value - f(np.array(x_k))) for x_k in x_conv], label=r"$\|f(x_k) - f^*\|_2$")
plt.semilogy([np.linalg.norm(np.array(x_conv[i]) - np.array(x_conv[i+1])) for i in range(len(x_conv) - 1)], label=r"$\|x_k - x_{k+1}\|_2$")
plt.semilogy([np.linalg.norm(f(np.array(x_conv[i])) - f(np.array(x_conv[i+1]))) for i in range(len(x_conv) - 1)], label=r"$\|f(x_k) - f(x_{k+1})\|_2$")
plt.xlabel(r"Number of iteration, $k$", fontsize=20)
plt.ylabel(r"Convergence rate", fontsize=20)
plt.xticks(fontsize = 20)
plt.yticks(fontsize = 20)
plt.legend(loc="best", fontsize=20)
plt.tight_layout()


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
При неудачном начальном приближении сходимость для плохо обусловенной задачи очень медленная
При случайном начальном приближении сходимость может быть гораздо быстрее теоретических оценок

Эксперимент на многомерной задаче
Пусть $A \in \mathbb{R}^{m \times n}$. Рассмотрим систему линейных неравенств: $Ax \leq 1$ при условии $|x_i| \leq 1$ для всех $i$.
Определение. Аналитическим центром системы неравенств $Ax \leq 1$ при условии $|x_i| \leq 1$ является решение задачи
$$
f(x) = - \sum_{i=1}^m \log(1 - a_i^{\top}x) - \sum_{i = 1}^n \log (1 - x^2_i) \to \min_x
$$
$$
f'(x) - ?
$$
Точное решение с помощью CVXPy
Can you write Python code for it?

import numpy as np

n = 1000
m = 2000
A = np.random.rand(n, m)

x = cvx.Variable(n)

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)
x = x.value
print("Optimal value =", prob.value)


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Пример иного поведения методов
$$
f(x) = \sin(\sin(\sin(\sqrt{x}))), \; x \in [2, 60]
$$
Can you write Python code for it?

f = lambda x: np.sin(np.sin(np.sin(np.sqrt(x))))
x_true = (3 * np.pi / 2)**2
a = 2
b = 60
epsilon = 1e-8
plt.plot(np.linspace(a,b), f(np.linspace(a,b)))
plt.xticks(fontsize = 28)
_ = plt.yticks(fontsize = 28)


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Решение с помощью градиентного спуска
Can you write Python code for it?

import cvxpy as cvx
print(cvx.installed_solvers())

# !pip install jax
# !pip install jaxlib

import jax.numpy as jnp
import jax

# from jax.config import config
# config.update("jax_enable_x64", True)

A = jnp.array(A)
print(A.dtype)
x0 = jnp.zeros(n)
f = lambda x: -jnp.sum(jnp.log(1 - A.T@x)) - jnp.sum(jnp.log(1 - x*x))
grad_f = lambda x: jnp.sum(A @ (jnp.diagflat(1 / (1 - A.T @ x))), \
                          axis=1) + 2 * x / (1 - jnp.power(x, 2))
grad_f_jax = jax.grad(f)
print(jnp.linalg.norm(grad_f(x0) - grad_f_jax(x0)))


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Сравнение скорости сходимости и времени работы методов
Метод дихотомии
Can you write Python code for it?

left_boud_bs = []
right_bound_bs = []
approximation_bs = []

callback_bs = lambda a, b: my_callback(a, b,
            left_boud_bs, right_bound_bs, approximation_bs)

x_opt = binary_search(f, a, b, epsilon, callback_bs)
print(np.abs(x_opt - x_true))


I am working on the file "Spring2021/intro_gd.ipynb".
The first task is:
Сходимость
Can you write Python code for it?

plt.figure(figsize=(8,6))
plt.semilogy(np.abs(x_true - np.array(approximation_bs, dtype=np.float64)), label="Binary")
plt.semilogy(np.abs(x_true - np.array(approximation_gs, dtype=np.float64)), label="Golden")
plt.legend(fontsize=28)
plt.xticks(fontsize=28)
_ = plt.yticks(fontsize=28)
plt.xlabel(r"Number of iterations, $k$", fontsize=26)
plt.ylabel("Error rate upper bound", fontsize=26)


I am working on the file "Spring2021/newton_quasi.ipynb".
The first task is:
Метод Ньютона даёт очень точное решение за длительное время
Градиентный спуска даёт не очень точное решение, но гораздо быстрее
Часто бывает, что очень точное решение не требуется, поэтому градиентный спуск может быть предпочтительнее

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)$ необходимо минимизировать
Квазиньютоновские методы частично решают эти проблемы

Как уменьшить сложность хранения и вычисления?

Сложность вычисления можно уменьшить с помощью

Квазиньютоновские методы, они же методы переменной метрики
Требуется хранение матрицы $n \times n$

Сложность вычисления и хранения можно уменьшить

квазиньютоновские методы с ограниченной памятью, например L-BFGS (Limited Broyden-Fletcher-Goldfarb-Shanno)
НЕ требуется хранить матрицу
вместо этого хранятся $k \ll n$ векторов из $\mathbb{R}^n$

Единообразный способ получения метода Ньютона и градиентного спуска

градиентный метод получен из аппроксимации первого...
Can you write Python code for it?

import numpy as np
import liboptpy.unconstr_solvers as methods
import liboptpy.step_size as ss
%matplotlib inline
import matplotlib.pyplot as plt
import scipy.optimize as scopt
plt.rc("text", usetex=True)

n = 3000
m = 100
x0 = np.zeros(n)
max_iter = 100
tol = 1e-5
A = np.random.rand(m, n) * 10

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

def bb_method(f, gradf, x0, tol=1e-6, maxiter=100, callback=None, alpha_type=1):
    it = 0
    x_prev = x0.copy()
    current_tol = np.linalg.norm(gradf(x_prev))
    alpha = 1e-4
    while current_tol > tol and it < maxiter:
        it += 1
        current_grad = gradf(x_prev)
        if it != 1:
            g = current_grad - prev_grad
            if alpha_type == 1:
                alpha = g.dot(s) / g.dot(g)
            elif alpha_type == 2:
                alpha = s.dot(s) / g.dot(s)
        if callback:
            callback(x_prev)
        x_next = x_prev - alpha * current_grad
        current_tol = np.linalg.norm(gradf(x_next))
        prev_grad = current_grad
        s = x_next - x_prev
        x_prev = x_next
    if callback:
        callback(x_prev)
    return x_next

method = {
    "BB 1": methods.fo.BarzilaiBorweinMethod(f, grad_f, init_alpha=1e-4, type=1),
    "BFGS": methods.fo.BFGS(f, grad_f),
    "DFP": methods.fo.DFP(f, grad_f),
    "LBFGS": methods.fo.LBFGS(f, grad_f),
}

for m in method:
    print("\t Method {}".format(m))
    _ = method[m].solve(x0=x0, tol=tol, max_iter=max_iter, disp=True)

print("\t Method BFGS Scipy")
scopt_conv = []
scopt_res = scopt.minimize(f, x0, method="BFGS", jac=grad_f, callback=lambda x: scopt_conv.append(x),
                           tol=tol, options={"maxiter": max_iter})
print("Result: {}".format(scopt_res.message))
if scopt_res.success:
    print("Convergence in {} iterations".format(scopt_res.nit))
print("Function value = {}".format(f(scopt_res.x)))

plt.figure(figsize=(8, 6))

for m in method:
   ...


I am working on the file "Spring2021/newton_quasi.ipynb".
The first task is:
Плохо обусловленная задача
Can you write Python code for it?

n = 50
D = np.arange(1, n+1)
U = np.random.randn(n, n)
U, _ = np.linalg.qr(U)
A = U.dot(np.diag(D)).dot(U.T)
b = np.random.randn(n)
eig_vals = np.linalg.eigvals(A)
print("Condition number = {}".format(np.max(eig_vals) / np.min(eig_vals)))

f = lambda x: 0.5 * x.T.dot(A.dot(x)) - b.dot(x)
gradf = lambda x: A.dot(x) - b
x0 = np.random.randn(n)

method = {
    "BB 1": methods.fo.BarzilaiBorweinMethod(f, gradf, init_alpha=1e-4, type=1),
    "BB 2": methods.fo.BarzilaiBorweinMethod(f, gradf, init_alpha=1e-4, type=2),
    "BFGS": methods.fo.BFGS(f, gradf),
    "DFP": methods.fo.DFP(f, gradf),
    "GD": methods.fo.GradientDescent(f, gradf, ss.ExactLineSearch4Quad(A, b)),
    "LBFGS": methods.fo.LBFGS(f, gradf, hist_size=10),
}

for m in method:
    print("\t Method {}".format(m))
    _ = method[m].solve(x0=x0, tol=tol, max_iter=max_iter, disp=True)

print("\t Method BFGS Scipy")

scopt_conv = []
scopt_res = scopt.minimize(f, x0, method="BFGS", jac=gradf, callback=lambda x: scopt_conv.append(x),
                           tol=tol, options={"maxiter": max_iter})
print("Result: {}".format(scopt_res.message))
if scopt_res.success:
    print("Convergence in {} iterations".format(scopt_res.nit))
print("Function value = {}".format(f(scopt_res.x)))

print("\t Method L-BFGS Scipy")

scopt_lbfgs_conv = []
scopt_res = scopt.minimize(f, x0, method="L-BFGS-B", jac=gradf, tol=tol,
                           options={"maxiter": max_iter, 'maxcor': 10, "ftol": 1e-10, "gtol": 1e-6},
                           callback=lambda x: scopt_lbfgs_conv.append(x),
                           )
print("Result: {}".format(scopt_res.message))
if scopt_res.success:
    print("Convergence in {} iterations".format(scopt_res.nit))
print("Function value = {}".format(f(scopt_res.x)))

plt.figure(figsize=(12, 8))
fontsize = 26
for m in method:
    plt.semilogy([np.linalg.norm(gradf(x)) for x in method[m].get_convergence()], label=m)

plt.semilogy([np.linalg.norm(gradf(x)) for x in [x0] + scopt_conv], label='BFGS...


I am working on the file "Spring2021/newton_quasi.ipynb".
The first task is:
Реализация метода Ньютона
Can you write Python code for it?

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


I am working on the file "Spring2021/newton_quasi.ipynb".
The first task is:
Сравнение времени работы
Can you write Python code for it?

%timeit newton.solve(x0, tol=1e-3, max_iter=5000).block_until_ready()
%timeit gd.solve(x0, tol=1e-3, max_iter=5000).block_until_ready()


I am working on the file "Spring2021/newton_quasi.ipynb".
The first task is:
Сравнение с градиентным спуском
Can you write Python code for it?

newton = methods.so.NewtonMethod(f, grad_f, hess_f,
                                 ss.Backtracking("Armijo", rho=0.4, beta=0.1, init_alpha=1.),
                                linsolver=jnp.linalg.solve)
# newton = methods.so.NewtonMethod(f, grad_f, hess_f, ss.ConstantStepSize(5e-1))
x_newton = newton.solve(x0, tol=1e-7, max_iter=50, disp=True)

gd = methods.fo.GradientDescent(f, grad_f, ss.Backtracking("Armijo", rho=0.5, beta=0.1, init_alpha=1.))
# agd = methods.fo.AcceleratedGD(f, grad_f, ss.ConstantStepSize(1e-3))
# gd = methods.fo.GradientDescent(f, grad_f, ss.ConstantStepSize(5e-3))
x_gd = gd.solve(x0, tol=1e-3, max_iter=3000, disp=True)
# x_agd = agd.solve(x0, tol=1e-3, max_iter=3000, disp=True)

%matplotlib inline
import matplotlib.pyplot as plt
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.grid(True)
plt.legend(loc="best", fontsize=24)


I am working on the file "Spring2021/newton_quasi.ipynb".
The first task is:
Точное решение с помощью CVXPy
Can you write Python code for it?

import cvxpy as cvx
x = cvx.Variable(n)

obj = cvx.Minimize(cvx.norm(x, 2)**2 / 2 + C * cvx.sum(cvx.logistic(cvx.multiply(-y, X * x))) / m)
prob = cvx.Problem(obj)
prob.solve(solver="SCS", verbose=True, max_iters=1000)
print("Optimal value =", prob.value)


I am working on the file "Spring2021/penalty_admm.ipynb".
The first task is:
Pro & Contra
Pro

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

Contra

решение задачи безусловной оптимизации не всегда является решением исходной задачи
если целевая функция определена только на допустимом множестве, то метод внешних штрафов не применим
квадратичная аппроксимация $Q(x, \mu)$ может быть неадекватной

Двойственная задача: напоминание

Исходная задача

\begin{align}
& \min f(x) \
\text{s.t. } & Ax = b
\end{align}

Лагранжиан

$$
L(x, \lambda) = f(x) + \lambda^{\top}(Ax - b)
$$

Двойственная задача

$$
\max_{\lambda} g(\lambda),
$$
где $g(\lambda) = \inf_x L(x, \lambda)$

Восстановление решения исходной заадчи

$$
x^ = \arg\min_x L(x, \lambda^)
$$
Решение двойственной задачи

Градиентный подъём, так как задача без ограничений

$$
\lambda_{k+1} = \lambda_k + \alpha_k g'(\lambda_k)
$$

При этом градиент двойственной функции

$$
g'(\lambda_k) = A\hat{x} - b,
$$
где $\hat{x} = \arg\min_x L(x, \lambda_k)$

Объединим два шага в один и получим

\begin{align}
& x_{k+1} = \arg\min_x L(x, \lambda_k)\
& \lambda_{k+1} = \lambda_k + \alpha_k (Ax_{k+1} - b)
\end{align}
Важный частный случай

Функция сепарабельна
Обновление $x$ распадается на параллельные задачи по каждой координате
Can you write Python code for it?

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.rc("text", usetex=True)
import cvxpy as cvx

def dual_ascent(update_x, A, b, alpha, x0, lambda0, max_iter):
    x = x0.copy()
    lam = lambda0.copy()
    conv_x = [x]
    conv_lam = [lam]
    for i in range(max_iter):
        x = update_x(x, lam, A, b)
        lam = lam + alpha * (A @ x - b)
        conv_x.append(x.copy())
        conv_lam.append(lam.copy())
    return x, lam, conv_x, conv_lam


I am working on the file "Spring2021/penalty_admm.ipynb".
The first task is:
Метод модифицированной функции Лагранжа
Мотивация: для метода штрафных функций решения подзадач могут нарушать ограничения, и известно только, что
$$
g_i(x^_k) \approx \frac{\lambda^_i}{\mu_k} \to 0, \quad \mu_k \to \infty
$$
Можно ли изменить $Q(x, \mu)$ так, чтобы избежать этого нарушения ограничений?
Идея: добавлять штраф не к целевой функции, а к функции Лагранжа. Аналог прямо-двойственного метода, так как за одну итерацию происходит обновление как прямых, так и двойственных переменных
Задача с ограничениями-равенствами
\begin{equation}
\begin{split}
& \min f(x)\
\text{s.t. } & g_i(x) = 0, \; i=1,\ldots,m
\end{split}
\end{equation}
Модифицированная функция Лагранжа
$$
M(x, \lambda, \mu) = f(x) + \sum\limits_{i=1}^m\lambda_i g_i(x) + \frac{\mu}{2}\sum\limits_{i=1}^mg^2_i(x)
$$
Необходимое условие минимума $M(x_k, \lambda^k, \mu_k)$
$$
f'(x_k) + \sum\limits_{i=1}^m (\lambda^k_i + \mu_k g_i(x_k) ) g'_i(x_k) \approx 0
$$
Отсюда получаем выражение для $\lambda^{k+1}$
$$
\lambda^{k+1}_i = \lambda^k_i + \mu_k g_i(x_k)
$$
Нарушение ограничений
$$
g_i(x_k) \approx \frac{\lambda^*_i - \lambda^k_i}{\mu_k} \to 0
$$
- В методе штрафных функций сходимость к 0 была порядка $1/\mu_k$
- В методе модифицированной функции Лагранжа сходимость к 0 быстрее, поскольку не только знаменатель возрастает, но и числитель стремится к 0
Задача с ограничениями типа равенства и неравенства
\begin{equation}
\begin{split}
& \min_x f(x)\
\text{s.t. } & g_i(x) = 0, \; i=1,\ldots,m \
& h_j(x) \leq 0, \; j = 1,\ldots,p
\end{split}
\end{equation}
Модифицированная функция Лагранжа
$$
M(x, \mu, \lambda, \nu) = f(x) + \sum\limits_{i=1}^m \left[\lambda_ig_i(x) + \frac{\mu}{2}g^2_i(x)\right] + \frac{1}{2\mu}\sum\limits_{j=1}^p\left[ (\nu_j + \mu h_j(x))_+^2 - \nu^2_j \right],
$$
где $\lambda$ - двойственные переменные для ограничений-равенств, $\nu$ - двойственная переменная для ограничений-неравенств.
Обновление двойственных переменных для неравенств
$$
\nu^{k+1}j = (\nu^k_j + \mu_k h_j(x_k))+
$$
Общая схема

Формируется функция $M$
По необходимым...
Can you write Python code for it?

def augmented_lagrangian(update_x, A, b, rho0, x0, lambda0, max_iter):
    x = x0.copy()
    lam = lambda0.copy()
    conv_x = [x]
    conv_lam = [lam]
    rho = rho0
    for i in range(max_iter):
        x = update_x(x, lam, A, b)
        lam = lam + rho * (A @ x - b)
        conv_x.append(x.copy())
        conv_lam.append(lam.copy())
    return x, lam, conv_x, conv_lam

def update_x_al(x, lam, A, b):
    return np.linalg.solve(P + rho * A.T @ A, c - A.T @ lam + A.T @ b)

rho = 10
max_iter = 1000
x_al, lam_al, conv_x_al, conv_lam_al = augmented_lagrangian(update_x_al, A, b, rho, x0, lam0, max_iter)
print(np.linalg.norm(A @ x_al - b))
print(0.5 * x_al @ P @ x_al - c @ x_al)

plt.plot([f(x) for x in conv_x_da], label="DA")
plt.plot([f(x) for x in conv_x_al], label="AL")
# plt.yscale("log")
plt.xscale("log")
plt.legend(fontsize=20)
plt.xlabel("\# iterations", fontsize=20)
plt.ylabel("Objective", fontsize=20)

plt.plot([L(x, lam) for x, lam in zip(conv_x_da, conv_lam_da)],
         label="DA")
plt.plot([L(x, lam) for x, lam in zip(conv_x_al, conv_lam_al)],
         label="AL")
plt.legend(fontsize=20)
plt.xscale("log")
plt.xlabel("\# iterations", fontsize=20)
plt.xlabel("Lagrangian", fontsize=20)

plt.semilogy([np.linalg.norm(A @ x - b) for x in conv_x_da], label="DA")
plt.semilogy([np.linalg.norm(A @ x - b) for x in conv_x_al], label="AL")
plt.legend(fontsize=20)
plt.xscale("log")
plt.xlabel("\# iterations", fontsize=20)
plt.ylabel("$\|Ax - b\|_2$", fontsize=20)
plt.grid(True)
plt.yticks(fontsize=20)
plt.xticks(fontsize=20)


I am working on the file "Spring2021/penalty_admm.ipynb".
The first task is:
Модельный пример
\begin{align}
& \min \frac{1}{2}x^{\top}Px - c^{\top}x\
\text{s.t. } & Ax = b
\end{align}

Лагранжиан $L(x, \lambda) = \frac{1}{2}x^{\top}Px - c^{\top}x + \lambda^{\top}(Ax - b)$
Обновление прямых переменных

$$
x_{k+1} = P^{-1}(c - A^{\top}\lambda_k)
$$
Can you write Python code for it?

m, n = 10, 20
A = np.random.randn(m, n)
b = np.random.randn(m)
P = np.random.randn(n, n)
P = P.T @ P
c = np.random.randn(n)
spec = np.linalg.eigvalsh(P)
mu = spec.min()
print(mu)

x = cvx.Variable(n)
obj = 0.5 * cvx.quad_form(x, P) - c @ x
problem = cvx.Problem(cvx.Minimize(obj), [A @ x == b])
problem.solve(verbose=True)
print(np.linalg.norm(A @ x.value - b))
print(problem.value)

x0 = np.random.randn(n)
lam0 = np.random.randn(m)

max_iter = 100000
alpha = 1e-3

def f(x):
    return 0.5 * x @ P @ x - c @ x

def L(x, lam):
    return f(x) + lam @ (A @ x - b)

def update_x(x, lam, A, b):
    return np.linalg.solve(P, c - A.T @ lam)

x_da, lam_da, conv_x_da, conv_lam_da = dual_ascent(update_x, A, b, alpha, x0, lam0, max_iter)
print(np.linalg.norm(A @ x_da - b))
print(0.5 * x_da @ P @ x_da - c @ x_da)

plt.figure(figsize=(10, 8))
plt.plot([f(x) for x in conv_x_da], label="Objective")
plt.plot(problem.value * np.ones(len(conv_x_da)), label="Traget value")
# plt.yscale("log")
plt.xscale("log")
plt.legend(fontsize=20)
plt.xlabel("\# iterations", fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid(True)

plt.plot([L(x, lam) for x, lam in zip(conv_x_da, conv_lam_da)],
         label="Lagrangian")
plt.legend(fontsize=20)
plt.xlabel("\# iterations", fontsize=20)

plt.semilogy([np.linalg.norm(A @ x - b) for x in conv_x_da], label="$\|Ax - b\|_2$")
plt.legend(fontsize=20)
plt.xlabel("\# iterations", fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid(True)


I am working on the file "Spring2021/penalty_admm.ipynb".
The first task is:
Существенная проблема

Слагаемое $\|Ax - b\|_2^2$ сделало лагранжиан НЕсепарабельным!

Сделаем его сепарабельным и получим ADMM
Задача станет такой
\begin{align}
& \min f(x) + I_{Ax = b} (z)\
\text{s.t. } & x = z
\end{align}
Для неё модифицированный лагранжиан примет вид
$$
L_{\rho}(x, z, \lambda) = f(x) + I_{Ax = b} (z) + \lambda^{\top}(x - z) + \frac{\rho}{2}\|x - z\|_2^2
$$

Теперь метод примет вид

\begin{align}
& x_{k+1} = \arg\min_x L_{\rho}(x, z_k, \lambda_k)\
& z_{k+1} = \arg\min_z L_{\rho}(x_{k+1}, z, \lambda_k) \
& \lambda_{k+1} = \lambda_k + \rho (x_{k+1} - z_{k+1})
\end{align}
- Обновление $z$ эквивалентно $\pi_{Ax = b}(x_{k+1} + \frac{\lambda_k}{\rho})$
Can you write Python code for it?

def admm(update_x, update_z, rho0, x0, z0, lambda0, max_iter):
    x = x0.copy()
    z = z0.copy()
    lam = lambda0.copy()
    conv_x = [x]
    conv_z = [z]
    conv_lam = [lam]
    rho = rho0
    for i in range(max_iter):
        x = update_x(x, z, lam, A, b)
        z = update_z(x, z, lam, A, b)
        lam = lam + rho * (x - z)
        conv_x.append(x.copy())
        conv_z.append(z.copy())
        conv_lam.append(lam.copy())
    return x, z, lam, conv_x, conv_z, conv_lam

def update_x_admm(x, z, lam, A, b):
    n = x.shape[0]
    return np.linalg.solve(P + rho*np.eye(n), -lam + c + rho * z)

def update_z_admm(x, z, lam, A, b):
    x_hat = lam / rho + x
    return x_hat - A.T @ np.linalg.solve(A @ A.T, A @ x_hat - b)

z0 = np.random.randn(n)
lam0 = np.random.randn(n)
rho = 10

x_admm, z_admm, lam_admm, conv_x_admm, conv_z_admm, conv_lam_admm = admm(update_x_admm,
                                                                         update_z_admm,
                                                                         rho, x0, z0, lam0,
                                                                         max_iter=100)
print(f(x_admm))

plt.figure(figsize=(10, 8))
plt.plot([f(x) for x in conv_x_da], label="DA")
plt.plot([f(x) for x in conv_x_al], label="AL")
plt.plot([f(x) for x in conv_x_admm], label="ADMM x")
plt.plot([f(z) for z in conv_z_admm], label="ADMM z")
# plt.yscale("log")
plt.xscale("log")
plt.legend(fontsize=20)
plt.xlabel("\# iterations", fontsize=20)
plt.ylabel("Objective", fontsize=20)
plt.grid(True)
plt.yticks(fontsize=20)
plt.xticks(fontsize=20)

plt.semilogy([np.linalg.norm(A @ x - b) for x in conv_x_da], label="DA")
plt.semilogy([np.linalg.norm(A @ x - b) for x in conv_x_al], label="AL")
plt.semilogy([np.linalg.norm(A @ x - b) for x in conv_x_admm], label="ADMM")
plt.legend(fontsize=20)
plt.xscale("log")
plt.xlabel("\# iterations", fontsize=20)
plt.ylabel("$\|Ax - b\|_2$",...


I am working on the file "Spring2021/penalty_admm.ipynb".
The first task is:
Учтём, что все свойства сохранятся при аффинных преобразованиях

Тогда наша задача в общем виде может быть записана как

\begin{align}
& \min f(x) + g(z)\
\text{s.t. } & Ax + Bz = d
\end{align}

Модифицированный лагранжиан для неё будет

$$
L_{\rho}(x, z, \lambda) = f(x) + g(z) + \lambda^{\top}(Ax + Bz - d) + \frac{\rho}{2}\|Ax + Bz - d\|_2^2
$$
- В этом случае сепарабельность по $z$ и $x$, но не внутри этих переменных
- В итоге, после внесения линейного слагаемого в квадратичное получим
\begin{align}
& x_{k+1} = \arg\min_x \left( f(x) + \frac{\rho}{2}\|Ax + Bz_k - d + u_k \|2^2 \right)\
& z{k+1} = \arg\min_z  \left( g(z) + \frac{\rho}{2}\|Ax_{k+1} + Bz - d + u_k \|2^2 \right)\
& u{k+1} = u_k + x_{k+1} - z_{k+1},
\end{align}
где $u_k = \lambda_k / \rho$
Как это всё использовать?

Часто приводить вашу задачу к стандартному виду с предыдущего слайда неудобно
Поэтому лучше для конкретной задачи приводить её руками к виду, который допускает применение ADMM
Выписать аналитически все решения вспомогательных задач
Реализовать их вычисления наиболее оптимальным образом (сделать факторизации матриц, которые не меняются с итерациями)

Задача линейного программирования
\begin{align}
& \min c^{\top}x\
\text{s.t. } & Ax = b\
& x \geq 0
\end{align}

Модифицированный лагранжиан

$$
L_{\rho}(x, z, \lambda) = c^{\top}x + I_{z \geq 0}(z) + \lambda^{\top}(x - z) + \frac{\rho}{2}\|x - z\|_2^2,
$$
где $c^{\top}x$ определена на множестве $Ax = b$.

Шаг обновления по $x$ примет вид

$$
x_{k+1} = \arg\min_{x: \; Ax = b} c^{\top}x +\lambda^{\top}x + \frac{\rho}{2}\|x - z\|_2^2
$$

Получим систему из условий оптимальности

$$
\begin{bmatrix}
\rho I & A^{\top} \
A & 0
\end{bmatrix}
\begin{bmatrix}
x_{k+1}\
\mu
\end{bmatrix}
=
\begin{bmatrix}
-\lambda_k - c + \rho z_k\
b
\end{bmatrix}
$$
Can you write Python code for it?

import scipy.optimize as scopt

m, n = 10, 200
A = np.random.rand(m, n)
b = np.random.rand(m)
c = np.random.rand(n)

scipy_linprog_conv = []
def callback_splin(cur_res):
    scipy_linprog_conv.append(cur_res)

res = scopt.linprog(c, A_eq=A, b_eq=b,
                    bounds=[(0, None) for i in range(n)],
                    callback=callback_splin, method="simplex")
print(res)

def update_x_admm(x, z, lam, A, b):
    n = x.shape[0]
    m = A.shape[0]
    C = np.block([[rho * np.eye(n), A.T], [A, np.zeros((m, m))]])
    rhs = np.block([-lam - c + rho * z, b])
    return np.linalg.solve(C, rhs)[:n]

def update_z_admm(x, z, lam, A, b):
    x_hat = lam / rho + x
    return np.clip(x_hat, 0, np.max(x_hat))

x0 = np.random.randn(n)
z0 = np.random.randn(n)
lam0 = np.random.randn(n)
rho = 10

x_admm, z_admm, lam_admm, conv_x_admm, conv_z_admm, conv_lam_admm = admm(update_x_admm,
                                                                         update_z_admm,
                                                                         rho, x0, z0, lam0, max_iter=10000)
print(c @ x_admm, res.fun)

print(c @ x_admm - res.fun, np.linalg.norm(x_admm - res.x))

plt.figure(figsize=(10, 8))
plt.plot([c @ x for x in conv_x_admm[:100]], label="ADMM")
plt.plot([c @ res.x for res in scipy_linprog_conv], label="Scipy")
plt.legend(fontsize=20)
plt.grid(True)
plt.xlabel("\# iterations", fontsize=20)
plt.ylabel("$c^{\\top}x_k$", fontsize=20)
plt.yticks(fontsize=20)
plt.xticks(fontsize=20)


I am working on the file "Spring2022/cg.ipynb".
The first task is:
График сходимости
Can you write Python code for it?

plt.figure(figsize=(8,6))
plt.semilogy([np.linalg.norm(grad_f(x)) for x in cg_quad.get_convergence()], label=r"$\|f'(x_k)\|^{CG}_2$", linewidth=2)
plt.semilogy([np.linalg.norm(grad_f(x)) for x in scopt_cg_array[:max_iter]], label=r"$\|f'(x_k)\|^{CG_{PR}}_2$", linewidth=2)
plt.semilogy([np.linalg.norm(grad_f(x)) for x in gd.get_convergence()], label=r"$\|f'(x_k)\|^{G}_2$", linewidth=2)
plt.legend(loc="best", fontsize=20)
plt.xlabel(r"Iteration number, $k$", fontsize=20)
plt.ylabel("Convergence rate", fontsize=20)
plt.xticks(fontsize=18)
_ = plt.yticks(fontsize=18)

print([np.linalg.norm(grad_f(x)) for x in cg_quad.get_convergence()])

plt.figure(figsize=(8,6))
plt.plot([f(x) for x in cg_quad.get_convergence()], label=r"$f(x^{CG}_k)$", linewidth=2)
plt.plot([f(x) for x in scopt_cg_array], label=r"$f(x^{CG_{PR}}_k)$", linewidth=2)
plt.plot([f(x) for x in gd.get_convergence()], label=r"$f(x^{G}_k)$", linewidth=2)
plt.legend(loc="best", fontsize=20)
plt.xlabel(r"Iteration number, $k$", fontsize=20)
plt.ylabel("Function value", fontsize=20)
plt.xticks(fontsize=18)
_ = plt.yticks(fontsize=18)


I am working on the file "Spring2022/cg.ipynb".
The first task is:
Неквадратичная функция
$$
f(w) = \frac12 \|w\|2^2 + C \frac1m \sum{i=1}^m \log (1 + \exp(- y_i \langle x_i, w \rangle)) \to \min_w
$$
Can you write Python code for it?

import numpy as np
import sklearn.datasets as skldata
import scipy.special as scspec
import jax
import jax.numpy as jnp
from jax.config import config
config.update("jax_enable_x64", True)

n = 300
m = 1000

X, y = skldata.make_classification(n_classes=2, n_features=n, n_samples=m, n_informative=n//3, random_state=0)
X = jnp.array(X)
y = jnp.array(y)
C = 1
@jax.jit
def f(w):
    return jnp.linalg.norm(w)**2 / 2 +  C * jnp.mean(jnp.logaddexp(jnp.zeros(X.shape[0]), -y * (X @ w)))

autograd_f = jax.jit(jax.grad(f))
def grad_f(w):
    denom = scspec.expit(-y * X.dot(w))
    return w - C * X.T.dot(y * denom) / X.shape[0]

x0 = jax.random.normal(jax.random.PRNGKey(0), (n,))
print("Initial function value = {}".format(f(x0)))
print("Initial gradient norm = {}".format(jnp.linalg.norm(autograd_f(x0))))
print("Initial gradient norm = {}".format(jnp.linalg.norm(grad_f(x0))))


I am working on the file "Spring2022/cg.ipynb".
The first task is:
Распределение собственных значений
Can you write Python code for it?

%matplotlib inline
import matplotlib.pyplot as plt
plt.rc("text", usetex=True)
plt.rc("font", family='serif')
eigs = np.linalg.eigvalsh(A)
plt.semilogy(np.unique(eigs))
plt.ylabel("Eigenvalues", fontsize=20)
plt.xticks(fontsize=18)
_ = plt.yticks(fontsize=18)


I am working on the file "Spring2022/cg.ipynb".
The first task is:
Реализация метода Флетчера-Ривса
Can you write Python code for it?

def ConjugateGradientFR(f, gradf, x0, num_iter=100, tol=1e-8, callback=None, restart=False):
    x = x0
    grad = gradf(x)
    p = -grad
    it = 0
    while np.linalg.norm(gradf(x)) > tol and it < num_iter:
        alpha = utils.backtracking(x, p, method="Wolfe", beta1=0.1, beta2=0.4, rho=0.5, f=f, grad_f=gradf)
        if alpha < 1e-18:
            break
        x = x + alpha * p
        if callback is not None:
            callback(x)
        grad_next = gradf(x)
        beta = grad_next.dot(grad_next) / grad.dot(grad)
        p = -grad_next + beta * p
        grad = grad_next.copy()
        it += 1
        if restart and it % restart == 0:
            grad = gradf(x)
            p = -grad
    return x


I am working on the file "Spring2022/cg.ipynb".
The first task is:
Реализация метода сопряжённых градиентов
Can you write Python code for it?

def ConjugateGradientQuadratic(x0, A, b, tol=1e-8, callback=None):
    x = x0
    r = A.dot(x0) - b
    p = -r
    while np.linalg.norm(r) > tol:
        alpha = r.dot(r) / p.dot(A.dot(p))
        x = x + alpha * p
        if callback is not None:
            callback(x)
        r_next = r + alpha * A.dot(p)
        beta = r_next.dot(r_next) / r.dot(r)
        p = -r_next + beta * p
        r = r_next
    return x

import liboptpy.unconstr_solvers as methods
import liboptpy.step_size as ss

max_iter = 70
print("\t CG quadratic")
cg_quad = methods.fo.ConjugateGradientQuad(A, b)
x_cg = cg_quad.solve(x0, tol=1e-7, max_iter=max_iter, disp=True)

print("\t Gradient Descent")
gd = methods.fo.GradientDescent(f, grad_f, ss.ExactLineSearch4Quad(A, b))
x_gd = gd.solve(x0, tol=1e-7, max_iter=max_iter, disp=True)

print("Condition number of A =", abs(max(eigs)) / abs(min(eigs)))


I am working on the file "Spring2022/hb_acc_grad.ipynb".
The first task is:
Главное про метод тяжёлого шарика

Двухшаговый метод
Не обязательно монотонный
Параметры зависят от неизвестных констант
Решает проблему осцилляций для плохо обусловленных задач
Сходимость для сильно выпуклых функций совпадает с оптимальной оценкой

Ускоренный метод Нестерова

Предложен в 1983 г. Ю.Е. Нестеровым

Одна из возможных форм записи

\begin{equation}
\begin{split}
& y_0 = x_0 \
& x_{k+1} = y_k - \alpha_k f'(y_k)\
& y_{k+1} = x_{k+1} + \frac{k}{k + 3} (x_{k+1} - x_k)
\end{split}
\end{equation}

Сравните с методом тяжёлого шарика
Tакже не обязательно монотонен
Для любителей геометрии есть альтернативный метод под названием geometric descent с такой же скоростью сходимости

Геометрическая интерпретация ускоренного метода Нестерова

Теорема сходимости

Пусть $f$ выпукла с Липшицевым градиентом, а шаг $\alpha_k = \frac{1}{L}$. Тогда ускоренный метод Нестерова сходится как

$$
f(x_k) - f^ \leq \frac{2L \|x_0 - x^\|_2^2}{(k+1)^2}
$$

Пусть $f$ сильно выпукла с липшицевым градиентом. Тогда ускоренный метод Нестерова при шаге $\alpha_k = \frac{1}{L}$ сходится как

$$
f(x_k) - f^* \leq L\|x_k - x_0\|_2^2 \left(1 - \frac{1}{\sqrt{\kappa}} \right)^k
$$
Тестовая задача
$$
f(x) = \frac{1}{2}x^{\top}Ax - b^{\top}x \to \min_x,
$$
где матрица $A$ положительно полуопределённая, то есть функция НЕ является сильно выпуклой
Can you write Python code for it?

import numpy as np
import liboptpy.unconstr_solvers.fo as fo
import liboptpy.step_size as ss
import liboptpy.base_optimizer as base
import matplotlib.pyplot as plt
%matplotlib inline

np.random.seed(42)
n = 100
A = np.random.randn(n, n)
A = A.T.dot(A)
A_eigvals = np.linalg.eigvalsh(A)
mu = np.min(A_eigvals)
A = A - (mu - 1e-6) * np.eye(n)
x_true = np.random.randn(n)
b = A.dot(x_true)
f = lambda x: 0.5 * x.dot(A.dot(x)) - b.dot(x)
grad = lambda x: A.dot(x) - b
A_eigvals = np.linalg.eigvalsh(A)
L = np.max(A_eigvals)
mu = np.min(A_eigvals)
print(L, mu)

class HeavyBall(base.LineSearchOptimizer):
    def __init__(self, f, grad, step_size, beta, **kwargs):
        super().__init__(f, grad, step_size, **kwargs)
        self._beta = beta

    def get_direction(self, x):
        self._current_grad = self._grad(x)
        return -self._current_grad

    def _f_update_x_next(self, x, alpha, h):
        if len(self.convergence) < 2:
            return x + alpha * h
        else:
            return x + alpha * h + self._beta * (x - self.convergence[-2])

    def get_stepsize(self):
        return self._step_size.get_stepsize(self._grad_mem[-1], self.convergence[-1], len(self.convergence))

beta_test = 0.8
methods = {
    "GD fixed": fo.GradientDescent(f, grad, ss.ConstantStepSize(1 / L)),
    "GD Armijo": fo.GradientDescent(f, grad,
            ss.Backtracking("Armijo", rho=0.5, beta=0.1, init_alpha=1.)),
    r"HB, $\beta = {}$".format(beta_test): HeavyBall(f, grad, ss.ConstantStepSize(1 / L), beta=beta_test),
    "Nesterov": fo.AcceleratedGD(f, grad, ss.ConstantStepSize(1 / L)),
}
x0 = np.random.randn(n)
max_iter = 10000
tol = 1e-6

for m in methods:
    _ = methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)

figsize = (10, 8)
fontsize = 26
plt.figure(figsize=figsize)
for m in methods:
    plt.semilogy([np.linalg.norm(grad(x)) for x in methods[m].get_convergence()], label=m)
#     plt.semilogy([np.linalg.norm(x - x_true) for x in methods[m].get_convergence()], label=m)
#     plt.semilogy([f(x) - f(x_true) for...


I am working on the file "Spring2022/hb_acc_grad.ipynb".
The first task is:
Главное про ускоренный метод Нестерова

Теоретически оптимальная скорость сходимости для выпуклых и сильно выпуклых функций
Необходимо подбирать шаг
Волнообразное поведение может быть подавлено с помощью рестартов
Нелинейные композиции градиентов могут дать более быстрые на практике методы (см самое начало семинара)
Оригинальное доказательство сложно понять на интуитивном уровне, есть гораздо более простые подходы, будут рассказаны позднее

Адаптивный выбор константы $L$

В методе Нестерова шаг у градиента равен $\frac{1}{L}$, где $L$ - константа Липшица градиента
Однако она обычно неизвестна
НО! У нас есть условие на эту константу, а именно

$$
f(y) \leq f(x) + \langle f'(x), y - x \rangle + \frac{L}{2}\|x - y\|_2^2
$$

Аналогия с правилами Армихо и прочими такого типа
Более подробно про этот метод смотрите в книге лектора

```python
def backtracking_L(f, h, grad, x, L0, rho):
L = L0

fx = f(x)

gradx = grad(x)

while True:

    y = x - 1 / L * h

    if f(y) <= fx - 1 / L * gradx.dot(h) + 1 / (2 * L) * h.dot(h):

        break

    else:

        L = L * rho

return L

```
Эксперименты
Can you write Python code for it?

beta_test = 0.9
methods = {
    "GD Armijo": fo.GradientDescent(f, grad,
            ss.Backtracking("Armijo", rho=0.5, beta=0.1, init_alpha=1.)),
    r"HB, $\beta = {}$".format(beta_test): HeavyBall(f, grad, ss.ConstantStepSize(1 / L), beta=beta_test),
    "Nesterov": fo.AcceleratedGD(f, grad, ss.ConstantStepSize(1e-3)),
    "Nesterov adaptive": fo.AcceleratedGD(f, grad, ss.Backtracking(rule_type="Lipschitz", rho=0.5, init_alpha=1)),
}
x0 = np.zeros(n)
max_iter = 2000
tol = 1e-6

for m in methods:
    _ = methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)

figsize = (10, 8)
fontsize = 26
plt.figure(figsize=figsize)
for m in methods:
    plt.semilogy([np.linalg.norm(grad(x)) for x in methods[m].get_convergence()], label=m)
plt.legend(fontsize=fontsize, loc="best")
plt.xlabel("Number of iteration, $k$", fontsize=fontsize)
plt.ylabel(r"$\| f'(x_k)\|_2$", fontsize=fontsize)
plt.xticks(fontsize=fontsize)
_ = plt.yticks(fontsize=fontsize)

for m in methods:
    print(m)
    %timeit methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)


I am working on the file "Spring2022/hb_acc_grad.ipynb".
The first task is:
Тестовая задача 2
$$
f(w) = \frac12 \|w\|2^2 + C \frac1m \sum{i=1}^m \log (1 + \exp(- y_i \langle x_i, w \rangle)) \to \min_w
$$
Can you write Python code for it?

n = 300
m = 1000
import sklearn.datasets as skldata
import jax
import jax.numpy as jnp
import scipy.optimize as scopt
from jax.config import config
config.update("jax_enable_x64", True)

X, y = skldata.make_classification(n_classes=2, n_features=n, n_samples=m, n_informative=n//3)
C = 1

@jax.jit
def f(w):
    return jnp.linalg.norm(w)**2 / 2 +  C * jnp.mean(jnp.logaddexp(jnp.zeros(X.shape[0]), -y * (X @ w)))

# def grad(w):
#     denom = scspec.expit(-y * X.dot(w))
#     return w - C * X.T.dot(y * denom) / X.shape[0]

autograd_f = jax.jit(jax.grad(f))
x0 = jnp.ones(n)
print("Initial function value = {}".format(f(x0)))
print("Initial gradient norm = {}".format(jnp.linalg.norm(autograd_f(x0))))

alpha_test = 5e-3
beta_test = 0.9

methods = {
    r"GD, $\alpha_k = {}$".format(alpha_test): fo.GradientDescent(f, autograd_f, ss.ConstantStepSize(alpha_test)),
    "GD Armijo": fo.GradientDescent(f, autograd_f,
            ss.Backtracking("Armijo", rho=0.7, beta=0.1, init_alpha=1.)),
    r"HB, $\beta = {}$".format(beta_test): HeavyBall(f, autograd_f, ss.ConstantStepSize(alpha_test), beta=beta_test),

}
# x0 = np.random.rand(n)
# x0 = jnp.zeros(n)
x0 = jax.random.normal(jax.random.PRNGKey(0), (X.shape[1],))
max_iter = 400
tol = 1e-3

for m in methods:
    print(m)
    _ = methods[m].solve(x0=x0, max_iter=max_iter, tol=tol)

scopt_cg_array = []
def callback(x, arr):
    arr.append(x)

scopt_cg_callback = lambda x: callback(x, scopt_cg_array)
x = scopt.minimize(f, x0, tol=tol, method="CG", jac=autograd_f, callback=scopt_cg_callback, options={"maxiter": max_iter})
x = x.x

figsize = (10, 8)
fontsize = 26
plt.figure(figsize=figsize)
for m in methods:
    plt.semilogy([np.linalg.norm(autograd_f(x)) for x in methods[m].get_convergence()], label=m)
plt.semilogy([np.linalg.norm(autograd_f(x)) for x in scopt_cg_array], label="CG PR")
plt.legend(fontsize=fontsize, loc="best")
plt.xlabel("Number of iteration, $k$", fontsize=fontsize)
plt.ylabel(r"$\| f'(x_k)\|_2$", fontsize=fontsize)
plt.xticks(fontsize=fontsize)
_...


I am working on the file "Spring2022/intro_gd.ipynb".
The first task is:
$f(x) = x\log x$
Can you write Python code for it?

x_range = np.linspace(1e-10, 4)
plt.plot(x_range, x_range * np.log(x_range))

x0 = 1
f = lambda x: x * np.log(x)
grad = lambda x: np.log(x) + 1
beta1 = 0.3
beta2 = 0.7
plot_alpha(f, grad, x0, -grad(x0), np.linspace(1e-3, 0.9, 10), beta1, beta2)


I am working on the file "Spring2022/intro_gd.ipynb".
The first task is:
Значение теорем сходимости (Б.Т. Поляк Введение в оптимизацию, гл. 1, $\S$ 6)

Что дают теоремы сходимости

класс задач, для которых можно рассчитывать на применимость метода (важно не завышать условия!)
выпуклость
гладкость

качественное поведение метода
существенно ли начальное приближение
по какому функционалу есть сходимость

оценку скорости сходимости
теоретическая оценка поведения метода без проведения экспериментов
определение факторов, которые влияют на сходимость (обусловленность, размерность, etc)
иногда заранее можно выбрать число итераций для достижения заданной точности

Что НЕ дают теоремы сходимости

сходимость метода ничего не говорит о целесообразности его применения
оценки сходимости зависят от неизвестных констант - неконструктивный характер
учёт ошибок округления и точности решения вспомогательных задач

Мораль: нужно проявлять разумную осторожность
и здравый смысл!
Классификация задач

Безусловная оптимизация
целевая функция липшицева
градиент целевой функции липшицев

Условная оптимизация
многогранник
множество простой структуры
общего вида

Классификация методов
Какой размер истории нужно хранить для обновления?

Одношаговые методы

$$
x_{k+1} = \Phi(x_k)
$$

Многошаговые методы

$$
x_{k+1} = \Phi(x_k, x_{k-1}, ...)
$$
Какой порядок поизводных нужно вычислить?

Методы нулевого порядка: оракул возвращает только значение функции $f(x)$

Методы первого порядка: оракул возвращает значение функции $f(x)$ и её градиент $f'(x)$

Методы второго порядка: оракул возвращает значение функции $f(x)$, её градиент $f'(x)$ и гессиан $f''(x)$.

Q: существуют ли методы более высокого порядка?
А: Implementable tensor methods in unconstrained convex optimization by Y. Nesterov, 2019
Методы спуска. Градиентный спуск
Что такое методы спуска?
Последовательность $x_k$ генерируется по правилу
$$
x_{k+1} = x_k + \alpha_k h_k
$$
так что
$$
f(x_{k+1}) < f(x_k)
$$
Направление $h_k$ называется направлением убывания.
Замечание: существуют методы, которые не требуют монотонного убывания функции от...
Can you write Python code for it?

%matplotlib notebook
import matplotlib.pyplot as plt
plt.rc("text", usetex=True)
import ipywidgets as ipywidg
import numpy as np
import liboptpy.unconstr_solvers as methods
import liboptpy.step_size as ss
from tqdm import tqdm

f = lambda x: np.power(x, 2)
gradf = lambda x: 2 * x

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

def update(x0, step):
    gd = methods.fo.GradientDescent(f, gradf, ss.ConstantStepSize(step))
    _ = gd.solve(np.array([x0]), max_iter=10)
    x_hist = gd.get_convergence()
    x = np.linspace(-5, 5)
    ax.clear()
    ax.plot(x, f(x), color="r", label="$f(x) = x^2$")
    y_hist = np.array([f(x) for x in x_hist])
    x_hist = np.array(x_hist)
    plt.quiver(x_hist[:-1], y_hist[:-1], x_hist[1:]-x_hist[:-1], y_hist[1:]-y_hist[:-1],
               scale_units='xy', angles='xy', scale=1, width=0.005, color="green", label="Descent path")
    ax.legend()
    fig.canvas.draw()

step_slider = ipywidg.FloatSlider(value=0.8, min=0, max=1.2, step=0.1, description="Step")
x0_slider = ipywidg.FloatSlider(value=1.5, min=-4, max=4, step=0.1, description="Initial point")
_ = ipywidg.interact(update, x0=x0_slider, step=step_slider)

def plot_alpha(f, grad, x, h, alphas, beta1, beta2):
    df = np.zeros_like(alphas)
    for i, alpha in enumerate(alphas):
        df[i] = f(x + alpha * h)
    upper_bound = f(x) + beta1 * alphas * grad(x) * h
    lower_bound = f(x) + beta2 * alphas * grad(x) * h
    plt.plot(alphas, df, label=r"$f(x + \alpha h)$")
    plt.plot(alphas, upper_bound, label="Upper bound")
    plt.plot(alphas, lower_bound, label="Lower bound")
    plt.xlabel(r"$\alpha$", fontsize=18)
    plt.legend(loc="best", fontsize=18)

f = lambda x: x**2
grad = lambda x: 2 * x
beta1 = 0.1
beta2 = 0.9
x0 = 0.5
plot_alpha(f, grad, x0, -grad(x0), np.linspace(1e-3, 1.01, 10), beta1, beta2)


I am working on the file "Spring2022/intro_gd.ipynb".
The first task is:
Решение с помощью градиентного спуска
Can you write Python code for it?

import cvxpy as cvx
print(cvx.installed_solvers())

# !pip install jax
# !pip install jaxlib

import jax.numpy as jnp
import jax

# from jax.config import config
# config.update("jax_enable_x64", True)

A = jnp.array(A)
print(A.dtype)
x0 = jnp.zeros(n)
f = lambda x: -jnp.sum(jnp.log(1 - A.T@x)) - jnp.sum(jnp.log(1 - x*x))
grad_f = lambda x: jnp.sum(A @ (jnp.diagflat(1 / (1 - A.T @ x))), \
                          axis=1) + 2 * x / (1 - jnp.power(x, 2))
grad_f_jax = jax.grad(f)
print(jnp.linalg.norm(grad_f(x0) - grad_f_jax(x0)))


I am working on the file "Spring2022/newton_quasi.ipynb".
The first task is:
Метод Ньютона даёт очень точное решение за длительное время
Градиентный спуска даёт не очень точное решение, но гораздо быстрее
Часто бывает, что очень точное решение не требуется, поэтому градиентный спуск может быть предпочтительнее

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)$ необходимо минимизировать
Квазиньютоновские методы частично решают эти проблемы

Как уменьшить сложность хранения и вычисления?

Сложность вычисления можно уменьшить с помощью

Квазиньютоновские методы, они же методы переменной метрики
Требуется хранение матрицы $n \times n$

Сложность вычисления и хранения можно уменьшить

квазиньютоновские методы с ограниченной памятью, например L-BFGS (Limited Broyden-Fletcher-Goldfarb-Shanno)
НЕ требуется хранить матрицу
вместо этого хранятся $k \ll n$ векторов из $\mathbb{R}^n$

Единообразный способ получения метода Ньютона и градиентного спуска

градиентный метод получен из аппроксимации первого...
Can you write Python code for it?

import numpy as np
import liboptpy.unconstr_solvers as methods
import liboptpy.step_size as ss
%matplotlib inline
import matplotlib.pyplot as plt
import scipy.optimize as scopt
plt.rc("text", usetex=True)

n = 3000
m = 100
x0 = np.zeros(n)
max_iter = 100
tol = 1e-5
A = np.random.rand(m, n) * 10

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

def bb_method(f, gradf, x0, tol=1e-6, maxiter=100, callback=None, alpha_type=1):
    it = 0
    x_prev = x0.copy()
    current_tol = np.linalg.norm(gradf(x_prev))
    alpha = 1e-4
    while current_tol > tol and it < maxiter:
        it += 1
        current_grad = gradf(x_prev)
        if it != 1:
            g = current_grad - prev_grad
            if alpha_type == 1:
                alpha = g.dot(s) / g.dot(g)
            elif alpha_type == 2:
                alpha = s.dot(s) / g.dot(s)
        if callback:
            callback(x_prev)
        x_next = x_prev - alpha * current_grad
        current_tol = np.linalg.norm(gradf(x_next))
        prev_grad = current_grad
        s = x_next - x_prev
        x_prev = x_next
    if callback:
        callback(x_prev)
    return x_next

method = {
    "BB 1": methods.fo.BarzilaiBorweinMethod(f, grad_f, init_alpha=1e-4, type=1),
    "BFGS": methods.fo.BFGS(f, grad_f),
    "DFP": methods.fo.DFP(f, grad_f),
    "LBFGS": methods.fo.LBFGS(f, grad_f),
}

for m in method:
    print("\t Method {}".format(m))
    _ = method[m].solve(x0=x0, tol=tol, max_iter=max_iter, disp=True)

print("\t Method BFGS Scipy")
scopt_conv = []
scopt_res = scopt.minimize(f, x0, method="BFGS", jac=grad_f, callback=lambda x: scopt_conv.append(x),
                           tol=tol, options={"maxiter": max_iter})
print("Result: {}".format(scopt_res.message))
if scopt_res.success:
    print("Convergence in {} iterations".format(scopt_res.nit))
print("Function value = {}".format(f(scopt_res.x)))

plt.figure(figsize=(8, 6))

for m in method:
   ...


I am working on the file "Spring2022/newton_quasi.ipynb".
The first task is:
Сравнение времени работы
Can you write Python code for it?

%timeit newton.solve(x0, tol=1e-3, max_iter=5000).block_until_ready()
%timeit agd.solve(x0, tol=1e-3, max_iter=5000).block_until_ready()


I am working on the file "Spring2022/newton_quasi.ipynb".
The first task is:
Сравнение с градиентным спуском
Can you write Python code for it?

newton = methods.so.NewtonMethod(f, grad_f, hess_f,
                                 ss.Backtracking("Armijo", rho=0.1, beta=0.1, init_alpha=1.),
#                                  ss.ConstantStepSize(1.),
                                linsolver=jnp.linalg.solve)
# newton = methods.so.NewtonMethod(f, grad_f, hess_f, ss.ConstantStepSize(5e-1))
x_newton = newton.solve(x0, tol=1e-8, max_iter=100, disp=True)

gd = methods.fo.GradientDescent(f, grad_f, ss.Backtracking("Armijo", rho=0.1, beta=0.1, init_alpha=1.))
# agd = methods.fo.AcceleratedGD(f, grad_f, ss.ConstantStepSize(1e-2))
# gd = methods.fo.GradientDescent(f, grad_f, ss.ConstantStepSize(1e-5))
x_gd = gd.solve(x0, tol=1e-3, max_iter=100, disp=True)
# x_agd = agd.solve(x0, tol=1e-3, max_iter=3000, disp=True)

%matplotlib inline
import matplotlib.pyplot as plt
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.grid(True)
plt.legend(loc="best", fontsize=24)


I am working on the file "Spring2022/newton_quasi.ipynb".
The first task is:
Точное решение с помощью CVXPy
Can you write Python code for it?

import cvxpy as cvx
x = cvx.Variable(n)

obj = cvx.Minimize(cvx.norm(x, 2)**2 / 2 + C * cvx.sum(cvx.logistic(cvx.multiply(-y, X * x))) / m)
prob = cvx.Problem(obj)
prob.solve(solver="SCS", verbose=True, max_iters=1000)
print("Optimal value =", prob.value)


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Access the data array/list as array using .values
Can you write Python code for it?


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Add or delete columns and write data to .csv file with one command line.
Can you write Python code for it?

import numpy as np
np.zeros(240000)

glad['temperature'] = np.zeros(240000)

glad.head()

del glad['vel_Error']

del glad['Pos_Error']

glad.head()

glad.to_csv('./test.csv')

glad.to_csv?

glad.to_csv('./test_without_index.csv', index = False)


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Data I/O
Data cleaning...
Can you write Python code for it?

glad = pd.read_csv('./GLAD_15min_filtered_S1_41days_sample.csv')

glad

glad.shape


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
How to plot every drifter trajectory, aka spagetti plot?
Grouping Data Frames
In order to aggregate the data of each drifter, we can use group-by method. We can specify which column to group by. In this case, 'ID' will be the choice.
Can you write Python code for it?


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
In this case, indexing by position may not be practical. Instead, we can designate the column of row label 'ID' as an 'index'. It is common operation to pick a column as index to work on. When indexing the dataframe, explicitly designate the row and columns, even if with colon (':').
Can you write Python code for it?

glad_id = glad.set_index('ID')

glad_id.head()

glad_id.loc['CARTHE_021']


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Indexing and Slicing
.iloc[ ] : indexing by position
.loc[ ]  : indexing by index
Can you write Python code for it?


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Iterate over the dictinary above to access the coordinates of each drifter.
Can you write Python code for it?

drifter_ids = drifter_grouped.groups.keys()

for drifter_id in drifter_ids:
    print(drifter_id)

glad_id.head()

plt.figure(figsize = (6, 8))
min_lat, max_lat = 23, 30.5
min_lon, max_lon = -91.5, -85
ax = plt.axes(projection = ccrs.PlateCarree())
ax.set_extent([min_lon, max_lon, min_lat, max_lat], ccrs.PlateCarree())
ax.coastlines(resolution = '50m', color = 'black')
ax.gridlines(crs = ccrs.PlateCarree(), draw_labels = True, color = 'grey')
for drifter_id in drifter_ids:
    lon = glad_id.loc[drifter_id, 'Longitude'].values
    lat = glad_id.loc[drifter_id, 'Latitude'].values
    ax.plot(lon, lat)


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Ploting with matplotlib and cartopy
Can you write Python code for it?

import matplotlib.pyplot as plt
import cartopy.crs as ccrs

plt.figure(figsize = (6, 8))
min_lat, max_lat = 23, 30.5
min_lon, max_lon = -91.5, -85
ax = plt.axes(projection = ccrs.PlateCarree())
ax.set_extent([min_lon, max_lon, min_lat, max_lat], ccrs.PlateCarree())
ax.coastlines(resolution = '50m', color = 'black')
ax.gridlines(crs = ccrs.PlateCarree(), draw_labels = True, color = 'grey')
ax.plot(lon, lat)


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Select data in certain time period.
Set the date as index.
Can you write Python code for it?

glad_date = glad_orig.set_index('Date')

glad_date.head()


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
Use .values to access the data stored in the dataframe.
Can you write Python code for it?

lat = glad_id.loc['CARTHE_021', 'Latitude'].values
lat

lon = glad_id.loc['CARTHE_021', 'Longitude'].values
lon


I am working on the file "Spring_2019/LB26/Pandas_dataframe_tutorial.ipynb".
The first task is:
You can access the items of a dictionary by referring to its key name, inside square brackets
Can you write Python code for it?


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
Before we fit our model, we need to add a few more things:
* An optimizer
* A loss function
* Metrics to measure performance
Can you write Python code for it?


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
For the model that I made, each epoch took about 80 seconds to complete. I wouldn't recommend more than two or three epochs (to keep this tutorial moving)
Can you write Python code for it?


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
Let's Evaluate Our Model
To see if our model is any good, we need to evaluate it on the test images. This will tell us how well our model generalizes to other MNIST data
Can you write Python code for it?

print(test_images.shape) # check that the dimensions of the test data match the training data in case of any errors
test_loss, test_acc = model.???

print('Test loss:', test_loss)
print('Test accuracy:', test_acc*100.0, '%')


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
Let's make sure we didn't break anything and display some images
Can you write Python code for it?

plt.figure(figsize=(10,2))
for i in range(5):
    plt.subplot(1,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i].reshape(28, 28), cmap=plt.cm.binary)
    plt.xlabel(train_labels[i])


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
Now we can build our model!
Can you write Python code for it?


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
Read Data and Reshape
The data we're using is the MNIST digit dataset. It's a set of grayscale images so there should be only 1 channel
Can you write Python code for it?

(train_images, train_labels), (test_images, test_labels) = keras.datasets.mnist.load_data()

# reshape images to specify that it's a single channel
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1)
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1)


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
Tensorboard!
Let's collect some data so we can see what Tensorboard can do!
Can you write Python code for it?


I am working on the file "Spring_2019/LB27/tensorflow_exercise.ipynb".
The first task is:
To see what we get, open a terminal and run the following:
tensorboard --logdir=./logs/
where logdir is the relative path to your logs folder
Now Let's Train Our Model
We need to feed in our data to the model we've constructed and decide how many epochs (loops through the training set) we want to do.
Can you write Python code for it?

# This is just me making sure that my logs go to the right place
os.chdir('C:/Users/jpa84/Documents/Python Scripts/Lunch_Bytes/')
!pwd


I am working on the file "Spring_2019/LB27/tensorflow_tutorial_complete.ipynb".
The first task is:
Before we build our model, we need to add a few more things:
* An optimizer
* A loss function
* Metrics to measure performance
Can you write Python code for it?

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])


I am working on the file "Spring_2019/LB27/tensorflow_tutorial_complete.ipynb".
The first task is:
Cool! the model is about 99% accurate (in my test runthrough)
What About Digits Not From MNIST?
Test data doesn't represent data "from the wild" exactly, so let's read in some high resolution images and see how the model performs.
This part of the notebook doesn't involve much TensorFlow so I've left the code intact.
Can you write Python code for it?

mnist_dream_path = 'images/mnist_dream.mp4'
mnist_prediction_path = 'images/mnist_dream_predicted.mp4'

# download the video if you haven't
if not os.path.isfile(mnist_dream_path):
    print('downloading the sample video...')
    vid_url = 'https://github.com/lexfridman/mit-deep-learning/raw/master/tutorial_deep_learning_basics' + '/' + mnist_dream_path

    mnist_dream_path = urllib.request.urlretrieve(vid_url)[0]

def cv2_imshow(img):
    ret = cv2.imencode('.png', img)[1].tobytes()
    img_ip = IPython.display.Image(data=ret)
    IPython.display.display(img_ip)

cap = cv2.VideoCapture(mnist_dream_path)
vw = None
frame = -1 # counter for debugging (mostly), 0-indexed

# go through all the frames and run our classifier on the high res MNIST images as they morph from number to number
while True: # should 481 frames
    frame += 1
    ret, img = cap.read()
    if not ret: break

    assert img.shape[0] == img.shape[1] # should be a square
    if img.shape[0] != 720:
        img = cv2.resize(img, (720, 720))

    #preprocess the image for prediction
    img_proc = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_proc = cv2.resize(img_proc, (28, 28))
    img_proc = preprocess_images(img_proc)
    img_proc = 1 - img_proc # inverse since training dataset is white text with black background

    net_in = np.expand_dims(img_proc, axis=0) # expand dimension to specify batch size of 1
    net_in = np.expand_dims(net_in, axis=3) # expand dimension to specify number of channels

    preds = model.predict(net_in)[0]
    guess = np.argmax(preds)
    perc = np.rint(preds * 100).astype(int)

    img = 255 - img
    pad_color = 0
    img = np.pad(img, ((0,0), (0,1280-720), (0,0)), mode='constant', constant_values=(pad_color))

    line_type = cv2.LINE_AA
    font_face = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 1.3
    thickness = 2
    x, y = 740, 60
    color = (255, 255, 255)
  ...


I am working on the file "Spring_2019/LB27/tensorflow_tutorial_complete.ipynb".
The first task is:
Let's Evaluate Our Model
To see if our model is any good, we need to evaluate it on the test images. This will tell us how well our model generalizes to other MNIST data
Can you write Python code for it?

print(test_images.shape) # check that the dimensions of the test data in case of any errors
test_loss, test_acc = model.evaluate(test_images, test_labels)

print('Test loss:', test_loss)
print('Test accuracy:', test_acc*100.0, '%')


I am working on the file "Spring_2019/LB27/tensorflow_tutorial_complete.ipynb".
The first task is:
Let's make sure we didn't break anything and display some images
Can you write Python code for it?


I am working on the file "Spring_2019/LB27/tensorflow_tutorial_complete.ipynb".
The first task is:
Read Data and Reshape
The data we're using is the MNIST digit dataset. It's a set of grayscale images so there should be only 1 channel
Can you write Python code for it?


I am working on the file "Spring_2019/LB29/GettingData_XR.ipynb".
The first task is:
Let's Import Some Data through NOAA
Can you write Python code for it?

%%time

heights = []                                   # empty array to append opened netCDF's to
temps = []
date_range = np.arange(1995,2001,1)            # range of years interested in obtaining, remember python starts counting at 0 so for 10 years we actually need to say through 2005

for i in date_range:
    url_h = 'https://www.esrl.noaa.gov/psd/thredds/dodsC/Datasets/ncep.reanalysis2/pressure/hgt.%s.nc' % i    # string subset -->  %.s and % i will insert the i in date_range we are looping through
    url_t = 'https://www.esrl.noaa.gov/psd/thredds/dodsC/Datasets/ncep.reanalysis2/pressure/air.%s.nc' % i
    print(url_h, url_t)
    h1 = xr.open_dataset(url_h)
    t1 = xr.open_dataset(url_t)                                # open netcdfs with xarray
    heights.append(h1)                                         # append opened files
    temps.append(t1)


I am working on the file "Spring_2019/LB29/GettingData_XR.ipynb".
The first task is:
Resample into daily averaged
Can you write Python code for it?

%%time
concat_h = concat_h.resample(time = '24H').mean(dim = 'time')             # resample can take various time arguments such as season, year, minutes
concat_t = concat_t.resample(time = '24H').mean(dim = 'time')


I am working on the file "Spring_2019/LB29/GettingData_XR.ipynb".
The first task is:
Select what you're looking for
Can you write Python code for it?

%%time

for i in range(len(heights)):
    heights[i] = heights[i].sel(lat = slice(90,0), level = 500)
    temps[i] = temps[i].sel(lat = slice(90,0), level = 925)


I am working on the file "Spring_2019/LB29/GettingData_XR.ipynb".
The first task is:
Turn list of urls into one large, combined (concatenated) dataset based on time
Can you write Python code for it?

%%time
concat_h = xr.concat(heights, dim = 'time')                           # aligns all the lat, lon, lev, values of all the datasets based on dimesnion of time

%%time
concat_t = xr.concat(temps, dim = 'time')


I am working on the file "Spring_2019/LB29/GettingData_XRDASK.ipynb".
The first task is:
Take a peak to ensure everything was read successfully and understand the dataset that you have
Can you write Python code for it?

concat_h, concat_t

%%time
concat_h = concat_h.sel(lat = slice(90,0), level = 500).resample(time = '24H').mean(dim = 'time')

%%time
concat_t = concat_t.sel(lat = slice(90,0), level = 925).resample(time = '24H').mean(dim = 'time')


I am working on the file "Spring_2019/LB29/GettingData_XRDASK.ipynb".
The first task is:
Take another peak
Can you write Python code for it?


I am working on the file "Spring_2019/LB29/GettingData_XRDASK.ipynb".
The first task is:
Turn list of urls into one large, combined (concatenated) dataset based on time
Can you write Python code for it?

%%time
concat_h = xr.open_mfdataset(heights)                          # aligns all the lat, lon, lev, values of all the datasets based on dimesnion of time

%%time
concat_t = xr.open_mfdataset(temps)


I am working on the file "Spring_2019/LB29/GettingData_XRDASK.ipynb".
The first task is:
Write out data for processing
Can you write Python code for it?

%%time
concat_h.to_netcdf('heights_9520.nc')

%%time
concat_t.to_netcdf('temps_9520.nc')


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Accessing the underlying array of data is done via the data property. Here we can see that we have a Dask array. If this array were to be backed by a NumPy array, this property would point to the actual values in the array.
Use Standard Xarray Operations
In almost all cases, operations using xarray objects are identical, regardless if the underlying data is stored as a Dask array or a NumPy array.
Can you write Python code for it?

da2 = da.groupby('time.month').mean('time')
da3 = da - da2
da3


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Call .compute() or .load() when you want your result as a xarray.DataArray with data stored as NumPy arrays.
If you started Client() above then you may want to watch the status page during computation.
Can you write Python code for it?

computed_da = da3.load()
type(computed_da.data)

computed_da


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Each data variable in xarray is called a DataArray. These are the fundemental labeled array object in xarray. Much like the Dataset, DataArrays also have dimensions and coordinates that support many of its label-based opperations.
Can you write Python code for it?


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Open a sample dataset
We will use some of xarray's tutorial data for this example. By specifying the chunk shape, xarray will automatically create Dask arrays for each data variable in the Dataset. In xarray, Datasets are dict-like container of labeled arrays, analogous to the pandas.DataFrame. Note that we're taking advantage of xarray's dimension labels when specifying chunk shapes.
Can you write Python code for it?

ds = xr.tutorial.open_dataset('air_temperature',
                              chunks={'lat': 25, 'lon': 25, 'time': -1})
ds


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Persist data in memory
If you have the available RAM for your dataset then you can persist data in memory.
This allows future computations to be much faster.
Can you write Python code for it?


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Quickly inspecting the Dataset above, we'll note that this Dataset has three dimensions akin to axes in NumPy (lat, lon, and time), three coordinate variables akin to pandas.Index objects (also named lat, lon, and time), and one data variable (air). Xarray also holds Dataset specific metadata in as attributes.
Can you write Python code for it?


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Since xarray stores each of its coordinate variables in memory, slicing by label is trivial and entirely lazy.
Can you write Python code for it?


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
Time Series Operations
Because we have a datetime index time-series operations work efficiently. Here we demo the use of xarray's resample method:
Can you write Python code for it?


I am working on the file "Spring_2019/LB29/xarray_DASKTUT.ipynb".
The first task is:
and rolling window operations:
Can you write Python code for it?

da_smooth = da.rolling(time=30).mean().persist()
da_smooth


I am working on the file "Spring_semester_first_session.ipynb".
The first task is:
And the fermion particle number parity operator
$$\hat{P}=(-1)^{\hat{N}}$$
Can you write Python code for it?

def parity_Fock_operator(NN=3):
    '''
    Returns particle number parity operator in Fock space of NN fermions
    '''
    return scsp.dia_matrix((list(map(lambda x:1-2*mod(bin(x).count("1"),2),arange(0,2**NN))),[0]),
                                   shape=(2**NN,2**NN))


I am working on the file "Spring_semester_first_session.ipynb".
The first task is:
Kitaev model
$$
\hat{H}=\mu\sum_p\hat{c}^\dagger_p\hat{c}p+t\left (\sum_p\hat{c}^\dagger{p+1}\hat{c}p+ \mathrm{h.c.}\right)+
\Delta\left(\sum_p\hat{c}^\dagger{p+1}\hat{c}^\dagger_p+ \mathrm{h.c.} \right)
$$
Task: Write a routine that builds up the Kitaev model in Fock space, and calculate the spectrum.
Can you write Python code for it?

def Kitaev_wire_BDG_Ham_Fock_Ham(cc,t,Delta,mu,**kwargs):
    '''
    Builds Kitaev wire Hamiltonian in Fock space.
    '''
    H=    t*sum(cc[p+1].H*cc[p]+cc[p].H*cc[p+1]   for p in range(len(cc)-1)) \
     +Delta*sum(cc[p+1]*cc[p]+cc[p].H*cc[p+1].H   for p in range(len(cc)-1)) \
        +mu*sum(cc[p].H*cc[p]                     for p in range(len(cc)))
    return H

def playFock(N=10,Delta=0.2):
    cc=fermion_Fock_matrices(N)
    dat=[]
    for u in uran:
        val=scspl.eigsh(Kitaev_wire_BDG_Ham_Fock_Ham(cc,1,Delta,u),return_eigenvectors=False,k=50,which='SA')
        dat.append(sort((val-val[-1])[:-1]))
    plot(uran,dat,'r-',lw=2);
    plot(uran,array(dat)[:,0],'r-',lw=2,label=r'$E^{\mathrm{Fock}}_n-E^{\mathrm{Fock}}_{GS}$');

#This lets you play with the Fock spectrum
uran=linspace(-3,3,50)
interact(playFock,N=(6,7),Delta=(0,2,0.1));
xlabel(r'$\mu$',fontsize=16);
ylabel(r'$E^{\mathrm{Fock}}_n-E^{\mathrm{Fock}}_0$',fontsize=16);
grid();


I am working on the file "Spring_semester_first_session.ipynb".
The first task is:
Single site s-wave superconductor
$$
\hat{H}=\mu\left(\hat{c}^\dagger_\uparrow\hat{c}\uparrow+\hat{c}^\dagger\downarrow\hat{c}\downarrow\right)
+\Delta\left(\hat{c}^\dagger\uparrow\hat{c}^\dagger_\downarrow + \hat{c}\downarrow\hat{c}\uparrow \right)
$$
Can you write Python code for it?

def Singe_Site_Superconductor_Fock_Ham(cc,mu,Delta,**kwargs):
    '''
    Returns Fock space representation of the Hamiltonian
    of the single site s-wave superconductor.
    '''
    H=    mu*(cc[0].H*cc[0]+cc[1].H*cc[1]) \
      +Delta*(cc[0].H*cc[1].H+cc[1]*cc[0])
    return H.todense()


I am working on the file "Spring_semester_first_session.ipynb".
The first task is:
Some more handy simple Fock space operators are defined below. First the total fermion number operator:
$$ \hat{N}=\sum_p \hat{c}^\dagger_p\hat{c}_p$$
Can you write Python code for it?

def particle_number_Fock_operator(NN=3):
    '''
    Returns particle number operator in Fock space of NN fermions
    '''
    return scsp.dia_matrix((list(map(lambda x:bin(x).count("1"),arange(0,2**NN))),[0]),
                                   shape=(2**NN,2**NN))