ハースストーンの類似カードをDeep Learningで探してみた

最近Doc2Vecが面白いので色々やってます。
以下のような記事を見つけたので自分でもやってみました。
→ [Doc2VecでMTGの類似カードを探してみた](http://kitashirakawakiriko.hatenablog.com/entry/2016/08/18/230702)


# Doc2Vecとは
> Word2VecはWord(単語)をベクトルとして捉えるが、Doc2Vec(Paragraph2Vec)はDocument(文書)をWordの集合として見てベクトルを割り当てることで、文書間の類似度やベクトル計算などを実現することができる。

> 例えば、ニュース記事同士の類似度、レジュメ同士の類似度、本同士の類似度、もちろん人のプロフィールと本の類似度なども算出することができ、テキストで表されて者同士であれば、全てが対象となる。

引用元: http://qiita.com/okappy/items/32a7ba7eddf8203c9fa1

# 使用した言語・パッケージ
- Python3.5.2
    - gensim (自然言語処理ライブラリ)
        - doc2vec
    - Scrapy (クローラー&スクレイピング)
    - BeautifulSoup (HTMLパーサー)
- Mecab (形態素解析)


# 対象データ
Blizzard社の[ハースストーン](http://us.battle.net/hearthstone/ja/)のカード922枚のテキスト
※ One Night in Karazhanまで

カードサンプル
![00912eb0f9c8d30b0bc9804db54210a3-1.png](https://qiita-image-store.s3.amazonaws.com/0/99505/2910066c-e65e-0916-f67d-0b2309916394.png)


# コーパス作成
コーパスは4亀の[Hearthstoneカードリスト](http://www.4gamer.net/games/209/G020915/FC20140702001/)からスクレイピングします。
今回はScrapyを使用しました。
以下のコマンドでプロジェクトを作成します。

```bash:terminal
$ scrapy startproject hearth_stone
```

以下のようなファイルが作成されます。

```bash:terminal
$ tree hearth_stone/
hearth_stone
├── hearth_stone
│   ├── __init__.py
│   ├── __pycache__
│   ├── items.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── __pycache__
└── scrapy.cfg

4 directories, 6 files
```

まずは`items.py`に出力するデータ構造を定義します。
今回は名前(name)とテキスト(text)しか必要ありませんが今後使うかもしれないのでカードが持つ全てのデータをスクレイピングします。

```python:items.py
# -*- coding: utf-8 -*-
import scrapy


class HearthStoneItem(scrapy.Item):
    name = scrapy.Field()
    rarity = scrapy.Field()
    ruby = scrapy.Field()
    type = scrapy.Field()
    hero = scrapy.Field()
    race = scrapy.Field()
    text = scrapy.Field()
    mana = scrapy.Field()
    attack = scrapy.Field()
    health = scrapy.Field()
```

次はSpider（クローラー）を作成します。

```bash:terminal
# For example, to create a new spider:
# scrapy genspider mydomain mydomain.com

$ scrapy genspider hearthstone 4gamer.net
```

以下のようなファイルが作成されます。

```python:spiders/hearthstone.py
# -*- coding: utf-8 -*-
import scrapy


class HearthstoneSpider(scrapy.Spider):
    name = "hearthstone"
    allowed_domains = ["4gamer.net"]
    start_urls = ['http://4gamer.net/']

    def parse(self, response):
        pass
```

これを4亀のサイト用に書き換えます。

```python:spiders/hearthstone.py
# -*- coding: utf-8 -*-
import scrapy
from ..items import HearthStoneItem
from bs4 import BeautifulSoup


class HearthStoneSpider(scrapy.Spider):
    name = "hearth_stone"
    allowed_domains = ["4gamer.net"]
    start_urls = ['http://www.4gamer.net/games/209/G020915/FC20140702001/']

    def parse(self, response):
        soup = BeautifulSoup(response.body, "lxml")

        for card in soup.find("div", id="UNIT_LIST").findAll("div"):
            item = HearthStoneItem()
            item['name'] = card.find("span", class_="name").string
            item['rarity'] = card.find("span", class_="rarity").string
            item['ruby'] = card.find("span", class_="ruby").string
            item['type'] = card.find("span", class_="type").string
            item['hero'] = card.find("span", class_="class").string
            item['race'] = card.find("span", class_="race").string
            item['text'] = card.find("span", class_="card_comment").find("p").string
            item['mana'] = card.find("span", class_="mana").string
            item['attack'] = card.find("span", class_="attack").string
            item['health'] = card.find("span", class_="health").string
            yield item
```

これでスクレイピングをする準備が整ったのでクローラーを実行します。
`-o`で出力ファイル名を指定できます。(ファイルタイプは拡張子から自動判定)

```bash:terminal
$ scrapy crawl hearthstone -o hearth_stone.json
```

クロール/スクレイピングに成功していれば以下のようなファイルが作成されます。
暗号みたいになっていますがUnicodeになっているだけなので大丈夫です。

```json:hearth_stone.json
[
{"attack": "4", "ruby": "Abomination", "rarity": "\u30ec\u30a2", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "5", "name": "\u6d9c\u308c\u3057\u3082\u306e", "race": "-", "health": "4", "text": "\u6311\u767a\uff06\u65ad\u672b\u9b54\uff1a\u5168\u3066\u306e\u30ad\u30e3\u30e9\u30af\u30bf\u30fc\u306b2\u30c0\u30e1\u30fc\u30b8\u3092\u4e0e\u3048\u308b\u3002"},
{"attack": "1", "ruby": "Abusive Sergeant", "rarity": "\u30b3\u30e2\u30f3", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "1", "name": "\u9b3c\u8ecd\u66f9", "race": "-", "health": "1", "text": "\u96c4\u53eb\u3073\uff1a\u3053\u306e\u30bf\u30fc\u30f3\u306e\u9593\u3001\u30df\u30cb\u30aa\u30f31\u4f53\u306b\u653b\u6483\u529b\uff0b2\u3092\u4ed8\u4e0e\u3059\u308b\u3002"},
{"attack": "3", "ruby": "Acidic Swamp Ooze", "rarity": "\u30d5\u30ea\u30fc", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "2", "name": "\u9178\u6027\u6cbc\u30a6\u30fc\u30ba", "race": "-", "health": "2", "text": "\u96c4\u53eb\u3073\uff1a\u6575\u306e\u6b66\u5668\u3092\u7834\u58ca\u3059\u308b\u3002"},
{"attack": "4", "ruby": "Acidmaw ", "rarity": "\u30ec\u30b8\u30a7\u30f3\u30c9", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u30cf\u30f3\u30bf\u30fc", "mana": "7", "name": "\u30a2\u30b7\u30c3\u30c9\u30e2\u30fc", "race": "\u7363", "health": "2", "text": "\u81ea\u5206\u4ee5\u5916\u306e\u30df\u30cb\u30aa\u30f3\u304c\u30c0\u30e1\u30fc\u30b8\u3092\u53d7\u3051\u308b\u5ea6\u3001\u305d\u306e\u30df\u30cb\u30aa\u30f3\u3092\u7834\u58ca\u3059\u308b\u3002"},
{"attack": "1", "ruby": "Acolyte of Pain", "rarity": "\u30b3\u30e2\u30f3", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "3", "name": "\u82e6\u75db\u306e\u4f8d\u796d", "race": "-", "health": "3", "text": "\u3053\u306e\u30df\u30cb\u30aa\u30f3\u304c\u30c0\u30e1\u30fc\u30b8\u3092\u53d7\u3051\u308b\u5ea6\u3001\u30ab\u30fc\u30c9\u30921\u679a\u5f15\u304f\u3002"},
{"attack": "3", "ruby": "Al'Akir the Windlord", "rarity": "\u30ec\u30b8\u30a7\u30f3\u30c9", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u30b7\u30e3\u30fc\u30de\u30f3", "mana": "8", "name": "\u98a8\u306e\u738b\u30a2\u30e9\u30ad\u30a2", "race": "-", "health": "5", "text": "\u75be\u98a8\u3001\u7a81\u6483\u3001\u8056\u306a\u308b\u76fe\u3001\u6311\u767a"},
{"attack": "0", "ruby": "Alarm-o-Bot", "rarity": "\u30ec\u30a2", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "3", "name": "\u30a2\u30e9\u30fc\u30e0\u30ed\u30dc", "race": "\u30e1\u30ab", "health": "3", "text": "\u81ea\u5206\u306e\u30bf\u30fc\u30f3\u306e\u958b\u59cb\u6642\u3001\u3053\u306e\u30df\u30cb\u30aa\u30f3\u3092\u3001\u81ea\u5206\u306e\u624b\u672d\u306e\u30e9\u30f3\u30c0\u30e0\u306a\u30df\u30cb\u30aa\u30f3\u3068\u5165\u308c\u66ff\u3048\u308b"},
{"attack": "3", "ruby": "Aldor Peacekeeper", "rarity": "\u30ec\u30a2", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u30d1\u30e9\u30c7\u30a3\u30f3", "mana": "3", "name": "\u30a2\u30eb\u30c0\u30fc\u306e\u5e73\u548c\u306e\u756a\u4eba", "race": "-", "health": "3", "text": "\u96c4\u53eb\u3073\uff1a\u6575\u306e\u30df\u30cb\u30aa\u30f31\u4f53\u306e\u653b\u6483\u529b\u30921\u306b\u5909\u3048\u308b\u3002"},
{"attack": "8", "ruby": "Alexstrasza", "rarity": "\u30ec\u30b8\u30a7\u30f3\u30c9", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "9", "name": "\u30a2\u30ec\u30af\u30b9\u30c8\u30e9\u30fc\u30b6", "race": "\u30c9\u30e9\u30b4\u30f3", "health": "8", "text": "\u96c4\u53eb\u3073\uff1a\u30d2\u30fc\u30ed\u30fc1\u4eba\u306e\u6b8b\u308a\u4f53\u529b\u309215\u306b\u3059\u308b\u3002"},
{"attack": "2", "ruby": "Alexstrasza's Champion ", "rarity": "\u30ec\u30a2", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u30a6\u30a9\u30ea\u30a2\u30fc", "mana": "2", "name": "\u30a2\u30ec\u30af\u30b9\u30c8\u30e9\u30fc\u30b6\u306e\u52c7\u8005", "race": "-", "health": "3", "text": "\u96c4\u53eb\u3073\uff1a\u81ea\u5206\u306e\u624b\u672d\u306b\u30c9\u30e9\u30b4\u30f3\u30ab\u30fc\u30c9\u304c\u3042\u308b\u5834\u5408\u3001\u653b\u6483\u529b\uff0b1\u3068\u7a81\u6483\u3092\u5f97\u308b\u3002"},
{"attack": "2", "ruby": "Amani Berserker", "rarity": "\u30b3\u30e2\u30f3", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "2", "name": "\u30a2\u30de\u30cb\u306e\u72c2\u6226\u58eb", "race": "-", "health": "3", "text": "\u6fc0\u6012\uff1a\u653b\u6483\u529b\uff0b3\u3002"},
{"attack": null, "ruby": "Ancestor's Call", "rarity": "\u30a8\u30d4\u30c3\u30af", "type": "\u546a\u6587", "hero": "\u30b7\u30e3\u30fc\u30de\u30f3", "mana": "4", "name": "\u7956\u970a\u306e\u58f0", "race": "-", "health": null, "text": "\u5404\u30d7\u30ec\u30a4\u30e4\u30fc\u306e\u624b\u672d\u304b\u3089\u3001\u30e9\u30f3\u30c0\u30e0\u306a\u30df\u30cb\u30aa\u30f31\u4f53\u3092\u305d\u308c\u305e\u308c\u306e\u9663\u5730\u306b\u8ffd\u52a0\u3059\u308b\u3002"},
{"attack": null, "ruby": "Ancestral Healing", "rarity": "\u30b3\u30e2\u30f3", "type": "\u546a\u6587", "hero": "\u30b7\u30e3\u30fc\u30de\u30f3", "mana": "0", "name": "\u7956\u970a\u306e\u7652\u3057", "race": "-", "health": null, "text": "\u30df\u30cb\u30aa\u30f31\u4f53\u306e\u4f53\u529b\u3092\u4e0a\u9650\u307e\u3067\u56de\u5fa9\u3057\u3001\u6311\u767a\u3092\u4ed8\u4e0e\u3059\u308b\u3002"},
{"attack": null, "ruby": "Ancestral Knowledge ", "rarity": "\u30b3\u30e2\u30f3", "type": "\u546a\u6587", "hero": "\u30b7\u30e3\u30fc\u30de\u30f3", "mana": "2", "name": "\u7956\u970a\u306e\u77e5\u8b58", "race": "-", "health": null, "text": "\u30ab\u30fc\u30c9\u30922\u679a\u5f15\u304f\u3002\u30aa\u30fc\u30d0\u30fc\u30ed\u30fc\u30c9\uff1a (2)"},
{"attack": null, "ruby": "Ancestral Spirit", "rarity": "\u30ec\u30a2", "type": "\u546a\u6587", "hero": "\u30b7\u30e3\u30fc\u30de\u30f3", "mana": "2", "name": "\u7956\u970a\u306e\u5c0e\u304d", "race": "-", "health": null, "text": "\u30df\u30cb\u30aa\u30f31\u4f53\u306b\u3001\u300c\u65ad\u672b\u9b54\uff1a\u3053\u306e\u30df\u30cb\u30aa\u30f3\u3092\u518d\u5ea6\u53ec\u559a\u3059\u308b\u300d\u3092\u4ed8\u4e0e\u3059\u308b\u3002"},
{"attack": "5", "ruby": "Ancient Brewmaster", "rarity": "\u30b3\u30e2\u30f3", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "4", "name": "\u8001\u7df4\u306e\u9152\u9020\u5927\u5e2b", "race": "-", "health": "4", "text": "\u96c4\u53eb\u3073\uff1a\u5473\u65b9\u306e\u30df\u30cb\u30aa\u30f31\u4f53\u3092\u6226\u5834\u304b\u3089\u81ea\u5206\u306e\u624b\u672d\u306b\u623b\u3059\u3002"},
{"attack": "2", "ruby": "Ancient Mage", "rarity": "\u30ec\u30a2", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "4", "name": "\u8001\u7df4\u306e\u30e1\u30a4\u30b8", "race": "-", "health": "5", "text": "\u96c4\u53eb\u3073\uff1a\u96a3\u63a5\u3059\u308b\u30df\u30cb\u30aa\u30f3\u306b\u3001\u546a\u6587\u30c0\u30e1\u30fc\u30b8\uff0b1\u3092\u4ed8\u4e0e\u3059\u308b\u3002"},
{"attack": "5", "ruby": "Ancient of Lore", "rarity": "\u30a8\u30d4\u30c3\u30af", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u30c9\u30eb\u30a4\u30c9", "mana": "7", "name": "\u77e5\u8b58\u306e\u53e4\u4ee3\u6a39", "race": "-", "health": "5", "text": "\u9078\u629e\uff1a\u30ab\u30fc\u30c9\u30921\u679a\u5f15\u304f\u3002\u307e\u305f\u306f\u3001\u4f53\u529b\u30925\u56de\u5fa9\u3059\u308b\u3002"},
{"attack": "5", "ruby": "Ancient of War", "rarity": "\u30a8\u30d4\u30c3\u30af", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u30c9\u30eb\u30a4\u30c9", "mana": "7", "name": "\u6226\u306e\u53e4\u4ee3\u6a39", "race": "-", "health": "5", "text": "\u9078\u629e\uff1a\u653b\u6483\u529b\uff0b5\u3002\u307e\u305f\u306f\u3001\u4f53\u529b\uff0b5\u3068\u6311\u767a\u3002"},
{"attack": "4", "ruby": "Ancient Watcher", "rarity": "\u30ec\u30a2", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "2", "name": "\u53e4\u4ee3\u306e\u756a\u4eba", "race": "-", "health": "5", "text": "\u653b\u6483\u3067\u304d\u306a\u3044\u3002"},
{"attack": "1", "ruby": "Angry Chicken", "rarity": "\u30ec\u30a2", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u4e2d\u7acb", "mana": "1", "name": "\u30a2\u30f3\u30b0\u30ea\u30fc\u30c1\u30ad\u30f3", "race": "\u7363", "health": "1", "text": "\u6fc0\u6012\uff1a\u653b\u6483\u529b\uff0b5\u3002"},
{"attack": "9", "ruby": "Anima Golem", "rarity": "\u30a8\u30d4\u30c3\u30af", "type": "\u30df\u30cb\u30aa\u30f3", "hero": "\u30a6\u30a9\u30fc\u30ed\u30c3\u30af", "mana": "6", "name": "\u30a2\u30cb\u30de\u30fb\u30b4\u30fc\u30ec\u30e0", "race": "\u30e1\u30ab", "health": "9", "text": "\u6bce\u30bf\u30fc\u30f3\u306e\u7d42\u4e86\u6642\u306b\u3001\u3053\u306e\u30df\u30cb\u30aa\u30f3\u304c\u81ea\u5206\u306e\u552f\u4e00\u306e\u30df\u30cb\u30aa\u30f3\u3067\u3042\u308b\u5834\u5408\u3001\u3053\u306e\u30df\u30cb\u30aa\u30f3\u3092\u7834\u58ca\u3059\u308b\u3002"},
# 省略
]
```

# 学習モデル作成
作成したコーパスを使用してdoc2vecに学習させ、モデルを作成します。
以下のようなディレクトリ構成で実行しています。

```bash:terminal
doc2vec
├── card2vec.py
└── hearth_stone # (Scrapy Project)
    ├── hearth_stone
    │   ├── __init__.py
    │   ├── __pycache__
    │   │   ├── __init__.cpython-35.pyc
    │   │   ├── items.cpython-35.pyc
    │   │   └── settings.cpython-35.pyc
    │   ├── items.py
    │   ├── pipelines.py
    │   ├── settings.py
    │   └── spiders
    │       ├── __init__.py
    │       ├── __pycache__
    │       │   └── __init__.cpython-35.pyc
    │       └── hearthstone.py
    ├── hearth_stone.json
    └── scrapy.cfg

```

## 学習、モデル作成パート
以下のコードでdoc2vecのモデルを作成します。

```python:card2vec.py
# -*- coding: utf-8 -*-
import json
import MeCab
from gensim.models import doc2vec
import os


def load_json(target_game_name):
    # カード名とカードテキストの入力データ作成
    names = []
    text = ""
    texts = []

    # Mecabの出力を分かち書きに指定
    mecab = MeCab.Tagger("-Owakati")
    
    json_path = target_game_name + "/" + target_game_name + ".json"
    
    # カードのテキストを形態素解析し、分かち書きしたものを改行区切りで一つのstringにする
    with open(json_path, "r") as file:
        card_dict = json.load(file)
        for card in card_dict:
            if card["name"] not in names:
                names.append(card["name"])
                mecab_result = mecab.parse(card["text"])
                if mecab_result is False:
                    text += "\n"
                    texts.append("")
                else:
                    text += mecab_result
                    texts.append(card["text"])


    with open(target_game_name + ".txt", "w") as file:
        file.write(text)

    return names, texts


def generate_doc2vec_model(target_game_name):
    print("Training Start")
    # カードテキスト読み込み
    card_text = doc2vec.TaggedLineDocument(target_game_name + ".txt")
    # 学習
    model = doc2vec.Doc2Vec(card_text, size=300, window=8, min_count=1,
                            workers=4, iter=400, dbow_words=1, negative=5)

    # モデルの保存
    model.save(target_game_name + ".model")
    print("Training Finish")
    return model


if __name__ == '__main__':
    TARGET_GAME_NAME = "hearth_stone"
    names, texts = load_json(TARGET_GAME_NAME)

    if os.path.isfile(TARGET_GAME_NAME + ".model") is True:
        model = doc2vec.Doc2Vec.load(TARGET_GAME_NAME + ".model")
    else:
        model = generate_doc2vec_model(TARGET_GAME_NAME)
```

## 類似カードの推定パート

```python:card2vec.pyの続き
    # 類似カードを求めたいカード名
    TARGET_CARD_NAME = "ホガー"
    card_index = names.index(TARGET_CARD_NAME)

    # 類似カードと類似度のタプル（類似度上位10件）のリストを受け取る
    similar_docs = model.docvecs.most_similar(card_index)
    print(names[card_index])
    print(texts[card_index])
    print("--------------------is similar to--------------------")
    for similar_doc in similar_docs:
        print(names[similar_doc[0]] + " " + str(similar_doc[1]))
        print(texts[similar_doc[0]], "\n")
```
# 実行結果
カード名を入力として類似しているカードを出力してみます。
今回は個人的に好きなカードを入力しています。

## 入力カード：「ホガー」
```bash:terminal
$ python card2vec.py
ホガー
自分のターンの終了時、挑発を持つ2/2のノールを1体召喚する。
--------------------is similar to--------------------

エルウィンの変災ホガー 0.9119920134544373
このミニオンがダメージを受ける度、挑発を持つ2/2のノールを1体召喚する。

オブシディアン・デストロイヤー 0.8980860114097595
自分のターンの終了時、挑発を持つ1/1のスカラベを1体召喚する。

召喚石 0.8811841011047363
自分が呪文を使う度、同コストのランダムなミニオンを1体召喚する。

ミラーイメージ 0.8686900734901428
挑発を持つ0/2のミニオンを2体召喚する。

イセラ 0.8627046346664429
自分のターンの終了時、夢カードを1枚自分の手札に追加する。
# 省略
```

|入力|出力|
|:--:|:--:|
|![ref_upload.png](https://qiita-image-store.s3.amazonaws.com/0/99505/71c15142-d39f-0789-860c-c4aa057dc6bd.png)|![ref_upload (1).png](https://qiita-image-store.s3.amazonaws.com/0/99505/ef39e2cb-095f-cb07-6a12-ec2d111b635a.png)|

入力の「ホガー」に対して類似度が一番高かったのは「エルウィンの変災ホガー」です。
見ての通り、テキストだけで同じホガー系のカードが出力されたの面白いですね。
2番目,3番目のカードもカードのテキスト的にも「〜〜なとき\*\*\*\*を〜体召喚する」なのでかなり高い精度で取れています。


## 入力カード:「アイアン・ジャガーノート」

```bash:terminal
アイアン・ジャガーノート
雄叫び：敵のデッキのランダムな位置に「埋設地雷」1枚を追加する。「埋設地雷」は引かれた際に爆発し、10ダメージを与える。
--------------------is similar to--------------------

土蜘蛛 0.8792235851287842
敵のデッキのそれぞれランダムな位置に「待ち伏せ」3枚を追加する。「待ち伏せ」が引かれた際、自分の陣地に4/4のネルビアンを1体召喚する。

エリーズ・スターシーカー 0.8761336803436279
雄叫び：自分のデッキのランダムな位置に「黄金のサルへの地図」1枚を追加する。

破壊兵器 0.8525710105895996
自分のターンの開始時、ランダムな敵1体に2ダメージを与える。

古代のシェード 0.8471906185150146
雄叫び：自分のデッキのランダムな位置に「古代の呪い」1枚を追加する。「古代の呪い」を引くと、自分が7ダメージを受ける。

峡谷の暴君ムクラ 0.8456800580024719
雄叫び：「バナナ」2枚を自分の手札に追加する。
# 省略
```

|入力|出力|
|:--:|:--:|
|![ref_upload (3).png](https://qiita-image-store.s3.amazonaws.com/0/99505/915f365a-9cf5-f99d-744f-71b335af77b8.png)|![ref_upload (4).png](https://qiita-image-store.s3.amazonaws.com/0/99505/fcb84395-9f23-6e00-5b6b-d81caa752035.png)|

これも「敵のデッキのランダムな位置に\*\*\*\*を〜枚追加する。\*\*\*\*が引かれた場合〜〜する。」といった特徴がしっかりと取れています。
デッキのランダムな位置にカードを挿入する系の「エリーズ・スターシーカー」や「古代のシェード」も上位に来ています。


## 入力カード:「ロード・ジャラクサス」

```bash:terminal
ロード・ジャラクサス
雄叫び：自分のヒーローは破壊され、以後ロード・ジャラクサスが自分のヒーローとなる。
--------------------is similar to--------------------

筆頭家老エグゼクタス 0.8202652335166931
断末魔：炎の王ラグナロスが自分のヒーローとなる。

号令 0.8199278712272644
このターンの間、味方のミニオンたちの体力は1より低くならない。カードを1枚引く。

エリーズ・スターシーカー 0.8164669275283813
雄叫び：自分のデッキのランダムな位置に「黄金のサルへの地図」1枚を追加する。

タスカーの槍試合選手 0.8136664032936096
雄叫び：各プレイヤーのデッキのミニオンのうち1枚を表示する。自分のミニオンの方がコストが高かった場合、自分のヒーローの体力を7回復する。

必殺の一矢 0.8121815323829651
ランダムな敵のミニオン1体を破壊する。
# 省略
```

|入力|出力|
|:--:|:--:|
|![ref_upload (5).png](https://qiita-image-store.s3.amazonaws.com/0/99505/c15b00d3-dff9-5a46-7a26-faa39c8071f3.png)|![ref_upload (6).png](https://qiita-image-store.s3.amazonaws.com/0/99505/90859e83-ff35-becc-d372-85f2f0dc29ea.png)|

「ロード・ジャラクサス」といえば自分のヒーローが変わるという変わった効果のカードです。
こういった効果をもったカードは「ロード・ジャラクサス」と「筆頭家老エグゼクタス」しかいません。
この結果ではこの２つの類似度が高くなっているのでかなりうまく特徴が取れているといえます。

# まとめ
このようにハースストーンのようなカードテキストのような短いテキストでもdoc2vecでうまくベクトル化できることがわかりました。
コーパスとしても922枚と少ないものでかなりの精度が出せました。
Doc2Vecのパラメーターに関しては参考文献に示している論文を参考にしました。
余談ですが遊戯王のカードでもやってみたのですが、自分が遊戯王に詳しくないので似ているかどうかピンと来なかったのでやめました。
Githubにソースとモデル一式をUPしておくので、興味がある方がいましたらやってみてください。
https://github.com/GuiltyMorishita/card2vec
※シャドーバースは日本語がカオスすぎたのでやめました。


# 参考文献
[Distributed Representations of Sentences and Documents](http://cs.stanford.edu/~quocle/paragraph_vector.pdf)
[An Empirical Evaluation of doc2vec with Practical Insights into Document Embedding Generation](https://pdfs.semanticscholar.org/66d0/c1a5933caa3e5f1e21d7cb676ca2bfab32ab.pdf)