Локальная минимизация по одной переменной#
Цель семинара - научиться минимизировать произвольно заданную непрерывную функцию.
Начнём с локальных методов от одной переменной - наиболее показательный раздел.
Данная задача также называется задачей оптимизации. Понятно, что эта задача аналогична поиску максимума.
Дихотомия#
Пусть имеем не слишком осциллирующую непрерывную функцию \(f:[a, b] \rightarrow \mathbb{R}\). Алгоритм дихотомии выглядит следующим образом:
Алгоритм дихотомии
Возьмём \([a, b]\) за начальную локализацию минимума. Фиксируем малый \(\varepsilon > 0\).
Пусть текущая локализация минимума - отрезок \([a_n, b_n]\). Берём точку \(c_n = \frac{a_n + b_n}{2}\) и смотрим, в какую сторону от неё функция уменьшается. Иными словами, считаем \(f_\text{left} = f(c_n - \varepsilon)\) и \(f_\text{right} = f(c_n + \varepsilon)\).
Если \(f_\text{left} < f_\text{right}\), то локальный минимум слева и \([a_{n+1}, b_{n+1}] = [a_n, c_n]\). Иначе, локальный минимум справа и \([a_{n+1}, b_{n+1}] = [c_n, b_n]\).
Если длина \([a_{n+1}, b_{n+1}]\) меньше \(\varepsilon\), останавливаемся и выдаём среднюю точку. Иначе, на шаг 1.
Метод прост и красив, но требует много вычислений функции (что может быть очень дорого). Так же для метода не нужна дифференциируемость \(f\) и ошибка уменьшается экспонциально от количества шагов:
Метод чисел Фиббоначи (золотого сечения)#
Если операция вычисления непрерывной функции \(f:[a, b] \rightarrow \mathbb{R}\) является очень дорогой по времени, разумно использовать следующий алгоритм чисел Фиббоначи, использующий вызовы функции с предыдущих итераций. В нём мы заранее фиксируем \(N\) - количество вызовов функции \(f\).
В алгоритме используются \(F_n\) - стандартные числа Фиббоначи \((1, 1, 2, 3, 5, ...)\).
Алгоритм чисел Фиббоначи
Задаются начальные границы отрезка \(a, b\) и число итераций \(N\). Счётчик \(n = N\).
Рассчитывают начальные точки деления: \(x_1=a+(b-a) \frac{F_{n-2}}{F_n}, \quad x_2=a+(b-a) \frac{F_{n-1}}{F_n}\)
И значения в них целевой функции: \(f_1=f\left(x_1\right), f_2=f\left(x_2\right)\).
Уменьшаем счётчик \(n = N - 1\)
Если \(f_1>f_2\), то сужаем отрезок слева \(a=x_1, \; x_1=x_2\). И модифицированный шаг 1: \(x_2=b-\left(x_1-a\right), \; f_1=f_2, \; f_2=f\left(x_2\right)\)
Иначе, сужаем отрезок справа \(b=x_2, \; x_2=x_1\). Модифицированный шаг 1: \(x_1=a+\left(b-x_2\right), \; f_2=f_1, \; >f_1=f\left(x_1\right)\).
Если \(n=1\), то останавливаемся и выдаём \(\frac{x_1 + x_2}{2}\).
Иначе, на шаг 2.
Ошибку в вычислении минимума от числа итераций \(N\) можно оценить как
Несложно обобщить данный метод на произвольной число итераций с учётом \(\Phi=\lim _{n \rightarrow \infty} \frac{F_{n+1}}{F_n}\) (это соответствует \(N = \infty\) в алгоритме). В таком случае критерием остановки будет \(x_2 - x_1 < \varepsilon\). Такой метод называется методом золотого сечения.
На практике рекомендуется использовать реализованный кем-то другим алгоритм Брента - он использует параболическую интерполяцию (аппроксимацию по трём точкам) для ускорения сходимости метода золотого сечения.
import numpy as np
from scipy.optimize import minimize_scalar # За нас всё уже реализовали :)
def f(x):
return np.cos(x)
res = minimize_scalar(f) # По умолчанию использует метод Брента
print(res)
res.x, res.fun, res.nit # Сошёлся за 8 итераций!
message:
Optimization terminated successfully;
The returned value satisfies the termination criteria
(using xtol = 1.48e-08 )
success: True
fun: -1.0
x: 3.1415926536439596
nit: 8
nfev: 12
(3.1415926536439596, -1.0, 8)