kanayamaのブログ

twitter: @tkanayama_

ポケモン人気投票データを使って推薦システムを作る

[祝] ポケモン25周年

@tkanayama_ です。先日、ポケモン公式による投票企画「#キミにきめた」が実施されました。これは、Twitter上で以下のようにハッシュタグをつけてツイートすることにより、ポケモンの人気投票を行う企画です。

f:id:tepppei:20210225102457p:plain
投票の例

今回の投票ルールで特筆すべき点は「一人が投票できるポケモンの種類数に制限がない*1」という点です。例えば上の例であれば、@tkanayama_さんは「オタチ」と「ヒノアラシ」の両方に投票したことになります。

このルールを見て私は、ポケモンうしの共起関係を使ってポケモンの推薦システムを作れるのではないかと考えました。例えば、今回の投票を分析した結果「シャワーズとブースターとサンダースが同一ユーザーによって投票されやすい」ということが分かったとします。もし「シャワーズとブースターが好きだ」という人がいたら、その分析結果を使って「シャワーズとブースターが好きな人は、サンダースというポケモンも好きみたいですよ」とオススメすることができます(現段階ではたかだか900種類程度しかポケモンがいないため、推薦システムの価値は薄いかもしれません。しかし、この25年で900種類のポケモンが発見されたということは、200年後くらいには1万種類のポケモンがいると予想できます。その場合、なかなか全ポケモンを把握するのは難しいので、推薦システムが真価を発揮することでしょう)。

〜推薦システムに詳しい方へ〜

この記事は、簡易的な共起分析とクラスタリングでデータを観察したのち、最後にナイーブなMatrix Factorizationに突っ込んだという内容です。手法的に新しい点は特にありません。なお、「あるポケモンに投票した」という行動を「そのポケモンが好きである」と見なして、implicit feedbackの問題設定を暗に仮定しています。

本編

ステップ1. 基本的な集計をする

今回の企画が企画倒れになる最大のリスクとして、「ルール上は複数のポケモンへの投票が可能だが、実際にはほとんどの人は1種類のポケモンにしか投票しておらず、ポケモンうしの共起関係が取得できない」という可能性が考えられます。そこで、いきなり推薦システムの学習を行うのではなく、まずは簡単な集計を行なってこの可能性を検証しておきます。

まず、「同一のアカウントが同じポケモンに複数回投票した」という重複を取り除いたところ、ユニークな(ユーザー名, ポケモン名)の組がおよそ15万ペアあることがわかりました。

f:id:tepppei:20210226170155p:plain
得られたデータ(黒塗り部分にはツイッタースクリーンネーム(@xxxx)が入っています。)

次に、各ユーザーの投票数の分布を集計しました。

f:id:tepppei:20210225151248p:plain:w300
投票数の分布(投票数21以上は割愛)

この表は、例えば1種類のポケモンにのみ投票しているユーザー数は52,543人であり、2種類のポケモンに投票しているユーザー数は8,900人である、ということを表しています。やはり単推しが圧倒的に多いですね。中には20種類以上のポケモンに投票している人もいました(この表からは省略しています)。

今回の企画で必要なデータは「2種類以上のポケモンに投票しているユーザー」です。一方、あまりにもたくさんの種類のポケモンに投票しているユーザーは、もしかしたら機械的ポケモンを列挙しているだけかもしれないので、ノイズになるかもしれません(数百匹のポケモンを心から愛している方ももちろんいらっしゃると思いますが!)。それを踏まえて、「2種類以上100種類未満のポケモンに投票しているユーザー」の数を数えたところ、約2万人でした。悪くない規模感です。

これ以降は、この2万人のユーザーによる投票データを扱っていきます。

ステップ2. ポケモンうしの共起度を計算する

さて、ステップ1によりデータ数不足による企画倒れリスクは無さそうだということがわかりました。次にリスクとして思い浮かぶのは、「実はポケモンの好みは人によってバラバラであり、『このポケモンに投票した人はこのポケモンにも投票しやすい』といった傾向は全く存在しない」という可能性です。この可能性も、簡単な集計によって確認しておきます。

「あるポケモンポケモンAと呼びます)に投票したユーザーのうち、あるポケモンポケモンBと呼びます)にも投票したユーザーの割合」を、ポケモンBのポケモンAに対する共起度と呼ぶことにします。(例えば、ピカチュウに投票した人が100人いたとして、そのうち20人がライチュウにも投票していたらライチュウピカチュウに対する共起度は0.2になります。)

この定義に基づいて、共起度を全てのポケモンの組み合わせについて計算し、可視化した結果が下記です。

f:id:tepppei:20210225191336p:plain:w500
共起度行列(全体)。番号はポケモン全国図鑑ナンバーを表している。

行列サイズが大きすぎてよくわかりませんね。この行列から、いくつか特徴が現れている箇所を抽出しました。

f:id:tepppei:20210225193407p:plain
共起度行列(一部抜粋)

まず、対角線が全て1になっているのは、共起度の定義から自明です(例えば、イーブイに投票した人のうちイーブイに投票した人の割合は当然100%です)。

次に、上から見ていきます。ブイズ(イーブイ・ブースター・・・ニンフィア)どうしの共起度は、予想通り高いようです。あるブイズに投票した人は、別のブイズにも投票していることが多い、ということです。一方でもう少しよく見ると、ブラッキーリーフィアの行だけは黒が目立つことがわかります。これは、「ブラッキーリーフィアに投票した人は、他のブイズにあまり投票していない」ということを意味しています。ブラッキーリーフィアは他のブイズと一線を画しているようです。(考察:ブラッキーポケモンバトルでもかなり強いので、ブイズ勢だけではなくポケモンバトル勢などより幅広い層から支持されていることが考えられます。リーフィアはなぜでしょう…?)

次は、ヤナッキーバオッキーヒヤッキーを並べてみました。これらはポケットモンスター ブラック・ホワイトに登場する猿型のポケモンで、三猿と呼ばれています。これらも、セットで扱われることが多いので互いに共起度が高くなっています。しかしよく見ると、先ほどと同様に「バオッキーに投票した人はヤナッキーヒヤッキーにあまり投票していない」という非対称性が生じている点が面白いです。(考察:バオッキーは、5年前のポケモン人気投票2016で全720ポケモン中最下位だったという悲しい過去を持っていることがよく知られています。この過去を知っている人が、ヤナッキーヒヤッキーではなくバオッキー単体に投票した可能性が考えられます。実際、公式が発表している投票結果を確認したところ、バオッキーヤナッキーヒヤッキーと比べて票が多かったようです。)

最後に、ワニノコメグロコに注目してください。ワニノコは図鑑番号158のみずタイプポケモンメグロコは図鑑番号551のあく・じめんタイプポケモンなので、属性だけ見ると一見何の関係もありません。しかし、両者はワニとであるという共通点があります。ワニ好きの方々がしっかりワニノコメグロコ両方に投票していることがわかります。

ステップ3. ポケモンどうしをグループ分けする

ステップ2により、少なくともいくつかの種類のポケモンにおいて、同時に投票されやすい傾向が確認できました。このように局所的にデータを眺める作業もとても楽しいのですが、今度はもう少し大局的に、ポケモンどうしをグルーピングしてみようと思います。

ステップ2で作った行列のそれぞれの行を、そのポケモンの特徴ベクトルであると見なします。この特徴ベクトルを用いて、k-meansアルゴリズムで80個のクラスタに教師なしクラスタリングしてみました(機械学習に馴染みのない方は、「投票傾向が似ているポケモンどうしを自動でグループ分けする方法」だと思ってください><)。

グループ分けの結果全体はこのブログの末尾に貼りました。半分くらいのグループは解釈しにくいグループでしたが、面白い解釈ができるグループもいくつか存在したので、以下に抜粋します。

グループID グループに属するポケモン(自動分類) グループの傾向(私の解釈)
0 ランターン, メリープ, モココ, チルタリス ふわふわ系
9 ミュウツー, ミュウ, スイクン, バンギラス, バシャーモ, ボスゴドラ, ボーマンダ, カイオーガ, グラードン, レックウザ, ガブリアス, ディアルガ, ギラティナ, ゼクロム 古めの伝説+強そうなやつ
22 ワニノコ, オーダイル, メグロコ, ワルビル ワニ
72 ニャース, ウツボット, ベロリンガ, マタドガス, ソーナンス, ドクケイル, サボネア, ハブネーク, チリーン, マネネ, マスキッパ, メガヤンマ ロケット団

ステップ4. 推薦システムを作る

最後に、実際に推薦システムを作ってみようと思います。

推薦システムは大きく分けて

  1. ユーザー(今回はツイッター投稿者)とアイテム(今回はポケモン)の属性情報に基づいて推薦する方法
  2. ユーザーとアイテムの相互作用に基づいて推薦する方法

が存在します(実用上は両者のハイブリッドモデルが用いられることが多いと思います)。

今回は、残念ながら手元にユーザーの属性情報(年齢・性別・居住地 etc.)などを持っていません。一方で、ユーザーとポケモンの相互作用には何か傾向がありそうだということが、ここまでの集計から分かりました。そこで、今回は「2. ユーザーとアイテムの相互作用に基づいて推薦する方法」の代表的な手法である、Matrix Factorizationと呼ばれる手法を適用してみます。

問題設定を簡単に説明します。全ユーザーの80%がtrainingユーザー、20%がtestユーザーとなるようにランダムに割り当てます。次に、testユーザーの投票のうち50%が学習用データ、50%が検証用データになるようにランダムに分割します。最後に、trainingユーザーの全投票データと、testユーザーの学習用データを用いてMatrix Factorizationのモデルを学習します。

さて、このような問題設定でMatrix Factorizationを学習し、検証用データに対して推薦を行なった結果の一部を下記に示します。

ユーザー名 実際の投票 推薦結果(1位〜10位)
@XXXXX シャワーズ, オーダイル, メグロコ, ワルビル, (ワニノコ ウパー, ワニノコ, オーダイル, ナマコブシ, メグロコ, ワルビル, ユキハミ, ヌオー, ウールー, パルキア
@YYYYY エーフィ, シャワーズ, グレイシア, ミュウツー, (ニンフィア), (イーブイ), (ブースター), (サンダース), (マッシブーン), (ピカチュウ), (バドレックス) シャワーズ, エーフィ, サンダース, ブースター, グレイシア, イーブイ, ニンフィア, ブラッキー, ミュウツー, ウインディ
@ZZZZZ ヒトモシ, シャンデラ, インテレオン, ライチュウ, (マポイップ), (ランプラー), (ストリンダー), (サーナイト), (コオリッポ), (ドリュウズ ピカチュウ, マッシブーン, バドレックス, ニドラン♂, シャンデラ, ミミッキュ, トリトドン, デデンネ, コイル, サニーゴ

ユーザー@XXXXXさんは、シャワーズ, オーダイル, メグロコ, ワルビル, ワニノコに投票していました。このうち、ワニノコだけは学習から外してあります。つまり、シャワーズ, オーダイル, メグロコ, ワルビルに投票したという情報を使ってワニノコを推薦できればOKです。このとき、推薦結果の上位10位に正しくワニノコが入っていることが確認できました。ウパーやナマコブシなど、水辺にいそうなポケモンも推薦に含まれており、いい感じです。

ユーザー@YYYYYさんは、主にブイズに投票しています。エーフィ, シャワーズ, グレイシア, ミュウツーに投票したという情報から、無事に他のブイズを推薦することができています。しかし、マッシブーンやバドレックスといったポケモンはさすがに推薦することができなかったようです。

最後のユーザー@ZZZZZは、全然うまく推薦できておらず、ほとんど外してしまっています。@ZZZZZさんのように投票傾向が読みにくいユーザーも数多く存在するため、今回の推薦シスエテムは問題設定自体が難しいということがわかります。

最後に、簡単な定量評価としてrecall@k(テストデータにおける各ユーザーの投票うちの何割を、上位k位までの推薦によってカバーできているか?の平均値)を計算しました。

k recall@k
10 0.26
30 0.40
50 0.48

recall@50が約0.5ですので、50匹推薦すれば投票の半分くらいを当てることができるということになります。

まとめ

今回は、簡単な集計から始めて、最後に実際に推薦システムを学習してみました。推薦性能向上のためには、先ほども少し触れた通りユーザー・ポケモンそれぞれの属性情報をうまく使っていくことが鍵になりそうです。効きそうなユーザー属性としてはぱっと思いつくのは

  • 性別(好きなポケモンには男女差がありそう)
  • 年齢(若い世代は新しいポケモンや伝説のポケモンに人気が集まりそう)
  • 国籍(国によってカッコいい・カワイイの捉え方が違いそう)

あたりでしょうか。ただ、どうやってユーザー情報を取得するのかが課題です。基本的なユーザー属性であれば過去のツイートから推定できるかもしれません*2

また、ポケモンの属性としては

などが考えられます。

最後になりましたが、ポケモン25周年おめでとうございます!これからも、1ファンとして応援しています。

宣伝

他にも関連する技術記事を書いているので、よければ見ていってください。

ポケモン

推薦システム系

補足など

ツイートの収集

Tweepyというライブラリを用いてTwitterのsearch APIを叩き、収集しました。Tweepyを使えばページネーション関連の実装を自前でする必要がなく、簡単に収集することができました。

(注意:この方法で収集された投票結果は、ポケモン公式が公表している数字と比べて少ないです。これは、Twitterが無料公開している検索APIの仕様によるものだと考えられます。ただ、推薦システムを作る上では必ずしも全データを取得する必要はないので、気にせずこのまま進めます。投票関連の数字はポケモン公式が公表しているものを正としてください。)

consumer_key = os.environ.get('CONSUMER_KEY')
consumer_secret = os.environ.get('CONSUMER_SECRET')
access_token = os.environ.get('ACCESS_TOKEN')
access_token_secret = os.environ.get('ACCESS_TOKEN_SECRET')

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)

api = tweepy.API(auth)

def _limit_handled(cursor):
    while True:
        try:
            yield next(cursor)
        except TweepError as e:
            time.sleep(15 * 60)


def download_votes(name, query):
    data = []
    search_cursor = tweepy.Cursor(api.search, q=query, count=100, result_type='mixed', since_id=SINCE_ID).items()
    for result in _limit_handled(search_cursor):
        data.append(dict(user=result.user.screen_name, pokemon=name, time=result.created_at))
    return pd.DataFrame(data)

ステップ2の結果一覧

私のポケモンに関する知識を総動員して、グループの傾向を記入してみました。それでも、解釈に苦しむグループもたくさんあったので、もし共通点を見つけられたかたは教えてください。

グループID グループの傾向 グループに属するポケモン
0 ふわふわ系 ランターン, メリープ, モココ, チルタリス
1 フシギソウ, フシギバナ, マルマイン, メタモン, プテラ, ヤミカラス, キモリ, ポチエナ, グラエナ, ルンパッパ, ユレイドル, グライオン, ダイケンキ, ハトーボー, ケンホロウ, モグリュー, ドリュウズ, フシデ, テッシード, ナットレイ, バルジーナ, ゲノセクト, ドラミドロ, ココガラ, アオガラス
2 マンキー, ブーバー, ププリン, カラサリス, ツチニン, ノズパス, バルビート, イルミーゼ, ブーピッグ, トドグラー, コロトック, スボミー, スコルピ, ミルホッグ, ヒヤッキー, コロモリ, ドテッコツ, ガマゲロゲ, ハハコモリ, ホイーガ, マラカッチ, イシズマイ, ゴチム, ゴチミル, ゴチルゼル, ダブラン, コアルヒー, バニリッチ, バイバニラ, メブキジカ, プルリル, ママンボウ, ギギアル, リグレー, ランプラー, ゴルーグ, ワシボン, バルチャイ, ハリボーグ, メェークル, シュシュプ, カメテテ, ウデッポウ, チゴラス, メレシー, バケッチャ, クレベース, ケララッパ, デンヂムシ, シロデスナ, サルノリ, タタッコ, ゾウドウ
3 ヘルガー, ドダイトス, マニューラ, ワルビアル, キュレム, イベルタル, ジガルデ, ディアンシー, ネギガナイト
4 ブイズの一部 イーブイ, エーフィ, グレイシア, ニンフィア
5 メッソン一族+? メッソン, ジメレオン, インテレオン, アーマーガア, ワンパチ
6 ゲッコウガのみ ゲッコウガ
7 毒タイプ多め アーボ, アーボック, ニドキング, ナゾノクサ, クサイハナ, ラフレシア, マダツボミ, ウツドン, ベトベトン, イワーク, サワムラー, アリアドス, ゴクリン, マルノーム, ヤブクロン, ダストダス
8 ドラゴンタイプ キングドラ, タツベイ, コモルー, ガバイト, キバゴ, オノンド, オノノクス, クリムガン, モノズ, ジヘッド, ガチゴラス, オンバット, オンバーン, ジジーロン, ジャラコ, ジャランゴ, ジャラランガ, アップリュー, ダクマ
9 古めの伝説+強そうなやつ ミュウツー, ミュウ, スイクン, バンギラス, バシャーモ, ボスゴドラ, ボーマンダ, カイオーガ, グラードン, レックウザ, ガブリアス, ディアルガ, ギラティナ, ゼクロム
10 水御三家+? ミズゴロウ, ポッチャマ, シェイミ, ミジュマル, アシマリ
11 ヒトカゲ, リザード, トランセル, カイリキー, パウワウ, ジュゴン, クラブ, キングラー, トサキント, ストライク, カイロス, コイキング, ギャラドス, トゲチック, ヌマクロー, キャモメ, ロゼリア, ホエルコ, ホエルオー, ダンバル, メタング, ゴンベ, ネマシュ, マシェード, メルメタル, カムカメ
12 ウルトラビースト+? ウルガモス, ウツロイド, フェローチェ, デンジュモク, テッカグヤ, カミツルギ, アクジキング, ベベノム, アーゴヨン, ツンデツンデ, ズガドーン
13 ブルンゲルのみ ブルンゲル
14 ネイティ, ニョロトノ, エイパム, ヨーギラス, バクオング, サマヨール, レントラー, ドータクン, ヒポポタス, ダルマッカ, ズルッグ, ズルズキン, シビルドン, コジョフー, コジョンド, キリキザン, マーイーカ, エレザード, ボクレー, オーロット, ドデカバシ, ドロバンコ, バンバドロ, オニシズクモ
15 コクーン, オニスズメ, ダグトリオ, ユンゲラー, ベトベター, シェルダー, スリーパー, タマタマ, アズマオウ, ルージュラ, ポリゴン, チョンチー, マリル, アンノーン, テッポウオ, アゲハント, マユルド, チェリンボ, スカンプー, フィオネ, デスカーン, オーベム, アイアント, トルネロス, ボルトロス, ランドロス, コフキムシ, ニダンギル, トゲデマル, ハギギシリ, ダダリン, コスモウム, マギアナ, ヨクバリス, サシカマス, サニゴーン, バリコオル, マホミル, イシヘンジン
16 虫タイプ ビードル, スピアー, パラセクト, コンパン, モルフォン, オタチ, キレイハナ, ウソッキー, ヤンヤンマ, フォレトス, ブルー, グランブル, ハリーセン, ツボツボ, ヘラクロス, ウリムー, イノムー, ケムッソ, アメモース, ガーメイル, アゴジムシ, シズクモ, サッチムシ, レドーム
17 ピカチュウのみ ピカチュウ
18 ポニータ, ムクバード, ムクホーク, ドンカラス, グレッグル, ユクシー, アグノム, アルセウス, ビクティニ, シママ, ゼブライカ, フラエッテ, フラージェス, ツツケラ, オドリドリ, カリキリ, ラランテス, アマージョ, キュワワー, コソクムシ, ヒメンカ, ワタシラガ, ウールー, バイウールー, エレズン
19 ラティアスのみ ラティアス
20 ネコっぽい ヒノアラシ, ザングース, フォッコ, テールナー, ニャスパー, ニャオニクス, ニャビー
21 キツネ ロコン, ゾロア, ゾロアーク
22 ワニ ワニノコ, オーダイル, メグロコ, ワルビル
23 ビリリダマ, ヒマナッツ, フタチマル, カジッチュ, デスバーン, イエッサン, パッチルドン, ウオチルドン
24 サンド, サンドパン, アチャモ
25 ライチュウ, キュウコン, ジャローダ, チョロネコ, レパルダス, ナマコブシ, パルスワン
26 タッツー, グライガー, タネボー, コノハナ, ペリッパー, ラルトス, アサナン, チャーレム, ナックラー, ビブラーバ, ヘイガニ, ヨマワル, ユキワラシ, オニゴーリ, タマザラシ, ハヤシガメ, タテトプス, トリデプス, ミノムッチ, ユキカブリ, ユキノオー, モンメン, カプ・コケコ
27 ラグラージのみ ラグラージ
28 最近の伝説+? コオリッポ, ムゲンダイナ, ブリザポス, レイスポス, バドレックス
29 なんか小さくてかわいいやつ+アシレーヌ パチリス, エモンガ, デデンネ, アシレーヌ
30 化石系+ミカルゲ オムナイト, オムスター, カブト, カブトプス, ミカルゲ
31 スリープ, サイホーン, クヌギダマ, バルキー, キバニア, ドンメル, ノクタス, ヤジロン, ラブカス, タマンタ, ヤングース
32 ラッキー, デリバード, ハピナス, ハスボー, キルリア, エネコロロ, チルット, カクレオン, パールル, サクラビス, エテボース, リーシャン, ドーミラー, ダイノーズ, ドッコラー, ヌイコグマ, タイプ:ヌル, コスモッグ, カマスジョー, タイレーツ
33 ゴースト, ナッシー, ナゲキ, ダゲキ, ルチャブル, フーパ, ベロバー, ギモー
34 ヒメグマ, キノココ, キノガッサ, バネブー, ヒンバス, ソーナノ, ポッタイシ, フカマル, モジャンボ, クレセリア, ハーデリア, マメパト, ココロモリ, クルマユ, プロトーガ, アバゴーラ, アーケン, アーケオス, ユニラン, ギアル, ギギギアル, シビビール, マッギョ, ケケンカニ
35 ゼニガメ, カメックス, マグマラシ, レディバ, レディアン, ハネッコ, ポポッコ, ワタッコ, マグマッグ, オドシシ, ラクライ, ライボルト, トロピウス, ニャルマー, ブニャット, ペラップ, ジャノビー, ムーランド, シキジカ, ゴーゴート, トリミアン, ゼルネアス, シルヴァディ, ソルガレオ, ルナアーラ, ゼラオラ, ニャイキング
36 コイル, クロバット, ハッサム, サーナイト, クチート, ロズレイド, トゲキッス, エルレイド, ユキメノコ, バチュル, グソクムシャ, ユキハミ, モスノウ
37 ラッタ, オコリザル, ドククラゲ, ガルーラ, エレブー, イトマル, トゲピー, ポリゴン2, ジグザグマ, ハスブレロ, ズガイドス, ラムパルド, ヤナッキー, バスラオ, デンチュラ, ブリガロン, ヤンチャム, ゴロンダ, ペロッパフ, ペロリーム, エリキテル, デカグース, イワンコ
38 スターミー, ルギア, フローゼル
39 ヌメルゴン ヌメラ, ヌメルゴン
40 レシラムのみ レシラム
41 レアコイル, ネイティオ, テッカニン, サメハダー, ルナトーン, ソルロック, ネンドール, アーマルド, チェリム, ドラピオン, ジバコイル, ポリゴンZ, ロトム, イワパレス, シンボラー, チョボマキ, ウォーグル, メラルバ, ビビヨン, アマルルガ, クワガノン, アブリー, アブリボン, メテノ, イオルブ, パッチラゴン, ウオノラゴン
42 ムウマ, サニーゴ, ヌケニン, カゲボウズ, ジュペッタ, フワンテ, フワライド, ムウマージ, ヨノワール, デスマス, ランクルス, ヒトモシ, シャンデラ, パンプジン, マーシャドー, ヤバチャ, バチンウニ
43 サザンドラのみ サザンドラ
44 ニドラン♂のみ ニドラン♂
45 ブリムオン一族 ミブリム, テブリム, ブリムオン
46 ジュナイパーのみ ジュナイパー
47 エースバーン一族 ヒバニー, ラビフット, エースバーン
48 耐久ポケモン多め? ウパー, ヌオー, ミミロル, パルキア, エルフーン, シュバルゴ, アギルダー
49 フシギダネ, オニドリル, ピクシー, パラス, ニョロゾ, ニョロボン, ケーシィ, フーディン, ゴローニャ, ドードー, ドードリオ, パルシェン, ゴース, カラカラ, ドガース, モンジャラ, シードラ, ヒトデマン, バリヤード, サンダー, アリゲイツ, ピィ, キリンリキ, ノコッチ, ハガネール, オクタン, マンタイン, ゴマゾウ, ドンファン, カポエラー, ムチュール, エレキッド, ライコウ, サナギラス, ダーテング, スバメ, アメタマ, ゴニョニョ, マクノシタ, ルリリ, ヤミラミ, ドジョッチ, ナマズン, シザリガー, リリーラ, ミロカロス, トドゼルガ, ハンテール, レジロック, ナエトル, ヒコザル, ゴウカザル, エンペルト, ビッパ, ビーダル, ミノマダム, ミツハニー, カラナクシ, スカタンク, カバルドン, ケイコウオ, ネオラント, ベロベルト, ドサイドン, エレキブル, マンムー, ダークライ, ツタージャ, ヤナップ, バオッキー, ヒヤップ, タブンネ, ローブシン, オタマロ, ガマガル, クルミル, ペンドラー, チュリネ, ドレディア, スワンナ, バニプッチ, カブルモ, タマゲタケ, モロバレル, シビシラス, クマシュン, ツンベアー, ゴビット, コマタナ, バッフロン, コバルオン, テラキオン, メロエッタ, ゲコガシラ, ホルード, ヤヤコマ, コフーライ, フラベベ, ヒトツキ, フレフワン, ガメノデス, クズモー, ヌメイル, クレッフィ, カチコール, フクスロー, マケンカニ, ヨワシ, ヒドイデ, ドヒドイデ, ヤレユータン, ナゲツケサル, スナバァ, ネッコアラ, カプ・テテフ, カプ・ブルル, カプ・レヒレ, ネクロズマ, メルタン, ポットデス, モルペコ, ザル―ド
50 炎タイプ+カメール カメール, バクフーン, マグカルゴ, マフォクシー, ニャヒート, ガオガエン
51 フェアリータイプ ミミッキュ, マホイップ
52 マッシブーンのみ マッシブーン
53 ブイズの一部 シャワーズ, サンダース, ブースター, ブラッキー
54 キャタピー, バタフリー, コラッタ, ピッピ, プリン, プクリン, ズバット, ペルシアン, コダック, ワンリキー, メノクラゲ, ギャロップ, ヤドン, ヤドラン, エビワラー, フリーザー, ミニリュウ, エアームド, ドーブル, アノプス, ルクシオ, ピンプク, ヒヒダルマ, ウッウ, ストリンダー
55 キテルグマ, ゴリランダー, タルップル
56 レジ系 レジアイス, レジスチル, レジギガス, レジエレキ, レジドラゴ
57 ゴローン, ホーホー, キマワリ, ニューラ, リングマ, ミルタンク, ドゴーム, ポワルン, ジーランス, コロボーシ, ウソハチ, チャオブー, ミネズミ, ヨーテリー
58 ドラパルト一族+カジリガメ カジリガメ, ドラメシヤ, ドロンチ, ドラパルト
59 ポッポ, ピジョン, ピジョット, ニョロモ, カモネギ
60 ジュカイン ジュプトル, ジュカイン
61 フライゴン, ラティオス, ジラーチ
62 炎タイプ多め ファイヤー, ブビィ, エンテイ, ホウオウ, ワカシャモ, バクーダ, コータス, モウカザル, ブーバーン, ヒードラン, エンブオー, バオップ, クイタラン, ヒノヤコマ, シシコ, カエンジシ, ボルケニオン, ヤトウモリ, バクガメス
63 炎の犬 ガーディ, ウインディ, デルビル
64 ガラガラ, カビゴン, ヤドキング, ナマケロ, ヤルキモノ, ケッキング, パッチール, デオキシス, ドクロッグ, ムシャーナ, ダンゴロ, ガントル, ギガイアス, フリージオ, ケロマツ
65 +ー プラスル, マイナン
66 ギルガルドのみ ギルガルド
67 リザードン, ラプラス, アブソル
68 モクローのみ モクロー
69 ゴルバット, イシツブテ, メタグロス, ビークイン, ファイアロー, カラマネロ, ブロスター
70 イケメンな犬 ルカリオ, ルガルガン
71 剣盾の代表 ザシアン, ザマゼンタ
72 ロケット団 ニャース, ウツボット, ベロリンガ, マタドガス, ソーナンス, ドクケイル, サボネア, ハブネーク, チリーン, マネネ, マスキッパ, メガヤンマ
73 全体的にゴツい ニドラン♀, ニドリーナ, ニドクイン, ニドリーノ, ゴルダック, サイドン, ケンタロス, ヨルノズク, ハリテヤマ, ココドラ, コドラ
74 ゲンガーのみ ゲンガー
75 ディグダ, ハクリュー, メガニウム, マリルリ, セレビィ, エネコ, ブイゼル, ミミロップ, マナフィ, ムンナ, ビリジオン, ホルビー, アマルス
76 オオタチ, ピチュー, マッスグマ, オオスバメ, ムックル, コリンク, トリトドン, リオル, リーフィア, エムリット, ポカブ, チラーミィ, チラチーノ, ケルディオ, ハリマロン, オシャマリ, アマカジ, アママイコ
77 ゴーリキー, カイリュー, エンニュート
78 チコリータ, ベイリーフ, デンリュウ
79 バチンキー, ホシガリス, クスネ, フォクスライ, タンドン, トロッゴン, セキタンザン, スナヘビ, サダイジャ, ヤクデ, マルヤクデ, オトスパス, オーロンゲ, タチフサグマ, ダイオウドウ, ジュラルドン, ウーラオス

Matrix Factorizationの実装

LightFM というライブラリを用いて下記のように実装しました。Lossとしてwarpを用いました。何か特別な設定や処理はしていません。

from lightfm import LightFM

model = LightFM(loss='warp')
model.fit(data['train'], epochs=30, num_threads=2)

*1:実際のルールはもう少し複雑で、「異なるツイートであれば同一のポケモンに何回投票しても良いが、同一ツイート同一ポケモンは1票分としてカウントする」などの制約もあります。今回のブログの趣旨とはあまり関係がないので説明を省略しました。ルール詳細は公式ページを確認してください。

*2:そこまでするなら、わざわざ年齢や性別を明示的に推定しなくても、過去のツイート情報を直接推薦システムに入れるのもアリかも?