Python
gensim

GensimPy3を使って小説家になろうのトピックモデルを解析

More than 3 years have passed since last update.

gensimというトピックモデルを扱うPythonのライブラリがある。公式ではPythonのバージョン 2.5 <= Python < 3.0 にしか対応していない。

しかしSamantpさんがgensimPy3というライブラリを公開している。gensimをforkしてPython3.3に対応させたものだ。

今回はこのgensimPy3を使ってしょとうさんの小説家になろうのランキングをトピックモデルで解析(gensim) と同じ事ができるか実験した。

※gensimの使い方参考 http://yuku-tech.hatenablog.com/entry/20110623/1308810518

GensimPy3のインストール

https://github.com/samantp/gensimPy3

からソースコードをcloneしてくる。

git clone git@github.com:samantp/gensimPy3.git

で、

python setup.py test

したら

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa3 in position 3973: invalid start byte

なるエラーが出て不安になった。でも無視してインストールした。

python setup.py install

なぜかSyntaxErrorが出たりしたけれどもインストールには成功した。

環境の違い

しょとうさんの記事 http://sucrose.hatenablog.com/entry/2013/04/27/225218 との違いを以下に記す。

  • Python3.3.1 on pyenvを使用
  • gensimはgensimPy3を使用
  • BeautifulSoupのかわりにpyqueryを使用
  • urllib2のかわりにrequestsを使用

pyqueryはJQueryと同じ記法のセレクタが使えるので、Chromeの開発者ツールのConsoleを使って試行錯誤しやすい。便利。

ソースコード

topic_model_in_narou.py
# -*- coding: utf-8 -*-
import requests
from pyquery import PyQuery as pq
import gensim
import pdb


def fetch_narou_ranking_html():
    r = requests.get('http://yomou.syosetu.com/rank/list/type/total_total/')
    r.encoding = 'utf-8'
    return r.text


def collect_tags(d):
    d_novels = d('.s')
    tags = []
    for d_novel in d_novels:
        d_tag_name_list = d_novel.findall('a')
        tags_in_a_novel = [d_tag_name.text for d_tag_name in d_tag_name_list]
        tags.append(tags_in_a_novel)
    return tags


if __name__ == "__main__":
    html = fetch_narou_ranking_html()
    d = pq(html.encode('utf-8'))
    tags = collect_tags(d)

    dictionary = gensim.corpora.Dictionary(tags)
    dictionary.filter_extremes(3)
    corpus = [dictionary.doc2bow(text) for text in tags]
    lda = gensim.models.ldamodel.LdaModel(corpus=corpus, num_topics=10, id2word=dictionary)
    for x in lda.show_topics(-1, 5):
        print(x)

このコードでモデルを作ってトピック表示ができた。結果は以下のとおり。

0.115*ファンタジー + 0.068*魔法 + 0.041*チート + 0.034*ハーレム + 0.028*転生
0.173*魔法 + 0.095*ファンタジー + 0.039*ダーク + 0.033*トリップ + 0.027*転生
0.106*転生 + 0.087*ハーレム + 0.074*主人公最強 + 0.063*チート + 0.052*ファンタジー
0.079*ファンタジー + 0.069*チート + 0.062*恋愛 + 0.059*転生 + 0.041*異世界トリップ
0.088*ファンタジー + 0.063*異世界トリップ + 0.051*ハーレム + 0.039*冒険 + 0.039*OVL文庫大賞応募作
0.105*チート + 0.103*ファンタジー + 0.062*主人公最強 + 0.058*魔法 + 0.044*転生
0.099*ファンタジー + 0.089*転生 + 0.045*主人公最強 + 0.045*最強 + 0.034*魔法
0.051*魔法 + 0.051*成り上がり + 0.051*モンスター + 0.039*VRMMO + 0.039*シリアス
0.140*ファンタジー + 0.077*チート + 0.054*転生 + 0.043*魔法 + 0.038*冒険
0.168*ファンタジー + 0.073*魔法 + 0.052*恋愛 + 0.026*冒険 + 0.026*戦争

うん、ファンタジーばっかり……。同じジャンルが多すぎてトピックに分けられていない。pixiv小説あたりからデータ取ってきたほうがよさそう。

追記

dictionary.filter_extremes(no_below=5, no_above=0.5, keep_n=100000)関数の値を変えてフィルタリングすればファンタジーばっかりな状況を変えられるかもしれないと考えて、修正を加えた。

topic_model_in_narou.py
# -*- coding: utf-8 -*-
import requests
from pyquery import PyQuery as pq
import gensim
import pdb


def fetch_narou_ranking_html():
    r = requests.get('http://yomou.syosetu.com/rank/list/type/total_total/')
    r.encoding = 'utf-8'
    return r.text


def collect_tags(d):
    d_novels = d('.s')
    tags = []
    for d_novel in d_novels:
        d_tag_name_list = d_novel.findall('a')
        tags_in_a_novel = [d_tag_name.text for d_tag_name in d_tag_name_list]
        tags.append(tags_in_a_novel)
    return tags


if __name__ == "__main__":
    html = fetch_narou_ranking_html()
    d = pq(html.encode('utf-8'))
    tags = collect_tags(d)

    dictionary = gensim.corpora.Dictionary(tags)
    dictionary.filter_extremes(no_below=5, no_above=0.05, keep_n=10000)  # 変更
    corpus = [dictionary.doc2bow(text) for text in tags]
    lda = gensim.models.ldamodel.LdaModel(corpus=corpus, num_topics=20, id2word=dictionary)
    for x in lda.show_topics(-1, 5):
        print(x)

no_aboveの値を0.05にした。全体の5%以上に出現しているタグはカウントしないことにした。

gensim公式サイト http://radimrehurek.com/gensim/corpora/dictionary.html

結果がこちら。

0.166*成長 + 0.133*コメディ + 0.100*バトル + 0.100*成り上がり + 0.067*なろうコン大賞
0.106*SF + 0.054*トリップ + 0.054*VRMMO + 0.054*なろうコン大賞 + 0.054*成り上がり
0.142*ドラゴン + 0.142*ダーク + 0.073*奴隷 + 0.073*貴族 + 0.073*バトル
0.120*魔法使い/魔女 + 0.081*精霊 + 0.081*エルフ + 0.081*獣人 + 0.081*成り上がり
0.136*勇者 + 0.136*コメディー + 0.092*迷宮 + 0.092*最強 + 0.092*VRMMO
0.140*異世界召喚 + 0.106*国家/民族 + 0.071*VRMMO + 0.053*溺愛 + 0.036*奴隷
0.153*騎士 + 0.078*勘違い + 0.078*中世 + 0.078*魔法使い/魔女 + 0.078*異世界召喚
0.125*召喚 + 0.125*冒険者 + 0.125*獣人 + 0.125*最強 + 0.125*ご都合主義
0.151*勇者 + 0.091*モンスター + 0.091*美形 + 0.061*戦争 + 0.061*溺愛
0.163*モンスター + 0.122*友情 + 0.082*貴族 + 0.082*成り上がり + 0.082*最強
0.260*精霊 + 0.054*貴族 + 0.054*コメディー + 0.054*シリアス + 0.054*国家/民族
0.143*冒険者 + 0.096*バトル + 0.096*シリアス + 0.096*内政 + 0.049*成り上がり
0.189*コメディ + 0.143*勘違い + 0.096*VRMMORPG + 0.096*コメディー + 0.049*高校生
0.147*ドラゴン + 0.118*最強 + 0.060*エルフ + 0.060*バトル + 0.060*戦争
0.173*奴隷 + 0.088*トリップ + 0.088*魔術 + 0.045*成長 + 0.045*モンスター
0.173*内政 + 0.088*勇者 + 0.088*トリップ + 0.045*最強 + 0.045*奴隷
0.130*シリアス + 0.088*バトル + 0.088*高校生 + 0.088*戦記 + 0.088*異世界転移
0.143*スキル + 0.107*テンプレ + 0.072*戦争 + 0.072*成り上がり + 0.072*魔術
0.206*戦争 + 0.070*内政 + 0.070*中世 + 0.070*召喚 + 0.070*国家/民族
0.130*奴隷 + 0.130*ギルド + 0.088*貴族 + 0.088*召喚 + 0.045*戦争

やっぱりファンタジーばっかりでした……。

でもよーく見ると「シリアス, バトル, 高校生, 戦記, 異世界転移」や「SF, トリップ, VRMMO, なろうコン大賞, 成り上がり」「戦争, 内政, 中世, 召喚, 国家/民族」のような、ちょっとは別ジャンルに見えそうなものもあるので、初期設定のdictionary.filter_extremes()処理を行ったときよりはましになった。