プログラミングと機械学習のメモ
日付: カテゴリ: 自然言語処理
こんにちは!
今回は、自然言語処理の続きで、自然言語処理におけるLDA(トピックモデル)の使い方について説明します。
なお、LDAの内部の理論はとても難しいので、LDAの概要だけ説明します。
イメージを掴んで欲しいのが第一のため、正確でない部分も多々ありますが、ご容赦ください。
LDAは教師なし学習の一種で、文書のクラスタリングを行います。
LDA のモデリングの際、以下の仮定を置きます。
何を言っているかよくわからないと思うので、例を説明します。
例えば、あなたがニュースを分類しようとします。
そこで、あなたはスポーツの記事、政治の記事、音楽の記事などのトピックごとに分類します。
この記事はスポーツ記事である確率は60%、政治の記事である確率30%、音楽の記事である確率が10%かな、という感じで分けます。
この分布を「文書-トピック分布」と呼びます。
ところで、このスポーツ記事、政治記事、音楽記事はどのように決めているのでしょうか。
これは、それぞれの記事で特徴となっている単語の量で決定します。
スポーツ記事であれば、「野球」、「サッカー」、「テニス」等、 政治記事であれば、「内閣」、「政府」、「政策」等、 音楽記事であれば、「ライブ」、「バンド」、「オリコン」等が出てきやすいのではないでしょうか。
これを、それぞれの記事ごとに単語の分布(構成比)として表現します。
スポーツ記事は野球10%、サッカー9%、テニス8%みたいな感じです。
この分布を「トピック-単語分布」と呼びます。
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()
まずは、形態素解析です。
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に入力するデータの作成が完了しました。
ちなみに、テキストに対して以下のような単語に分けています。
さて、実際に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")
次はテーブルで順位を表示してみます。
topic_word_df["rank"] = topic_word_df.groupby("topic")["score"].rank()
topic_word_df.pivot(index='topic', columns='rank', values='word')
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()
トピック1,2はsports-watch、3はit-life-hackやkaden-channel,smax、4はmovie-enter、5はdokujo-tsushinが多くなりました。トピック-単語分布から想像していたものと近い結果が得られたのではないでしょうか。
上記の方法はまあまあプログラミングが必要でしたが、実は簡単に可視化することもできます。それが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')
実際の結果はこんな感じになります。
今回は、自然言語処理のトピックモデルの実践について解説していきました。文書がどんな性質を持っているのか可視化してみたいときはとても便利ですので、ぜひ使用してみてください!