LLM埋め込みとHDBSCANによる非構造化テキストのクラスタリング
大規模言語モデルの埋め込みとHDBSCANを組み合わせて、ラベルなしテキストデータから自動的にトピックを発見するテキストクラスタリングパイプラインの構築方法を紹介。埋め込み生成、UMAPによる次元削減、HDBSCANによるクラスタリングを解説。
現在の生成AIのブームでは、チャットインターフェースとプロンプトに焦点が当たりがちですが、大規模言語モデル(LLM)の応用範囲はそれだけに留まりません。その最も強力な能力の一つは、生の非構造化テキストをセマンティックに豊かな数学的表現、すなわち埋め込みに変換することです。この埋め込みを用いると、さまざまな機械学習タスクが可能になり、クラスタリングも例外ではありません。
特に、埋め込みはHDBSCANのような高度な密度ベースクラスタリング手法と組み合わせることで、テキストコレクション内の隠れたトピックやパターンを事前ラベルなしで発見できます。本記事では、ゼロからテキストベースのクラスタリングパイプラインを構築する手順を説明します。
ステップバイステップの実装
まず、必要なPythonライブラリをインストールします。sentence-transformersはHugging Faceから事前学習済みLLMをロードして埋め込みを生成するため、umap-learnは次元削減のために使用します。さらに、scikit-learnとpandasも必要です。
!pip install sentence-transformers umap-learn次に、データを取得します。fetch_20newsgroups関数でニュースグループデータセットを読み込み、3つのカテゴリ(sci.space、sci.med、rec.autos)から150サンプルを抽出します。ラベルはクラスタリングのために無視します。
import pandas as pd
from sklearn.datasets import fetch_20newsgroups
categories = ['sci.space', 'sci.med', 'rec.autos']
newsgroups = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes'))
df = pd.DataFrame({'text': newsgroups.data, 'true_label': newsgroups.target})
df = df[df['text'].str.strip().str.len() > 100].sample(150, random_state=42).reset_index(drop=True)
print(f"Loaded {len(df)} text documents.")埋め込みの生成には、軽量で効率的なall-MiniLM-L6-v2モデルを使用します。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(df['text'].tolist(), show_progress_bar=True)
print(f"Embedding matrix shape: {embeddings.shape}")元の埋め込み次元は高いため、UMAPを用いて5次元に削減します。
import umap
reducer = umap.UMAP(n_neighbors=15, n_components=5, min_dist=0.0, random_state=42)
reduced_embeddings = reducer.fit_transform(embeddings)
print(f"Reduced matrix shape: {reduced_embeddings.shape}")次に、HDBSCANを適用します。最小クラスターサイズを8、最小サンプル数を3に設定します。
from sklearn.cluster import HDBSCAN
clusterer = HDBSCAN(min_cluster_size=8, min_samples=3, store_centers='centroid')
df['cluster'] = clusterer.fit_predict(reduced_embeddings)
cluster_counts = df['cluster'].value_counts()
print("Cluster Distribution:")
print(cluster_counts)結果として2つのクラスターが検出され、ノイズポイントはありませんでした。サンプルテキストを確認すると、クラスター0は宇宙と医学、クラスター1は自動車に関するトピックでした。
さらに、各次元ペアの散布図を描画して可視化できます。
import matplotlib.pyplot as plt
import seaborn as sns
import itertools
reduced_df = pd.DataFrame(reduced_embeddings, columns=[f'UMAP_D{i+1}' for i in range(reduced_embeddings.shape[1])])
reduced_df['cluster'] = df['cluster']
dim_pairs = list(itertools.combinations(reduced_df.columns[:-1], 2))
num_plots = len(dim_pairs)
num_cols = 3
num_rows = (num_plots + num_cols - 1) // num_cols
plt.figure(figsize=(num_cols * 5, num_rows * 4))
for i, (dim1, dim2) in enumerate(dim_pairs):
plt.subplot(num_rows, num_cols, i + 1)
sns.scatterplot(x=dim1, y=dim2, hue='cluster', data=reduced_df, palette='viridis', s=70, alpha=0.7)
plt.title(f'{dim1} vs {dim2}')
plt.grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()まとめ
LLM埋め込みとHDBSCANを組み合わせることで、テキストの真のセマンティックな意味を捉え、クラスター数を自動決定し、ノイズを検出できるパイプラインを構築できました。このアプローチはさまざまな教師なしテキスト分析タスクに応用可能です。