🎹

自作音ゲーの譜面難易度予測AIを作成する

2023/07/09に公開

概要

・PyTorch Lightningで自作音ゲーの譜面難易度予測モデルを学習(Python)
・モデルと学習済み重みを読み込み推論するサーバープログラムの作成(C++, LibTorch)
・TCP通信を用いたプロセス間通信で自作音ゲーと譜面難易度予測AIサーバーを連携

はじめに

定空間(ていくま)と申します。Zennは初投稿です。
普段は個人で「nature prhysm」という音ゲーを作っています。
上から降ってくる音符と同じ色、同じレーンのボタンを叩く音ゲーです。
https://www.freem.ne.jp/win/game/16015
今回はそのゲームのアップデートで、譜面の難易度をAIで予測する機能を追加したので技術的な詳細を説明する記事を書いてみようと思います。

モデル

作成するAIのモデルについて説明します。
この音ゲーの譜面難易度は5刻みで5%~100%までの数値で表されます。
よって実数値を予測する回帰モデルとします。
入力には譜面の情報が必要ですが、この音ゲーでは譜面読み込み時にその譜面の特徴を表すレーダー値、区間ごとの音符密度、各色ごとの音符密度のデータを算出しています。
難易度予測するだけならこれだけあれば十分だと思ったのでこれらのデータを入力値としました。よってこのモデルは人が事前に定義した特徴量を入力としているため、ディープラーニングモデルではなく機械学習モデルとなります。

学習データの詳細

24個の入力値と出力(教師データ)を一つのベクトルにまとめるため、入力値それぞれにx_1~x_{24}と名前を付けます。出力値はyです。

レーダー値(算出アルゴリズムは省略……いつか説明したいですね)

  • Global(x_1)
    音符の平均密度です。
  • Local(x_2)
    音符の4秒間での最大密度です。
  • Chain(x_3)
    縦連度です。
  • Unstability(x_4)
    速度変化度です。
  • Streak(x_5)
    ロングノート度です。
  • Color(x_6)
    色の複雑さです。

各色ごとの音符密度(x_{7}~x_{15})

赤・緑・青・水色・紫・黄色・白・黒・虹の9つの音符それぞれの平均密度の値です。

区間ごとの音符密度(x_{16}~x_{24})

最初の音符から最後の音符までを9等分して計算した音符密度です。

学習用データはゲーム側で全譜面読み込み時に以下のようなCSVを出力するコードを書いて用意しました。

モデルの実装・学習

モデルの実装にはPyTorch Lightningを使いました。
https://www.pytorchlightning.ai/index.html

PyTorchより手軽に簡単にモデルの構築、学習ができるらしいです。

anacondaでモデル学習用のpython環境を作り、PyTorch Lightningをインストールします。

学習に使用したコード
https://github.com/Take-Ma223/NPADP_PyTorch
NPADPというのは「nature prhysm auto difficulty prediction」の略です。

ネットワークの詳細はこちら
https://github.com/Take-Ma223/NPADP_PyTorch/blob/master/model.py#L69-L103

全結合層を4つ繋げただけの超シンプルなネットワークです。
損失関数はMSE(平均二乗誤差)で、バッチサイズ100、エポック数1000、学習率0.00001です。
学習について、最初はロスは大きかったですが学習率を下げるとロスは16.0500まで下がりました。MSEなので平均4%ぐらいの誤差で予測できています。

連携プログラムの作成

AI作成のメインはこれで終わりですが、このままでは手動でpythonを実行してデータを入力しないと結果が得られないので、ゲームと連携することを考える必要があります。

ゲーム側はC++,DXライブラリを使って作られており、ゲームとの連携を考えるにあたり、次の案がありました。

  1. サーバーを借りてpythonプログラムをそこで動かし、インターネットを介してゲームと通信する
    そんなめんどくさい&サーバー代かかる事やりたくなかったので不採用。ちなみにこのゲームのスコアランキングサーバーは家にあるRaspberry Piで動いています。
  2. pythonプログラムをpyinstallerでexe化して同梱
    pyinstallerでexe化する際に仮想環境のライブラリ全てを取り込むらしく、exeファイルがかなり大きくなってしまうため不採用。
  3. PyTorchをC++で扱うためのライブラリLibTorchを使ってゲームで直接モデルを読み込み予測できるようにする
    既存のプロジェクトでLibTorchを使えるようにするのがめちゃくちゃ苦戦した&コンパイル時にゲームで使っていた他のライブラリと競合を起こしてしまい、上手くいかなかったため不採用。
  4. LibTorchを使ってモデルを読み込み予測する連携プログラムを作り、ゲーム側から呼んで結果をファイル出力で受け取る
    とりあえずはこれでゲーム起動中に指定した譜面の難易度予測値を受け取りゲームで表示することができるようになりました。

しかし4の手法には問題がありました。それは処理が遅いという点です。
予測するたびに連携プログラムを起動し、データはコマンドライン引数で渡し、予測値は連携プログラムの出力したファイルをゲームで読み込んで受け取っていました。AIの予測にかかる時間よりもファイルのIO、そして連携プログラムの起動にかかる時間の方が圧倒的に長い状態でした。

そこで連携プログラムをサーバー化することを考えました。
具体的には連携プログラムへの入力、連携プログラムからの出力受け取りをTCP通信で行い、連携プログラムにはゲーム起動中ずっとサーバーとして裏で常駐して貰います。
これによって難易度予測のオーバーヘッドを解消し、起動時に全曲一気に難易度予測ができるようになりました(早すぎていまいちAIに予測させてる実感が無いかもしれません)。

そうしてできたプログラムがこちら(コードは汚いです……)
https://github.com/Take-Ma223/NPADP_pred_cpp

LibTorchはPyTorch公式がVisual Studio用のテンプレートを用意してくれています。
https://pytorch.org/cppdocs/installing.html
(LibTorch Project Templateをクリック)

2023/7/9時点ではVisual Studio 2019用のページに飛びますが、自分は2022を使っていたので
「For a version supporting Visual Studio 2022, get the LibTorch Project (64-bit) here.」の記載から2022版をダウンロードして使いました。

おわりに

最初は自分が個人の環境でAIで難易度予測出来たら面白そうかなと思っていただけでしたが、作っていく内にプレイヤーの方に使って欲しくなり、ゲームに組み込んで何とか形にすることができました。

雑ですがLibTorchを使う際に参考になったページです。

https://qiita.com/kai_ware/items/35f75ca83864f349c986#torchtensorから組み込み型の値を取得
https://colab.research.google.com/github/YutaroOgawa/pytorch_tutorials_jp/blob/main/notebook/5_Deployment/5_3_LOADING_A_TORCHSCRIPT_MODEL_IN_Cpp_jp.ipynb#scrollTo=_8_2reKjf5O8

Discussion