前回に続いてCoherence(UMass)の話です。
今回は実際にプログラムで算出してみます。
Perplexityの時は、架空の言語で実験していましたが、あのデータではCoherenceを試すのには都合が悪いことが多いので、
久しぶりに 20newsgroups のデータを使います。
とりあえず読み込んで単語区切りの配列にしておきます。
簡単な前処理として小文字に統一し、アルファベット以外の文字は除去しておきましょう。
from sklearn.datasets import fetch_20newsgroups
import re
# データ読み込み
twenty = fetch_20newsgroups(
subset="all",
remove=('headers', 'footers', 'quotes')
)
texts = twenty.data
# 簡単な前処理
# 小文字に統一
texts = [text.lower() for text in texts]
# アルファベット以外は除去
texts = [re.sub("[^a-z]+", " ", text).strip() for text in texts]
# 空白で区切り配列に変換
texts = [text.split(" ") for text in texts]
モデルを学習しておきます。
from gensim.corpora.dictionary import Dictionary
from gensim.models import LdaModel
# 単語と単語IDを対応させる辞書の作成
dictionary = Dictionary(texts)
# 出現が20回未満の単語と、50%より多くのドキュメントに含まれる単語は除く
dictionary.filter_extremes(no_below=20, no_above=0.5)
# LdaModelが読み込めるBoW形式に変換
corpus = [dictionary.doc2bow(text) for text in texts]
# トピック数 20 でモデル作成
lda = LdaModel(corpus, num_topics=20, id2word=dictionary)
# 学習結果
lda.show_topics(num_topics=20, num_words=5)
"""
[(0, '0.017*"was" + 0.016*"were" + 0.015*"they" + 0.010*"on" + 0.009*"with"'),
(1, '0.025*"you" + 0.018*"be" + 0.016*"this" + 0.013*"on" + 0.011*"if"'),
(2, '0.031*"key" + 0.014*"chip" + 0.013*"s" + 0.013*"be" + 0.013*"this"'),
(3, '0.019*"he" + 0.016*"s" + 0.014*"game" + 0.013*"team" + 0.012*"was"'),
(4, '0.154*"ax" + 0.067*"m" + 0.036*"p" + 0.034*"q" + 0.032*"f"'),
(5, '0.018*"not" + 0.014*"you" + 0.013*"as" + 0.013*"are" + 0.013*"be"'),
(6, '0.014*"with" + 0.014*"this" + 0.013*"have" + 0.012*"windows" + 0.011*"or"'),
(7, '0.019*"have" + 0.016*"my" + 0.013*"if" + 0.013*"with" + 0.012*"this"'),
(8, '0.016*"s" + 0.015*"as" + 0.012*"you" + 0.012*"be" + 0.012*"are"'),
(9, '0.016*"x" + 0.014*"on" + 0.011*"or" + 0.009*"are" + 0.009*"with"'),
(10, '0.017*"with" + 0.017*"scsi" + 0.015*"card" + 0.015*"mb" + 0.013*"x"'),
(11, '0.016*"echo" + 0.014*"surface" + 0.013*"planet" + 0.012*"launch" + 0.011*"moon"'),
(12, '0.045*"x" + 0.013*"section" + 0.011*"s" + 0.011*"file" + 0.010*"if"'),
(13, '0.025*"image" + 0.017*"you" + 0.014*"or" + 0.011*"file" + 0.011*"can"'),
(14, '0.015*"on" + 0.013*"s" + 0.011*"by" + 0.011*"be" + 0.009*"from"'),
(15, '0.031*"you" + 0.020*"t" + 0.020*"they" + 0.014*"have" + 0.013*"s"'),
(16, '0.024*"edu" + 0.013*"by" + 0.012*"from" + 0.012*"com" + 0.008*"university"'),
(17, '0.049*"he" + 0.044*"was" + 0.029*"she" + 0.019*"her" + 0.013*"s"'),
(18, '0.013*"are" + 0.011*"with" + 0.010*"or" + 0.010*"car" + 0.010*"s"'),
(19, '0.020*"power" + 0.019*"myers" + 0.014*"g" + 0.014*"e" + 0.012*"period"')]
"""
ストップワードの除去などの前処理をもっと真面目にやった方が良さそうな結果になってますね。。。
一旦今回の目的の Coherence の計算に進みます。
実はこの学習したldaのオブジェクト自体も算出用のメソッドを持っているのですが、
それとは別にCoherenceの計算専用のクラスがあります。
参考: models.coherencemodel
これを使うのが簡単でしょう。
from gensim.models.coherencemodel import CoherenceModel
# Coherenceの計算
cm = CoherenceModel(
model=lda,
corpus=corpus,
dictionary=dictionary,
coherence='u_mass', # Coherenceの算出方法を指定。 (デフォルトは'c_v')
topn=20 # 各トピックの上位何単語から算出するか指定(デフォルト20)
)
print(cm.get_coherence())
# -1.661056661022431
簡単に得られましたね。
前の記事で UMass Coherence はトピックごとに求まると言う話をしました。
それは、get_coherence_per_topic()で得られます。
coherence_per_topic = cm.get_coherence_per_topic()
print(coherence_per_topic)
"""
[
-0.6804875178873847,
-0.7418651773889635,
-2.112586843668905,
-1.5566020659262867,
-1.3794139986539538,
-0.9397322672431955,
-0.9144876198536442,
-1.050640800753007,
-0.791666801060629,
-1.5573334717678569,
-1.7592326101494569,
-2.4339787874196244,
-3.4187325854772057,
-1.4492302603021243,
-0.8756627315871455,
-0.8056235761203832,
-2.121420273613335,
-3.5341207908402237,
-1.1732696265877514,
-3.925045414147548
]
"""
# 平均を計算 (cm.get_coherence()と一致する)
print(sum(coherence_per_topic)/len(coherence_per_topic))
# -1.661056661022431
配列は0番目のトピックから順番にそれぞれのCoherenceを示します。
この他、ldaオブジェクトが持っている、 top_topics() というメソッドでもCoherenceを得られます。
Get the topics with the highest coherence score the coherence for each topic.
というドキュメントの説明通り、本来は、coherenceが高いtopicsを求めるためのものなので注意が必要です。
(高い順にソートされてしまうこととか、topnの引数が小さいと途中で打ち切られることとか。)
何故かこれは coherenceを算出する方法のデフォルトが’u_mass’です。(CoherenceModelは’c_v’なのに。)
このような感じで使います。
lda.top_topics(corpus=corpus, coherence='u_mass', topn=20)
"""
[([(0.017169138, 'was'),
(0.015623925, 'were'),
(0.015399945, 'they'),
(0.010244668, 'on'),
(0.00926054, 'with'),
(0.009065351, 'had'),
(0.0085669095, 'their'),
(0.008559594, 'by'),
(0.00840216, 's'),
(0.008026317, 'at'),
(0.007591937, 'from'),
(0.007375864, 'as'),
(0.0072524482, 'are'),
(0.007035575, 'have'),
(0.00656652, 'all'),
(0.0062510483, 'or'),
(0.0058769537, 'who'),
(0.005784945, 'this'),
(0.0057791225, 'there'),
(0.0056513455, 'but')],
-0.6804875178873847),
# 長いので2トピック目以降の結果は略
]
"""
トピックごとのタプルの配列が戻り、タプルの2番目の要素がcoherenceです。
少し扱いにくいですね。