エアホッケーでは, スマッシャーと呼ばれる器具を用いて盤上のパックを打ち合い, 相手のゴールに入れて得点を競い合います. 今回はAIにスマッシャーをコントロールさせます.
配布されているプログラムでは, 自分の位置, パックの位置, 敵の位置を入力とし, 自分の動作(速度ベクトル)を出力としたニューラルネットワークを差分進化で学習させます.
Google Driveのリンクからダウンロードし, 適当なフォルダで展開してください. その後, Unity HubのOpenから展開した.projフォルダを開いてください.
Google DriveにはUnityのライブラリは上げていませんが, Unity Hubで開く際に自動的に生成されます.
macOS, Unity 2021.3.1f1以外で開く際には警告が出ますが, 警告に従ってバージョンの変更やパッケージのインストールなどをすれば以下の環境では動作することを確認しています.
macOS Monterey 12.4
Windows 10
Windows 11
Unity 2021.3.0f1
Unity 2021.3.1f1
Unity 2021.3.3f1
差分進化(Differential Evolution, DE)は1995年に発表されたメタヒューリスティクスであり, 最適化手法の中でも強力な方法のひとつとして知られています. 差分進化では以下のようなアルゴリズムを用います.
個体をランダムな値で初期化します.
Mutation
親となる個体をランダムに選びます. また, 個体をもう2つランダムに選んで , とします.
Crossover
差分ベクトル を計算し, 親 にこの差分ベクトルを足したものを子供とします.
ここで、はスケーリングパラメータと呼ばれる定数です.
2.と3.を繰り返し, 決められた個数の子供を生成します.
Selection
親と子を比較し, 優れている方を残します.
1.~3.を繰り返します.
スケーリングパラメータが大きいと大ぶりな探索となり, 探索は早くなりますが収束が不安定になります. 差分進化について詳しくは参考文献などを参照してください.
このプログラムでは個体それぞれが行動を決定するニューラルネットワーク(NN)を持っています. また, NNは1列の実数の配列(DNA)としても保存されています. このDNAをベクトルとして上記のCrossoverなどの演算をしています.
差分進化の管理はDEEnvironment
が行っています.
まず, DEEnvironment
が個体1, 個体2, 個体3をランダムに選びます. それぞれのDNAを, , とします.
DEEnvironment
はNNBrain
に, , を送り, NNBrain
がを計算します. これが子供のDNAとなります.
なお, この実験では交叉率crossrate
が定められており, その割合の子供が新しいDNAを持ちます. その他の子供は親のDNAをそのまま引き継ぎます.
これを繰り返し, 子供を生成していきます. 現在は100個の子供を生成する設定になっています.
DEEnvironment
はHockeyAgent
を呼んでボードの状態やプレーヤーの位置などの情報を取得し, その情報をNNBrain
に送ります.
NNBrain
は個体のNNの情報を持っており, 受け取ったボードの状態と個体のNNを使ってプレーヤーが次に取るべき行動action
を計算して返します. このプログラムでは, action
はプレーヤーの速度ベクトルとして伝えられます.
DEEnvironment
は返ってきたaction
をHockeyAgent
に送り, 実際にその行動を取るように指示します.
HockeyAgent
は受け取ったaction
をHockeyPlayer
に送り, 実際にUnity上でプレーヤーの位置を移動させます.
最後に, HockeyAgent
はボードの状態をもとに個体に対して報酬を与えます. 報酬の値は現在以下のように決まっています.
この操作を繰り返すことで対戦が進んでいきます. 時間切れになると対戦が止まり, 2つの個体の報酬値が決まります. これを繰り返し, 全ての個体についての報酬値が決まります.
最後に, 全ての個体について親と子の報酬値を比較し, 子供の方が高い場合は子供, そうでない場合は親のDNAを残します.
/Assets/Scripts/AI/DEEnvironment.cs
AgentとBrainを管理し, 一定期間ごとにAgentとBrainを更新します. fixedUpdate()
は毎フレーム呼ばれる関数です.
/Assets/Scripts/HockeyController/HockeyAgent.cs
ボードの状態を観測し, 必要な情報(自分の位置, 敵の位置, パックと自分の位置の差)を取得します. また, actionを受け取ってHockeyPlayer.csに渡します. ゲームの残り時間も管理します. ボードの状態に従い個体の報酬を管理します.
/Assets/Scripts/HockeyController/HockeyPlayer.cs
action
を受け取り, 実際にパックを移動させます. 受け取ったaction
は陣地を出る可能性や制限速度を超える可能性があるため, その場合は動きを制限します.
/Assets/Scripts/AI/NNBrain.cs
ボードの状態を入力として受け取り, NNを使って次に取るべき行動を計算し返します.
/Assets/Scripts/HockeyPlayer/ManualPlayer.cs
ManualPlayの時にのみ使われます. キーボードやマウスからの入力に従ってプレーヤーを動かします.
/Assets/Scripts/HockeyPlayer/ComputerPlayer.cs
ManualPlayの時にのみ使われます. それまでに学習したNNBrain
のうち最も成績の良かったNNを使ってプレーヤーと対戦します.
NEEnvironment
, NERuntime
, QEnvironment
, QBrain
など
現在使われていません.
ProjectタブのAssets > ScenesからHockeyGameをダブルクリックして開きます.
画面上部の再生ボタンを押すと学習が始まります. 学習中にはGame画面に表示されるスライダでプログラムの実行速度を調整できます. コンピュータへの負荷を少なくしたい場合は、描画をオフにすることもできます。
ManualPlayがオフであればPlayer1とPlayer2が対決しそれぞれが学習します(Populationが2ずつ増えるのはこれが理由です)。ManualPlayをオンにすると, その時点までに学習したNNのうち最も成績が良かったNNが制御するComputerPlayer
とプレーヤーがキーボードなどで操作するManualPlayer
が対決します.
Hierarchy > Environmentを選択すると, 以下の画面が表示されます.
パラメータ | 説明 |
---|---|
Total Population | 1世代ごとの個体数 |
Mutation Scaling Factor | スケーリングファクターF(上述) |
Cross Rate | 交叉率(上述) |
Input Size | 入力レイヤの大きさ(6) - 自分のx座標 - 自分のz座標 - 自分とパックのx座標の差 - 自分とパックのz座標の差 - 相手のx座標 - 相手のz座標 |
Hidden Size | 隠れレイヤの大きさ |
Hidden Layers | 隠れレイヤの数 |
Output Size | 出力レイヤの大きさ(2) - 移動の速度ベクトルのx成分 - 移動の速度ベクトルのz成分 |
N Agents | 2 (同時に対戦するプレーヤー数, 2で固定) |
Waiting Flag Restart Flag Manual Mode Flag |
Agentによって試合の管理に利用される変数です |
また, Hierarchy > Player1, Player2を選択すると, 以下の画面が表示されます.
パラメータ | 説明 |
---|---|
Max Speed | プレーヤーの動くことができる速さの上限です. |
Max Battle Time | 試合時間の上限です |
差分進化 - Wikipedia
https://ja.wikipedia.org/wiki/差分進化
差分進化法でハイパーパラメータチューニング - Qiita
https://qiita.com/n-suzuki/items/b8d4ccc4b6936120567e