トピックモデルの評価指標Perplexityの実験

このブログでトピックモデルの記事を書いたことがあるのですが、
トピック数の決め方について書いてないのに気づいたので評価指標を紹介します。

参考: pythonでトピックモデル(LDA)

トピックモデルのトピック数を決めるときは、Perplexityもしくは、Coherenceと呼ばれる指標を参考にします。
今回の記事では、Perplexityを紹介します。

と言っても、数学的な定義やその意味についてはいろんな場所で紹介されているので、
この記事では趣向を変えて、架空のデータで実験して理解を深めることを目指します。

まず、 Perplexity の定義は、各単語の出現確率(尤度)の逆数の幾何平均です。
(数式はいろんなサイトに乗っているので省略します。
書籍では、奥村学さんの「トピックモデルによる統計的潜在意味解析」などに載っています。)

この定義だけでは意味がわからないのですが、
「分岐数、または選択肢の数を表している」と説明されることが多いです。

例えば、ある文章があって、単語が一つ隠されていたとします。
このとき、LDAによって、その単語の選択肢が2000まで絞り込めていたら、
そのモデルの Perplexity は 2000です。
単語を絞り込めている方が優れたモデルとされるので、この値は低い方が良いモデルです。

まだわかりにくいので、ここから実験をしていきましょう。
次のような架空の世界があったとします。

– その世界の言葉には4個の話題(トピック)がある。
– 各話題ごとに、単語は5個ある。(つまりその世界に単語は20個しか無い)
– 各文章は一つの話題のみに言及する。

(これらの条件は正確にはトピックモデルではなく、混合ユニグラムモデルですが、わかりやすさのためご容赦ください。)

以上の設定のもとで、ランダムに100単語からなる文章をトピックごとに100個生成します。
コードを見ていただけるとわかりますが、4個の話題は色、動物、果物、国です。(なんでも良いのですが。)


import numpy as np

word_list = [
    ["white", "black", "red", "green", "blue"],
    ["dog", "cat", "fish", "bird", "rabbit"],
    ["apple", "banana", "lemon", "orange", "melon"],
    ["Japan", "America", "China", "England", "France"],
]
corpus = [
    " ".join(np.random.choice(word_list[topic], 100))
    for topic in range(len(word_list)) for i in range(100)
]

さて、あとは以前紹介したコードで、LDAモデルを作って、Perplexityを計算してみましょう。
scikit-learnの場合、ドキュメントにある通り、モデルがperplexityというメソッドを持っています。

トピック数はこの例では4が正解だとわかっているので、4を使います。

本当は、データを訓練データと評価データにわけて、評価データでperplexityを計算する必要があるのですが、
今回は実験なので訓練に使ったデータでそのまま評価します。


from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation

tf_vectorizer = CountVectorizer()
bow = tf_vectorizer.fit_transform(corpus)

# LDAのモデル作成と学習
lda = LatentDirichletAllocation(
    n_components=4,
)
lda.fit(bow)
# perplexityの計算
print(lda.perplexity(bow))
# 5.268629755256359

Perplexity は約 5.27 と、 5に近い値が出ましたね。
このLDAモデルで単語が5個くらいまで絞り込めていることがわかります。

Perplexity がトピック数の決定に使えることをみるために、他のトピック数でも計算してみましょう。


for c_num in range(1, 9):
    lda = LatentDirichletAllocation(
        n_components=c_num,
    )
    lda.fit(bow)
    print(f"トピック数: {c_num}, Perplexity: {lda.perplexity(bow)}")
"""
トピック数: 1, Perplexity: 20.033955224623902
トピック数: 2, Perplexity: 10.330848184515682
トピック数: 3, Perplexity: 7.397066706843117
トピック数: 4, Perplexity: 5.268629755256354
トピック数: 5, Perplexity: 5.305381334487885
トピック数: 6, Perplexity: 5.3074106945229875
トピック数: 7, Perplexity: 5.3206895866734305
トピック数: 8, Perplexity: 5.3529382429024315
"""

トピック数が1個の時は、全く絞り込めていないので、全単語数の20に近い値が出ています。
トピック数が2の場合は、半分に絞れているので約10ですね。
そして、トピック数が4の時に、大体5単語に絞れており、
それ以上トピック数を増やしても大きな改善はありません。
このことから、トピック数は4がベストだろうと判断することができます。

現実世界のデータで試すと、こんなに綺麗にトピック数を決めれたことが無く、
Perplexity の有効性に疑問を持っていたのですが、
理論的にはなかなか良い指標であることが確認できました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です