トピックモデルによるクラスタリング
目次
22. トピックモデルによるクラスタリング¶
トピックモデルとは文書中の単語出現分布を元に傾向(≒トピックらしきもの)を観察しようとするアプローチで、クラスタリングの一種である。なお、一般的なクラスタリング(例えばk平均法)では一つのサンプルが一つのクラスタに属するという前提でグルーピングを行うのに対し、トピックモデルでは一つのサンプルが複数のクラスタを内包しているという前提でグルーピングを行う。次の例を眺めるとイメージをつかみやすいだろう。
基本的には文書を BoW (CountVectrizor) やそれの重みを調整した TF-IDF 等の「文書単語行列」を作成し、ここから文書館類似度や単語間類似度を元に集約(≒次元削減)を試みる。文書単語行列の作成方法や次元削減方法、類似度の求め方などで様々なアルゴリズムが提案されている。ここでは (1) Bow + LDA によりトピックモデルを行い、PyLDAvisによる可視化を通してトピックを観察してみよう。
なお、トピックモデルの注意点として、トピックそのものは人手による解釈が求められる点が挙げられる。例えば先に上げたトピックモデル入門:WikipediaをLDAモデル化してみたにおける図2(下図)では「政治」「スポーツ」「国際」といったトピックが並んでいるが、実際には「4-1. トピック観察」を行う必要がある。実際に観察してみよう。
22.1. データの準備¶
これまで見てきたいつものやつ。
!curl -O https://ie.u-ryukyu.ac.jp/~tnal/2022/dm/static/r_assesment.pkl
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 34834 100 34834 0 0 100k 0 --:--:-- --:--:-- --:--:-- 103k
import collections
import numpy as np
import pandas as pd
import spacy
from wordcloud import WordCloud
nlp = spacy.load("ja_ginza")
assesment_df = pd.read_pickle('r_assesment.pkl')
assesment_df.head()
title | grade | required | q_id | comment | |
---|---|---|---|---|---|
0 | 工業数学Ⅰ | 1 | True | Q21 (1) | 特になし |
1 | 工業数学Ⅰ | 1 | True | Q21 (2) | 正直わかりずらい。むだに間があるし。 |
2 | 工業数学Ⅰ | 1 | True | Q21 (2) | 例題を取り入れて理解しやすくしてほしい。 |
3 | 工業数学Ⅰ | 1 | True | Q21 (2) | 特になし |
4 | 工業数学Ⅰ | 1 | True | Q21 (2) | スライドに書く文字をもう少しわかりやすくして欲しいです。 |
# 分かち書き
poses = ['PROPN', 'NOUN', 'VERB', 'ADJ', 'ADV'] #名詞、動詞、形容詞、形容動詞
assesment_df['wakati'] = ''
for index, comment in enumerate(assesment_df['comment']):
doc = nlp(comment)
wakati_words = []
for token in doc:
if token.pos_ in poses:
wakati_words.append(token.lemma_)
wakati_text = ' '.join(wakati_words)
assesment_df.at[index, 'wakati'] = wakati_text
assesment_df
title | grade | required | q_id | comment | wakati | |
---|---|---|---|---|---|---|
0 | 工業数学Ⅰ | 1 | True | Q21 (1) | 特になし | 特に なし |
1 | 工業数学Ⅰ | 1 | True | Q21 (2) | 正直わかりずらい。むだに間があるし。 | 正直 わかる ずらい むだ 間 ある |
2 | 工業数学Ⅰ | 1 | True | Q21 (2) | 例題を取り入れて理解しやすくしてほしい。 | 例題 取り入れる 理解 する |
3 | 工業数学Ⅰ | 1 | True | Q21 (2) | 特になし | 特に なし |
4 | 工業数学Ⅰ | 1 | True | Q21 (2) | スライドに書く文字をもう少しわかりやすくして欲しいです。 | スライド 書く 文字 もう 少し わかる する |
... | ... | ... | ... | ... | ... | ... |
165 | データマイニング | 3 | False | Q22 | 課題が難しいものが多く、時間を多くとってもらえたのは非常に良かったですがかなりきつかったです... | 課題 難しい もの 多い 時間 多い とる もらえる 非常 良い かなり きつい ござる |
166 | ICT実践英語Ⅰ | 3 | False | Q22 | オンラインなどで顔を合わせてやりたかったです。 | オンライン 顔 合わせる やる |
167 | 知能情報実験Ⅲ | 3 | True | Q21 (2) | unityの操作方法の説明などを最初に行ってもらえたらもう少しスムーズにできたのではないかと思う。 | unity 操作方法 説明 最初 行く もらえる もう 少し スムーズ できる 思う |
168 | 知能情報実験Ⅲ | 3 | True | Q22 | それぞれに任せるといった形で進められたものだったのでそれなりに進めやすかったですが、オンライ... | それぞれ 任せる いう 形 進める もの なり 進める オンライン 班 員 指導 全く する... |
169 | 知能情報実験Ⅲ | 3 | True | Q22 | モバイルアプリ班\r\nHTML/CSS,JavaScriptなどを用いてアプリケーションを... | モバイルアプリ 班 \r\n HTML CSS javascript 用いる アプリケーショ... |
170 rows × 6 columns
22.2. 文書ベクトルの作成¶
ここでは CountVectorizer (Bag-of-Words) で作成してみよう。
from sklearn.feature_extraction.text import CountVectorizer
stop_words = ['こと', '\r\n', 'ため', '思う', 'いる', 'ある', 'する', 'なる']
vectorizer = CountVectorizer(stop_words=stop_words)
bow_tf_vector = vectorizer.fit_transform(assesment_df['wakati'])
print('bow_tf_vector.shape = ', bow_tf_vector.shape)
bow_tf_vector.shape = (170, 738)
22.3. LDAによるトピックモデル解析¶
sklearnでは LatentDirichletAllocation として用意されている。
from sklearn.decomposition import LatentDirichletAllocation
NUM_TOPICS = 20 #トピック数
max_iter = 100 #LDAによる学習回数
lda = LatentDirichletAllocation(n_components=NUM_TOPICS, max_iter=max_iter, learning_method='online',verbose=True)
data_lda = lda.fit_transform(bow_tf_vector)
iteration: 1 of max_iter: 100
iteration: 2 of max_iter: 100
iteration: 3 of max_iter: 100
iteration: 4 of max_iter: 100
iteration: 5 of max_iter: 100
iteration: 6 of max_iter: 100
iteration: 7 of max_iter: 100
iteration: 8 of max_iter: 100
iteration: 9 of max_iter: 100
iteration: 10 of max_iter: 100
iteration: 11 of max_iter: 100
iteration: 12 of max_iter: 100
iteration: 13 of max_iter: 100
iteration: 14 of max_iter: 100
iteration: 15 of max_iter: 100
iteration: 16 of max_iter: 100
iteration: 17 of max_iter: 100
iteration: 18 of max_iter: 100
iteration: 19 of max_iter: 100
iteration: 20 of max_iter: 100
iteration: 21 of max_iter: 100
iteration: 22 of max_iter: 100
iteration: 23 of max_iter: 100
iteration: 24 of max_iter: 100
iteration: 25 of max_iter: 100
iteration: 26 of max_iter: 100
iteration: 27 of max_iter: 100
iteration: 28 of max_iter: 100
iteration: 29 of max_iter: 100
iteration: 30 of max_iter: 100
iteration: 31 of max_iter: 100
iteration: 32 of max_iter: 100
iteration: 33 of max_iter: 100
iteration: 34 of max_iter: 100
iteration: 35 of max_iter: 100
iteration: 36 of max_iter: 100
iteration: 37 of max_iter: 100
iteration: 38 of max_iter: 100
iteration: 39 of max_iter: 100
iteration: 40 of max_iter: 100
iteration: 41 of max_iter: 100
iteration: 42 of max_iter: 100
iteration: 43 of max_iter: 100
iteration: 44 of max_iter: 100
iteration: 45 of max_iter: 100
iteration: 46 of max_iter: 100
iteration: 47 of max_iter: 100
iteration: 48 of max_iter: 100
iteration: 49 of max_iter: 100
iteration: 50 of max_iter: 100
iteration: 51 of max_iter: 100
iteration: 52 of max_iter: 100
iteration: 53 of max_iter: 100
iteration: 54 of max_iter: 100
iteration: 55 of max_iter: 100
iteration: 56 of max_iter: 100
iteration: 57 of max_iter: 100
iteration: 58 of max_iter: 100
iteration: 59 of max_iter: 100
iteration: 60 of max_iter: 100
iteration: 61 of max_iter: 100
iteration: 62 of max_iter: 100
iteration: 63 of max_iter: 100
iteration: 64 of max_iter: 100
iteration: 65 of max_iter: 100
iteration: 66 of max_iter: 100
iteration: 67 of max_iter: 100
iteration: 68 of max_iter: 100
iteration: 69 of max_iter: 100
iteration: 70 of max_iter: 100
iteration: 71 of max_iter: 100
iteration: 72 of max_iter: 100
iteration: 73 of max_iter: 100
iteration: 74 of max_iter: 100
iteration: 75 of max_iter: 100
iteration: 76 of max_iter: 100
iteration: 77 of max_iter: 100
iteration: 78 of max_iter: 100
iteration: 79 of max_iter: 100
iteration: 80 of max_iter: 100
iteration: 81 of max_iter: 100
iteration: 82 of max_iter: 100
iteration: 83 of max_iter: 100
iteration: 84 of max_iter: 100
iteration: 85 of max_iter: 100
iteration: 86 of max_iter: 100
iteration: 87 of max_iter: 100
iteration: 88 of max_iter: 100
iteration: 89 of max_iter: 100
iteration: 90 of max_iter: 100
iteration: 91 of max_iter: 100
iteration: 92 of max_iter: 100
iteration: 93 of max_iter: 100
iteration: 94 of max_iter: 100
iteration: 95 of max_iter: 100
iteration: 96 of max_iter: 100
iteration: 97 of max_iter: 100
iteration: 98 of max_iter: 100
iteration: 99 of max_iter: 100
iteration: 100 of max_iter: 100
22.4. トピックの観察。¶
pyLDAvisによりトピックを観察してみよう。
下図の左側がトピック分布を表している。丸の大きさがトピック内に含まれる文書数、丸と丸の距離はトピック間の距離。
下図の右側が単語の発生頻度を表している。
トピックを選択するとそのトピックにおける単語の発生頻度を観察できる。
単語を選択すると、その単語がどのようにトピック分布上にバラけているかを観察できる。
import pyLDAvis
from pyLDAvis import sklearn as sklearn_vis
pyLDAvis.enable_notebook()
dash = sklearn_vis.prepare(lda, bow_tf_vector, vectorizer, mds='tsne')
dash
/Users/tnal/.venv/dm/lib/python3.8/site-packages/past/builtins/misc.py:45: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
from imp import reload
/Users/tnal/.venv/dm/lib/python3.8/site-packages/sklearn/utils/deprecation.py:87: FutureWarning: Function get_feature_names is deprecated; get_feature_names is deprecated in 1.0 and will be removed in 1.2. Please use get_feature_names_out instead.
warnings.warn(msg, category=FutureWarning)
/Users/tnal/.venv/dm/lib/python3.8/site-packages/pyLDAvis/_prepare.py:246: FutureWarning: In a future version of pandas all arguments of DataFrame.drop except for the argument 'labels' will be keyword-only.
default_term_info = default_term_info.sort_values(
/Users/tnal/.venv/dm/lib/python3.8/site-packages/sklearn/manifold/_t_sne.py:780: FutureWarning: The default initialization in TSNE will change from 'random' to 'pca' in 1.2.
warnings.warn(
/Users/tnal/.venv/dm/lib/python3.8/site-packages/sklearn/manifold/_t_sne.py:790: FutureWarning: The default learning rate in TSNE will change from 200.0 to 'auto' in 1.2.
warnings.warn(
/Users/tnal/.venv/dm/lib/python3.8/site-packages/sklearn/manifold/_t_sne.py:819: FutureWarning: 'square_distances' has been introduced in 0.24 to help phase out legacy squaring behavior. The 'legacy' setting will be removed in 1.1 (renaming of 0.26), and the default setting will be changed to True. In 1.3, 'square_distances' will be removed altogether, and distances will be squared by default. Set 'square_distances'=True to silence this warning.
warnings.warn(