Rosenbrock関数の最適化
======================

このチュートリアルでは、Rosenbrock関数を例として、CMA-ESを用いた最適化の基本的な方法を説明します。

Rosenbrock関数とは
------------------

Rosenbrock関数は、最適化アルゴリズムの性能評価によく使用されるベンチマーク関数です。

2変数の場合、Rosenbrock関数は次式で定義されます。

.. math::

   f(x_1, x_2)
   =
   (1-x_1)^2 + 100(x_2-x_1^2)^2

この関数の大域的最適解は、

.. math::

   (x_1, x_2) = (1, 1)

であり、そのときの目的関数値は、

.. math::

   f(1,1) = 0

となります。

Rosenbrock関数は、最適解の周辺に細長く湾曲した谷を持つため、
単純な最適化手法では最適解を見つけることが難しい問題として知られています。

目的関数を定義する
------------------

まず、Rosenbrock関数をPythonの関数として定義します。

.. code-block:: python

   def rosenbrock(x):
       return (
           (1.0 - x[0]) ** 2
           + 100.0 * (x[1] - x[0] ** 2) ** 2
       )

``rosenbrock()`` は、2つの要素を持つリストを引数として受け取り、
Rosenbrock関数の値を返します。

例えば、

.. code-block:: python

   print(rosenbrock([1.0, 1.0]))

を実行すると、

.. code-block:: text

   0.0

と表示されます。

初期値と探索範囲を設定する
--------------------------

次に、CMA-ESに渡すパラメータを設定します。

ここでは、初期点を ``(-1.0, 1.0)`` とし、
各変数の探索範囲を ``[-5.0, 5.0]`` とします。

.. code-block:: python

   param = {
       "xinit": [-1.0, 1.0],
       "bound": [
           [-5.0, 5.0],
           [-5.0, 5.0],
       ],
   }

``xinit`` は最適化の初期点を指定するパラメータです。

``bound`` は各変数の探索範囲を指定します。

この例では、

.. code-block:: text

   x[0] : -5.0 ～ 5.0
   x[1] : -5.0 ～ 5.0

となります。

.. note::

   ``xinit`` と ``bound`` に指定する数値は ``float`` である必要があります。
   そのため、``-1`` や ``5`` ではなく、``-1.0`` や ``5.0`` と指定します。

最適化を実行する
----------------

目的関数とパラメータを用意したら、
``nulavis.cmaes.run()`` を呼び出して最適化を実行します。

.. code-block:: python

   import nulavis

   result = nulavis.cmaes.run(
       rosenbrock,
       2,
       param,
   )

``run()`` の引数は次の3つです。

.. list-table::
   :header-rows: 1

   * - 引数
     - 説明
   * - ``rosenbrock``
     - 最適化する目的関数
   * - ``2``
     - 最適化する変数の数
   * - ``param``
     - CMA-ESのパラメータ

最適化結果を確認する
--------------------

``run()`` の戻り値には、最適化によって得られた結果が辞書形式で格納されています。

最良解は ``best_x`` から取得できます。

.. code-block:: python

   print("最適解:", result["best_x"])

最良目的関数値は ``best_f`` から取得できます。

.. code-block:: python

   print("目的関数値:", result["best_f"])

実行すると、例えば次のような結果が得られます。

.. code-block:: text

   最適解: [1.000..., 1.000...]
   目的関数値: 0.000...

CMA-ESは乱数を使用するため、実行するたびに結果が多少異なる場合があります。

完全なプログラム
----------------

ここまでの内容をまとめると、プログラム全体は次のようになります。

.. code-block:: python

   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関数の最適解である

.. math::

   (x_1, x_2) = (1, 1)

に近い解が得られます。

乱数シードを指定する
--------------------

最適化結果を再現したい場合は、``seed`` を指定します。

.. code-block:: python

   param = {
       "xinit": [-1.0, 1.0],
       "bound": [
           [-5.0, 5.0],
           [-5.0, 5.0],
       ],
       "seed": 12345,
   }

同じ環境・同じパラメータで実行する場合、同じ乱数系列を使用して最適化を実行できます。

最大反復回数を指定する
----------------------

最適化の最大反復回数は ``max_iter`` で指定できます。

.. code-block:: python

   param = {
       "xinit": [-1.0, 1.0],
       "bound": [
           [-5.0, 5.0],
           [-5.0, 5.0],
       ],
       "max_iter": 1000,
   }

この例では、最大1000回まで反復して最適化を行います。

ログ出力を抑制する
------------------

最適化中のログ出力が不要な場合は、``silent`` に ``True`` を指定します。

.. code-block:: python

   param = {
       "xinit": [-1.0, 1.0],
       "bound": [
           [-5.0, 5.0],
           [-5.0, 5.0],
       ],
       "silent": True,
   }

辞書を引数とする関数の最適化
----------------------------

先ほどの Rosenbrock関数は辞書を引数として渡すこともできます。
CMA-ESではこのような関数を最適化することも可能です。

.. code-block:: python

   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による最適化を実行しました。

#. 目的関数をPythonの関数として定義する
#. ``xinit`` と ``bound`` を指定する
#. ``nulavis.cmaes.run()`` を呼び出して最適化する
#. ``best_x`` と ``best_f`` から最適化結果を取得する

基本的な使い方を理解したら、``lambda``、``mu``、``sigma``、
``max_iter`` などのパラメータを変更することで、CMA-ESの動作を調整できます。

各パラメータの詳細については、 :doc:`api` を参照してください。
