Shingoの数学ノート

プログラミングと機械学習のメモ

PythonでLDA(トピックモデル)の実装

日付:    カテゴリ: 自然言語処理


こんにちは!

今回は、自然言語処理の続きで、自然言語処理におけるLDA(トピックモデル)の使い方について説明します。

なお、LDAの内部の理論はとても難しいので、LDAの概要だけ説明します。

イメージを掴んで欲しいのが第一のため、正確でない部分も多々ありますが、ご容赦ください。

LDAの概要

LDAは教師なし学習の一種で、文書のクラスタリングを行います。

LDA のモデリングの際、以下の仮定を置きます。

  • 文書は複数の潜在トピックから構成され、構成比は離散確率分布で表される。(「文書-トピック分布」と呼ぶ)
  • 潜在トピックは単語の出現分布として表される。(「トピック-単語分布」と呼ぶ)
  • 文書中の単語は、それぞれ「文書-トピック分布」から生成される潜在トピックをもち、「トピック分布-単語分布」から単語が生成されると仮定する。

何を言っているかよくわからないと思うので、例を説明します。

例えば、あなたがニュースを分類しようとします。

そこで、あなたはスポーツの記事、政治の記事、音楽の記事などのトピックごとに分類します。

この記事はスポーツ記事である確率は60%、政治の記事である確率30%、音楽の記事である確率が10%かな、という感じで分けます。

この分布を「文書-トピック分布」と呼びます。

ところで、このスポーツ記事、政治記事、音楽記事はどのように決めているのでしょうか。

これは、それぞれの記事で特徴となっている単語の量で決定します。

スポーツ記事であれば、「野球」、「サッカー」、「テニス」等、 政治記事であれば、「内閣」、「政府」、「政策」等、 音楽記事であれば、「ライブ」、「バンド」、「オリコン」等が出てきやすいのではないでしょうか。

これを、それぞれの記事ごとに単語の分布(構成比)として表現します。

スポーツ記事は野球10%、サッカー9%、テニス8%みたいな感じです。

この分布を「トピック-単語分布」と呼びます。

lda1

LDAでは、この「文書-トピック分布」と「トピック-単語分布」を求めることをタスクとします。

機械では、スポーツ、政治、音楽とラベル付けすることはできないので、「トピック-単語分布」からトピックのラベルを分析者が決定します。

クラスタリングをするなら、文書-トピック分布において、最も高い確率のトピックを選択すれば良いです。

PythonでLDAを実装

追記20200223:githubにコードを載せました。

https://github.com/Shingo425/NLP/blob/main/src/LDA_sample.ipynb

では実際にLDAを実装してみましょう。

必要なモジュールをインポートします。

import pandas as pd
import MeCab
import gensim
import numpy as np
import matplotlib.pyplot as plt

テキストデータはライブドアコーパスを使用します。

ライブドアコーパスの抽出については以下を参考にしています。

https://nxdataka.netlify.app/ldncsv/

「livedoornews.csv」をソースコードと同じ場所に置きましょう。

こんな感じの中身です。

livedoor = pd.read_csv("livedoornews.csv")
livedoor.head()
lda2

まずは、形態素解析です。

def parse(tweet_temp):
    t = MeCab.Tagger()
    temp1 = t.parse(tweet_temp)
    temp2 = temp1.split("\n")
    t_list = []
    for keitaiso in temp2:
        if keitaiso not in ["EOS",""]:
            word,hinshi = keitaiso.split("\t")
            t_temp = [word]+hinshi.split(",")
            if len(t_temp) != 10:
                t_temp += ["*"]*(10 - len(t_temp))
            t_list.append(t_temp)

    return t_list

def parse_to_df(tweet_temp):
    return pd.DataFrame(parse(tweet_temp),
                        columns=["単語","品詞","品詞細分類1",
                                 "品詞細分類2","品詞細分類3",
                                 "活用型","活用形","原形","読み","発音"])

次に、単語をBag-of-Words形式で保存し、LDAを使いやすい形に変形します。今回は、一般名詞と固有名詞のみを抽出します。

def make_lda_docs(texts):
    docs = []
    for text in texts:
        df = parse_to_df(text)
        extract_df = df[(df["品詞"]+"/"+df["品詞細分類1"]).isin(["名詞/一般","名詞/固有名詞"])]
        extract_df = extract_df[extract_df["原形"]!="*"]
        doc = []
        for genkei in extract_df["原形"]:
            doc.append(genkei)
        docs.append(doc)
    return docs

texts = livedoor["body"].values
docs = make_lda_docs(texts)
dictionary = gensim.corpora.Dictionary(docs)
corpus = [dictionary.doc2bow(doc) for doc in docs]

これでldaに入力するデータの作成が完了しました。

ちなみに、テキストに対して以下のような単語に分けています。

lda3

さて、実際にLDAを使用してみましょう。

今回は、クラスターをとりあえず6個にして分割してみます。

クラスターの数は、何回も回してみて決めましょう。

n_cluster = 6
lda = gensim.models.LdaModel(
                corpus=corpus,
                id2word=dictionary,
                num_topics=n_cluster, 
                minimum_probability=0.001,
                passes=20, 
                update_every=0, 
                chunksize=10000,
                random_state=1
                )

LDAのパラメータについては以下を参照してください。

https://radimrehurek.com/gensim/models/ldamodel.html

最後に、学習したldaのベクトルをarrに格納します。

corpus_lda = lda[corpus]
arr = gensim.matutils.corpus2dense(
        corpus_lda,
        num_terms=n_cluster
        ).T

トピック-単語分布の可視化

実際にトピック-単語分布を見てみましょう。

お手軽に見るなら「lda.show_topics()」で見れますが、しっかり可視化するなら、棒グラフやDataFrameで表示すると見やすいです。

まずは棒グラフで表示する方法から。

lists = []
for i in range(n_cluster):
    temp_df = pd.DataFrame(lda.show_topic(i),columns=["word","score"])
    temp_df["topic"] = i
    lists.append(temp_df)
topic_word_df = pd.concat(lists,ignore_index=True)

fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(20, 10))
for i,gdf in topic_word_df.groupby("topic"):
    gdf.set_index("word")["score"].sort_values().plot.barh(
        ax=axes[i//3, i%3],
        title="topic {} のトピック-単語分布".format(i),
        color="blue")
lda4

次はテーブルで順位を表示してみます。

topic_word_df["rank"] = topic_word_df.groupby("topic")["score"].rank()
topic_word_df.pivot(index='topic', columns='rank', values='word')
lda5

topic1と2はなんとなくスポーツの話題、3はIT系の話題、4は映画の話題、5は男女の話題みたいな意味付けができそうな気がしますね。

文書-トピック分布の可視化

ライブドアニュースコーパスでは、メディアの媒体データも付与されています。

そこで、それぞれのトピックについて、どんなメディアの文書の構成になっているかを可視化してみます。

今回は、それぞれの文書に最も高い確率のトピックを割り当てました。

livedoor_predict = livedoor.copy()
# topicの付与
livedoor_predict["pred_topic"] = np.argmax(arr,axis=1)
livedoor_predict["score"] = np.max(arr,axis=1)
cross = pd.crosstab(livedoor_predict["media"],livedoor_predict["pred_topic"])
# トピックの文書割合の可視化
fig, ax = plt.subplots(figsize=(10, 8))
for i in range(len(cross)):
    ax.barh(y=cross.columns, width = cross.iloc[i].values[::-1], left=cross.iloc[:i].sum()[::-1].values,tick_label=cross.columns[::-1])
ax.set(xlabel='個数', ylabel='トピック')
ax.legend(cross.index)
plt.show()
lda6

トピック1,2はsports-watch、3はit-life-hackやkaden-channel,smax、4はmovie-enter、5はdokujo-tsushinが多くなりました。トピック-単語分布から想像していたものと近い結果が得られたのではないでしょうか。

pyldavizによる可視化

上記の方法はまあまあプログラミングが必要でしたが、実は簡単に可視化することもできます。それがpyldavizです。以下のように使います。

import pyLDAvis
import pyLDAvis.gensim
pyLDAvis.enable_notebook()

vis = pyLDAvis.gensim.prepare(lda, corpus, dictionary, sort_topics=False)
vis

htmlで保存することもできます。

pyLDAvis.save_html(vis, 'pyldavis_output.html')

実際の結果はこんな感じになります。

pyldavisの結果

まとめ

今回は、自然言語処理のトピックモデルの実践について解説していきました。文書がどんな性質を持っているのか可視化してみたいときはとても便利ですので、ぜひ使用してみてください!

参考文献

トピックモデルによる統計的潜在意味解析

Comment Box is loading comments...