Имитация отжига

Имитация отжига иногда соглашается на ухудшение, чтобы выбраться из ловушки локального минимума — как остывающий металл находит низкоэнергетическую структуру.

Имитация отжига (simulated annealing) — стохастический метод глобальной оптимизации, принимающий ухудшающие шаги с вероятностью $\exp(-\Delta/T)$, где $T$ — медленно убывающая «температура».

Зачем нужны метаэвристики

Градиентный спуск и Ньютон — короли гладких выпуклых задач, но они беспомощны там, где: функция невыпукла и усыпана локальными минимумами; переменные дискретны (порядок городов) и градиента нет вовсе. Метаэвристики — это умные стратегии случайного поиска, которые не дают гарантий, но на практике находят отличные решения тяжёлых задач. Имитация отжига — одна из самых элегантных.

Физическая метафора

При отжиге металла его медленно охлаждают: при высокой температуре атомы свободно перескакивают (даже в менее выгодные позиции), а по мере остывания «застывают» в низкоэнергетической, почти идеальной кристаллической решётке. Алгоритм копирует это: «энергия» — значение цели $f$, «температура» $T$ управляет готовностью принимать ухудшения.

Правило приёма (критерий Метрополиса)

Сгенерировали соседнее решение с изменением цели $\Delta=f_{\text{new}}-f_{\text{cur}}$. Принимаем его так:

$$P(\text{принять})=\begin{cases}1,& \Delta\le0\ (\text{лучше — всегда берём}),\\[1mm]\exp(-\Delta/T),& \Delta>0\ (\text{хуже — иногда берём}).\end{cases}$$

При высоком $T$ даже сильные ухудшения вероятны — метод свободно бродит и убегает из впадин. При $T\to0$ ухудшения почти запрещены — метод превращается в обычный жадный спуск и фиксирует найденное. Ключ — медленное охлаждение (cooling schedule), обычно $T\leftarrow\gamma T$ с $\gamma\approx 0.99$.

Отжиг на коммивояжёре

Задача коммивояжёра: объехать все города по кратчайшему замкнутому маршруту. Соседнее решение — перестановка двух городов в маршруте. Запустим отжиг на 6 городах (seed фиксирован для воспроизводимости).

import random, math

cities = [(0,0),(1,5),(5,2),(6,6),(8,3),(2,8)]
def dist(a, b):
    return math.hypot(cities[a][0]-cities[b][0], cities[a][1]-cities[b][1])
def tour_len(t):
    return sum(dist(t[i], t[(i+1) % len(t)]) for i in range(len(t)))

random.seed(42)
tour = list(range(len(cities)))
random.shuffle(tour)
cur = tour_len(tour)
best, best_len = tour[:], cur
T = 10.0
for step in range(1, 2001):
    i, j = random.sample(range(len(cities)), 2)
    new = tour[:]
    new[i], new[j] = new[j], new[i]
    nl = tour_len(new)
    if nl < cur or random.random() < math.exp((cur - nl) / T):
        tour, cur = new, nl
        if cur < best_len:
            best, best_len = tour[:], cur
    T *= 0.997
    if step in (1, 500, 1000, 2000):
        print(f"шаг {step:4d}: T={T:.3f} текущая={cur:.3f} лучшая={best_len:.3f}")
print("Лучший маршрут:", best)
print("Длина:", round(best_len, 3))

Вывод:

шаг    1: T=9.970 текущая=33.085 лучшая=33.085
шаг  500: T=2.226 текущая=31.920 лучшая=24.886
шаг 1000: T=0.496 текущая=24.886 лучшая=24.886
шаг 2000: T=0.025 текущая=24.886 лучшая=24.886
приведённое сокращено

На шаге 500 текущая длина ($31.9$) хуже лучшей ($24.9$) — метод осознанно «гуляет» по плохим решениям, пока тепло. По мере остывания он застывает в найденном хорошем маршруте длиной $\approx24.9$. Чистый жадный спуск с того же старта застрял бы в первом же локальном минимуме.

Как работает под капотом

Магия — в члене $\exp(-\Delta/T)$. Это распределение Больцмана из статистической физики. Доказано: при достаточно медленном (логарифмическом) охлаждении отжиг сходится к глобальному оптимуму с вероятностью 1. На практике такое расписание непозволительно медленно, поэтому берут геометрическое ($T\leftarrow\gamma T$) и довольствуются «очень хорошим» решением. Баланс прост: остужать быстро — застрянешь рано (как жадный спуск); остужать медленно — найдёшь лучше, но дольше.

Частые ошибки

  • Слишком быстрое охлаждение. $T$ падает раньше, чем метод успел исследовать — застреваем в локальном минимуме.
  • Не хранить лучшее найденное. Текущее решение из-за приёма ухудшений может быть хуже исторического лучшего — всегда запоминайте best.
  • Старт с $T$, не связанной с масштабом $\Delta$. $T$ должна быть соизмерима с типичными изменениями цели, иначе приём всегда $\approx0$ или $\approx1$.

Итоги

  • Отжиг принимает ухудшения с вероятностью $\exp(-\Delta/T)$, что позволяет убегать из локальных минимумов.
  • Высокая $T$ — свободное блуждание, $T\to0$ — жадная фиксация; ключ в медленном охлаждении.
  • Работает и для невыпуклых, и для дискретных задач (коммивояжёр); всегда храните лучшее решение.
Проверьте себя
1. С какой вероятностью отжиг принимает ухудшающий шаг ($\Delta&gt;0$)?
AВсегда 1
BНикогда (0)
C$\exp(-\Delta/T)$ — зависит от температуры
D$1/\Delta$
2. Зачем отжигу принимать ухудшающие шаги?
AЭто ошибка алгоритма
BЧтобы выбираться из локальных минимумов
CЧтобы ускорить счёт
DЧтобы уменьшить температуру
3. К чему ведёт слишком быстрое охлаждение?
AК глобальному оптимуму
BК раннему застреванию в локальном минимуме (как жадный спуск)
CК расходимости
DК росту температуры