2021年3月7日日曜日

LDAの最適なトピック数を決めたい

トピックモデルのひとつとしてLDA(潜在的ディリクレ配分法)がある。文書に複数の潜在的なトピックがあることを仮定したモデルで、文書分類などに使われる。以前には国会会議録の発話を分類してみたこともある(PythonとLDAで国会会議録の発話を分類して可視化する)。LDAモデルを作成する時には事前にトピック数を指定する必要があが、これがけっこう悩ましい。今回はこのトピック数を決める方法を試してみる。


PerplexityとCoherence

LDAモデルを作成するときのトピック数を決める指標としてよく登場するのがPerplexityとCoherence。Perplexityは低い数値、Coherenceは高い数値が良いとされている。トピック数を変えてモデルを作成し、それぞれの値を算出して最適なトピック数を決めることになる。

ただ、PerplexityもCoherenceも絶対的な指標ではなく、これらを使ったからといって最適なトピック数が必ず決められるわけではない。今回は青空文庫にある夏目漱石の『こころ』のテキストを章ごとに分割してLDAモデルを作成し、PerplexityとCoherenceを求めてみる。


環境


WSL2(Ubuntu20.04)上にJupyter LabとMeCabがインストールされた環境。形態素解析を行うためのMeCabのインストール方法はMeCab: Yet Another Part-of-Speech and Morphological Analyzerを参照。
 

以下のPythonライブラリを使用するのでインストール。


テキストデータの準備

テキストデータを準備する。青空文庫にある夏目漱石の『こころ』を章ごとに形態素解析する。この処理には青空文庫にある夏目漱石の『こころ』のテキストを章ごとに分割して形態素解析するのコードを使う。このコードを実行すると、以下のように章ごとの単語リストが作成される。

[['人', '常に', '先生', '呼ぶ', '先生', '書く', '本名', '打ち明ける', '世間', 'かる', '遠慮', 'いう', '自然', '人', '記憶', '呼ぶ', '起す', '先生', 'いう', 'なる', '筆', '執る', '心持', 'よそよそしい', '頭文字', '使う', 'なる', '先生', '知り合い', 'なる', '鎌倉', '若々しい', '書生', '暑中', '休暇', '利用', 'する', '海水浴', '行く', '友達', 'ぜひ', '来る', ...


PerplexityとCoherenceの値を算出

続いてトピック数5から150まで5ずつ増やしてPythonライブラリのGensimでLDAモデルを作成し、算出したPerplexityとCoherenceの値をグラフ化する。Gensimには4種類のCoherence算出方法がある(Topic coherence pipeline)が、今回はu_massを使う。

%matplotlib inline

import pandas as pd
from gensim.corpora import Dictionary
from gensim.models import LdaModel, CoherenceModel
import matplotlib.pyplot as plt

from preprocess_kokoro import preprocess

# 『こころ』のテキストを章ごとに形態素解析
texts = preprocess()

dct = Dictionary(texts)
corpus = [dct.doc2bow(t) for t in texts]

def compute_metrics(model, texts, corpus, dct):
    # Perplexity算出
    pp = np.exp2(-model.log_perplexity(corpus))

    # Coherenceの算出
    cm = CoherenceModel(model=model, texts=texts, dictionary=dct, coherence='u_mass')
    ch = cm.get_coherence()

    return pp, ch

# トピック数を5から150まで5ずつ増やしてPerplexityとCoherenceを算出
perplexity = []
coherence = []
num_topic_range = range(5, 151, 5)
for num_topics in num_topic_range:
    model = LdaModel(corpus=corpus, id2word=dct, num_topics=num_topics, minimum_probability=0.0, random_state=0)
    pp, ch = compute_metrics(model, texts, corpus, dct)
    perplexity.append(pp)
    coherence.append(ch)

metrics = pd.DataFrame({'num_topic': num_topic_range, 'perplexity': perplexity, 'coherence': coherence})

# PerplexityとCoherenceをグラフ化
fig, ax1 = plt.subplots(figsize=(9, 6))
metrics.plot(x='num_topic', y='perplexity', color='b', legend=False, ax=ax1)
ax1.set_xlabel('num_topics')
ax1.set_ylabel('perplexity', color='b')
ax1.tick_params('y', colors='b')

ax2 = ax1.twinx()
metrics.plot(x='num_topic', y='coherence', color='r', legend=False, ax=ax2)
ax2.set_ylabel('coherence', color='r')
ax2.tick_params('y', colors='r')

fig.tight_layout()
plt.show()

結果は以下の通り。Perplexityは低い数値、Coherenceは高い数値が良いとされているが、それを適用するとトピック数最小の5が最適なトピック数ということになる。この結果を見る限り、今回のテキストデータについてはPerplexityとCoherenceは使えなさそう。


ldaturningによるトピック数の決定

ldaturningというRのパッケージがあって、これを使うとトピック数を決める指標となる4種類の値を算出してくれるらしい。Select number of topics for LDA modelにそのサンプルコードがある。このパッケージを使って、最適なトピック数を求めてみる。


データの準備

PerplexityとCoherenceを算出したときと同じデータを使いたいので、Pythonでベクトル化したデータをcsvに出力して、それをRで読み込む。以下はPythonでベクトルデータ(kokoro_vec.csv)を作成するコード。

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer

from preprocess_kokoro import preprocess

# 『こころ』のテキストを章ごとに形態素解析
texts = preprocess()

# 単語リストをタブ区切りテキストに変換
tab_separated = ['\t'.join(t) for t in texts]

# tokenizerでタブで分割するように指定
vectorizer = CountVectorizer(lowercase=False, tokenizer=lambda x: x.split('\t'))
vec = vectorizer.fit_transform(tab_separated)

df = pd.DataFrame(vec.toarray(), columns=vectorizer.get_feature_names())
display(df.head())
df.to_csv('kokoro_vec.csv', index=False, header=True)

PandasのDataFrameの中身は以下のとおり。









R環境の準備


RStudioを使ってもいいのだが、ここではJupyter LabでRを使えるようにする。まずはRのインストール。


Jupyter Lab上でRを使えるようにするためにIRkernelをインストールする。

これでJupyter LabでRが使える。














続いてldaturningに必要なパッケージをインストールする

最後にRのライブラリをインストール。


ldaturningで指標の算出

Rのライブラリldaturningを使うにはdocument-term matrix形式のデータ(DocumentTermMatrix)が必要。まずはPythonで作成したベクトルデータ(kokoro_vec.csv)からDocumentTermMatrixを作成し、ldaturningでトピック数が5から150まで5ずつ増やしていき指標値を算出する。さらにその結果をグラフ化する。

library("ldatuning")
library("dplyr")
library("tm")

# Pythonで作成したベクトルデータ読み込んでDocumentTermMatrix形式に変換
dtm <- read.csv("kokoro_vec.csv") %>% as.DocumentTermMatrix(weighting = weightTf)

# 4種類の指標値の算出
result = FindTopicsNumber(
    dtm,
    topics = seq(from = 5, to = 150, by = 5),
    metrics = c("Griffiths2004", "CaoJuan2009", "Arun2010", "Deveaud2014"),
    method = "Gibbs",
    control = list(seed = 77),
    mc.cores = 2L,
    verbose = TRUE
)

# 結果のグラフ化
ldatuning::FindTopicsNumber_plot(values = result)

結果は次の通り。4つの指標のうちCaoJuan2009とArun2010は小さいほど、Griffiths2004とDeveaud2014は大きいほどよい。この結果をみるとトピック数20あたりがよさそう。














1 件のコメント:

  1. 素晴らしい記事をありがとうございます!!!

    返信削除