再現性とチェックポイント#

Layer 2: Compose

長時間実行する最適化を再現可能にし、途中から再開できるようにします。

チェックポイント機能は低レベルの Optimizer APIで使います。 Optimizer(problem)を構築し、set_*()を連鎖させてコンポーネントを設定し、run()で実行します。 保存した状態からは run_from()で再開できます。

このページでできるようになること

このページを終えると、乱数シードで再現性を確保し、チェックポイントを保存・読み込みして最適化を再開できます。

高レベルAPIでの一回実行だけで足りる場合は 高レベルAPI を使い、実行状態を細かく構成する場合は 低レベルAPI を参照してください。 このページでは、低レベルAPIで再現可能な実行を保存し、途中から再開する手順を扱います。

乱数シードで実行を再現する#

同じ seed を Optimizer(problem, seed=...) に渡すと、乱数を使うすべての処理が同じ順序で初期化され、同一の結果が得られます。

import numpy as np
from saealib import (
    Problem,
    Optimizer,
    GA,
    CrossoverBLXAlpha,
    MutationUniform,
    SequentialSelection,
    TruncationSelection,
    RBFSurrogate,
    GaussianKernel,
    LocalSurrogateManager,
    MeanPrediction,
    IndividualBasedStrategy,
    LHSInitializer,
    Termination,
    max_fe,
)


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


DIM = 10
SEED = 0

problem = Problem(
    func=expensive_func,
    dim=DIM,
    n_obj=1,
    direction=np.array([-1.0]),
    lb=[-5.0] * DIM,
    ub=[5.0] * DIM,
)


def build_optimizer(max_fe_value):
    return (
        Optimizer(problem, seed=SEED)
        .set_initializer(
            LHSInitializer(n_init_archive=5 * DIM, n_init_population=4 * DIM, seed=SEED)
        )
        .set_algorithm(
            GA(
                crossover=CrossoverBLXAlpha(0.7, 0.4),
                mutation=MutationUniform(0.3),
                parent_selection=SequentialSelection(),
                survivor_selection=TruncationSelection(),
            )
        )
        .set_surrogate_manager(
            LocalSurrogateManager(RBFSurrogate(kernel=GaussianKernel()))
        )
        .set_acquisition(MeanPrediction())
        .set_strategy(IndividualBasedStrategy(evaluation_ratio=0.1))
        .set_termination(Termination(max_fe(max_fe_value)))
    )


ctx1 = build_optimizer(300).run()
ctx2 = build_optimizer(300).run()

print(np.allclose(ctx1.archive.get_array("f"), ctx2.archive.get_array("f")))  # True

以降の節でも、同じコンポーネント構成で Optimizer を再構築するために build_optimizer を使います。

再現性を保つには、SurrogateManagerとAcquisitionFunctionを同じ構成で再生成します。 LocalSurrogateManagerの引数にAcquisitionFunctionを渡す構成は現行APIでは使いません。

チェックポイントを保存して再開する#

run() が返す ctx は、ctx.save(path) によって1つのnpzファイルに保存できます。

ctx = build_optimizer(200).run()
ctx.save("checkpoint.npz")

保存したチェックポイントは OptimizationState.load(path, problem) で読み込めます。これを Optimizer.run_from(ctx) に渡すと、中断した箇所から再開します。

from saealib import OptimizationState

loaded_ctx = OptimizationState.load("checkpoint.npz", problem)

resumed_ctx = build_optimizer(600).run_from(loaded_ctx)
print(resumed_ctx.fe)  # includes the evaluations from before saving
print(resumed_ctx.data["resumed"])  # True

ctx.data["resumed"] は、run_from() で再開したコンテキストだけで True になるフラグです。

RunStartEvent などのコールバックからは、event.ctx.data["resumed"] として参照できます。

チェックポイントを自動保存する#

run()/iterate() に checkpoint_path を渡すと、checkpoint_interval 世代ごとに自動保存します。

checkpoint_path は単一ファイルではなくディレクトリとして扱われ、世代ごとに checkpoint_{gen:06d}.npz という名前のスナップショットが作成されます。

ctx = build_optimizer(300).run(checkpoint_path="checkpoints", checkpoint_interval=5)

再開するには、ディレクトリ内の最新のスナップショットを読み込みます。

from pathlib import Path

latest = sorted(Path("checkpoints").glob("checkpoint_*.npz"))[-1]
loaded_ctx = OptimizationState.load(latest, problem)

実行成功後にスナップショットを残したくない場合は、checkpoint_delete_on_success=True を指定します(ディレクトリ自体は残り、その中のファイルだけが削除されます)。

ctx = build_optimizer(300).run(
    checkpoint_path="checkpoints",
    checkpoint_interval=5,
    checkpoint_delete_on_success=True,
)

pickle形式で保存する#

npz形式では ctx だけを保存しますが、pickle形式では学習済みサロゲートのパラメータを含む Optimizer 全体を保存できます。

optimizer = build_optimizer(200)
ctx = optimizer.run()
optimizer.save_pickle(ctx, "checkpoint.pkl")

loaded_optimizer, loaded_ctx = Optimizer.load_pickle("checkpoint.pkl")

実行時にPythonまたはライブラリのバージョンについての UserWarning が表示されることがあります。

Termination で使うlambdaなど、標準の pickle でシリアライズできないオブジェクトを含む Optimizer はpickle形式で保存できません。

CheckpointCallbackを直接使う#

run() の checkpoint_path 引数は、内部で CheckpointCallback を登録するだけです。

同じ動作を明示的に接続するには、cbmanager に CheckpointCallback を登録します。

from saealib import CheckpointCallback

optimizer = build_optimizer(300)
callback = CheckpointCallback("checkpoints", interval=5, optimizer=optimizer)
callback.register(optimizer.cbmanager)

ctx = optimizer.run()

format="pickle" または format="both" を指定する場合は、optimizer 引数が必要です。

関連するConceptとReference#