Rosenbrock関数の最適化#

このチュートリアルでは、Rosenbrock関数を例として、CMA-ESを用いた最適化の基本的な方法を説明します。

Rosenbrock関数とは#

Rosenbrock関数は、最適化アルゴリズムの性能評価によく使用されるベンチマーク関数です。

2変数の場合、Rosenbrock関数は次式で定義されます。

\[f(x_1, x_2) = (1-x_1)^2 + 100(x_2-x_1^2)^2\]

この関数の大域的最適解は、

\[(x_1, x_2) = (1, 1)\]

であり、そのときの目的関数値は、

\[f(1,1) = 0\]

となります。

Rosenbrock関数は、最適解の周辺に細長く湾曲した谷を持つため、 単純な最適化手法では最適解を見つけることが難しい問題として知られています。

目的関数を定義する#

まず、Rosenbrock関数をPythonの関数として定義します。

def rosenbrock(x):
    return (
        (1.0 - x[0]) ** 2
        + 100.0 * (x[1] - x[0] ** 2) ** 2
    )

rosenbrock() は、2つの要素を持つリストを引数として受け取り、 Rosenbrock関数の値を返します。

例えば、

print(rosenbrock([1.0, 1.0]))

を実行すると、

0.0

と表示されます。

初期値と探索範囲を設定する#

次に、CMA-ESに渡すパラメータを設定します。

ここでは、初期点を (-1.0, 1.0) とし、 各変数の探索範囲を [-5.0, 5.0] とします。

param = {
    "xinit": [-1.0, 1.0],
    "bound": [
        [-5.0, 5.0],
        [-5.0, 5.0],
    ],
}

xinit は最適化の初期点を指定するパラメータです。

bound は各変数の探索範囲を指定します。

この例では、

x[0] : -5.0 ~ 5.0
x[1] : -5.0 ~ 5.0

となります。

注釈

xinit と bound に指定する数値は float である必要があります。 そのため、-1 や 5 ではなく、-1.0 や 5.0 と指定します。

最適化を実行する#

目的関数とパラメータを用意したら、 nulavis.cmaes.run() を呼び出して最適化を実行します。

import nulavis

result = nulavis.cmaes.run(
    rosenbrock,
    2,
    param,
)

run() の引数は次の3つです。

引数

説明

rosenbrock

最適化する目的関数

2

最適化する変数の数

param

CMA-ESのパラメータ

最適化結果を確認する#

run() の戻り値には、最適化によって得られた結果が辞書形式で格納されています。

最良解は best_x から取得できます。

print("最適解:", result["best_x"])

最良目的関数値は best_f から取得できます。

print("目的関数値:", result["best_f"])

実行すると、例えば次のような結果が得られます。

最適解: [1.000..., 1.000...]
目的関数値: 0.000...

CMA-ESは乱数を使用するため、実行するたびに結果が多少異なる場合があります。

完全なプログラム#

ここまでの内容をまとめると、プログラム全体は次のようになります。

import nulavis


def rosenbrock(x):
    return (
        (1.0 - x[0]) ** 2
        + 100.0 * (x[1] - x[0] ** 2) ** 2
    )


param = {
    "xinit": [-1.0, 1.0],
    "bound": [
        [-5.0, 5.0],
        [-5.0, 5.0],
    ],
}


result = nulavis.cmaes.run(
    rosenbrock,
    2,
    param,
)

print("最適解:", result["best_x"])
print("目的関数値:", result["best_f"])

このプログラムを実行すると、Rosenbrock関数の最適解である

\[(x_1, x_2) = (1, 1)\]

に近い解が得られます。

乱数シードを指定する#

最適化結果を再現したい場合は、seed を指定します。

param = {
    "xinit": [-1.0, 1.0],
    "bound": [
        [-5.0, 5.0],
        [-5.0, 5.0],
    ],
    "seed": 12345,
}

同じ環境・同じパラメータで実行する場合、同じ乱数系列を使用して最適化を実行できます。

最大反復回数を指定する#

最適化の最大反復回数は max_iter で指定できます。

param = {
    "xinit": [-1.0, 1.0],
    "bound": [
        [-5.0, 5.0],
        [-5.0, 5.0],
    ],
    "max_iter": 1000,
}

この例では、最大1000回まで反復して最適化を行います。

ログ出力を抑制する#

最適化中のログ出力が不要な場合は、silent に True を指定します。

param = {
    "xinit": [-1.0, 1.0],
    "bound": [
        [-5.0, 5.0],
        [-5.0, 5.0],
    ],
    "silent": True,
}

辞書を引数とする関数の最適化#

先ほどの Rosenbrock関数は辞書を引数として渡すこともできます。 CMA-ESではこのような関数を最適化することも可能です。

import nulavis


def rosenbrock(x):
    return (
        (1.0 - x[0]) ** 2
        + 100.0 * (x[1] - x[0] ** 2) ** 2
    )


param = {
    "xinit": {0: -1.0, 1: 1.0},
    "bound": {
        0: [-5.0, 5.0],
        1: [-5.0, 5.0],
    },
}


result = nulavis.cmaes.run(
    rosenbrock,
    2,
    param,
)

print("最適解:", result["best_x"])
print("目的関数値:", result["best_f"])

まとめ#

このチュートリアルでは、Rosenbrock関数を題材として、 以下の手順でCMA-ESによる最適化を実行しました。

  1. 目的関数をPythonの関数として定義する

  2. xinit と bound を指定する

  3. nulavis.cmaes.run() を呼び出して最適化する

  4. best_x と best_f から最適化結果を取得する

基本的な使い方を理解したら、lambda、mu、sigma、 max_iter などのパラメータを変更することで、CMA-ESの動作を調整できます。

各パラメータの詳細については、 APIリファレンス を参照してください。