元セブ島在住のエンジニアの僕「Tommy」が、プログラミング・英語・セブ事情を発信するブログ

Tommy's blog

【Python】自然言語を分類し可視化する方法【→プログラム初心者でもできる!】

11月 15, 2019

プログラム学生
自然言語処理ってどうやってやるんですか?
使う目的によっていろんな方法があるけど、今回は「文章中にでてくる単語の頻度と関係を調べる」という目的で、LDAというモデルを使った方法を説明するね。
Tommy

この記事で分かること

  • Pythonを使った形態素解析の方法が分かる
  • Pythonを使った辞書、コーパスの作り方が分かる
  • Pythonを使って初歩的な自然言語処理の方法が分かる
  • Pythonを使って頻度情報を可視化する方法が分かる

Pythonで自然言語を分類し可視化する概要

PythonでLDAというトピックモデルを使った自然言語処理の方法を説明します。

ここでは、指定した複数の文書(自然言語が並ぶ文章のこと)に対して、LDA分析を行い、LDAトピックモデル、辞書、コーパスを算出します。

また、LDAトピックモデルをWord Cloudという図を表示させ、文書中の単語の出現頻度を可視化します。

Pythonで自然言語を分類し可視化する前提環境

自然言語処理の方法の前提環境は、下の記事で説明している私の環境と同じ環境で確認しています。

また、レンタルサーバなどのLinuxサーバを使っている場合は、下の記事でプログラミングする環境の作り方を説明しています。

もし、まだプログラムする環境ができていない方は、先にこちらの記事を読んで、プログラミングできる環境をつくることをおすすめします。

Pythonで自然言語を分類し可視化するサンプルプログラム

この章では、複数の文書(自然言語が並ぶ文章)に対して、自然言語処理を行い、結果を確認するサンプルプログラムを説明します。

自然言語を分類し可視化するサンプルプログラムの作成

自然言語処理の中でも、複数の文書(自然言語が並ぶ文章)に対して、LDAトピックモデルをWord Cloudという図を表示させ、文書中の単語の出現頻度を可視化することを行います。サンプルプログラムは次の順番で説明します。

  1. ライブラリのインポート
  2. LDA解析に使うパラメータを指定
  3. treetaggerのインスタンスを取得する
  4. 動詞と名詞だけ抽出
  5. 形態素に分解した配列を作成
  6. 辞書とコーパス作成
  7. LDAモデルの作成
  8. LDAモデルと辞書とコーパスの作成
  9. トピックモデルの画像作成
  10. 自然言語処理一連の処理

1.ライブラリのインポート

ここでは、全体の処理に必要なライブラリのインポートを行っています。

  • 形態素解析のツールであるtreetaggerのPythonラッパーのtreetaggerwrapper。
  • トピック分析を行う為のライブラリgensim。
  • 数値演算を行うライブラリnumpy
  • ワードクラウドという図を描画する為のライブラリwordcloud。
  • データをグラフにプロットする為のライブラリmatplotlib。
# Natural language processing
import treetaggerwrapper
import gensim
import numpy as np

# Wordcould
from wordcloud import WordCloud

#import matplotlib
import matplotlib.pylab as plt

  1. ライブラリ「treetaggerwrapper」をインポートする。
  2. ライブラリ「gensim」をインポートする。
  3. ライブラリ「numpy」を別名の「np」としてインポートする。
  4. ライブラリ「wordcloud」からモジュール「WordCloud」をインポートする。
  5. ライブラリ「matplotlib.pylab」 を別名「plt」としてインポートする。

2.LDA解析に使うパラメータを指定

次に、LDA解析に使うパラメータを指定します。

LDA_TOPIC_NUM:トピック数
MINIMUM_PROBABILITITY:所属確率の閾値(所属確率が0.001以上のトピックを返す)

#LDA parameter
LDA_TOPIC_NUM = 2
MINIMUM_PROBABILITITY = 0.001

  1. グローバル変数「LDA_TOPIC_NUM 」の値を「2」に初期化する。
  2. グローバル変数「MINIMUM_PROBABILITITY 」の値を「0.001」に初期化する。

3.treetaggerのインスタンスの取得

次は、「TreeTagger」のインスタンスを取得しています。「TreeTagger」は、ある英語の文章を単語とその品詞に分解してくれるツールです。品詞の分類は下記のサイトの種類があります。

Tree Tagger Tags

この環境では、ツール「TreeTagger」が「/usr/local/src/tree-tagger」のディレクトリに配置されているものとして説明します。

def get_treetagger_instatnce():

	return treetaggerwrapper.TreeTagger(TAGLANG='en',TAGDIR='/usr/local/src/tree-tagger')

  1. 関数「get_treetagger_instatnce」を定義宣言する。
  2. オブジェクト「treetaggerwrapper」の関数「TreeTagger」の戻り値を、関数「get_treetagger_instatnce」の戻り値として返却する。

4.動詞と名詞だけ抽出

tagsで渡されるタグのリストを1つずつtagに格納しながら、名詞と動詞(Be動詞は除く)だけを選別しています。最終的に戻り値docには、名詞と動詞のタグ(単語)がリストされていることになります。

def create_wordtaglist_1doc(tags):

	doc = []
	for tag in tags:

		# The case of Noun
		if tag.split()[1].startswith("N"):
			doc.append(tag.split()[2])

		# The case of Verb except for be verb
		elif tag.split()[1].startswith("V") and not tag.split()[1].startswith("VB"):
			doc.append(tag.split()[2])

	return doc

関数「create_wordtaglist_1doc」を定義宣言する。

  1. 変数「doc」を空のリストで初期化する。
  2. 「tags」の要素を1つずつ変数「tag」に格納しながら次の処理を繰り返す。
    1. 「tag」をスペースで分割した2番目の要素が文字「N」で始まっていた場合は次の処理を実行する。
      1. 「tag」をスペースで分割した3番目の要素を変数「doc」に加える。
    2. 「tag」をスペースで分割した2番目の要素が文字「V」で始まり、かつ文字列「VB」で始まっていない場合は次の処理を実行する。
      1. 「tag」をスペースで分割した3番目の要素を変数「doc」に加える。
  3. 「doc」を戻り値として返却する。

5.形態素に分解した配列を作成

doc_listの登録されている1つ1つの文章をdocに格納しながら、create_wordtaglist_1docから返却される1文書辺りの形態素解析結果のtagリストをdocsに保存しています。

なので、docsはリストを要素に持つリストなので、2次元のリストになります。

def create_morphological_docs(tagger,doc_list):
	docs = []
	for doc in doc_list:
		tags = tagger.TagText(doc)
		docs.append(create_wordtaglist_1doc(tags))
	return docs

  1. 関数「create_morphological_docs」を定義宣言する。
  2. 変数「docs」を空のリストで初期化する。
  3. 変数「doc_list」の要素を1つずつ「doc」に格納しながら、次の処理を繰り返す。
    1. 変数「tags」 を変数「doc」を引数に指定した場合のオブジェクト「tagger」の関数「TagText」の戻り値で初期化する。
    2. 変数「tags」を引数に指定した時の関数「create_wordtaglist_1doc」の戻り値を変数「docs」に加える。
  4. 変数「docs」の値を戻り値として返却する。

6.辞書とコーパスの作成

gensimライブラリのcorpora.Dictionaryをコールして、辞書を作成してdictionaryに格納しています。

その後、dictionary.doc2bowをコールして1docあたりのコーパスをリスト化してcorpusに格納しています。

def create_dic_and_corpus(docs):

	# create dictonary
	dictionary = gensim.corpora.Dictionary(docs)

	# create corpus
	corpus = [dictionary.doc2bow(doc) for doc in docs]

	return corpus,dictionary

  1. 関数「create_dic_and_corpus」を定義宣言する。
  2. 変数「docs」を引数に指定した時のライブラリ「gensim」の関数「corpora.Dictionary」の戻り値で変数「dictionary」 を初期化する。
  3. 変数「doc」のそれぞれの要素を引数に指定した時の関数「dictionary.doc2bow」の戻り値のリストで変数「corpus」を初期化する。
  4. 変数「corpus」と変数「dictionary」を戻り値として返却する。

7.LDAモデルの作成

def create_LDA_model(corpus,dictionary,topic_num):

	# LDA (Latent Dirichlet Allocation) processing
	tfidf = gensim.models.TfidfModel(corpus)
	corpus_tfidf = tfidf[corpus]
	lda = gensim.models.LdaModel(
							corpus=corpus_tfidf,
							id2word=dictionary,
							num_topics=topic_num,
							minimum_probability=MINIMUM_PROBABILITITY,
              )
	return lda

  1. 関数「create_LDA_model」を定義宣言する。
  2. 変数「tfidf」 をライブラリ「gensim」の関数「models.TfidfModel」からの戻り値(オブジェクト)で初期化する。
  3. 変数「corpus_tfidf」 をオブジェクト「tfidf」に引数で得られた変数「corpus」を指定した戻り値で初期化する。
  4. 変数「lda」 = ライブラリ「gensim」のメンバ関数「models.LdaModel」からの戻り値で初期化する。
  5. 変数「lda」の値を戻り値として返却する。

8.LDAモデルと辞書とコーパスの作成

create_morphological_docsでコーパスと辞書を作成します。その後、先程求めたコーパスと辞書を元に、create_LDA_modelでLDAモデルを計算しています。LDAモデル、辞書、コーパス、文書のリストを返却しています。

def make_lda_model(doc_list,topic_num):

	docs = create_morphological_docs(get_treetagger_instatnce(),doc_list)

	corpus,dictionary = create_dic_and_corpus(docs)

	lda = create_LDA_model(corpus=corpus,dictionary=dictionary,topic_num=topic_num)

	return lda,dictionary,corpus,docs

  1. 関数「make_lda_model」を定義宣言する。
  2. 変数「docs」 = create_morphological_docs(get_treetagger_instatnce(),doc_list)
  3. 変数「corpus」と変数「dictionary」を関数「create_dic_and_corpus」からの戻り値で初期化する。
  4. 変数「lda」を関数「create_LDA_model」の戻り値で初期化する。
  5. オブジェクト「lda」のメンバ「dictionary,corpus,docs」を戻り値として返却する。

9.トピックモデルの画像作成

ここでは、トピックモデルをWord Cloudで描画しています。トピックが4つよりも少ない場合は、1行のレイアウトでWord Cloudを表示し、4つ以上になると、数に応じて列を増やしています。ちなみに、Word Cloudの描画は、480*320のサイズで背景を黒に設定しています。

def make_topic_image(lda_model,corpus,dictionary):

	print('lda_model.num_topics : ' + str(lda_model.num_topics))

	if lda_model.num_topics < 4:
		fig, axs = plt.subplots(ncols=lda_model.num_topics, nrows=1,figsize=(15,7))
	else:
		fig, axs = plt.subplots(ncols=4, nrows=int(math.ceil((lda_model.num_topics+1)/4)),figsize=(15,7))

	axs = axs.flatten()

	for i, t in enumerate(range(lda_model.num_topics)):

		x = dict(lda_model.show_topic(t, 30))

		WC = WordCloud(
			width=480,
			height=320,
			background_color='black',
			max_words=2000,
			).fit_words(x)

		axs[i].imshow(WC)
		axs[i].axis('off')
		axs[i].set_title('Topic '+str(t))

	plt.tight_layout()
	plt.savefig('wordcloud/'+'best_topic_wordcould.png')
	plt.show()

  1. 関数「make_topic_image」を定義宣言する。
  2. オブジェクト「lda_model」のメンバ変数「num_topics」の内容を表示する。
  3. オブジェクト「lda_model」のメンバ変数「num_topics」が 値「4」よりも小さい場合は次の処理を実行する。
    1. 変数「fig」と変数「axs」をオブジェクト「plt」のメンバ関数「subplots」からの戻り値で初期化する。
  4. オブジェクト「lda_model」のメンバ変数「num_topics」が 値「4」以上の場合は次の処理を実行する。
    1. 変数「fig」と変数「axs」をオブジェクト「plt」のメンバ関数「subplots」からの戻り値で初期化する。
  5. 変数「axs」 をオブジェクト「 axs」のメンバ関数「flatten」からの戻り値で初期化する。
  6. オブジェクト「lda_model」のメンバ変数「num_topics」のそれぞれの要素を変数「t」に、その要素の順番を変数「i」に格納しながら、次の処理を繰り返す。
    1. 辞書形式に変換する関数「dict」に「lda_model.show_topic」を指定した時の戻り値で、変数「x」を初期化する。
    2. 変数「WC」 を関数「WordCloud」の戻り値で初期化する。
    3. 変数「i」番目のオブジェクト「axs」のメンバ関数「imshow」を呼び出す。
    4. 変数「i」番目のオブジェクト「axs」のメンバ関数「axis」を呼び出す。
    5. 変数「i」番目のオブジェクト「axs」のメンバ関数「set_title」を呼び出す。
  7. ライブラリ「plt」のメンバ変数「tight_layout」を呼び出す。
  8. ライブラリ「plt」のメンバ変数「savefig」を呼び出す。
  9. ライブラリ「plt」のメンバ変数「show」を呼び出す。

10.自然言語処理一連の処理

doc_listに5つの文書(文章)をリスト形式で格納します。これが分析対象のデータとなります。その後、doc_listを引数にLDAモデルを作成する為にmake_lda_modelのメソッドをコールしています。

これによってLDAモデル、辞書、コーパス、文書リストが得られます。最後に、LDAモデルをWord Cloudという図で描写する為に、make_topic_imageのメソッドを呼び出します。

if __name__ == '__main__':

# Target doc list
	doc_list = [
				u'This is Tommy.',
				u'My name is Tommy.',
				u'It is python code.',
				u'Python is very cool.',
				u'You are a specialist.'
				]

	# Create lda dictionary corpus
	lda,dictionary,corpus,docs = make_lda_model(doc_list=doc_list,topic_num=LDA_TOPIC_NUM)
	print(lda)
	print(dictionary)
	print(corpus)

	# Making and showing topic image
	make_topic_image(lda_model=lda,corpus=corpus,dictionary=dictionary)

  1. 変数「doc_list」 を初期化する。
  2. 変数「lda」と変数「dictionary」と変数「corpus」と変数「docs」を関数「make_lda_model」からの戻り値で初期化する。
  3. 変数「lda」の内容を表示する。
  4. 変数「dictionary」の内容を表示する。
  5. 変数「corpus」の内容を表示する。
  6. 変数「lda」と変数「corpus」と変数「dictionary」を引数に指定して、関数「make_topic_image」を呼び出す。

自然言語を分類し可視化するプログラムの実行結果

上記メイン関数での処理の結果、下記のような結果が得られました。単語が5種類、トピック数が2、ディケイとチャンクサイズはデフォルトの値になっています。Print文で、LDAモデル、辞書、コーパスを順に表示しているので、その結果が下記のようになります。

LdaModel(num_terms=5, num_topics=2, decay=0.5, chunksize=2000)
Dictionary(5 unique tokens: [u'python', u'specialist', u'code', u'name', u'Tommy'])
[[(0, 1)], [(0, 1), (1, 1)], [(2, 1), (3, 1)], [(2, 1)], [(4, 1)]]
lda_model.num_topics : 2

下の図がWord Cloudという図で単語の出現頻度を描写した結果です。文書数が極端に少ないので、トピックのしての特徴があまりよく分かりませんが、「名詞としての主旨としてトピック」と、「Pythonのスペシャリストとしてのトピック」の2つ分かれているような図になっているような気がします。