はじめに
この実験では、文書コーパスに非負行列因子分解 (NMF: Non-negative Matrix Factorization) と潜在的ディリクレ配置 (LDA: Latent Dirichlet Allocation) を適用して、コーパスのトピック構造の加法モデルを抽出します。出力は、重みに基づいた上位いくつかの単語を使用して棒グラフとして表される各トピックのプロットになります。
VM のヒント
VM の起動が完了したら、左上隅をクリックして ノートブック タブに切り替え、Jupyter Notebook を使って練習しましょう。
Jupyter Notebook の読み込みには数秒かかる場合があります。Jupyter Notebook の制限により、操作の検証は自動化できません。
学習中に問題がある場合は、Labby にお問い合わせください。セッション後にフィードバックを提供してください。すぐに問題を解決いたします。
データセットの読み込み
20 ニュースグループのデータセットを読み込み、ベクトル化します。最初に、いくつかのヒューリスティックを使って無用な用語をフィルタリングします。投稿からヘッダー、フッター、引用返信を取り除き、一般的な英単語、1 つの文書にのみ出現する単語、または少なくとも 95% の文書に出現する単語を削除します。
from sklearn.datasets import fetch_20newsgroups
n_samples = 2000
n_features = 1000
print("Loading dataset...")
data, _ = fetch_20newsgroups(
shuffle=True,
random_state=1,
remove=("headers", "footers", "quotes"),
return_X_y=True,
)
data_samples = data[:n_samples]
特徴量の抽出
NMF のために tf-idf 特徴量と、LDA のために単語出現回数の特徴量を使って、データセットから特徴量を抽出します。
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
## NMF のために tf-idf 特徴量を使用します。
print("Extracting tf-idf features for NMF...")
tfidf_vectorizer = TfidfVectorizer(
max_df=0.95, min_df=2, max_features=n_features, stop_words="english"
)
tfidf = tfidf_vectorizer.fit_transform(data_samples)
## LDA のために単語出現回数の特徴量を使用します。
print("Extracting tf features for LDA...")
tf_vectorizer = CountVectorizer(
max_df=0.95, min_df=2, max_features=n_features, stop_words="english"
)
tf = tf_vectorizer.fit_transform(data_samples)
NMF を適用する
2 つの異なる目的関数:フロベニウスノルムと一般化クルバック・ライブラー収束を使って NMF を適用します。後者は確率的潜在意味インデックス付けと同等です。
from sklearn.decomposition import NMF
n_components = 10
n_top_words = 20
init = "nndsvda"
## NMF モデルをフィットさせる
print(
"Fitting the NMF model (Frobenius norm) with tf-idf features, "
"n_samples=%d and n_features=%d..." % (n_samples, n_features)
)
nmf = NMF(
n_components=n_components,
random_state=1,
init=init,
beta_loss="frobenius",
alpha_W=0.00005,
alpha_H=0.00005,
l1_ratio=1,
).fit(tfidf)
## NMF モデルの上位の単語をプロットする
def plot_top_words(model, feature_names, n_top_words, title):
fig, axes = plt.subplots(2, 5, figsize=(30, 15), sharex=True)
axes = axes.flatten()
for topic_idx, topic in enumerate(model.components_):
top_features_ind = topic.argsort()[: -n_top_words - 1 : -1]
top_features = [feature_names[i] for i in top_features_ind]
weights = topic[top_features_ind]
ax = axes[topic_idx]
ax.barh(top_features, weights, height=0.7)
ax.set_title(f"Topic {topic_idx +1}", fontdict={"fontsize": 30})
ax.invert_yaxis()
ax.tick_params(axis="both", which="major", labelsize=20)
for i in "top right left".split():
ax.spines[i].set_visible(False)
fig.suptitle(title, fontsize=40)
plt.subplots_adjust(top=0.90, bottom=0.05, wspace=0.90, hspace=0.3)
plt.show()
tfidf_feature_names = tfidf_vectorizer.get_feature_names_out()
plot_top_words(
nmf, tfidf_feature_names, n_top_words, "Topics in NMF model (Frobenius norm)"
)
## 一般化クルバック・ライブラー収束を使って NMF モデルをフィットさせる
print(
"\n" * 2,
"Fitting the NMF model (generalized Kullback-Leibler "
"divergence) with tf-idf features, n_samples=%d and n_features=%d..."
% (n_samples, n_features),
)
nmf = NMF(
n_components=n_components,
random_state=1,
init=init,
beta_loss="kullback-leibler",
solver="mu",
max_iter=1000,
alpha_W=0.00005,
alpha_H=0.00005,
l1_ratio=0.5,
).fit(tfidf)
## 一般化クルバック・ライブラー収束を使った NMF モデルの上位の単語をプロットする
tfidf_feature_names = tfidf_vectorizer.get_feature_names_out()
plot_top_words(
nmf,
tfidf_feature_names,
n_top_words,
"Topics in NMF model (generalized Kullback-Leibler divergence)",
)
## MiniBatchNMF モデルをフィットさせる
from sklearn.decomposition import MiniBatchNMF
batch_size = 128
print(
"\n" * 2,
"Fitting the MiniBatchNMF model (Frobenius norm) with tf-idf "
"features, n_samples=%d and n_features=%d, batch_size=%d..."
% (n_samples, n_features, batch_size),
)
mbnmf = MiniBatchNMF(
n_components=n_components,
random_state=1,
batch_size=batch_size,
init=init,
beta_loss="frobenius",
alpha_W=0.00005,
alpha_H=0.00005,
l1_ratio=0.5,
).fit(tfidf)
## フロベニウスノルムを使った MiniBatchNMF モデルの上位の単語をプロットする
tfidf_feature_names = tfidf_vectorizer.get_feature_names_out()
plot_top_words(
mbnmf,
tfidf_feature_names,
n_top_words,
"Topics in MiniBatchNMF model (Frobenius norm)",
)
## 一般化クルバック・ライブラー収束を使って MiniBatchNMF モデルをフィットさせる
print(
"\n" * 2,
"Fitting the MiniBatchNMF model (generalized Kullback-Leibler "
"divergence) with tf-idf features, n_samples=%d and n_features=%d, "
"batch_size=%d..." % (n_samples, n_features, batch_size),
)
mbnmf = MiniBatchNMF(
n_components=n_components,
random_state=1,
batch_size=batch_size,
init=init,
beta_loss="kullback-leibler",
alpha_W=0.00005,
alpha_H=0.00005,
l1_ratio=0.5,
).fit(tfidf)
## 一般化クルバック・ライブラー収束を使った MiniBatchNMF モデルの上位の単語をプロットする
tfidf_feature_names = tfidf_vectorizer.get_feature_names_out()
plot_top_words(
mbnmf,
tfidf_feature_names,
n_top_words,
"Topics in MiniBatchNMF model (generalized Kullback-Leibler divergence)",
)
LDA を適用する
tf 特徴量を使って LDA モデルを適用します。
from sklearn.decomposition import LatentDirichletAllocation
print(
"\n" * 2,
"Fitting LDA models with tf features, n_samples=%d and n_features=%d..."
% (n_samples, n_features),
)
lda = LatentDirichletAllocation(
n_components=n_components,
max_iter=5,
learning_method="online",
learning_offset=50.0,
random_state=0,
)
t0 = time()
lda.fit(tf)
print("done in %0.3fs." % (time() - t0))
tf_feature_names = tf_vectorizer.get_feature_names_out()
plot_top_words(lda, tf_feature_names, n_top_words, "Topics in LDA model")
まとめ
この実験では、文書コーパスに対して非負行列分解と潜在ディリクレ配分を適用して、コーパスのトピック構造の加算モデルを抽出する方法を学びました。また、重みに基づいた上位いくつかの単語を使って各トピックを棒グラフとして表現する方法でトピックをプロットする方法も学びました。