アニメ作品をベクトル化して距離を測れるようにすれば、この人はこのアニメが好きだから似ているあのアニメも好きなんじゃないかといったアニメ推薦システムに使えるのではと思い、機械学習の手法であるdoc2vecを用いてアニメのベクトル化を行います。言うなればanime2vecです。
必要なライブラリ
pip install mecab-python3(形態素解析をするため)
pip install gensim(doc2vecを使うため)
pip install mysql-connector-python(データベースを使うため)
使用するデータ
jawiki-latest-pages-articles.xml.bz2
wikipediaのダンプから日本のwiki記事のページデータをダウンロードします。これをwikiextract.pyで抽出します。するとデータがたくさんでき、extractedフォルダに保存されていきます。かなり時間がかかりますので、その間ご飯を食べることができるほど余裕があります。今回使用するのはアニメ作品のみのwiki記事です。
↑これが参照先のwikiデータです。出力ファイルはwiki_**(数字)でフォルダはAAからCGまでで、後に行くほどページ番号がおおきくなっていきます。
・カテゴリーデータベース
jawikilatest-categorylink.sqlをダウンロードして実行するとこのようになります。
http://www.mwsoft.jp/programming/munou/wikipedia_data_list.html#categorylinks.sql
を参考にしました。
cl_from | cl_to | cl_sortkey | cl_timestamp |
page_idに紐付く | カテゴリ名 | カテゴリ名(ひらがな表記) | タイムスタンプ |
これでページ(id)に対するカテゴリーを取得することができます。これを利用してアニメで行を選択することによってアニメのID群のみを集めることができます。
問題として、unicodeで解読できないものがあるため、コードを変換する必要があります。ページ番号だけ抜き出します。MySQLの選択として”%アニメ作品”とします。これは正規表現の「カテゴリが”アニメ作品”という文字列で終わる」記事のみを収集することができます。
wikiextract.py で抽出されたwiki データファイルはdocタグの連続になっているため、xml形式になっていないです。なので、ダミーの親タグをつけて無理矢理変換を行います。この親タグがルートタグになるので子ノード一覧を参照することでdocタグ一覧を参照することができます。
メソッド
e.get(‘id’):docタグのidを取得する
e.text:docタグのテキスト部分を取得する
注意点としてテキスト部分でタグのようなものがあると誤動作でxml形式が崩れるため、引数でパースを入れる必要があります。
ソースコード
コードは以下の通りです。
# -*- coding: utf-8 -*
import xml.etree.ElementTree as ET
import mysql.connector
import matplotlib.pyplot as plt
import os
import MeCab
from gensim.models.doc2vec import Doc2Vec
from gensim.models.doc2vec import TaggedDocument
from lxml import etree
#mecab用のパーサー
m = MeCab.Tagger ("-Owakati")
#xml用のパーサー
parser = etree.XMLParser(recover=True)
#MySQLに接続するための準備
conn = mysql.connector.connect(user='root', password='root', host='localhost', database='ja2',charset="utf8")
cur = conn.cursor()
cur.execute("select * from categorylinks where cl_to like '%アニメ作品';")
ani_list=[]
#結果から0列目の要素のみをリストに追加する
for row in cur.fetchall():
ani_list.append(row[0])
cur.close
conn.close
#liは集めるべき記事IDのリスト
ani_list.sort()
li=list(set(ani_list))
li.sort()
#学習用文書データのリスト
training_docs = []
#ディレクトリ指定
directory = os.listdir('./extracted')
#AA-ZZにソートする
directory.sort()
step=0
#タグ付き文書のリスト
sent=[]
f2=open("anidata.txt","w")
for i in range(len(directory)):
directory2 = os.listdir('./extracted/'+directory[i])
directory2.sort()
for i2 in range(len(directory2)):
f=open('./extracted/'+directory[i]+"/"+directory2[i2])
d=f.read()
#無理やり木構造にして読み込み
tree = ET.fromstring(""+d+" ",parser=parser)
for e in tree:
d=e.get('id')
if e.get('id')==None:
d="0"
if int(d)==int(li[step]):
f2.write(m.parse (e.text))
sent.append(TaggedDocument(words=(m.parse (e.text)).split(" "),
tags=[e.get('title')]))
step=step+1
elif (int(d)>int(li[step])):
step=step+1
if step>len(li)-1:
for i in range(len(sent)):
training_docs.append(sent[i])
model = Doc2Vec(documents=training_docs, min_count=1, dm=0)
model.save('doc2vec.model')
exit(1)
stepは探すべき記事idの番地、liが探すべき記事idのリストです。stepの昇順にソートしておくことで計算量が記事数とほぼ同じになります。
結果として、記事タイトルと記事本文のペアができます。本文に対してmecabを入れることで単語に分割することができます。これにより、doc2vecのモデルができます。
doc2vecに入れるデータ構造
id | タイトル | 説明文の分かち書き |
0 | なんちゃら戦記 | この、物語、は、… |
テストとしての類似度TOP40を出力しました。コードは以下の通り、
from gensim.models.doc2vec import Doc2Vec
from gensim.models.doc2vec import TaggedDocument
model = Doc2Vec.load('doc2vec.model')
print(model.docvecs.most_similar("冴えない彼女の育てかた", topn=40))
結果
そうでもない…。アニメの内容をチェックする必要がありそうです。
改定版(10/1)
文章をベクトル化するときに名詞と形容詞のみを単語として抽出することができれば、記号など余計なものを無視することができると考えられます。そのためにはmecabの品詞情報を使って条件分岐をします。
node = m.parseToNode(e.text)
w=[]
while node:
if node.feature.split(",")[0]=="名詞" or node.feature.split(",")[0]=="形容詞":
w.append(node.surface)
node=node.next
node.feature.split(“,”)[0]が単語の品詞情報、node.surfaceが単語そのものです。
改訂版(10/23)
doc2vecに食わせる文章を概要の部分のみにすることによって余計な情報を抜き出すことができるようになります。
python wikiextracter.py -html (wikiのダンプデータ)
↑のコマンドを打つことで文章の抽出にタグが含まれるようになります。