さいとー・ま

さいとー・ま

さいとう・まの。おしごとは manoestasmanoあっとgmail.com (あっとを いれかえてください)まで。

テキストマイニングへの道05――トピックモデルとの格闘3

hunihunisaito.hatenablog.com
前回の続き。

可視化と単語リストとそれぞれの割り当てを出すためのスクリプトをそれぞれ一般化して保存しておく。

可視化

#pandasはデータフレーム操作、MeCabは日本語形態素解析ライブラリ、reは正規表現ライブラリ、CountVectorizerは文書の単語の出現頻度をカウントするためのツール、gensimはトピックモデリングライブラリ、tqdmは進捗バー表示用ライブラリ、numpyは数値計算用ライブラリです。
import pandas as pd
import MeCab
import re
import pyLDAvis
import pyLDAvis.gensim_models as gensimvis
from sklearn.feature_extraction.text import CountVectorizer
from gensim.corpora.dictionary import Dictionary
from gensim.models import LdaModel
from tqdm import tqdm
import numpy as np

#csvファイルを読み込んで、pandasのデータフレームとして扱っています。ファイル名は"***.csv"で、最初の列をインデックスとして扱っています。reset_index()を用いて、インデックスを再設定しています
#変更箇所
df = pd.read_csv("***.csv",index_col=0).reset_index()
#stopwordsの指定
with open("stopword***.txt","r", encoding="utf-8" ) as f:
stopwords = f.read().split("\n")

#Neologdによるトーカナイザー(リストで返す関数・名詞のみ)
def mecab_tokenizer(text):

replaced_text = text.lower()
replaced_text = re.sub(r'[【】]', ' ', replaced_text) # 【】の除去
replaced_text = re.sub(r'[()()]', ' ', replaced_text) # ()の除去
replaced_text = re.sub(r'[[]\[\]]', ' ', replaced_text) # []の除去
replaced_text = re.sub(r'\d+\.*\d*', '', replaced_text) #数字を0にする

path = "-r C:/MeCab/mecabrc-u"
mecab = MeCab.Tagger(path)
parsed_lines = mecab.parse(replaced_text).split("\n")[:-2]

# #表層形を取得
# surfaces = [l.split('\t')[0] for l in parsed_lines]
#原形を取得
token_list = [l.split("\t")[3] for l in parsed_lines]
#品詞を取得
pos = [l.split("\t")[4].split("-")[0] for l in parsed_lines]
# 名詞,動詞,形容詞のみに絞り込み
target_pos = ["名詞", "形容詞"]
token_list = [t for t, p in zip(token_list, pos) if p in target_pos]

# stopwordsの除去
token_list = [t for t in token_list if t not in stopwords]

# ひらがなのみの単語を除く
kana_re = re.compile("^[ぁ-ゖ]+$")
token_list = [t for t in token_list if not kana_re.match(t)]

return token_list

#df全体に対してmecab_tokenizerを適用し、形態素解析を行なったリストを返す関数
def make_docs(df,column_number):
docs=[]
for i in range(len(df)):
text = df.iloc[i,column_number]
docs.append(mecab_tokenizer(text))
return docs

#形態素解析の実行 変更箇所 数字で何列目を分析するか選ぶ。今回は一列目がテキストだったので、0
d = make_docs(df,0)

#辞書の作成 gensimの「Dictionary」クラスを用いて、形態素解析された文書群「d」から、単語の辞書を作成します。
dictionary = Dictionary(d)
#出現がx文書に満たない単語と、y%以上の文書に出現する単語を極端と見做し削除する 上記で作成された単語の辞書から、「no_below」で指定した文書数より出現頻度の低い単語、「no_above」で指定した割合以上の文書に出現する高頻度の単語を除外します。
#変更箇所
x =5
y =0.5
dictionary.filter_extremes(no_below=x,no_above=y)
# LdaModelが読み込めるBoW形式に変換 各文書を単語の出現回数を表すBoW(Bag of Words)形式に変換し、「corpus」というリストに格納します。
corpus = [dictionary.doc2bow(text) for text in d]

print(f"Number of unique tokens: {len(dictionary)}")
print(f"Number of documents: {len(corpus)}")

#gensimの「LdaModel」クラスを用いて、トピック数「num_topics」を指定し、BoW形式の文書群「corpus」と辞書「dictionary」からトピックモデルを作成します。「alpha」はディリクレ事前分布のハイパーパラメータで、デフォルト値は「1.0/num_topics」となっています。
num_topics =5
lda = LdaModel(corpus, id2word =dictionary, num_topics=num_topics, alpha=0.01)

#作成されたトピックモデルから、各トピックに属する上位の単語を取り出して、Pandasのデータフレームにまとめます。「get_topic_terms()」関数で各トピックの単語の確率を取得し、「topn=15」で確率の高い上位15単語を抽出します。「id2token」で単語IDを単語に変換し、「append()」でリスト「word」に格納します。それをPandasのデータフレームに追加し、「df.T」で転置したデータフレームを出力します。
df =pd.DataFrame() #からのデータフレーム
for t in range(num_topics): #トピック数だけ繰り返せ
word=[]
for i, prob in lda.get_topic_terms(t, topn=15): #ここで、lda.get_topic_terms(t, topn=15)は、t番目のトピックに属する上位15単語とその単語の確率を返す関数です。取得された単語と確率は、リストwordに格納されます。
# lda.get_topic_terms(t, topn=15)は、トピックtに属する上位15単語とその単語の出現確率を返す関数です。for i, prob in lda.get_topic_terms(t, topn=15):で、単語のidとその単語の出現確率が順番に取り出されます。ここで、iは単語のidを表し、int(i)で整数型に変換しています。
word.append(dictionary.id2token[int(i)]) #dictionary.id2tokenは、単語のidから単語そのものへと変換する辞書です。そのため、dictionary.id2token[int(i)]は、idがiである単語を表します。この単語をwordリストに追加することで、トピックtに属する上位15単語がwordリストに格納されます。
_ = pd.DataFrame([word],index=[f'topic{t+1}']) #リストwordを1行のデータフレームに変換し、インデックスに'topic{t+1}'を指定します。
df = df.append(_) # データフレーム_を、空のデータフレームdfに追加します。
df.T #最後に、列と行を入れ替えたデータフレームを出力します。


#PyLDAvisの実装
pyLDAvis.enable_notebook()

vis = gensimvis.prepare(
lda, corpus, dictionary, n_jobs = 1, sort_topics = False
)

pyLDAvis.display(vis)

単語リストと割り当て

#単語リストおよび割り当て
#pandasはデータフレーム操作、MeCabは日本語形態素解析ライブラリ、reは正規表現ライブラリ、CountVectorizerは文書の単語の出現頻度をカウントするためのツール、gensimはトピックモデリングライブラリ、tqdmは進捗バー表示用ライブラリ、numpyは数値計算用ライブラリです。
import pandas as pd
import MeCab
import re
import pyLDAvis
import pyLDAvis.gensim_models as gensimvis
from collections import defaultdict
from sklearn.feature_extraction.text import CountVectorizer
from gensim.corpora.dictionary import Dictionary
from gensim.models import LdaModel
from tqdm import tqdm
import numpy as np

#csvファイルを読み込んで、pandasのデータフレームとして扱っています。ファイル名は"***.csv"で、最初の列をインデックスとして扱っています。reset_index()を用いて、インデックスを再設定しています
#変更箇所
df = pd.read_csv("***.csv",index_col=0).reset_index()
#stopwordsの指定
with open("stopword***.txt","r", encoding="utf-8" ) as f:
stopwords = f.read().split("\n")

#Neologdによるトーカナイザー(リストで返す関数・名詞のみ)
def mecab_tokenizer(text):

replaced_text = text.lower()
replaced_text = re.sub(r'[【】]', ' ', replaced_text) # 【】の除去
replaced_text = re.sub(r'[()()]', ' ', replaced_text) # ()の除去
replaced_text = re.sub(r'[[]\[\]]', ' ', replaced_text) # []の除去
replaced_text = re.sub(r'\d+\.*\d*', '', replaced_text) #数字を0にする

path = "-r C:/MeCab/mecabrc-u"
mecab = MeCab.Tagger(path)
parsed_lines = mecab.parse(replaced_text).split("\n")[:-2]

# #表層形を取得
# surfaces = [l.split('\t')[0] for l in parsed_lines]
#原形を取得
token_list = [l.split("\t")[3] for l in parsed_lines]
#品詞を取得
pos = [l.split("\t")[4].split("-")[0] for l in parsed_lines]
# 名詞,動詞,形容詞のみに絞り込み
target_pos = ["名詞", "形容詞"]
token_list = [t for t, p in zip(token_list, pos) if p in target_pos]

# stopwordsの除去
token_list = [t for t in token_list if t not in stopwords]

# ひらがなのみの単語を除く
kana_re = re.compile("^[ぁ-ゖ]+$")
token_list = [t for t in token_list if not kana_re.match(t)]

return token_list

#df全体に対してmecab_tokenizerを適用し、形態素解析を行なったリストを返す関数
def make_docs(df,column_number):
docs=
for i in range(len(df)):
text = df.iloc[i,column_number]
docs.append(mecab_tokenizer(text))
return docs

#形態素解析の実行 変更箇所 数字で何列目を分析するか選ぶ。今回は一列目がテキストだったので、0
d = make_docs(df,0)

#辞書の作成 gensimの「Dictionary」クラスを用いて、形態素解析された文書群「d」から、単語の辞書を作成します。
dictionary = Dictionary(d)
#出現がx文書に満たない単語と、y%以上の文書に出現する単語を極端と見做し削除する 上記で作成された単語の辞書から、「no_below」で指定した文書数より出現頻度の低い単語、「no_above」で指定した割合以上の文書に出現する高頻度の単語を除外します。
#変更箇所
x =5
y =0.5
dictionary.filter_extremes(no_below=x,no_above=y)
# LdaModelが読み込めるBoW形式に変換 各文書を単語の出現回数を表すBoW(Bag of Words)形式に変換し、「corpus」というリストに格納します。
corpus = [dictionary.doc2bow(text) for text in d]

print(f"Number of unique tokens: {len(dictionary)}")
print(f"Number of documents: {len(corpus)}")

#gensimの「LdaModel」クラスを用いて、トピック数「num_topics」を指定し、BoW形式の文書群「corpus」と辞書「dictionary」からトピックモデルを作成します。「alpha」はディリクレ事前分布のハイパーパラメータで、デフォルト値は「1.0/num_topics」となっています。
num_topics =15
lda = LdaModel(corpus, id2word =dictionary, num_topics=num_topics, alpha=0.01)

#作成されたトピックモデルから、各トピックに属する上位の単語を取り出して、Pandasのデータフレームにまとめます。「get_topic_terms()」関数で各トピックの単語の確率を取得し、「topn=15」で確率の高い上位15単語を抽出します。「id2token」で単語IDを単語に変換し、「append()」でリスト「word」に格納します。それをPandasのデータフレームに追加し、「df.T」で転置したデータフレームを出力します。
df =pd.DataFrame() #からのデータフレーム
for t in range(num_topics): #トピック数だけ繰り返せ
word=
for i, prob in lda.get_topic_terms(t, topn=15): #ここで、lda.get_topic_terms(t, topn=15)は、t番目のトピックに属する上位15単語とその単語の確率を返す関数です。取得された単語と確率は、リストwordに格納されます。
# lda.get_topic_terms(t, topn=15)は、トピックtに属する上位15単語とその単語の出現確率を返す関数です。for i, prob in lda.get_topic_terms(t, topn=15):で、単語のidとその単語の出現確率が順番に取り出されます。ここで、iは単語のidを表し、int(i)で整数型に変換しています。
word.append(dictionary.id2token[int(i)]) #dictionary.id2tokenは、単語のidから単語そのものへと変換する辞書です。そのため、dictionary.id2token[int(i)]は、idがiである単語を表します。この単語をwordリストに追加することで、トピックtに属する上位15単語がwordリストに格納されます。
_ = pd.DataFrame([word],index=[f'topic{t+1}']) #リストwordを1行のデータフレームに変換し、インデックスに'topic{t+1}'を指定します。
df = df.append(_) # データフレーム_を、空のデータフレームdfに追加します。
df.T #最後に、列と行を入れ替えたデータフレームを出力します。
print(df.T)

# クラスタリング結果を出力
score_by_topic = defaultdict(int)
raw_test_texts = []
with open('transexcluded20221028test.txt', 'r', encoding='utf-8') as f:
for line in f:
raw_test_texts.append(line.strip())

for unseen_doc, raw_train_text in zip(corpus, raw_test_texts):
for topic, score in lda[unseen_doc]:
score_by_topic[int(topic)] = float(score)
for i in range(num_topics):
print('{:.2f}'.format(score_by_topic[i]), end='\t')
print(raw_train_text, end='\t')
print()