デッキの自動生成
- fulline
- 2016年7月20日
- 読了時間: 6分
大学在学中、「Latent Dirichlet Allocation(以下LDA)」という言語モデルを少し勉強したことがありました。LDAをものすご~くザックリ言うと、ある文書に含まれる単語の傾向から、その文書がどのトピックに属しやすいかを確率的に表現したモデルです。
例えば
「台風」「フェーン」
という単語が複数含まれる文書Aがあるとします。
また、学習後のLDAの3トピックにはそれぞれ
1.「召喚」「セット」「攻撃」「ツイツイ」
2.「晴れ」「雨」「曇り」「暴風」
3.「りんご」「レモン」「もも」「いちご」
が出やすいとわかっているものとします。
この時、3つのトピックについて、それぞれ「遊戯王」「天気」「果物」となんとなく予想でき、文書Aはトピック2に属しやすいだろうと分かると思います。
これをプログラムでも出来るようにしようというのがLDAになるわけです。
また、このモデルは、使い方によっては「単語Aはどのトピックに属しやすく、単語Aと近い関係にある単語は何か?」という推定も可能になります。
これは、「商品Aを買われる方は、他にも商品Bを買う可能性が高いです」といった推薦手法にも応用されたりしています。
今回は「文書」を「デッキ」、「単語」を「カード」と捉えることで、LDAを用いた自動デッキ生成をしてみようと思います。これを利用すれば、「このカードを使ったデッキを作りたいけど、シナジーのあるカードを見つけるのが難しい」という問題を解消できる!!...かもしれません。
さて、ここからが本題です。
今回、LDAを用いますが1から実装していくのは大変ですので、「gensim」を利用していきます。大まかな流れは
1.学習用のデータ(デッキ)を用意
2.データを取り込みコーパスを作る
3.LDAを作成(作成済みの場合はモデルを読み込む)
4.利用したいカード1枚(カードA)を選択
5.カードAがどのトピックに属しやすいかの確率を計算
6.確率的にトピックを決定
7.トピック内の各カードに対する確率からランダムにデッキに入れるカードを決定
8.6〜7をメインデッキ枚数が40枚になるまで繰り返す
という感じです。
1.学習用のデータ(デッキ)を用意
学習データが沢山あるほど、学習後のモデルはより良いものになっていく可能性が上がります。しかし、ADSで作ったデッキだけでは、数は少ないですし、自分の趣向が入る分、偏った学習データになりかねません。そこで、今回は「遊戯王☆カード検索」に投稿されている皆様のデッキを利用させてもらおうと思います。こちらのサイトはRESTfulなURLになっているため、URL内に条件を入れてあげるだけで希望のデッキを検索することが可能です。また、ADSでそのまま利用できるように「.ydk」と同じ形で表示してくれるページもあります。
URL内の「?」以降の「○○=2&△△=1」が検索クエリになっており、ここに諸条件を満たせば、スムーズに欲しいデータが取れると思います。
デッキを検索したい場合は「https://ocg.xpg.jp/deck/deck.fcgi?〜」
デッキの「ydk」型を見たい場合は「https://ocg.xpg.jp/deck.fcgi?ListNo=○○&Text=5」
でいけると思います。
僕は「urllib2」「lxml」「tqdm」を利用して100個ずつデッキを取得していますが、あまりやり過ぎると怒られる可能性があるので、ここではプログラムの説明を省きます。
2.データを取り込みコーパスを作る
コーパスとは、データ処理をしやすくするために単語の種類とその数を数値化したものになります。例えば、文書A(「雨」「曇り」「曇り」「晴れ」)と文書B(「傘」「虹」「雨」)があり、これをコーパスにすると
文書A:((1,1),(2,2),(3,1))
文書B:((4,1),(5,1),(1,1))
という感じになります。
gensimでの処理としては、初めに各単語を数値化させるための辞書を作成し、その辞書を元に各文書内に含まれている単語の種類と各単語の数を示すコーパスを作成するという流れになります。

3.LDAを作成(作成済みの場合はモデルを読み込む)
LDAの作成は「models.ldamodel.LdaModel()」に"コーパス"、"辞書"及び"トピック数"を引数に入れてあげれば完了します。
毎回実行するたびにLDAを作成するわけにもいかないので、作成したLDAモデルはしっかり保存しておきましょう。
ついでに、モデルを作成済みの場合は各データを読み込まないといけませんが、イマイチ読み込む関数に統一性がないので一緒に載せておきます。

4.利用したいカード1枚(カードA)を選択
5.カードAがどのトピックに属しやすいかの確率を計算
カードAを1枚選択したら、そのカードのID(カードの左下に書かれている数字列)を使います。次に、そのカードが各トピックにどれだけ属しやすいかという確率が必要ですが、このモデルでは「文書(デッキ)に対しての各トピックに属する確率」はあるものの、「単語(カード)に対しての各トピックに属する確率」は求められていません。
そこで今回は各トピックにおけるカードAの確率を取り出し、それを多項分布にしたものを「単語(カードA)に対しての各トピックに属する確率」としています。
もしモデル内にカードIDが存在しない場合は、各トピックに属する確率を一定にしています。
(LDA的にそれでは良くない気もするけど...)

6.確率的にトピックを決定
7.トピック内の各カードに対する確率からランダムにデッキに入れるカードを決定
ランダムでカードを選択する際は、「numpy.random.multinomial()」を用います。この関数は事前に設定した多項分布の確率に従って要素を選択することができるランダム関数です。
「デッキに入れるカードを選択するトピックの決定」→「デッキに入れるカードを決定」を行っていきます。

8.6〜7をメインデッキ枚数が40枚になるまで繰り返す
さて、ここまで来たら後はデッキ枚数が40枚になるまでループさせるだけ...という訳にはいけません。むしろ、ここからが面倒でした。なぜ面倒なのかと言いますと
・融合、シンクロ、エクシーズモンスターはエクストラデッキに入れないといけない
・同一カードは3枚までで、リミットレギュレーションの存在を忘れてはいけない
という2点について気をつけなければならないためです。
枚数制限に関しては、「lflist.conf」からリストを持ってくれば一応対応可能です。
中身にはカードIDの隣に入れられる枚数があるのでリスト化します。選ばれたカードがリストに存在しなければリストにIDとあと入れられる枚数「2」を追加し、リストに存在すればその数値から1を引きます。リスト内のカードIDの入れられる枚数が「0」ならばデッキに入れずにカードを選びなおせば、枚数制限に引っかかることがありません。
カードの種類判定には、「cards.cdb」の中へ検索をかけ、「type」を調べれば良いのですが、この「type」は、カードの種類を数値化したものの合計になっています。各タイプの値は等比数列になっており、大きい値を与えられたタイプから順に引き算出来るかを確認していけば良いです。融合、シンクロ、エクシーズモンスターを表す値で引き算が出来た時点でエクストラデッキに入れるという処理を挟めば解決ですね!!詳しい数値についてはADSのwikiを見てください。
以上でデッキの作成が完了しましたっ!!
実際に作成してみましょう。
《DDネクロ・スライム》の場合

サイドデッキは、選択したカードの提示用になっています。
...なんとなく、DDモンスターが選ばれてはいるようですが、微妙ですね(汗)
デッキを完全ランダムに取得しているので、教示モデルとしてはあまり良くないですね。
実験的に、ある程度のカテゴリを絞って教示用デッキを取得して、綺麗にできるかやってみれば良かったなと終わってから思う今日この頃。
教示用デッキ数を増やしていけば、もう少し良い結果が出そうです。
Comments