はじめに
この記事は「ただただアウトプットを癖付けるための Advent Calendar 2024」に投稿した記事です。
最初の記事にも書いた通り、私は生物物理の実験を専門にしている研究者です。
最近はデータ解析のため機械学習のコード開発も行っており、幸いにもその成果がNeurIPSに採択されました。
前々回から学習のため、今回はブラックジャックでの行動を最適化するという課題に取り組んでいます。
今回の記事では、前回定式化した問題を実際にpythonで実装し、最適化を行ってみた結果を報告します。
関連記事
前の記事 「生物物理屋が数理最適化でブラックジャックに勝つ手を探してみた話 後編」
次の記事「【生物物理屋による論文紹介】複数種類の物理モデルを学習したシミュレーター」
実装
次に、定式化した数理最適化問題を実装します。
今回こそ離散的な変数の最適化をおこないます。
実装したものをこちらのリポジトリに置いています。よければご覧ください。
ライブラリの選定
まずはpythonの数理最適化ライブラリを選定します。
ライブラリの一覧はこちらを参考にしました。
今回の問題設定は非線形かつ離散的な問題です。
したがって、非線形最適化ライブラリを選定する必要があります。
今回の問題設定だと、以下のような方針で最適化をおこなうことになります。
- 状態空間を定め、そこでの勝敗を計算する関数を定義する
- バースト状態とディーラーのターンにおける方策を与える
- それ以外の状態について、方策を与える
- 状態価値関数を計算する
- 評価関数を計算する
- 評価関数を最大化するような方策を求める
非線形であり、やや複雑な問題設定になるため、pyomoやGekkoあたりが適していると考えられます。
今回は、Gekkoを用いて最適化を行います。
Gekkoのドキュメンテーションが充実していたためです。
変数
まずは変数を定義します。
前回scipyでの最適化用に定義した変数を、Gekko用に変える形です。
まずは、Gekkoオブジェクトをインスタンス化します。
その後、インスタンスのArrayメソッドによって変数の配列を定義します。
この配列のそれぞれにVarメソッドにより定義された変数を割り当てれば完了です。
Varメソッドでの定義は、値の上下限と、整数かどうかという引数を取ります。
ここでは下限0,上限1の整数としました。
目的関数の定義
これも、scipy用に定義したものの流用を試しました。
評価関数を返す関数を、インスタンスのObjメソッドに渡すことで完了します。
書き換えその1
しかしながら、この定義の方法では動きませんでした。
方策すべてを使って評価関数を定義する式が複雑すぎて、Gekkoで扱われる範囲を超えていたためです。
このため、評価関数の内部計算のそれぞれをGekko用に書き換えることとなりました。
とは言っても、方策を反映した勝率を返すif文をインスタンスのif3メソッドに置き換えたことと、各状態における勝率を中間変数MVとして扱い、確率による加重平均をsumメソッドとArrayメソッドで書き直したくらいです。
書き換えその2
動くようにはなりましたが、なぜか最適化の結果としてすべての方策がスタンドになってしまっています。
勝率を最小化させてみても同じ結果なので、変数の参照にエラーが残っているようです。
どうもこの原因は、変数=定数となる対応関係を入れるときに、変数と定数を別々に定義して、通常の代入で処理していたことのようです。
これでは、python上での参照先を変えただけで、Gekkoでの等式とは認識されません。
定数との等式ならEquation,ほかの変数と等しいならConnectionをつかって、式を認識させてやる必要があります。
また、if3関数がまったく反映されていませんでした。
if3では値が非負ならTrueの扱いになっていたためです。
なお、方策から0.5を引いて判定させてもうまく動かなかったため、結局if3は使わず、方策をあたかも確率のようにして重み付け平均をとる操作を採用しました。
さらには、中間変数をMVで定義していたために、すべて制御変数になってしまっていました。
これをSVに変更し、状態変数として定義し直しました。
結果
まず、solveした際に表示される自由度が方策の数と合致するようになりました。
また、方策はすべて1として初期化したのですが、いくつかは0となったので、変数へのアクセスもきちんとできています。
さらに、各状態でスタンドおよびヒットを選択した場合の勝率と方策を比較したところ、より勝率が高い方策が選ばれていました。