最近Doc2Vecが面白いので色々やってます。
以下のような記事を見つけたので自分でもやってみました。
→ Doc2VecでMTGの類似カードを探してみた
Doc2Vecとは
Word2VecはWord(単語)をベクトルとして捉えるが、Doc2Vec(Paragraph2Vec)はDocument(文書)をWordの集合として見てベクトルを割り当てることで、文書間の類似度やベクトル計算などを実現することができる。
例えば、ニュース記事同士の類似度、レジュメ同士の類似度、本同士の類似度、もちろん人のプロフィールと本の類似度なども算出することができ、テキストで表されて者同士であれば、全てが対象となる。
引用元: http://qiita.com/okappy/items/32a7ba7eddf8203c9fa1
使用した言語・パッケージ
- Python3.5.2
- gensim (自然言語処理ライブラリ)
- doc2vec
- Scrapy (クローラー&スクレイピング)
- BeautifulSoup (HTMLパーサー)
- gensim (自然言語処理ライブラリ)
- Mecab (形態素解析)
対象データ
Blizzard社のハースストーンのカード922枚のテキスト
※ One Night in Karazhanまで
コーパス作成
コーパスは4亀のHearthstoneカードリストからスクレイピングします。
今回はScrapyを使用しました。
以下のコマンドでプロジェクトを作成します。
$ scrapy startproject hearth_stone
以下のようなファイルが作成されます。
$ 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)しか必要ありませんが今後使うかもしれないのでカードが持つ全てのデータをスクレイピングします。
# -*- 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(クローラー)を作成します。
# For example, to create a new spider:
# scrapy genspider mydomain mydomain.com
$ scrapy genspider hearthstone 4gamer.net
以下のようなファイルが作成されます。
# -*- 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亀のサイト用に書き換えます。
# -*- 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
で出力ファイル名を指定できます。(ファイルタイプは拡張子から自動判定)
$ scrapy crawl hearthstone -o hearth_stone.json
クロール/スクレイピングに成功していれば以下のようなファイルが作成されます。
暗号みたいになっていますがUnicodeになっているだけなので大丈夫です。
[
{"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に学習させ、モデルを作成します。
以下のようなディレクトリ構成で実行しています。
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のモデルを作成します。
# -*- 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)
類似カードの推定パート
# 類似カードを求めたいカード名
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")
実行結果
カード名を入力として類似しているカードを出力してみます。
今回は個人的に好きなカードを入力しています。
入力カード:「ホガー」
$ 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枚自分の手札に追加する。
# 省略
入力 | 出力 |
---|---|
入力の「ホガー」に対して類似度が一番高かったのは「エルウィンの変災ホガー」です。
見ての通り、テキストだけで同じホガー系のカードが出力されたの面白いですね。
2番目,3番目のカードもカードのテキスト的にも「〜〜なとき****を〜体召喚する」なのでかなり高い精度で取れています。
入力カード:「アイアン・ジャガーノート」
アイアン・ジャガーノート
雄叫び:敵のデッキのランダムな位置に「埋設地雷」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枚を自分の手札に追加する。
# 省略
入力 | 出力 |
---|---|
これも「敵のデッキのランダムな位置に****を〜枚追加する。****が引かれた場合〜〜する。」といった特徴がしっかりと取れています。
デッキのランダムな位置にカードを挿入する系の「エリーズ・スターシーカー」や「古代のシェード」も上位に来ています。
入力カード:「ロード・ジャラクサス」
ロード・ジャラクサス
雄叫び:自分のヒーローは破壊され、以後ロード・ジャラクサスが自分のヒーローとなる。
--------------------is similar to--------------------
筆頭家老エグゼクタス 0.8202652335166931
断末魔:炎の王ラグナロスが自分のヒーローとなる。
号令 0.8199278712272644
このターンの間、味方のミニオンたちの体力は1より低くならない。カードを1枚引く。
エリーズ・スターシーカー 0.8164669275283813
雄叫び:自分のデッキのランダムな位置に「黄金のサルへの地図」1枚を追加する。
タスカーの槍試合選手 0.8136664032936096
雄叫び:各プレイヤーのデッキのミニオンのうち1枚を表示する。自分のミニオンの方がコストが高かった場合、自分のヒーローの体力を7回復する。
必殺の一矢 0.8121815323829651
ランダムな敵のミニオン1体を破壊する。
# 省略
入力 | 出力 |
---|---|
「ロード・ジャラクサス」といえば自分のヒーローが変わるという変わった効果のカードです。
こういった効果をもったカードは「ロード・ジャラクサス」と「筆頭家老エグゼクタス」しかいません。
この結果ではこの2つの類似度が高くなっているのでかなりうまく特徴が取れているといえます。
まとめ
このようにハースストーンのようなカードテキストのような短いテキストでもdoc2vecでうまくベクトル化できることがわかりました。
コーパスとしても922枚と少ないものでかなりの精度が出せました。
Doc2Vecのパラメーターに関しては参考文献に示している論文を参考にしました。
余談ですが遊戯王のカードでもやってみたのですが、自分が遊戯王に詳しくないので似ているかどうかピンと来なかったのでやめました。
Githubにソースとモデル一式をUPしておくので、興味がある方がいましたらやってみてください。
https://github.com/GuiltyMorishita/card2vec
※シャドーバースは日本語がカオスすぎたのでやめました。
参考文献
Distributed Representations of Sentences and Documents
An Empirical Evaluation of doc2vec with Practical Insights into Document Embedding Generation