Dance Dance Revolutionについて
運動したほうがいいと周囲に言われたため, Dance Dance Revolution(以下DDR)に取り組んでいる.
DDRというのはリズムゲームの一種で, ディスプレイ上の矢印に併せて筐体のパネルを踏むゲームである.
DDRにはstepmaniaというエミュレーターがあり, このstepmaniaの譜面データから, 適切な難易度を類推するモデルを畳み込みニューラルネットワークで作成した.
学習データについて
stepmaniaの譜面を公開しているサイトから, テキトーに「.sm」をダウンロードし学習データとした.
今回は, 約5000譜面を学習データとし, 加えて左右の上下の入れ替えることでデータの水増しを行った(左右や上下を入れ替えても難易度がそこまで変わらないと仮定).
譜面を(時間)×(状態)の二次元配列に変換し, それをモデルの特徴量とした.
目的変数は,その譜面の難易度(19段階)である.
時間軸について
一部譜面には12分や32分などの矢印があるものの, 多く(85%ぐらい)の譜面は4分8分16分のみで構成されている.
そのため, 1小節を16等分する形で, 時間軸を表現した.
12分や32分などの特殊な矢印は, それと位置が近い16分の箇所に書き換えを行った.
状態軸(矢印の種類/BPM)について
DDRの矢印を(上下左右)×(矢印の種類:計4種)の計16通りに分類した.
- 普通の矢印
- フリーズアロー(長押し)の開始地点
- フリーズアロー(長押し)の終了地点
- ショックアロー(踏まない)
この16次元は, 該当する矢印があれば1なければ0のベクトルである.
加えてDDRではなぜかBPMが変化する曲が多い.
そのため, BPMの状態も入れ込む必要がある.
よって, 状態16種類+BPM一種類の計17次元のベクトルを入力データとした.
学習データのまとめ
学習モデルについて
今回は時系列解析等で使われる, 一次元の畳み込みニューラルネットで難易度類推器を作成した.
通常のそれとは違い, GlobalMaxPoolingとGlobalAveragePoolingを並列して行っている.
これは, 「最大の難所」と「全体の平均難度」の2つを抽出することを目的としており, 実際に精度が向上した.
コードの概要は以下になる.
input_x = keras.layers.Input(shape=(2704,17),name='in_X')
input_l = keras.layers.Input(shape=(1,),name='in_l')
u1= keras.layers.Conv1D(128,32, activation='relu')(input_x)
u1 = keras.layers.MaxPooling1D(5)(u1)
u_gmp = keras.layers.Conv1D(128,16, activation='relu')(u1)
u_gmp = keras.layers.GlobalMaxPooling1D()(u_gmp)
u_gap = keras.layers.Conv1D(128,16, activation='relu')(u1)
u_gap = keras.layers.GlobalAveragePooling1D()(u_gap)
u_gap = keras.layers.Multiply()([u_gap,input_l])
u2 = keras.layers.Concatenate()([u_gmp,u_gap])
u2 = keras.layers.Dense(64, activation='relu')(u2)
u2 = keras.layers.Dense(16, activation='relu')(u2)
out = keras.layers.Dense(1, activation='sigmoid')(u2)
model = keras.Model(inputs=[input_x,input_l], outputs=out)
入力データは可変長配列のため0埋めしてサイズを揃えている.
よって, そのままだと適切に平均が計算できない.
input_lはその補正項である.
$$input_l=\frac{最大の譜面の長さ}{その譜面の長さ}$$
結果
validationのRMSEが0.6程度となった.
そのため, 適切な難易度の目安になること程度なら期待できる