機械学習周りのプログラミング中心。 イベント情報
ポケモンバトルAI本電子書籍通販中

【ジュウオウ】ポケモン名っぽい駅名 part03終 Character-level Recurrent Neural Networkによるポケモン名らしさの定量化【一発ネタ】

前回学習させた、ポケモン名を表現するCharacter-level RNNを用いて、ポケモン名っぽい駅名を探します。

select766.hatenablog.com

前回の実験ではCharacter-level RNNを用いてポケモン名っぽい文字列を生成しました。このモデルは、与えられた文字列の自然さ(ポケモン名っぽさ)を判定することにも使えます。 前回の例を再掲すると、モデルは文字列を受け取って次の文字の確率を出力するというものでした。例えばf(サ) => ン=40%, イ=30%, ...f(サン) => ド=60%, ダ=30%,...というような入出力になります。確率が高いほど自然、すなわちポケモン名っぽいということになります。「サンド」の自然さを計算するには次のようにします。f() => ア=2%, イ=1%, ..., サ=2%, ..., ン=0%のように1文字目にも確率があります。f()における「サ」の確率(2%)、f(サ)における「ン」の確率(40%)、f(サン)における「ド」の確率(60%)を掛け合わせた0.02×0.40×0.60=0.0048が「サンド」の出現確率であり、この値が高いほど自然であると考えます*1。確率は常に1以下なので、文字列が長くなると必ず出現確率が下がってしまいます。そのため自然さの値は確率の対数を文字列の長さで割った値を用いました。

上記のアルゴリズムに対して駅名(読み仮名)を与えて、自然さの順に並べることでポケモン名っぽい駅名が見つかるはずです。

実験結果を示します。自然さの符号は反転していて、小さいほど自然であることを示します。なお、ポケモン名には「ヅ」が全く含まれていなかったため、この文字を含む駅(真鶴(マナヅル)等)については判定できませんでした。

上位50駅は以下のようになりました。(同じ読みの別の駅は2段に分けています)

駅名 読み仮名 自然さ 所在地 路線
十王 ジュウオウ 1.38 茨城県 JR東日本常磐線
関目 セキメ 1.40 大阪府 京阪電気鉄道京阪本線
竜王 リュウオウ 1.51 山梨県 JR東日本中央本線
十三 ジュウソウ 1.76 大阪府 阪急電鉄神戸本線
咲花 サキハナ 1.83 新潟県 JR東日本磐越西線
モリ 1.98 北海道 JR北海道函館本線
モリ 1.98 大阪府 水間鉄道水間線
中田 チュウデン 2.00 徳島県 JR四国牟岐線
毘沙門 ビシャモン 2.03 青森県 津軽鉄道津軽鉄道線
田丸 タマル 2.04 三重県 JR東海参宮線
花園 ハナゾノ 2.10 京都府 JR西日本山陰本線
花園 ハナゾノ 2.10 香川県 高松琴平電気鉄道長尾線
郡中 グンチュウ 2.11 愛媛県 伊予鉄道郡中線
吉里吉里 キリキリ 2.12 岩手県 三陸鉄道リアス線
五郎丸 ゴロウマル 2.14 福岡県 西日本鉄道甘木線
花泉 ハナイズミ 2.16 岩手県 JR東日本東北本線
知立 リュウ 2.20 愛知県 名古屋鉄道名古屋本線
鶴丸 ツルマル 2.21 鹿児島県 JR九州吉都線
加太 カブト 2.23 三重県 JR西日本関西本線
カブト 2.23 福島県 阿武隈急行線
山隈 ヤマグマ 2.24 福岡県 甘木鉄道甘木線
薬院 ヤクイン 2.24 福岡県 西日本鉄道天神大牟田線
丸亀 マルガメ 2.25 香川県 JR四国予讃線
丸森 マルモリ 2.25 宮城県 阿武隈急行線
亀甲 カメノコウ 2.26 岡山県 JR西日本津山線
みのり台 ミノリダイ 2.26 千葉県 新京成電鉄新京成線
山東 サンドウ 2.29 和歌山県 和歌山電鐵貴志川線
御宿 オンジュク 2.30 千葉県 JR東日本外房線
大安 ダイアン 2.32 三重県 三岐鉄道三岐線
宝殿 ホウデン 2.32 兵庫県 JR西日本山陽本線
玉来 タマライ 2.33 大分県 JR九州豊肥本線
桐生 リュウ 2.34 群馬県 JR東日本両毛線
六甲 ロッコ 2.34 兵庫県 阪急電鉄神戸本線
平城 ヘイジョウ 2.34 奈良県 近畿日本鉄道京都線
梅林 バイリン 2.35 広島県 JR西日本可部線
伊集院 イジュウイン 2.35 鹿児島県 JR九州鹿児島本線
倶知安 クッチャン 2.36 北海道 JR北海道函館本線
雫石 シズクイシ 2.37 岩手県 JR東日本田沢湖線
崎守 キモリ 2.38 北海道 JR北海道室蘭本線
比布 ピップ 2.40 北海道 JR北海道宗谷本線
大道 ダイドウ 2.44 山口県 JR西日本山陽本線
三日市 ミッカイチ 2.44 三重県 近畿日本鉄道鈴鹿線
人丸 ヒトマル 2.45 山口県 JR西日本山陰本線
海王丸 カイオウマル 2.45 富山県 万葉線新湊港線
宿院 シュクイン 2.48 大阪府 阪堺電気軌道阪堺線
堀切 ホリキリ 2.49 東京都 東武鉄道伊勢崎線
やぐま台 ヤグマダイ 2.50 愛知県 豊橋鉄道渥美線
蔵宿 ゾウシュク 2.50 佐賀県 松浦鉄道西九州線
ヒメ 2.52 岐阜県 JR東海太多線
金光 コンコウ 2.52 岡山県 JR西日本山陽本線
大釈迦 ダイシャカ 2.52 青森県 JR東日本奥羽本線
積志 セキシ 2.52 静岡県 遠州鉄道鉄道線
醍醐 ダイゴ 2.54 京都府 京都市営地下鉄東西線
醍醐 ダイゴ 2.54 秋田県 JR東日本奥羽本線

「ジュウオウ」(十王)が最もポケモン名っぽいという結果です。いてもおかしくない名前に感じられます。次点のセキメ(関目)ですが、「セキ」が含まれる実在のポケモン名は「セキタンザン」のみ、「キメ」は「ユキメノコ」のみで、そんなにポケモンっぽさを感じません。個人的にしっくりきたのは「ジュウソウ(十三)」「ツルマル(鶴丸)」「クッチャン(倶知安)」あたりでしょうか。「~ュウ」や「~マル」という終わり方がポケモンっぽさの一端にも見えます。駅名というくくりの中では一定の妥当性があるランキングに見えます。一方、ポケモン名には「-」(伸ばし棒)がよく用いられる一方で駅名にはほぼ登場しない(京コンピュータ前駅など、わずかに存在)ので、伸ばし棒をポケモン名らしさとしてとらえると駅名はポケモンらしさを欠いている面があるかもしれません。「カイリュー」ではなく「カイリュウ」という表記に変換して学習すれば少しは違うかもしれません。

ちなみに、東京都には駅が多いはずなのにほとんど上位には入っていないことがわかります。一番上で43位の「ホリキリ」(堀切)です。偶然なのか、地域ごとの駅名のつけ方に偏りがあるのかはわかりませんが。東京から一番行きにくそうなのは鶴丸駅でしょうか。宮崎空港経由で7時間かかります。

下位20駅も示しておきます。

駅名 読み仮名 自然さ
長者ヶ浜潮騒はまなす公園前 チョウジャガハマシオサイハマナスコウエンマエ 34.10
南阿蘇水の生まれる里白水高原 ミナミアソミズノウマレルサトハクスイコウゲン 34.04
東京ディズニーランド・ステーション トウキョウディズニーランドステーション 28.21
東京ディズニーシー・ステーション トウキョウディズニーシーステーション 25.13
西線9条旭山公園通 ニシセンクジョウアサヒヤマコウエンドオリ 23.75
馬出九大病院前 マイダシキュウダイビョウインマエ 22.63
やながわ希望の森公園 ヤナガワキボウノモリコウエンマエ 22.23
射水市新湊庁舎前 イミズシシンミナトチョウシャマエ 21.22
上越国際スキー場 ジョウエツコクサイスキージョウマエ 21.20
羽田空港国際線ターミナル ハネダクウコウコクサイセンターミナル 21.18
出雲科学館パークタウン前 イズモカガクカンパークタウンマ 21.06
羽田空港国内線ターミナル ハネダクウコウコクナイセンターミナル 20.21
修大附属鈴峯前 シュウダイフゾクスズガミネマエ 20.08
北九州貨物ターミナル キタキュウシュウカモツターミナル 19.87
JA広島病院前 ジェイエーヒロシマビョウインマエ 19.82
急患医療センター前 キュウカンイリョウセンターマエ 19.75
松江イングリッシュガーデン前 マツエイングリッシュガーデンマエ 19.34
尼崎センタープール前 アマガサキセンタープールマエ 18.94
阿蘇下田城ふれあい温泉 アソシモダジョウフレアイオンセン 18.58
高崎商科大学前 タカサキショウカダイガクマエ 18.30

単に長い駅名が出てくるだけであまり意味がありません。6文字以下の駅に絞って表示します。

駅名 読み仮名 自然さ
清瀬 キヨセ 8.93
下川辺 モカワベ 8.74
早稲田 ワセダ 8.70
羽床 ハユカ 8.50
日生 ヒナセ 8.48
水無瀬 ミナセ 8.43
広瀬川 ヒロセガワラ 8.40
下川沿 モカワゾイ 8.38
遊佐 ユザ 8.25
検見川 ケミガワ 8.24
瀬戸石 セトイシ 8.21
打出 ウチデ 8.20
南千歳 ミナミチトセ 8.20
名寄 ナヨロ 8.16
湯江 ユエ 8.14
布施 フセ 8.14
庭瀬 ニワセ 8.13
瀬上 セノウエ 8.10
小千谷 オヂヤ 8.08
三溝 サミゾ 8.07

分かりやすい傾向として、「セ」で終わる駅が多いです。調べてみると、「セ」で終わるポケモンは存在せず、また途中に現れるポケモンも8種類しかいませんでした。「瀬」は頻繁に駅名に現れる一方でポケモンっぽくない、ということが言えるのではないでしょうか。ちなみに、清瀬、早稲田、東大前は東京都の駅です。

くだらない思い付きでしたが、ポケモン名っぽさをCharacter-level RNNで表現し、これを用いて駅名のランキングを作ることができました。

jupyter notebookおよび学習したモデルを公開しています。

https://github.com/select766/pokemon-name-char-rnn

*1:モデルを学習する際には、実在のポケモン名においてこの確率が大きくなるようにパラメータを調整します

【ガメハチ】ポケモン名っぽい駅名 part02 Character-level Recurrent Neural Networkの学習【一発ネタ】

前回はポケモン名を一部に含む駅名を抽出する単純な実験を行いました。今回は、自然言語処理技術を導入してポケモン名をモデル化します。

select766.hatenablog.com

モデルの構造として、Character-level Recurrent Neural Network (Character-level RNN)というものを用いました。簡単に言えば、文字列が与えられたときに次の文字を予測してくれるようなモデルf再帰的に使う手法となります。ポケモン名で例えます。「サンド」「サイホーン」などがいることを考えると、最初の文字が「サ」のとき次の文字は「ン」や「イ」となるのが自然です。それを確率的なモデルで表現すると、f(サ) => ン=40%, イ=30%, ...というような入出力になります。「サンド」や「サンダー」がいるので、最初の2文字が「サン」のとき、次は「ド」や「ダ」が来るのが自然です。f(サン) => ド=60%, ダ=30%,...というような入出力であればこれが表現できます。「サンド」が先頭に来るポケモン名は「サンド」か「サンドパン」しかいないので、単純に完全一致で考えるとf(サンド) => EOS=50%, パ=50%というモデルになります。ここでEOSはEnd-of-sentenceで、文章(今回は1単語だけの特殊な文章になります)の終わりを表す特殊な文字です。これがモデルから出力されたら文章生成を終了します。このモデルだと実在するポケモン名がそのまま出てきて終わりになってしまい、実在しないがポケモンっぽい名前を表現できていません。そこで条件を緩和して、「サンド」の続きの文字ではなく「ンド」の続きの文字を考えると、「ネンドール」や「ペンドラー」もいますので、「サンドー」や「サンドラ」のような文字列もポケモンっぽいと考えてみます。f(サンド) => EOS=25%, パ=25%, ー=10%, ラ=10%, ...というようなモデルの出力が実現されれば、ポケモンっぽさを残しつつ実在しないポケモン名を生成できます。

Character-level RNNは以上のような「与えられた文字列に自然に続くような文字を生成」することができるモデルです。このモデルをポケモン名で学習させます。 実装はPyTorchのチュートリアルを改造することで行いました。Character-level RNNの正しい理解にはここでは説明していない隠れ状態などの説明が必要ですので、技術的な詳細はリンク先をお読みください。

pytorch.org

チュートリアルでは、言語(英語、フランス語など)を条件としてその言語風の人名を生成するという課題を解いていますが、日本語のポケモン名だけを使うので言語を条件として与える部分を取り払います。 文字の集合はアルファベットから、ポケモン名に使われているカタカナに置き換えました。具体的にはァアィイウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロワンヴーの79文字です。隠れ層のチャンネル数ですが、学習サンプル数が最大でポケモン数である887しかないので、5-fold cross validationの結果、かなり小さい8チャンネルとすることにしました。

学習したモデルに対して先頭の文字を与えて、最大確率の文字を取り出して連結することを繰り返してポケモン名っぽいものを生成してみました。

ァイアージ
アルガメ
ィアスワル
イルフィニ
ウツハニ
ェリーズル
エルビール
ォクワムシ
オンドール
カブル
ガメハチ
キュワル
ギギル
クロバージョ
グマネイル
ケンターン
ゲチャヒメ
コロッチルト
ゴルバル
サンカイル
ザルガッチ
シャマッチ
ジャラッチ
スカメイル
ズマル
セクレロズン
ゼルガー
ソンプル
ゾロアージ
タイドー
ダージャー
チャラーダ
ヂムシャー
ッチャラー
ツボツボ
テティオス
ディアル
トゲキール
ドングランウ
ナイト
ニャラード
ヌメル
ネコアージ
ノズクル
ハリボール
バルビー
パルネコ
ヒメルキ
ビル
ピジョン
フードラ
ブルバッチャ
プリージ
ヘルガニ
ベルバクロー
ペルホール
ホエル
ボーフィオ
ポポッチ
マル
ミルホーン
ムシャード
メノコ
モリン
ャマツチルク
ヤミルホール
ュウワウズル
ユキワル
ョロボット
ヨーボッグ
ライワル
リージャー
ルビール
レジョッチ
ロバッチ
ワルビル
ングラン
ヴァディオ
ージャラー

ピジョン」、「ワルビル」のように一部実在のポケモン名が出てきてしまっていますが、なんとなくポケモン名っぽい文字列が生成できていることがわかります。「ー」(伸ばし棒)で終わる名前が多数出ていますが、実在するポケモン名は111種類が伸ばし棒で終わっているのでそれが自然であるということを学習していることが期待できます。ところで、公式にはポケモンの名前の由来は説明されていないものの、「ヒトデマン」「クヌギダマ」のように実在の生物の名前を含んでいるポケモン名が多数あります。ポケモン名として使われたことがない生物の名前をモデルに取り入れれば、ポケモンらしさを保ちつつより生成のバリエーションを増やせる可能性があります。

今回はポケモン名をCharacter-level RNNに学習させ、ポケモン名っぽい文字列を生成しました。次回は最終回で、もともとの問いであったポケモン名っぽい駅名を探します。

【ハッサム】ポケモン名っぽい駅名 part01 文字列一致【一発ネタ】

とあるゲーム実況放送にて、「ハッサムというポケモンがいるが、北海道には発寒(はっさむと発音)駅がある」という話題がありました。

ポケモンの名前っぽさとは何か、駅名の中でほかにポケモン名っぽいものがないかが気になったので、自然言語処理の側面からちょっと遊んでみます。

今回はもっとも簡単にできる導入として、自然言語処理を使わず、ポケモン名と完全一致する駅名、ポケモン名が一部に含まれる駅名を探してみます。

データセットとして、ポケモン名一覧には

ポケモン一覧 - ポケモンWiki

の図鑑番号890(ムゲンダイナ)まで、駅名(読み仮名付き)には

日本全国の駅名一覧 - ブラウザで使えるWeb便利ツール

の(日本全国の駅名一覧(2016年更新)→TSVファイルで「駅名一覧」(ふりがな付)をダウンロード(UTF-8))を用いました。

ポケモン名は2文字から6文字*1のカタカナからなりますが、特殊なものがわずかにあるため、それを除いてカタカナのみの文字列にしました。具体的には、

  • ニドラン♀ニドラン♂は「ニドラン」にまとめる
  • ポリゴン・ポリゴン2・ポリゴンZは「ポリゴン」にまとめる
  • カプ・コケコ→カプコケコ(カプ系4種類それぞれ)
  • タイプ:ヌル→タイプヌル

と変換して887種類のポケモン名があるものとみなしました。

また、駅名の読み仮名データは「~えき」「~ていりゅうじょう」となっていますがこれらの接尾辞は削除し、pythonjaconvパッケージにてカタカナへ変換しました。

ポケモン名と完全一致する駅名、ポケモン名が一部に含まれる駅名を検索した結果は以下のようになりました。所在地などは処理には関係せず、参考として掲載しています。

駅名 読み仮名 所在地 路線
表参道 オモテサンド 東京都 東京メトロ銀座線
北参道 キタサンド 東京都 東京メトロ副都心線
山東 サンド 和歌山県 和歌山電鐵貴志川線
サンドーム西 サンドームニシ 福井県 福井鉄道福武線
宗吾参道 ソウゴサンド 千葉県 京成電鉄本線
岩原スキー場 イワッパラスキージョウマエ 新潟県 JR東日本上越線
加太 カブト 三重県 JR西日本関西本線
カブト 福島県 阿武隈急行線
兜沼 カブトヌマ 北海道 JR北海道宗谷本線
かぶと山 カブトヤマ 京都府 京都丹後鉄道宮津線
たのうら御立岬公園 タノウラオタチミサキコウエン 熊本県 肥薩おれんじ鉄道線
発寒 ハッサム 北海道 JR北海道函館本線
発寒中央 ハッサムチュウオウ 北海道 JR北海道函館本線
発寒南 ハッサムミナミ 北海道 札幌市営地下鉄東西線
来迎寺 ライコウ 新潟県 JR東日本信越本線
磐城守山 イワキモリヤマ 福島県 JR東日本水郡線
崎守 キモリ 北海道 JR北海道室蘭本線
島松 シママ 北海道 JR北海道千歳線

完全一致となったのはハッサムとカブトの2つでした。カブトは漢字の異なる2つの駅が存在します。 部分一致ではサンドが多数の駅名に含まれていました。部分一致の駅名として、かぶと山(カブトヤマ)はポケモン名として存在するかもしれないと思える一方、岩原スキー場前(イワッパラスキージョウマエ)はポケモンっぽいと言われても無理があるでしょう。 本当に求めたいのはポケモンっ「ぽい」であって実在するポケモン名と一致することではないはずです。

ちなみに東京駅からの到達難易度としては、表参道が20分以下で最も容易でしょう。最も難しいと思われるのは兜沼駅で、北海道最北端の稚内駅から南に4駅のところにあります。羽田から1日1便しかない稚内空港行きの飛行機を用いて7時間程度かかります。ポケモン好きのYouTuberの方は全駅訪問してみてはどうでしょうか。

今回は単純な部分一致のため「イワッパラスキージョウマエ」のように無理のある結果が得られました。次回はよりポケモンらしい雰囲気の駅名を探すため、自然言語処理を活用します。

*1:初代では5文字までだったが、X・Yより6文字の名前が登場

技術書典応援祭(技術書典8代替オンラインイベント)頒布予定

技術書典 応援祭新型コロナウイルス問題で中止となった技術書典8(2020年3月1日)の代替となるオンラインイベントです。2020年3月7日から約1か月間開催されます。

当サークル「ヤマブキ計算所」は技術書典 応援祭へ出展いたしますので、頒布内容をお知らせします。

頒布URL一覧 (2020-03-07追記): 第1巻 第2巻 第3巻

  • PokéAI本の第1・2・3巻を頒布します。第3巻はコミックマーケット97で初頒布したもので、技術書典では初めての頒布となります。
  • 全巻紙版(物理本)の通販を行います。初の通販となりますので、東京に来る機会のない方が紙版を入手する貴重な機会となります。
    • 各種委託サービスの手数料・契約の手間が大きいため常設の紙版通販は予定しておりません。
  • 紙版は本体価格500円+送料400円となります。主催者システムの都合で、まとめ買いしても送料は1冊あたり400円となることをご了承ください。
  • 電子版はBOOTHで購入するのと内容・価格とも変わりありません。
  • 第3巻については、電子版限定の追加コンテンツがついています。
    • 紙版を購入した場合も「あとがき」に記載の方法でダウンロードできます。
    • BOOTHですでに購入した場合でもファイルを差し替えていますので、再ダウンロードにより入手可能です。
    • 内容は第3巻執筆後の研究成果(この記事以降)を本の形にまとめたものです。本文14ページ。第4巻になる予定のコンテンツの一部を先読みできるというイメージでお考え下さい。

それではよろしくお願いいたします。

汎用行動選択モデルの学習 part05 教師あり学習の定性評価【PokéAI】

学習した汎用行動選択モデルの挙動を定性的に確認してみます。

前回の記事で最も精度が高かったモデル(3層、チャンネル数16、バッチ正規化なし)に対しvalidationデータを入力し、モデルの出力(選択した技)および正解データを表示します。正解データはパーティ固有モデルが出力したものです。

凡例

自分のポケモン(FはFriend)
自分のポケモンの技構成
相手のポケモン(OはOpponent)
=> 各行動に対する確率
モデル出力=最大確率の技 正解=正解データ
F ドククラゲ 204/204  
ハイドロポンプ おんがえし とっしん ヘドロばくだん
O スターミー 182/182  
=> ハイドロポンプ=2.4% おんがえし=7.3% とっしん=0.2% ヘドロばくだん=90.1%
モデル出力=ヘドロばくだん 正解=ヘドロばくだん

F ランターン 52/253  
なみのり どくどく おんがえし でんじほう
O ドンファン 80/215  
=> なみのり=89.0% どくどく=3.1% おんがえし=5.4% でんじほう=2.5%
モデル出力=なみのり 正解=なみのり

妥当な出力をしています。

F ゴローニャ 138/204 psn
すてみタックル ちきゅうなげ じしん ずつき
O パラセクト 25/182  
=> すてみタックル=10.7% ちきゅうなげ=18.0% じしん=59.8% ずつき=11.5%
モデル出力=じしん 正解=ずつき

じしんはパラセクトに対してタイプ相性が1/4なので間違っていると考えられます。

F マリルリ 90/226  
れいとうビーム はかいこうせん バブルこうせん どくどく
O ラフレシア 11/198  
=> れいとうビーム=84.2% はかいこうせん=0.3% バブルこうせん=15.2% どくどく=0.3%
モデル出力=れいとうビーム 正解=どくどく

れいとうビームが正しくて、正解データのどくどくは明らかに間違っています。ラフレシアはどくタイプを含んでいてどくどくは効果がありません。正解データも疑似的なものであり必ずしも正しくないため、これに対して正解率100%を目指すのは得策ではありません。

モデルの定量的な正解率は58.5%でしたが、正解データ自体が間違っている場合もあり、定性的には妥当な出力ができているように思われます。

汎用行動選択モデルの学習 part04 教師あり学習【PokéAI】

前回作成した教師データを用いて、汎用行動選択モデルの学習を試みます。

select766.hatenablog.com

以下のようにバトルの状態を入力とし、適切な行動(技)を選択するモデルを学習することが目標です。

F マタドガス 187/187  
ころがる 10まんボルト でんじほう だいもんじ
O オムスター 193/193  
=> 10まんボルト

モデルはDeep Neural Network (DNN)を用います。DNNは畳み込み、リカレントなど構造の自由度が極めて高いですが、今回はもっとも単純にf(バトルの状態,選択肢iの情報) => 選択肢iの優先度という入出力の全結合feed-forward networkにすることとしました。

バトルの状態はパーティ固有モデルと同様で、以下のようになります*1

自分/相手は、どちら側のパーティの情報を入力として与えるか。両方の場合は次元数が倍となります。

特徴 次元数 自分/相手 説明
有効な行動 4 自分 現在どの行動がとれるのかを表す有効な行動に該当する次元に1を設定
生存ポケモン 1 両方 瀕死でないポケモン数/全ポケモン
ポケモンタイプ 17 相手 場に出ているポケモンポケモンのタイプ(ノーマル・水・…)に該当する次元に1を設定
HP残存率 1 両方 場に出ているポケモンの現在HP/最大HP
状態異常 6 両方 場に出ているポケモンの状態異常(どく・もうどく・まひ・やけど・ねむり・こおりのうち該当次元に1を設定)
ランク補正 6 両方 場に出ているポケモンのランク補正(こうげき・ぼうぎょ・とくこう・とくぼう・すばやさ・命中・回避それぞれ、ランク/12+0.5を設定)
天候 3 - 場の天候(はれ・あめ・すなあらし)に該当する次元に1を設定

この特徴量には自分のパーティの構成や選択肢の内容が入っていません。モデルに与える選択肢を表現するベクトルとして今回はもっとも単純に、ポケモンと技のone-hotベクトルを用いました。以下の表の各行が選択肢1つに対応するベクトルとなります。

自分のポケモンマタドガスの場合:

選択肢 ポケモン=アズマオウ ポケモン=マタドガス 技=10まんボルト 技=ころがる 技=だいもんじ 技=どくどく 技=でんじほう
技:ころがる 0 1 0 1 0 0 0
技:10まんボルト 0 1 1 0 0 0 0
技:でんじほう 0 1 0 0 0 0 1
技:だいもんじ 0 1 0 0 1 0 0

ポケモンは実際には251次元、技も251次元です。このベクトルとバトルの状態ベクトルを連結したもの、合計558次元をDNNへの入力とします。

教師あり学習をするにあたり、一般的な分類問題の定式化に落とし込みます。softmax(f(バトルの状態,選択肢0のベクトル),f(バトルの状態,選択肢1のベクトル),f(バトルの状態,選択肢2のベクトル),f(バトルの状態,選択肢3のベクトル))を各選択肢の選択確率として、正解データとのcross entropy lossを最小化するように学習します。

今回から、深層学習ライブラリはPyTorchに移行しました。

全結合feed-forward networkは次のようなものになります。

import torch.nn as nn
import torch.nn.functional as F


class MLPModel(nn.Module):
    def __init__(self, input_dim):
        super().__init__()
        self.fc1 = nn.Linear(input_dim, 64)
        self.fc2 = nn.Linear(64, 1)

    def forward(self, x):
        h = x  # batch, feature_dim
        h = F.relu(self.fc1(h))
        h = self.fc2(h)
        return h

このモデルに選択肢ベクトルを変えて4回呼び出し、結果を連結して損失を計算することも可能ですが、実装上あまり効率が良くありません。 そこで、モデルの呼出しを1回ですべての計算を終えるテクニックがあります。それは、全選択肢に対応するベクトルを積み重ねた行列(558×4)を入力とし、全結合層をカーネルサイズ1の1D Convolutionに置き換えることです。これでも計算結果は等価になります。なぜそうなるかは「1x1 convolution」などで調べてみてください。

import torch.nn as nn
import torch.nn.functional as F


class MLPModel(nn.Module):
    def __init__(self, input_dim, n_layers=2, n_channels=64, bn=False):
        super().__init__()
        layers = []
        bn_layers = []
        cur_hidden_ch = input_dim
        for i in range(n_layers):
            layers.append(nn.Conv1d(cur_hidden_ch, n_channels, 1))  # in,out,ksize
            cur_hidden_ch = n_channels
            if bn:
                bn_layers.append(nn.BatchNorm1d(n_channels))
        self.layers = nn.ModuleList(layers)
        self.bn_layers = nn.ModuleList(bn_layers)
        self.output = nn.Conv1d(cur_hidden_ch, 1, 1)
        self.bn = bn

    def forward(self, x):
        h = x  # batch, feature_dim, 4
        for i in range(len(self.layers)):
            h = self.layers[i](h)
            if self.bn:
                h = self.bn_layers[i](h)
            h = F.relu(h)
        h = self.output(h)
        h = h.view(h.shape[0], -1)  # batch, 4
        return h

このように定義したモデルを学習させます。 パーティ1000個を1パーティ当たり100回他のパーティと対戦させ、10万バトル分の行動を得ました。同じバトルについて2つのパーティから見た状態を別のデータとして扱っています。900パーティ分のデータを学習データ、残り100パーティ分を評価(validation)データとして使用します。1回のバトルで複数のターンがあるので、学習データは34万サンプルとなりました。

上記のモデルのパラメータを変更して正解率を測定しました。学習は10エポック、OptimizerはAdam、lr=0.01、バッチサイズ256としました。層数は出力層以外のConvolutionの数です。チャンネル数は隠れ層の出力チャンネル数です。バッチ正規化は、各Convolutionの後にBatch Normalizationレイヤーを付加するか否かです。

層数 チャンネル数 バッチ正規化 Training正解率[%] Validation正解率[%]
1 16 False 55.2 52.5
1 16 True 67.9 55.6
1 64 False 58.2 54.6
1 64 True 72.5 55.2
1 256 False 63.8 53.8
1 256 True 75.1 53.1
2 16 False 65.6 54.2
2 16 True 68.6 56.4
2 64 False 70.5 55.9
2 64 True 73.2 54.6
2 256 False 70.1 55.4
2 256 True 76.2 53.2
3 16 False 66.2 58.5
3 16 True 67.9 56.0
3 64 False 68.2 57.8
3 64 True 73.6 54.4
3 256 False 68.6 54.8
3 256 True 75.9 53.6

Validation正解率が最大となるのは3層、チャンネル数16、バッチ正規化なしという結果になりました。パラメータ間に極端な差はないですが、層の数は多いほうが良い一方で、チャンネル数が多いとTraining正解率は高くなる一方でValidation正解率は下がってしまい過学習していることがわかります。バッチ正規化についても、過学習を誘発しているようです。今回の学習データ生成には計算時間が1日ほどかかるため、量を大幅に増加させることは難しいです。強化学習に移行するか、過学習しづらいようモデル構造を工夫することが将来課題です。

*1:3vs3バトルと同等のものを用いていますが、1vs1のため生存ポケモン数などは意味がありません。また有効な行動の数は技4つのみなので次元数が減っています

汎用行動選択モデルの学習 part03 パーティ固有行動選択モデルを用いた学習データの生成【PokéAI】

任意のパーティを受け取って、バトル中の行動(技)を選択する「汎用行動選択モデル」を学習していきます。

この記事では、モデルの構造を検討するための疑似教師データの作成を考えます。

汎用行動選択モデルではDeep Neural Networkを用いることを考えていますが、パーティの情報を取り入れるための構造や、パラメータ数の自由度が非常に大きいため適切な規模のものを選ぶ必要があります。 モデルを強化学習させると強化学習自体にも様々なパラメータが必要(割引率やreplay bufferのサイズ等)で、探索が大変です。 そのため強化学習の適切なパラメータがわかっているパーティ固有モデルを用いて局面ごとの行動の正解を生成します。パーティ固有モデルは1パーティに対し1つのモデルが対応します。様々なパーティに対しそれぞれパーティ固有モデルを強化学習させ、その行動データを集積し、それを1つの汎用行動選択モデルに教師あり学習させるという流れを提案します。パーティ固有モデルにより選択された行動を疑似教師データと呼ぶことにします。「疑似」とついているのは、パーティ固有モデルの強化学習が完ぺきではなく、必ずしも最適な行動を選択できるわけではないということを指しています。

疑似教師データの作成を実装しました。パーティをランダムに1000個生成させ、それぞれに対してパーティ固有モデルを学習させます。パーティの条件は前パートで検討した最終進化系ポケモンだけを含むものです。強化学習中の対戦相手は自分以外のパーティがランダムに行動するエージェントです。 こうして学習させたエージェント同士を対戦させ、対戦ログを保存する仕組みを用意しました。対戦ログには、各ターンにおける状況(相手のポケモンの種族、残りHP、状態異常など)・自分のパーティの情報(ポケモンの種族、覚えている技など)、エージェントが選択した行動が含まれます。

実際のログを整形していくつか表示します。

凡例

自分のポケモン(FはFriend)
自分のポケモンの技構成
相手のポケモン(OはOpponent)
=> 選択した行動
F マタドガス 187/187  
ころがる 10まんボルト でんじほう だいもんじ
O オムスター 193/193  
=> 10まんボルト

F ラフレシア 198/198  
はかいこうせん やどりぎのタネ ギガドレイン おんがえし
O ウソッキー 193/193  
=> ギガドレイン

F ラプラス 259/259  
いわくだき 10まんボルト サイコキネシス ハイドロポンプ
O オクタン 198/198  
=> サイコキネシス

F ブーバー 187/187  
すてみタックル サイコキネシス いわくだき ずつき
O ジュゴン 215/215  
=> いわくだき

必ずしも最適とは言えないですが、ある程度「こうかはばつぐん」な行動を選べています。

次の例は1つのバトル中の行動系列を示したものです。

F プテラ 204/204  
かげぶんしん つばさでうつ どくどく だいもんじ
O ポリゴン2 209/209  
=> どくどく

F プテラ 157/204  
かげぶんしん つばさでうつ どくどく だいもんじ
O ポリゴン2 185/209 tox 
=> だいもんじ

F プテラ 108/204  
かげぶんしん つばさでうつ どくどく だいもんじ
O ポリゴン2 106/209 tox 
=> かげぶんしん

F プテラ 63/204   evasion+1
かげぶんしん つばさでうつ どくどく だいもんじ
O ポリゴン2 56/209 tox 
=> かげぶんしん

F プテラ 63/204   evasion+2
かげぶんしん つばさでうつ どくどく だいもんじ
O ポリゴン2 4/209 tox 
=> だいもんじ

toxは猛毒状態、evasion+1は回避率が1段階上がった状態です。どくどくのあとかげぶんしんで逃げ回るという戦略のエージェントも存在しました。

次回は、このデータを教師データとしてパーティ固有モデルの学習を試みます。