Abnormally Distributed

統計解析担当のライフサイエンス研究者 -> データサイエンティスト@コンサル

LDAによるトピックモデル ②gensimによる実装

前回の記事ではLDAの概要や関連手法、確率モデルについて書いた。
今回はPythonのgensimというライブラリを用いて、LDAを実践してみる。
その前に、前回触れていなかったトピックモデルの評価方法について説明する。

評価指標

LDAは教師なしモデルであり、精度等の指標で評価することはできない。
一般的には、PerplexityやCoherenceといった指標が用いられる。

Perplexity

予測性能に関する指標。

定義は下記の通りで、単語ごとの尤度の幾何平均の逆数である。Mは(学習データの)文書数、Ndは文書dの単語数、wdは文書dにおける各単語の出現回数を表す。
教師なし学習では、テストデータの尤度が大きくなるほど、つまりpeplexityが小さくなるほど好ましい。

 \displaystyle perplexity(D _ {test}) = \exp \left(- \frac{\sum _ {d=1} ^ {M} \log p(w _ d)} {\sum _ {d=1} ^ {M} N _ d} \right)

直感的な理解についてはこちらを参照。
トピックモデルの評価指標 Perplexity とは何なのか?

要はPeplexityは選択肢の数を表している。
ある文書の1単語を決める際、何も仮定がない状態ではvocabulary中の全単語が候補になるが、モデルの元では候補となる単語がもっと絞り込めるようになる。この絞り込んだあとの候補数を表すのがPerplexityである。

Coherence

トピックの品質の良さ、人間にとっての解釈しやすさを表す指標。様々な計算方法が提唱されている。 例として、下記に2つのトピックとそのトピックにおいて出現頻度が高い単語を示す。

Topic1: guitar album track bass drum vocal
Topic2: know call name several refer hunter

Topic1は音楽に関するトピックだと明らかだが、Topic2は何を表しているのかはっきりわからない。この場合、Topic1の方がcoherenceが高くなるべきである。

coherenceが高いのは各トピックに含まれる単語の類似度が高い場合である。そこで、coherenceの算出方法としては、各トピックの上位N個の単語を抽出し、その中の各単語ペアについて何らかの類似度を計算して全ペアでの平均値を求めることが多い。
類似度の算出方法については、いくつかの方法が提唱されている。

coherenceの評価基準としては、トピックの良し悪しについて人がつけた正解ラベルとどの程度相関があるかになる。

各手法について、詳しくはこちらを参照。
トピックモデルの評価指標 Coherence 研究まとめ #トピ本

"Exploring the Space of Topic Coherence Measures" M. Röder, A Both, A. Hinneburg, 2015
https://dl.acm.org/doi/10.1145/2684822.2685324
gensimのCoherenceModelはこちらの論文に基づいている。

gensimによる実装

Kaggleのデータセットを用いる。
約20万件のニュース記事の見出しと短い説明文を含む。また、各記事のカテゴリーも記載されており、抽出されたトピックとの関連を確認することもできそう。

コードはKaggleのNotebookとして動かせるようになっている。
LDA_gensim | Kaggle

from time import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from IPython.core.display import display
pd.set_option('display.max_colwidth', 200)
%matplotlib inline

# load data
df = pd.read_json('../input/news-category-dataset/News_Category_Dataset_v2.json', lines=True)
df.sample(3)
category headline authors link short_description date
134770 HOME & LIVING Exploiting Familial Ideals for a Car Alissa Fleck, Contributor\nAlissa Fleck is a reporter based in New York City. https://www.huffingtonpost.com/entry/exploiting-familial-ideal-for-car-commercials_us_5b9dc09fe4b03a1dcc8c71ce Is anyone else more than a little crushed when images of gooey, familial perfection splash dazzlingly across the screen to a backdrop of dramatic music swelling to an emotional climax, only to hav... 2014/1/2
5634 POLITICS Why You Shouldn't Freak Out Even If The Stock Market Does Zach Carter https://www.huffingtonpost.com/entry/stock-market-dont-freak-out_us_5a846bc5e4b0774f31d15aaf Everything is fine ... except your paycheck. 2018/2/14
944 ENTERTAINMENT R. Kelly Accusers Say They Had To Ask Permission To Use The Bathroom Sara Boboltz https://www.huffingtonpost.com/entry/r-kelly-accusers-bathroom-permission_us_5af1e468e4b0c4f19327a1f3 And when they did, the women said he demanded they call him "Daddy." 2018/5/8

次に、テキストの前処理をする。
headlineとshort descriptionをつなげたデータを用いる。

from gensim.parsing.preprocessing import preprocess_string

combined_text = df['headline'] + df['short_description']
processed_text = combined_text.map(preprocess_string)

gensimのpreprocess_stringを使うと、テキストデータの一般的な前処理を一通り適用できる。 デフォルトでは下記7つの処理を実行する。

  1. strip_tags(): HTMLタグ等の除去
  2. strip_punctuation(): 句読点の除去
  3. strip_multiple_whitespaces(): 複数の空白の除去
  4. strip_numeric(): 数字の除去
  5. remove_stopwords(): ストップワード(頻出単語)の除去
  6. strip_short(): 短い単語の除去(デフォルトでは3文字未満)
  7. stem_text(): 小文字にして、語幹のみ抽出(porter-stemmerの適用)

実行前
Hugh Grant Marries For The First Time At Age 57The actor and his longtime girlfriend Anna Eberstein tied the knot in a civil ceremony.

実行後
[hugh, grant, marri, time, ag, actor, longtim, girlfriend, anna, eberstein, ti, knot, civil, ceremoni]

Porter stemmerは古典的なステマーだが、age→ag、tied→tiなど元の単語が分かりにくくなるものも多く、正直微妙かもしれない。

次に、各単語に番号を割り振ったディクショナリーを作成する。この際、極端に出現頻度が少ない単語や多い単語は除外する(filter_extremes)。 さらに、ディクショナリーを用いて前処理済みのデータをbag-of-words表現に変換する。

from gensim.corpora.dictionary import Dictionary

# Create a corpus from a list of texts
dictionary = Dictionary(processed_text)
dictionary.filter_extremes(no_below=10, no_above=0.7, keep_n=100000)

corpus = [dictionary.doc2bow(text) for text in processed_text]
corpus[10]
[(7, 1),
 (8, 1),
 (101, 1),
 (102, 1),
 (103, 1),
 (104, 1),
 (105, 1),
 (106, 1),
 (107, 1),
 (108, 1),
 (109, 1),
 (110, 1),
 (111, 1),
 (112, 1)]

このように、文章は(単語id, 出現回数)のリストで表現されている。

from gensim.models import LdaModel, LdaMulticore
lda_model = LdaMulticore(corpus, num_topics=20, id2word=dictionary, random_state=42, workers=3)

print(lda_model.log_perplexity(corpus))
print()

# Get the most significant topics 
for idx, topic in lda_model.print_topics(-1): # -1 for all topics
    print('Topic: {} Word: {}'.format(idx, topic))

log-perprexityは-9.15となった。 また、各トピックにおける単語の出現確率を高い順に出力している。

-9.149718461222365

Topic: 0 Word: 0.013*"tip" + 0.013*"food" + 0.013*"cancer" + 0.011*"life" + 0.010*"sleep" + 0.007*"help" + 0.007*"studi" + 0.006*"research" + 0.006*"need" + 0.006*"risk"
Topic: 1 Word: 0.030*"dai" + 0.024*"parent" + 0.018*"children" + 0.018*"need" + 0.012*"time" + 0.011*"know" + 0.009*"help" + 0.009*"kid" + 0.009*"child" + 0.009*"learn"
Topic: 2 Word: 0.021*"marriag" + 0.011*"anim" + 0.009*"gai" + 0.008*"appl" + 0.007*"kid" + 0.006*"famili" + 0.006*"time" + 0.006*"sex" + 0.006*"peopl" + 0.005*"coupl"
Topic: 3 Word: 0.027*"new" + 0.018*"york" + 0.016*"citi" + 0.015*"yoga" + 0.013*"exercis" + 0.010*"summer" + 0.008*"practic" + 0.006*"spiritu" + 0.005*"chees" + 0.005*"want"
Topic: 4 Word: 0.026*"fashion" + 0.025*"photo" + 0.017*"year" + 0.014*"video" + 0.011*"old" + 0.010*"style" + 0.009*"men" + 0.008*"school" + 0.008*"game" + 0.007*"week"
Topic: 5 Word: 0.027*"year" + 0.011*"babi" + 0.011*"best" + 0.010*"dai" + 0.010*"weight" + 0.010*"mother" + 0.010*"old" + 0.010*"mom" + 0.009*"hotel" + 0.009*"spring"
Topic: 6 Word: 0.013*"peopl" + 0.008*"kill" + 0.008*"polic" + 0.007*"cocktail" + 0.006*"pari" + 0.006*"china" + 0.006*"year" + 0.006*"video" + 0.005*"said" + 0.005*"human"
Topic: 7 Word: 0.023*"obama" + 0.013*"recip" + 0.010*"presid" + 0.009*"flavor" + 0.009*"dish" + 0.008*"trump" + 0.007*"grill" + 0.007*"late" + 0.007*"jimmi" + 0.006*"question"
Topic: 8 Word: 0.018*"red" + 0.015*"morn" + 0.011*"decor" + 0.009*"carpet" + 0.009*"ic" + 0.009*"welcom" + 0.008*"new" + 0.007*"fruit" + 0.007*"cream" + 0.006*"video"
Topic: 9 Word: 0.025*"love" + 0.015*"divorc" + 0.015*"life" + 0.012*"live" + 0.011*"photo" + 0.010*"time" + 0.010*"dai" + 0.010*"thing" + 0.008*"wai" + 0.008*"like"
Topic: 10 Word: 0.039*"photo" + 0.024*"wed" + 0.016*"check" + 0.013*"twitter" + 0.012*"huffpost" + 0.011*"look" + 0.011*"week" + 0.011*"facebook" + 0.011*"style" + 0.010*"video"
Topic: 11 Word: 0.014*"new" + 0.014*"super" + 0.012*"bowl" + 0.010*"workout" + 0.009*"resolut" + 0.008*"state" + 0.007*"america" + 0.006*"media" + 0.006*"polit" + 0.005*"trump"
Topic: 12 Word: 0.022*"studi" + 0.014*"new" + 0.010*"tumblr" + 0.010*"diseas" + 0.008*"peopl" + 0.007*"kate" + 0.007*"heart" + 0.007*"come" + 0.006*"time" + 0.006*"american"
Topic: 13 Word: 0.017*"new" + 0.011*"dai" + 0.010*"like" + 0.010*"video" + 0.008*"dad" + 0.008*"look" + 0.007*"photo" + 0.007*"eat" + 0.007*"gift" + 0.007*"year"
Topic: 14 Word: 0.030*"health" + 0.022*"care" + 0.011*"tax" + 0.008*"patient" + 0.008*"parti" + 0.007*"work" + 0.006*"want" + 0.006*"plan" + 0.006*"help" + 0.006*"republican"
Topic: 15 Word: 0.013*"click" + 0.013*"video" + 0.009*"clinton" + 0.009*"com" + 0.008*"star" + 0.008*"new" + 0.008*"photo" + 0.008*"hillari" + 0.007*"luxuri" + 0.007*"dog"
Topic: 16 Word: 0.014*"world" + 0.012*"healthi" + 0.012*"recip" + 0.011*"food" + 0.010*"wai" + 0.008*"chang" + 0.007*"time" + 0.006*"eat" + 0.006*"wine" + 0.006*"dii"
Topic: 17 Word: 0.031*"photo" + 0.011*"dress" + 0.010*"bodi" + 0.010*"look" + 0.010*"art" + 0.008*"like" + 0.008*"design" + 0.007*"diet" + 0.007*"green" + 0.006*"wai"
Topic: 18 Word: 0.014*"medit" + 0.011*"vintag" + 0.008*"bank" + 0.008*"san" + 0.007*"new" + 0.006*"ebai" + 0.006*"home" + 0.005*"googl" + 0.005*"sandi" + 0.005*"gun"
Topic: 19 Word: 0.016*"pinterest" + 0.015*"new" + 0.009*"water" + 0.007*"loss" + 0.007*"fit" + 0.007*"american" + 0.006*"park" + 0.006*"wall" + 0.006*"drink" + 0.005*"evolut"

WordCloudでトピックを可視化する。

from wordcloud import WordCloud
from PIL import Image
 
fig, axs = plt.subplots(ncols=4, nrows=int(lda_model.num_topics/4), figsize=(15,10))
axs = axs.flatten()
 
def color_func(word, font_size, position, orientation, random_state, font_path):
    return 'darkturquoise'

 
for i, t in enumerate(range(lda_model.num_topics)):
 
    x = dict(lda_model.show_topic(t, 30))
    im = WordCloud(
        background_color='white',
        color_func=color_func,
        random_state=0
    ).generate_from_frequencies(x)
    axs[i].imshow(im)
    axs[i].axis('off')
    axs[i].set_title('Topic '+ str(t))
        
plt.tight_layout()
plt.savefig('/kaggle/working/wordcloud.png')

f:id:kibya:20200517145648p:plain

ある程度は解釈できそうなトピックが見られる。 例えばTopic0はcancer, food, sleep, research, riskなどから、生活習慣と疾病リスクに関する研究結果の記事などが想像される。
またTopic1はparent, children, kid, learnなどから子供や教育に関するトピックだと思われる。

最後に、coherenceを算出する。top_topicsメソッドを用いると、各トピックのcoherenceと出現確率の高い単語を、coherenceの高い順に表示できる。
デフォルトでは、coherenceはu-massと呼ばれる手法で算出する。

topics = lda_model.top_topics(corpus, topn=10)

print('coherence: top words')
for topic in topics:
    print('{:.3f}: {}'.format(topic[1], ' '.join([i[1] for i in topic[0]])))
coherence: top words
-2.436: photo wed check twitter huffpost look week facebook style video
-2.708: love divorc life live photo time dai thing wai like
-2.939: dai parent children need time know help kid child learn
-3.486: new dai like video dad look photo eat gift year
-3.514: tip food cancer life sleep help studi research need risk
-3.548: health care tax patient parti work want plan help republican
-3.575: fashion photo year video old style men school game week
-3.992: photo dress bodi look art like design diet green wai
-4.208: world healthi recip food wai chang time eat wine dii
-4.223: year babi best dai weight mother old mom hotel spring
-4.578: marriag anim gai appl kid famili time sex peopl coupl
-4.654: studi new tumblr diseas peopl kate heart come time american
-4.664: new super bowl workout resolut state america media polit trump
-5.238: peopl kill polic cocktail pari china year video said human
-5.576: click video clinton com star new photo hillari luxuri dog
-6.067: red morn decor carpet ic welcom new fruit cream video
-6.260: new york citi yoga exercis summer practic spiritu chees want
-6.344: obama recip presid flavor dish trump grill late jimmi question
-6.711: pinterest new water loss fit american park wall drink evolut
-8.871: medit vintag bank san new ebai home googl sandi gun

coherenceが上位のトピックは確かに関連する単語が多いが、下位のトピックは一見して無関係の単語が多くなっており、たしかに人間の感覚と一致しているようだ。