コールバックによる進捗の監視

このチュートリアルでは,CallbackManager を使って最適化の進捗をログ出力・記録する方法を説明します.

デフォルトのログ出力

Optimizer は初期化時に logging_generationGenerationStartEvent へ自動登録します.Python の logging モジュールを有効にするだけで,世代ごとの情報が出力されます.

import logging
import numpy as np
from saealib import minimize

logging.basicConfig(level=logging.INFO, format="%(message)s")

def sphere(x):
    return np.sum(x ** 2)

result = minimize(sphere, dim=5, lb=[-5.0]*5, ub=[5.0]*5, max_fe=300, seed=0)

出力例(単目的):

Generation 1 started. fe: 25. Best f: [0.42]
Generation 2 started. fe: 27. Best f: [0.31]
...

多目的の場合はパレートフロントのサイズと各目的の範囲が出力されます.


カスタムハンドラの登録

低レベルAPIでは optimizer.cbmanager.register() でハンドラを追加できます.ハンドラはイベントオブジェクトを受け取る関数です.

from saealib.callback import GenerationEndEvent

def my_handler(event: GenerationEndEvent) -> None:
    ctx = event.ctx
    best_f = ctx.archive.get_array("f").min()
    print(f"gen={ctx.gen}  fe={ctx.fe}  best={best_f:.4f}")

optimizer.cbmanager.register(GenerationEndEvent, my_handler)

収束履歴の記録

クロージャを使うと,外部のリストへ結果を蓄積できます.

import numpy as np
from saealib.problem import Problem
from saealib.optimizer import Optimizer
from saealib.algorithms.ga import GA
from saealib.operators.crossover import CrossoverBLXAlpha
from saealib.operators.mutation import MutationUniform
from saealib.operators.selection import SequentialSelection, TruncationSelection
from saealib.surrogate.rbf import RBFsurrogate, gaussian_kernel
from saealib.surrogate.manager import LocalSurrogateManager
from saealib.acquisition.mean import MeanPrediction
from saealib.strategies.ib import IndividualBasedStrategy
from saealib.execution.initializer import LHSInitializer
from saealib.termination import Termination, max_fe
from saealib.callback import GenerationEndEvent

DIM = 5
problem = Problem(
    func=lambda x: np.sum(x ** 2),
    dim=DIM,
    n_obj=1,
    weight=np.array([-1.0]),
    lb=[-5.0] * DIM,
    ub=[ 5.0] * DIM,
)

history: list[tuple[int, float]] = []

def record_history(event: GenerationEndEvent) -> None:
    ctx = event.ctx
    best_f = float(ctx.archive.get_array("f").min())
    history.append((ctx.fe, best_f))

optimizer = (
    Optimizer(problem)
    .set_initializer(LHSInitializer(n_init_archive=5*DIM, n_init_population=4*DIM, seed=0))
    .set_algorithm(GA(
        crossover=CrossoverBLXAlpha(crossover_rate=0.7, alpha=0.4),
        mutation=MutationUniform(mutation_rate=0.3),
        parent_selection=SequentialSelection(),
        survivor_selection=TruncationSelection(),
    ))
    .set_surrogate_manager(LocalSurrogateManager(
        RBFsurrogate(gaussian_kernel, dim=DIM),
        MeanPrediction(weights=np.array([-1.0])),
        n_neighbors=20,
    ))
    .set_strategy(IndividualBasedStrategy(evaluation_ratio=0.1))
    .set_termination(Termination(max_fe(300)))
)

optimizer.cbmanager.register(GenerationEndEvent, record_history)
optimizer.run()

for fe, best in history:
    print(f"fe={fe:4d}  best_f={best:.6f}")

多目的問題: ハイパーボリュームの追跡

logging_generation_hv はハイパーボリュームを世代ごとにログ出力するファクトリ関数です.基準点(リファレンスポイント)を渡します.

import logging
from saealib.callback import GenerationStartEvent, logging_generation_hv

logging.basicConfig(level=logging.INFO, format="%(message)s")

hv_handler = logging_generation_hv(reference_point=np.array([1.1, 1.1]))
optimizer.cbmanager.register(GenerationStartEvent, hv_handler)

出力例:

Generation 1. fe: 50. HV: 0.312451
Generation 2. fe: 52. HV: 0.347820
...

ハイパーボリュームは最小化規約で計算されます.各目的の真の最良値より大きい値を基準点に設定してください.


デフォルトハンドラの無効化

logging_generation が不要な場合は unregister で除去できます.

from saealib.callback import logging_generation, GenerationStartEvent

optimizer.cbmanager.unregister(GenerationStartEvent, logging_generation)

既存ハンドラを別のものに置き換えるには replace を使います.

optimizer.cbmanager.replace(GenerationStartEvent, logging_generation, my_handler)

iterate() との使い分け

世代ごとの処理は iterate() でも書けますが,2 つのアプローチにはそれぞれ適した用途があります.

方法

適した用途

iterate()

スクリプト内で完結する一時的な処理,早期終了のカスタム条件

cbmanager

再利用可能なログや記録,複数の Optimizer に共通して使いたい処理


利用可能なイベント一覧

イベント

発火タイミング

RunStartEvent

最適化の開始時(1回)

RunEndEvent

最適化の終了時(1回)

GenerationStartEvent

各世代の開始時

GenerationEndEvent

各世代の終了時

PostAskEvent

アルゴリズムが候補を生成した直後

PostSurrogateFitEvent

サロゲートのフィット直後

PostEvaluationEvent

真の関数評価直後


参照