進捗のログ記録#

Layer 2: Compose

標準の logging モジュールで最適化の進捗を記録します。

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

このページを終えると、Python標準の logging モジュールで進捗、警告、独自イベントを記録できるようになります。

基本的な最適化の実行は 高レベルAPI を使い、世代単位の構成まで調整する場合は 低レベルAPI を参照してください。 このページでは、標準loggingの出力先とログレベルを設定し、組み込み・独自のイベントを記録する方法を扱います。

既定のログ動作を使う#

Optimizer は各世代の開始時に進捗を記録するハンドラ(logging_generation)を登録しますが、minimize/maximize の verbose=True(既定値)の場合に限られます。

ただし、このハンドラは logging.getLogger(__name__).info(...) を呼ぶだけなので、Pythonの logging モジュールでINFOレベルの出力を有効にしない限り何も表示されません。

import numpy as np
from saealib import minimize


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


DIM = 5

# nothing is printed here since logging.basicConfig has not been called yet
result = minimize(
    expensive_func, dim=DIM, lb=[-5.0] * DIM, ub=[5.0] * DIM, max_fe=100, seed=0
)

進捗を表示するには、logging.basicConfig でINFOレベルを有効にします。

import logging

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

result = minimize(
    expensive_func, dim=DIM, lb=[-5.0] * DIM, ub=[5.0] * DIM, max_fe=100, seed=0
)
# Generation 0 started. fe: 25. Best f: [14.04274116]
# Generation 1 started. fe: 27. Best f: [14.04274116]
# ...

logging_generation は目的数に応じて記録内容を切り替えます。単目的では最良の目的値を記録し、多目的では最初の非劣フロントのサイズと目的ごとの値の範囲を記録します。

進捗をまったく記録しない場合は、verbose=False を指定してハンドラの登録を停止します。

result = minimize(
    expensive_func,
    dim=DIM,
    lb=[-5.0] * DIM,
    ub=[5.0] * DIM,
    max_fe=100,
    seed=0,
    verbose=False,
)

進捗をファイルへ書き込む#

saealibロガーにFileHandlerを追加すると、進捗をファイルへ書き出せます。

import logging

file_handler = logging.FileHandler("optimization.log")
file_handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))

saealib_logger = logging.getLogger("saealib")
saealib_logger.addHandler(file_handler)
saealib_logger.setLevel(logging.INFO)

result = minimize(
    expensive_func, dim=DIM, lb=[-5.0] * DIM, ub=[5.0] * DIM, max_fe=100, seed=0
)

多目的問題のハイパーボリュームを記録する#

多目的問題では、logging_generation_hv(reference_point) が返すハンドラを登録すると、各世代のハイパーボリュームを記録します。

import numpy as np
from saealib import (
    Optimizer,
    Termination,
    max_fe,
    GenerationStartEvent,
    logging_generation,
    logging_generation_hv,
)
from saealib.benchmarks import zdt1

problem = zdt1(n_var=5)
optimizer = Optimizer(problem, seed=0).set_termination(Termination(max_fe(200)))

# remove the default logging_generation and swap in the HV-based one
optimizer.cbmanager.unregister(GenerationStartEvent, logging_generation)
optimizer.cbmanager.register(
    GenerationStartEvent, logging_generation_hv(reference_point=np.array([1.1, 1.1]))
)

ctx = optimizer.run()
# Generation 0. fe: 25. HV: 0.612345
# ...

最小化の規約では、reference_point は各目的で達成可能な最良値より大きい値にします。

warningレベルのログを扱う#

一部のコンポーネントは logger.warning(...) で数値上の問題を記録します。

たとえば RBFSurrogate は、カーネル行列の条件が悪くなったときに警告を発します。

saealibは独自の出力ハンドラを構成しません。出力を生成しないNullHandlerだけをインストールします。

警告などのsaealibログを表示したい場合は、Pythonのloggingモジュールを自分で構成します。たとえばWARNINGレベルのログを有効にすると、RBFSurrogateによるカーネル行列の悪条件警告が表示されます。

import logging
import numpy as np
from saealib import GaussianKernel, RBFSurrogate

logging.basicConfig(level=logging.WARNING)

surrogate = RBFSurrogate(kernel=GaussianKernel(length_scale=1.0), solver="solve")
surrogate.fit(np.array([[0.0], [0.0], [0.0]]), np.array([1.0, 2.0, 3.0]))

独自のログ記録を追加する#

logging_generation/logging_generation_hv と異なる内容を記録したい場合は、CallbackManager に独自のハンドラを登録します。

仕組みの詳細は CallbackManager を参照してください。

関連コンセプトと参考情報#