はじめに
焼きなまし法(Simulated Annealing, SA)は、金属の焼きなまし(アニーリング)プロセスに着想を得たメタヒューリスティクス最適化手法です。高温から徐々に冷却することで、金属の結晶構造が最低エネルギー状態に向かうように、目的関数の大域的最適解を探索します。
SAの特徴は、改悪解を確率的に受容することで局所最適から脱出できる点です。温度が高い序盤では大胆な探索を行い、温度が低下するにつれて精緻な局所探索に移行します。
アルゴリズム
メトロポリス基準
SAの核となるのはメトロポリス基準です。現在の解 \(x\) から近傍解 \(x'\) への遷移を以下の確率で受容します。
\[P(\text{accept}) = \begin{cases} 1 & \text{if } \Delta E \leq 0 \\ \exp\left(-\frac{\Delta E}{T}\right) & \text{if } \Delta E > 0 \end{cases} \tag{1}\]ここで \(\Delta E = f(x') - f(x)\) は目的関数値の変化量、\(T\) は現在の温度です。
- \(\Delta E \leq 0\)(改善): 常に受容
- \(\Delta E > 0\)(改悪): 温度 \(T\) に依存する確率で受容。\(T\) が高いほど受容確率が高い
ボルツマン分布との関係
温度 \(T\) で十分長い時間探索を続けると、解の分布はボルツマン分布に収束します。
\[P(x) \propto \exp\left(-\frac{f(x)}{T}\right) \tag{2}\]\(T \to 0\) では最適解に集中します。
温度スケジュール
幾何冷却(最も一般的)
\[T_{k+1} = \alpha \cdot T_k, \quad 0.9 \leq \alpha < 1 \tag{3}\]\(\alpha = 0.95\) が典型的な値です。
線形冷却
\[T_{k+1} = T_k - \delta \tag{4}\]対数冷却(理論的保証あり)
\[T_k = \frac{T_0}{\ln(k + 1)} \tag{5}\]大域最適解への収束が理論的に保証されますが、収束速度は極めて遅く実用的ではありません。
Python実装:連続最適化
Rastrigin関数(多峰性ベンチマーク)を焼きなまし法で最適化します。
import numpy as np
import matplotlib.pyplot as plt
def rastrigin(x):
"""Rastrigin関数"""
return 10 * len(x) + np.sum(x**2 - 10 * np.cos(2 * np.pi * x))
def simulated_annealing(objective, dim, bounds=(-5.12, 5.12),
T_init=100.0, alpha=0.995, max_iter=10000):
"""焼きなまし法による連続最適化"""
# 初期解
x = np.random.uniform(*bounds, size=dim)
best_x = x.copy()
best_f = objective(x)
current_f = best_f
T = T_init
history = [best_f]
for i in range(max_iter):
# 近傍解の生成(ガウスノイズ)
sigma = 0.5 * (T / T_init) # 温度に比例してステップ幅を調整
x_new = x + np.random.normal(0, sigma, size=dim)
x_new = np.clip(x_new, *bounds)
new_f = objective(x_new)
delta = new_f - current_f
# メトロポリス基準
if delta <= 0 or np.random.random() < np.exp(-delta / T):
x = x_new
current_f = new_f
if current_f < best_f:
best_x = x.copy()
best_f = current_f
T *= alpha
history.append(best_f)
return best_x, best_f, history
# --- 実行 ---
np.random.seed(42)
best_x, best_f, history = simulated_annealing(rastrigin, dim=10)
print(f"最良解の適応度: {best_f:.6f}")
# --- 収束曲線のプロット ---
plt.figure(figsize=(10, 5))
plt.plot(history)
plt.xlabel('Iteration')
plt.ylabel('Best Objective Value')
plt.title('Simulated Annealing on Rastrigin Function (10D)')
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Python実装:巡回セールスマン問題(TSP)
SAは組合せ最適化にも有効です。TSPを2-opt近傍で解きます。
import numpy as np
import matplotlib.pyplot as plt
def total_distance(tour, cities):
"""巡回路の総距離を計算"""
n = len(tour)
dist = 0
for i in range(n):
dist += np.linalg.norm(cities[tour[i]] - cities[tour[(i + 1) % n]])
return dist
def two_opt_swap(tour, i, j):
"""2-optスワップ: tour[i:j+1]を反転"""
new_tour = tour.copy()
new_tour[i:j+1] = tour[i:j+1][::-1]
return new_tour
def sa_tsp(cities, T_init=1000.0, alpha=0.9995, max_iter=100000):
"""焼きなまし法によるTSP"""
n = len(cities)
tour = list(range(n))
np.random.shuffle(tour)
current_dist = total_distance(tour, cities)
best_tour = tour.copy()
best_dist = current_dist
T = T_init
history = [best_dist]
for _ in range(max_iter):
# ランダムな2-optスワップ
i, j = sorted(np.random.choice(n, 2, replace=False))
new_tour = two_opt_swap(tour, i, j)
new_dist = total_distance(new_tour, cities)
delta = new_dist - current_dist
if delta <= 0 or np.random.random() < np.exp(-delta / T):
tour = new_tour
current_dist = new_dist
if current_dist < best_dist:
best_tour = tour.copy()
best_dist = current_dist
T *= alpha
history.append(best_dist)
return best_tour, best_dist, history
# --- 実行 ---
np.random.seed(42)
n_cities = 30
cities = np.random.rand(n_cities, 2) * 100
best_tour, best_dist, history = sa_tsp(cities)
print(f"最短距離: {best_dist:.2f}")
# --- 結果の可視化 ---
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 巡回路
tour_cities = cities[best_tour + [best_tour[0]]]
axes[0].plot(tour_cities[:, 0], tour_cities[:, 1], 'b-o', markersize=5)
axes[0].set_title(f'Best Tour (distance={best_dist:.2f})')
axes[0].set_aspect('equal')
axes[0].grid(True, alpha=0.3)
# 収束曲線
axes[1].plot(history)
axes[1].set_xlabel('Iteration')
axes[1].set_ylabel('Best Distance')
axes[1].set_title('SA Convergence on TSP')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
他の最適化手法との比較
| 特性 | SA | GA | CEM |
|---|---|---|---|
| 解の数 | 単一解 | 集団ベース | 集団ベース |
| 探索メカニズム | 確率的受容 | 交叉・突然変異 | 分布更新 |
| 離散問題 | 得意 | 得意 | 不得意 |
| パラメータ | 温度スケジュール | 交叉率・突然変異率 | エリート割合 |
| 並列化 | 困難 | 容易 | 容易 |
関連記事
- クロスエントロピー法:モンテカルロ最適化の実践的手法 - 確率分布の反復更新による最適化を行うCEMとの比較が有益です。
- 粒子群最適化(PSO)のPython実装 - 群知能ベースの最適化手法との比較ができます。
- ベイズ最適化の基礎とPython実装 - 評価コストが高い場合に有効なベイズ最適化を解説しています。
- MPPI(Model Predictive Path Integral)の数理 - サンプリングベースの最適制御手法を解説しています。
参考文献
- Kirkpatrick, S., Gelatt, C. D., & Vecchi, M. P. (1983). “Optimization by Simulated Annealing”. Science, 220(4598), 671-680.
- Metropolis, N., et al. (1953). “Equation of State Calculations by Fast Computing Machines”. The Journal of Chemical Physics, 21(6), 1087-1092.
- Bertsimas, D., & Tsitsiklis, J. (1993). “Simulated annealing”. Statistical Science, 8(1), 10-15.