2018年02月21日

TopicModelを使って,livedoorニュースコーパスを解析してみた

エモトです.さいきんバーフバリというインド映画にどハマりしました.インド映画特有の歌とダンスに加え,ストーリーやアクションシーンの完成度もすごい.出演陣も敵味方含めて全員かっこいい.敵役の中の人が私より年下だったことが一番の驚きでした.まだ見てない方は是非.オームシャンティオームやクリッシュもおすすめ.

Topic Modelとは

簡単に説明すると,文書は複数の潜在的なトピックから確率的に生成されると仮定したモデルです.下記に図例を示しました.この例では,ニュース記事は動物トピックとイベントトピックから文章が生成されて,特に動物トピックの影響が大きいのを示してます.なお,xxトピックと書いてますが,実際の計算ではトピックのラベル付けまで行われずに,計算終了後に別途でラベル付けを行います.

tm01.png

さて今回,そのTopic Modelを実装してみました.対象データは livedoor ニュースコーパス を使用しました.実装言語はPython,主な使用ライブラリはGensimを使用しています

1. データ作成

ニュースの本文を入力データ,カテゴリーをラベルとしました.本文はMeCabを使用して分かち書きにしました。なお、名詞と動詞の原形を使用しました。形容詞など他の品詞は、今回の実装では精度が上がらなかったのでデータから外しました。
docments = [["分かち書き", "された", "文章1"], ... ]
categories = ["文章1のカテゴリー", ...]
全データ7367件中,訓練データには9割,テストデータには1割を使用しました.

2. 実装

gensimのLDAmodel(Latent Dirichlet Allocation,潜在的ディリクレ配分法)を使用して実装しました.コードの一部を紹介します.なお,ストップワードの選定には,Slothlibのストップワード を使用しました.
from gensim import corpora
from gensim import models
from sklearn.model_selection import train_test_split

dictionary = corpora.Dictionary(documents)

# ストップワード
ignores = []
ignored_words_file = "./stop_words.txt"
with open(ignored_words_file, "r", encoding="utf-8") as f:
    ignores = [line.replace(os.linesep, "") for line in f]

# フィルタリング
dictionary.filter_extremes(no_below=20, no_above=0.5, keep_n=100000)
stop_ids = [dictionary.token2id[ig] for ig in ignores if ig in dictionary.token2id]
dictionary.filter_tokens(stop_ids)
dictionary.compactify()

コーパスデータを訓練とテストに分けて,LDAを学習します.学習時にトピック数を指定するのですが,今回は入力するカテゴリーと同数の9を設定しています.なお,学習メソッドのパラメータはヒューリスティックにいじって調整した値なので,本当に適正かどうかは不明です.
corpus = [dictionary.doc2bow(text) for text in documents]
x_train, x_test, y_train, y_test = train_test_split(corpus, categories, test_size=0.1, random_state=1)
    
lda = models.LdaModel(corpus=x_train, id2word=dictionary, num_topics=9, iterations=50, passes=20)

評価には,パープレキシティ,学習したトピックの上位語彙および分類結果を用いました.
def calc_perplexity(m, c):
    import numpy as np
    return np.exp(-m.log_perplexity(c))

# パープレキシティ
p = calc_perplexity(lda, x_test)

# 上位語彙
each_topics = []
for i in range(lda.num_topics):
    words = lda.show_topic(i, topn=topic_count)
    each_topics += [[w[0] for w in words]]

# 分類
for x in x_test:
    topics = lda.get_document_topics(x)
    top_topic = sorted(topics, key=lambda topic: topic[1], reverse=True)[0][0]

パープレキシティ(分類精度を示し,1に近いほど良い)です.訓練でさえ2000を超えているので,あまり良い分類精度ではないようでした.
訓練 テスト
2090.9748423332085 4603.607550110796

各カテゴリの上位語彙.学習したトピック(1列目)それぞれの上位5つの語彙を示してます.
topics 0 1 2 3 4
0 twitter ネット掲示板 情報 当選
1 使う 女子 人気 女性
2 話題 機能 見る 使う pc
3 思う 女性 結婚 男性 仕事
4 smartphone 対応 搭載 max 2012年
5 アプリ 更新 画面 表示 ソフトウェア
6 転職 写真 知る 仕事 得る
7 思う 選手 語る 日本 試合
8 映画 公開 作品 監督 本作

訓練データに対して,実際のラベル(1列目)がどのトピック(2列目以降)に分類されたかの表です.ニュースコーパスのカテゴリー分類に従えば,それぞれのデータ群が1つの固有トピックに集中するのが望ましい結果と言えるでしょう.
訓練 0 1 2 3 4 5 6 7 8
dokujo-tsushin 4 122 2 628 1 0 4 3 27
it-life-hack 52 12 385 20 66 107 121 8 15
kaden-channel 40 51 612 20 10 7 22 4 20
livedoor-homme 3 161 37 60 3 8 131 8 29
movie-enter 19 2 2 25 0 0 0 2 726
peachy 45 459 9 137 1 9 17 1 86
smax 5 11 22 0 416 334 0 0 3
sports-watch 38 2 0 28 0 0 1 728 5
topic-news 498 6 29 65 0 3 8 61 24

上記の結果をテストデータに対して行った結果.
テスト 0 1 2 3 4 5 6 7 8
dokujo-tsushin 0 9 0 66 0 0 1 0 3
it-life-hack 7 1 44 2 11 12 3 1 3
kaden-channel 5 6 54 4 1 4 1 1 2
livedoor-homme 1 25 9 5 0 0 19 2 10
movie-enter 0 0 0 3 0 0 0 0 91
peachy 2 48 1 13 0 2 1 0 11
smax 0 7 3 0 41 28 0 0 0
sports-watch 3 0 0 2 0 0 0 92 1
topic-news 56 1 1 9 0 0 0 7 2

分類結果の表から dokujo-tsushin と sports-watch と topic-news と movie-enter が上手に分類できるように見えます.しかしながら,トピック2は it-life-hack と kaden-channel が混在しました.ただ,その両者はもともと近しいジャンルのように見えるので,妥当な結果とも言えます.

HDP

ここで,LDAでトピックモデルを行ったとき,トピック数を指定して学習しました.例では入力データのカテゴリー数と同数を設定しましたが,本当にこのトピック数が適正な値かどうかは,わかりません.いくつもの候補を設定して,パープレキシティや分類結果を確認して決定する工程が必要になると思います.

その工程が面倒なので,HDP(Hierarchical Dirichlet Process,階層ディリクレ過程)を用いて,トピック数自体を推定しました.
# 推定トピック数の最大値はデフォルトで150に設定されている
hdp = models.hdpmodel.HdpModel(corpus, id2word=dictionary, alpha=0.1)

学習結果の評価は,こちら を参考に推定したトピックそれぞれの重みを加算して,比較しました.
def topic_prob_extractor(hdp, save_csv_file=None):
    import pandas as pd
    shown_topics = hdp.show_topics(num_topics=-1, formatted=False)
    topics_nos = [x[0] for x in shown_topics]
    weights = [sum([item[1] for item in shown_topics[topicN][1]]) for topicN in topics_nos]
    data_frame = pd.DataFrame({'topic_id': topics_nos, 'weight': weights})
    if save_csv_file is not None:
        data_frame.to_csv(save_csv_file)
    return data_frame
hdp.png
最も重みが高い9が最適なトピック数となるでしょうか.25や50手前でいきなり重みがポンっと上がっているのがすごく気になります.

最適なトピック数は?

今回だと,最適なトピック数は9のようにみえます.しかしながら,より複雑なデータ(不特定多数が書いた,文章作成規則があいまい,など)を対象とした場合,HDPでも判定が難しい方が多いです.色々な質問サイト を眺めていると,はやりHDPでは決められずに,LDAの計算を反復して決定してるのをよく見かけます.

まとめ

Topic Modelのトピック数を幾つに設定することが大きな問題です.望ましい結果と比べて地道に設定していくことになると思います.しかしながら,Topic Modelは良い分類結果をもたらし,非常に興味深いですね.
posted by Seesaa京都スタッフ at 14:00| Comment(0) | 機械学習 | このブログの読者になる | 更新情報をチェックする

2018年01月25日

fastText が Python をサポートしてた

エモトです.7.1ch最高かよ.引き続き通いたいと思ってます.

いぜんfastTextに関して,fastTextをPythonで使いたいならpyfasttext という記事を2017年10月に書いたように,fastTextはPythonを公式にサポートせず,公式サイトにも非公式のパッケージを使ってよという記載があったのを記憶してます.

ところが,fastText の readme を眺めていると, Building fastText for Python が追加されている.更新履歴を確認したところ,去年の11月あたりに追加されたようです.

$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ pip install .

pip1行で済まないのがちょっと気になりますが,非公式のパッケージは本家fastTextのバージョンアップに追いつかなくて利用できないことがあったので,公式で対応されたのは嬉しいことですね.

posted by Seesaa京都スタッフ at 17:31| Comment(0) | 機械学習 | このブログの読者になる | 更新情報をチェックする

2017年12月26日

Recurrent Convolutional Neural Networks (RCNN) を使ってテキスト分類してみた

エモトです.みなさん,ガルパン最終章は見られましたか?私はまだ二回しか見に行けてません.また近いうちに爆音上映をキメたいです.

深層学習(ディープラーニング)といえば,まず先に画像を対象にしたものが例に挙がりますが,今回はテキスト分類をしてみたいと思い,ちょっと試してみました.

手法

テキストは画像と比較しても,可変長であり,単語(語彙)を順々に書き繋いでいくので,Recurrent Neural Networks (RNN) ベースの手法が良いだろうと思い,Siwei Lai らが2015年に発表した Recurrent Convolutional Neural Networks for Text Classification から,Recurrent Convolutional Neural Networks (RCNN) を用いて,検証しました.

RCNN

RCNN は名前が示す通りに,recurrent と convolutional を組み合わせた NN です.時系列データの扱いにかけた recurrent に,画像分類でその性能を発揮した convolutional の組合せ,なにか強そうですね.ここで,同様な手法として,Bradbury らが提案した QRNN というのがあります.こちらは RCNN が組み合わせに対して,CNN を擬似的に recurrent にするように設計されています.対照実験を行いたいところですが,時間の都合上,今回は RCNN のみになります.

RCNN の実装は Keras で行いました.実装したモデルを下図になります.冒頭に深層学習と言いましたが,それほど層は深くないです.ここで,vanilla RNN ではなく LSTM を使用して,入力テキストの形態素解析には MeCab (mecab-ipadic-NEologd) ,埋め込み層には fastText を使用しました.

keras-rcnn.png


実験

アマゾンのレビューデータを用いて,コメントをレーティングごとに分類しました.商品286個のレビューデータ115,349件を用意して,以下のように分けて実験を行いました.

trainvaltesttotal
83051922823070115349

結果

学習したモデルでテストデータを分類した結果を下記に示します(レーティングは1から始まりますが,計算の都合上0からになっています).この表は行ごとに予測したレーティングが実際はどのレーティングだったかをカウントしています(対角行列のように対角線上に数値が集まっていれば良い結果といえます).

予測\正解01234
05051671318197
11401831798863
2101193634439394
3529647420271665
49891597250112074

予想と実際のレーティングが一致したのは,約68% となりました.少し精度が低いと思いましたが,表を見ると,低いレーティングは低く,高いレーティングは高く,正解に近く分布しているので,完全一致という指標でなければ,まずまずの結果のように見えます.言い訳をしておくと,レーティングという人間の曖昧さ,日本語という自然言語の難しさから,この手法と結果は悪くはないと思っています.

まとめ

RCNNでテキスト分類を行い,比較的良い結果が得られました.しかしながら,レーティングのようにラベル付けがはっきりしているデータは多くはなく,文章はメディア(媒体)や書き手によって,いくつもの形になります.銀の弾丸はないと.テキスト分離は難しい分野ですが,それゆえ面白いですね.

最後に,本年,本ブログへの訪問,ありがとうございました.来年もよろしくお願いします.
posted by Seesaa京都スタッフ at 10:37| Comment(0) | 機械学習 | このブログの読者になる | 更新情報をチェックする