クロスエントロピー法:モンテカルロ最適化の実践的手法

クロスエントロピー法(CEM)を解説。モンテカルロサンプリング、重点サンプリングの基礎から、エリートサンプルによる反復最適化の仕組み、Rastrigin関数を用いたPython実装まで紹介します。

クロスエントロピー法:モンテカルロ最適化の実践的手法

クロスエントロピー法 (Cross Entropy Method, CEM) は、モンテカルロ法における重点サンプリング (Importance Sampling) の一種であり、最適化問題や希少事象の確率推定に用いられるアルゴリズムです。

モンテカルロサンプリング

モンテカルロ法は、乱数を用いることで、複雑な問題の近似解を求める数値計算手法の総称です。

例えば、確率変数 \(X\) が確率密度関数 \(f(x)\) に従うとき、関数 \(H(X)\) の期待値 \(l = \mathbb{E}[H(X)] = \int H(x)f(x)dx\) を求めたいとします。 モンテカルロサンプリングでは、この期待値を \(N\) 個の独立同分布なサンプル \(X_1, \dots, X_N\) を用いて以下のように近似します。

\[ l*{MS} = \frac{1}{N} \sum*{i=1}^{N} H(X_i) \]

重点サンプリング (Importance Sampling)

モンテカルロサンプリングでは、期待値を計算したい確率分布 \(f(x)\) から直接サンプルを生成します。しかし、もし \(H(x)\) が大きな値をとる領域が \(f(x)\) の確率が低い領域にある場合、効率的なサンプリングができません。

重点サンプリングでは、目的の分布 \(f(x)\) ではなく、別のサンプリング分布 \(g(x)\) からサンプルを生成します。そして、そのサンプルに重み \(w(x) = f(x)/g(x)\) を掛けることで、期待値を推定します。

\[ l*{IS} = \frac{1}{N} \sum*{i=1}^N H(X_i) \frac{f(X_i)}{g(X_i)} = \mathbb{E}\_g\lbrace H(X)\frac{f(X)}{g(X)}\rbrace \]

ここで、\(X_i \sim g(x)\) です。重点サンプリングの効率は、サンプリング分布 \(g(x)\) の選択に大きく依存します。特に、分散を最小化する最適なサンプリング分布 \(g^*(x)\) は、\(g^*(x) \propto |H(x)|f(x)\) の形をとることが知られています。

クロスエントロピー法 (Cross Entropy Method)

クロスエントロピー法は、この重点サンプリングにおける最適なサンプリング分布 \(g^*(x)\) に近い分布を、反復的に見つけるためのアルゴリズムです。

「クロスエントロピー」は、2つの確率分布 \(p\) と \(q\) の間の類似度を測る尺度の一つで、以下のように定義されます。

\[ H(p, q) = -\int p(x) \log q(x) dx \]

KLダイバージェンス \(KL(p||q) = \int p(x) \log \frac{p(x)}{q(x)} dx = H(p,q) - H(p)\) の関係から、真の分布 \(p\) が固定されている場合、KLダイバージェンスを最小化することはクロスエントロピーを最小化することと等価になります。

CEMは、パラメータ化されたサンプリング分布 \(g(x;v)\) のパラメータ \(v\) を、以下の手順で最適化します。

  1. 初期化: パラメータ \(v\) を初期化し、サンプリング分布 \(g(x;v)\) を設定します。
  2. サンプリング: 現在のサンプリング分布 \(g(x;v)\) から \(N\) 個のサンプル \(X_1, \dots, X_N\) を生成します。
  3. 評価: 各サンプル \(X_i\) に対して、目的関数 \(H(X_i)\) を計算します。
  4. エリートサンプルの選択: 目的関数値が高い(または低い、最適化の方向による)上位 \(P\) パーセントのサンプルを「エリートサンプル」として選択します。
  5. パラメータの更新: エリートサンプルを用いて、サンプリング分布 \(g(x;v)\) のパラメータ \(v\) を更新します。この更新は、エリートサンプルの尤度を最大化するように行われます。これは、エリートサンプルの経験分布と \(g(x;v)\) の間のクロスエントロピーを最小化することに相当します。
  6. 収束判定: パラメータ \(v\) の変化が十分に小さくなるか、最大反復回数に達するまで、ステップ2に戻って繰り返します。

この反復プロセスにより、サンプリング分布 \(g(x;v)\) は徐々に最適解の領域に集中していき、効率的な探索が可能になります。

CEMとMPPIの比較

CEMと密接に関連する手法としてMPPI(Model Predictive Path Integral)があります。両者は重点サンプリングに基づく最適化手法ですが、サンプルの重み付け方法が異なります。

特性CEMMPPI
重み付けHard selection(上位\(P\)%のエリートサンプル)Soft weighting(コストに基づく指数重み)
更新式エリートサンプルの平均・分散で分布を更新全サンプルの重み付き平均で分布を更新
情報利用エリート以外のサンプル情報を捨てる全サンプルの情報を活用
温度パラメータなしあり(\(\lambda\)で重みの鋭さを制御)
適用領域汎用最適化主にモデル予測制御(MPC)

CEMの「上位\(P\)%を選ぶ」操作は、MPPIの温度パラメータ\(\lambda \to 0\)の極限に対応します。つまり、CEMはMPPIの特殊ケースとして理解できます。

実用的な使い分けの指針:

  • 離散的な最適化組合せ最適化にはCEMが適している
  • 連続的な制御問題ロボティクスにはMPPIが適している
  • MPPIはサンプル効率が高い(全サンプルの情報を使うため)が、温度パラメータの調整が必要

PythonによるCEMの実装

ここでは、クロスエントロピー法を用いて連続最適化問題を解くPython実装を紹介します。テスト関数として、多数の局所最小値を持つRastrigin関数を使用します。

Rastrigin関数

Rastrigin関数は、最適化アルゴリズムのベンチマークとして広く使われるテスト関数で、以下のように定義されます。

\[ f(\mathbf{x}) = An + \sum\_{i=1}^{n} \left[ x_i^2 - A\cos(2\pi x_i) \right] \]

ここで \(A = 10\)、\(n\) は次元数です。大域的最小値は \(\mathbf{x}^* = \mathbf{0}\) で \(f(\mathbf{0}) = 0\) です。多数の局所最小値があるため、単純な勾配法では大域的最小値に到達することが困難であり、CEMのような確率的探索手法の有効性を確認するのに適しています。

実装コード

import numpy as np
import matplotlib.pyplot as plt

# --- Rastrigin関数の定義 ---
def rastrigin(x):
    """Rastrigin関数(最小化対象)"""
    A = 10
    return A * len(x) + np.sum(x**2 - A * np.cos(2 * np.pi * x))

# --- クロスエントロピー法の実装 ---
def cross_entropy_method(
    objective_fn,   # 目的関数(最小化)
    dim,            # 探索空間の次元数
    n_samples=100,  # 各イテレーションでのサンプル数
    elite_frac=0.2, # エリートサンプルの割合
    n_iterations=50,# 最大イテレーション数
    initial_mean=None,  # 初期平均
    initial_std=5.0,    # 初期標準偏差
):
    """クロスエントロピー法による最小化"""

    # ステップ1: 初期化
    mean = initial_mean if initial_mean is not None else np.zeros(dim)
    std = np.full(dim, initial_std)
    n_elite = int(n_samples * elite_frac)

    # 記録用リスト
    best_scores = []      # 各イテレーションの最良スコア
    mean_history = []     # 平均の推移

    for iteration in range(n_iterations):
        # ステップ2: サンプリング(正規分布から)
        samples = np.random.normal(
            loc=mean, scale=std, size=(n_samples, dim)
        )

        # ステップ3: 評価(各サンプルの目的関数値を計算)
        scores = np.array([objective_fn(s) for s in samples])

        # ステップ4: エリートサンプルの選択(スコアが小さい上位P%)
        elite_indices = np.argsort(scores)[:n_elite]
        elite_samples = samples[elite_indices]

        # ステップ5: パラメータの更新(エリートサンプルの平均・標準偏差)
        mean = np.mean(elite_samples, axis=0)
        std = np.std(elite_samples, axis=0)

        # 記録
        best_scores.append(np.min(scores))
        mean_history.append(mean.copy())

        # 収束判定(標準偏差が十分小さくなったら終了)
        if np.all(std < 1e-6):
            print(f"反復 {iteration + 1} で収束しました。")
            break

    return mean, best_scores, mean_history

# --- 実行 ---
np.random.seed(42)
dim = 2  # 2次元問題

best_solution, best_scores, mean_history = cross_entropy_method(
    objective_fn=rastrigin,
    dim=dim,
    n_samples=200,
    elite_frac=0.1,
    n_iterations=100,
    initial_mean=np.array([3.0, -3.0]),  # 最適解から離れた初期点
    initial_std=3.0,
)

print(f"最適解: {best_solution}")
print(f"目的関数値: {rastrigin(best_solution):.6f}")

# --- 結果の可視化 ---
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# (a) 目的関数値の収束推移
axes[0].plot(best_scores, linewidth=2)
axes[0].set_xlabel("Iteration")
axes[0].set_ylabel("Best Score")
axes[0].set_title("Convergence of CEM")
axes[0].set_yscale("log")
axes[0].grid(True, alpha=0.3)

# (b) Rastrigin関数の等高線と探索経路
x_grid = np.linspace(-5, 5, 200)
y_grid = np.linspace(-5, 5, 200)
X, Y = np.meshgrid(x_grid, y_grid)
Z = np.array([
    [rastrigin(np.array([xi, yi])) for xi in x_grid]
    for yi in y_grid
])

axes[1].contourf(X, Y, Z, levels=30, cmap="viridis", alpha=0.8)
axes[1].colorbar = plt.colorbar(axes[1].contourf(X, Y, Z, levels=30, cmap="viridis", alpha=0.8), ax=axes[1])

# 平均の推移を矢印付きでプロット
mean_arr = np.array(mean_history)
axes[1].plot(mean_arr[:, 0], mean_arr[:, 1], "r.-", markersize=8, linewidth=1.5, label="Mean trajectory")
axes[1].plot(mean_arr[0, 0], mean_arr[0, 1], "rs", markersize=12, label="Start")
axes[1].plot(mean_arr[-1, 0], mean_arr[-1, 1], "r*", markersize=15, label="Final")
axes[1].plot(0, 0, "wx", markersize=12, markeredgewidth=3, label="Global optimum")
axes[1].set_xlabel("$x_1$")
axes[1].set_ylabel("$x_2$")
axes[1].set_title("CEM Search Trajectory on Rastrigin Function")
axes[1].legend(loc="upper right")

plt.tight_layout()
plt.savefig("cem_result.png", dpi=150, bbox_inches="tight")
plt.show()

上記のコードを実行すると、以下のことが確認できます。

  • 収束の様子: 目的関数値が反復ごとに急速に減少し、大域的最小値 \(f(\mathbf{0}) = 0\) に近づく
  • 探索経路: サンプリング分布の平均が初期点 \((3, -3)\) から原点 \((0, 0)\) へ移動していく過程
  • 局所最小値の回避: Rastrigin関数の多数の局所最小値を避け、大域的最小値に到達できている

CEMの主要なハイパーパラメータの影響:

  • n_samples(サンプル数):多いほど探索が安定するが、計算コストが増加する
  • elite_frac(エリート割合):小さいほど選択圧が強くなり収束が速いが、早期収束のリスクがある
  • initial_std(初期標準偏差):大きいほど広い範囲を探索できるが、収束に時間がかかる

関連記事

参考文献

  • Rubinstein, R. Y., & Kroese, D. P. (2013). The Cross-Entropy Method: A Unified Approach to Combinatorial Optimization, Monte-Carlo Simulation, and Machine Learning. Springer Science & Business Media.
  • 漆原 勉, “モンテカルロシミュレーションにおける重点サンプリング法に対する大偏差理論の適用について”

関連ツール