機械学習におけるクラスタリング(教師なし分類)で、まず名前が挙がるのがK-Means法です。シンプルかつ高速で非常に優れたアルゴリズムですが、万能ではありません。特に、三日月形やドーナツ形のような複雑な形状を持つデータ群の分類は苦手としています。
そんなK-Meansの弱点を克服し、**複雑なデータ構造を鮮やかに分割してくれるのが「SpectralClustering(スペクトラルクラスタリング)」**です。
この記事では、Pythonのscikit-learnライブラリを使い、SpectralClusteringの理論的な背景から実践的な使い方、そして精度を高めるためのパラメータ調整までを、コードを交えながら徹底的に解説します。
この記事を最後まで読めば、あなたはSpectralClusteringを使いこなし、データ分析の引き出しを一つ増やすことができるでしょう。
SpectralClusteringとは?K-Meansでは難しい「複雑なデータ」を解決
まず、なぜSpectralClusteringが必要とされるのか、その背景から理解を深めましょう。
K-Meansの限界点:なぜ三日月やドーナツ型のデータを分割できないのか?
K-Meansは、各データ点とクラスタ中心点との**「距離」**を基準にグループ分けを行います。具体的には、空間内の各点が、最も近い中心点を持つクラスタに所属するというルールです。
このアプローチは、各クラスタが球状にまとまっている(専門用語で**「凸な」**)データセットに対しては非常にうまく機能します。しかし、下の図のような三日月形(非凸)のデータではどうでしょうか。
K-Meansはデータの形状を考慮せず、単純に空間を直線で分割しようとします。そのため、直感とはかけ離れた、不自然な境界線でデータを分割してしまうのです。ドーナツ状のデータも同様に、内側と外側をうまく分離できません。
グラフ理論で解決!SpectralClusteringの基本的な考え方
この問題を解決するのがSpectralClusteringです。この手法は「距離」ではなく、データ点同士の**「つながり」や「関係性」**に着目します。
難しい数式は一旦置いておき、直感的なイメージで説明します。
- グラフの作成: まず、全てのデータ点をグラフの「ノード(点)」と見なします。そして、距離が近いノード同士を「エッジ(線)」で結びます。これにより、データセット全体が巨大なネットワーク(グラフ)構造として表現されます。
- グラフの分割: 次に、このグラフをいくつかの塊に分割する問題を考えます。このとき、「エッジの結びつきが強いノード群は同じグループに留め、結びつきが弱い部分でカットする」という方針で分割します。
この「グラフをうまく分割する」というアプローチにより、データが全体としてどのような形状をしていても、その繋がりに沿って自然なクラスタリングが可能になります。これが、SpectralClusteringが複雑なデータ構造に強い理由です。 (技術的には、この分割はデータから作成したラプラシアン行列の固有値・固有ベクトルを計算することで行われます。これが”Spectral”という名前の由来です。)
【基本編】Scikit-learnによるSpectralClusteringの簡単な使い方
理論はここまでにして、さっそくscikit-learnを使ってSpectralClusteringを動かしてみましょう。驚くほど簡単に実装できます。
必要なライブラリのインストールとインポート
まずは、scikit-learnと、結果を可視化するためのmatplotlibを準備します。未インストールの方は、ターミナルやコマンドプロンプトで以下のコマンドを実行してください。
pip install scikit-learn matplotlibインストールが終わったら、Pythonスクリプトで必要なライブラリをインポートします。
# データ操作・数値計算
import numpy as np
# グラフ描画
import matplotlib.pyplot as plt
# データセット生成用
from sklearn.datasets import make_moons
# SpectralClusteringモデル
from sklearn.cluster import SpectralClusteringmake_moonsで三日月形のサンプルデータを準備
今回は、先ほどから例に出している三日月形のデータをscikit-learnのmake_moons関数を使って生成します。
# 三日月形のデータを200個生成
# noise: データに加えるばらつきの度合い
# random_state: 乱数を固定し、毎回同じデータを生成するため
X_moon, y_moon = make_moons(n_samples=200, noise=0.05, random_state=0)
# 生成したデータがどのような形かプロットして確認
plt.figure(figsize=(8, 6))
plt.scatter(X_moon[:, 0], X_moon[:, 1], s=50, cmap='viridis')
plt.title('Original Moon-shaped Data')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.grid(True)
plt.show()fit_predictでモデル学習から予測までを3行で実装
scikit-learnのAPIは統一されており、非常に直感的に使えます。SpectralClusteringも、モデルをインスタンス化し、fit_predict()メソッドを呼び出すだけです。
# 1. SpectralClusteringのモデルをインスタンス化
# n_clusters: 分割したいクラスタの数
model = SpectralClustering(n_clusters=2, affinity='rbf', random_state=0)
# 2. データを学習させ、各データがどのクラスタに属するかを予測
labels = model.fit_predict(X_moon)
# 3. 予測結果(ラベル)を表示
print(labels[:20]) # 最初の20件だけ表示
# 出力例: [1 0 0 1 0 1 1 1 0 1 0 1 0 0 1 0 1 0 1 1]たったこれだけで、200個の各データ点がクラスタ0と1のどちらに分類されたかを示すラベル配列が得られました。
Matplotlibでクラスタリング結果を分かりやすく可視化
得られたラベルを使って、クラスタごとに色分けしてプロットし、結果を視覚的に確認しましょう。
# クラスタリング結果を可視化
plt.figure(figsize=(8, 6))
# c=labels とすることで、ラベルの値に応じて色を自動で変えてくれる
plt.scatter(X_moon[:, 0], X_moon[:, 1], c=labels, s=50, cmap='viridis')
plt.title('Result of Spectral Clustering')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.grid(True)
plt.show()見事です!三日月という複雑な形状に沿って、データが綺麗に2つのグループに分割されていることが一目でわかります。これがSpectralClusteringの基本的な使い方です。
【応用編】主要パラメータを理解して精度を高める
基本編ではデフォルトに近い設定で使いましたが、SpectralClusteringの性能を最大限に引き出すには、いくつかの重要なパラメータを理解し、データに合わせて調整する必要があります。ここでは、特に重要な3つのパラメータを深掘りします。
n_clusters:クラスタ数の決定
これは最も直感的で重要なパラメータです。データセットをいくつのグループに分けたいかを整数で指定します。この値は事前に決まっている必要があります。もし最適なクラスタ数が不明な場合は、シルエット分析などの他の手法を用いて事前に推定することが一般的です。
affinity:類似度の計算方法を決める最重要パラメータ (‘rbf’ vs ‘nearest_neighbors’)
affinityは、データ点同士の「つながりの強さ(類似度)」をどのように計算するかを定義します。これはモデルの挙動に大きく影響します。
'rbf'(Radial Basis Function): デフォルトの設定で、非常に広く使われます。全てのデータ点の組み合わせについて、距離が近いほど類似度が高くなるように計算します。滑らかで連続的なデータの塊を捉えるのが得意です。イメージとしては、各点が周囲にぼんやりとした影響力を及ぼし、その重なり合いで繋がりを評価する方法です。'nearest_neighbors': 各データ点が、自身に最も近いk個の点とのみエッジを持つグラフ(k-NNグラフ)を構築します。kの値はn_neighborsパラメータで指定します。細長く入り組んだ構造や、密度が不均一なデータに対して有効な場合があります。イメージとしては、各点が「最も親しい友人」とだけ手を繋ぎ、その繋がりを辿っていく方法です。
基本的には'rbf'でうまくいくことが多いですが、データ構造によっては'nearest_neighbors'が優れた結果をもたらすこともあります。
gamma:affinity='rbf'の挙動を左右する調整役
gammaは、affinity='rbf'が選択された場合にのみ有効なパラメータです。これは、各データ点の影響力が届く範囲を調整する役割を持ちます。
gammaの値が大きい: 各データ点の影響範囲が狭くなります。非常に近い点同士しか「似ている」と見なされず、モデルは局所的な構造に敏感になります。大きすぎると、全ての点が孤立してしまい、うまくクラスタリングできません。gammaの値が小さい: 各データ点の影響範囲が広くなります。遠くの点にも影響が及び、モデルは大域的な構造を捉えようとします。小さすぎると、全ての点が繋がっていると見なされ、やはりうまく分割できません。
gammaはデータのスケールに依存するため、適切な値を見つけるには試行錯誤が必要です。
パラメータを変えると結果はどう変わるか(コードで比較)
gammaの値を変えると、クラスタリング結果が劇的に変化する様子を見てみましょう。
# gammaの値を3パターン試す
gamma_values = [0.1, 1, 10]
# グラフ描画エリアを準備 (1行3列)
fig, axes = plt.subplots(1, 3, figsize=(20, 5))
for i, gamma in enumerate(gamma_values):
# モデルの作成と学習
model = SpectralClustering(n_clusters=2, affinity='rbf', gamma=gamma, random_state=0)
labels = model.fit_predict(X_moon)
# サブプロットに結果を描画
ax = axes[i]
ax.scatter(X_moon[:, 0], X_moon[:, 1], c=labels, s=50, cmap='viridis')
ax.set_title(f'Gamma = {gamma}')
ax.grid(True)
plt.suptitle('Effect of Gamma on Spectral Clustering', fontsize=16)
plt.show()この結果から、gamma=1が適切である一方、gamma=0.1では影響範囲が広すぎて区別がつかず、gamma=10では影響範囲が狭すぎて局所的な塊に分割されてしまっていることがわかります。このように、パラメータ調整が非常に重要であることが理解できたかと思います。
実例で比較!SpectralClustering vs K-Means
SpectralClusteringの有効性をより深く理解するために、K-Meansと比較してみましょう。
ケース1:三日月形データでの比較
まずは、これまで使ってきた三日月形データです。K-Meansもscikit-learnで簡単に実装できます。
from sklearn.cluster import KMeans
# KMeansモデル
kmeans = KMeans(n_clusters=2, random_state=0, n_init='auto')
kmeans_labels = kmeans.fit_predict(X_moon)
# SpectralClusteringモデル (再掲)
sc_labels = SpectralClustering(n_clusters=2, affinity='rbf', gamma=1, random_state=0).fit_predict(X_moon)
# 結果を並べて比較
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
axes[0].scatter(X_moon[:, 0], X_moon[:, 1], c=kmeans_labels, s=50, cmap='viridis')
axes[0].set_title('K-Means Result')
axes[0].grid(True)
axes[1].scatter(X_moon[:, 0], X_moon[:, 1], c=sc_labels, s=50, cmap='viridis')
axes[1].set_title('Spectral Clustering Result')
axes[1].grid(True)
plt.show()結果は一目瞭然です。K-Meansが空間を直線的に分割するのに対し、SpectralClusteringはデータの非線形な構造を見事に捉えています。
ケース2:同心円状データでの比較
次に、ドーナツ状のデータでも試してみましょう。scikit-learnのmake_circlesで生成します。
from sklearn.datasets import make_circles
# 同心円状のデータを生成
X_circ, y_circ = make_circles(n_samples=400, factor=0.5, noise=0.05, random_state=0)
# 各アルゴリズムでクラスタリング
kmeans_labels_circ = KMeans(n_clusters=2, random_state=0, n_init='auto').fit_predict(X_circ)
sc_labels_circ = SpectralClustering(n_clusters=2, affinity='rbf', gamma=1, random_state=0).fit_predict(X_circ)
# 結果を並べて比較
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
axes[0].scatter(X_circ[:, 0], X_circ[:, 1], c=kmeans_labels_circ, s=50, cmap='viridis')
axes[0].set_title('K-Means Result on Circles')
axes[0].grid(True)
axes[1].scatter(X_circ[:, 0], X_circ[:, 1], c=sc_labels_circ, s=50, cmap='viridis')
axes[1].set_title('Spectral Clustering Result on Circles')
axes[1].grid(True)
plt.show()この例でも、SpectralClusteringは内側の円と外側の円を完璧に分離していますが、K-Meansはデータの形状を認識できず、全く意味のない分割を行っています。
なぜ結果が異なるのか?アルゴリズムの違いを再確認
これらの違いは、両アルゴリズムが持つ根本的な**「仮定」**の違いに起因します。
- K-Meansの仮定: クラスタは凸型(球状)であり、クラスタ間の分離は線形である。
- SpectralClusteringの仮定: 密接に繋がった(グラフ上で近い)データ群がクラスタを形成する。
自分の扱っているデータがどのような構造を持っているかを事前に可視化し、適切なアルゴリズムを選択することが、精度の高いクラスタリングへの第一歩です。
まとめ:SpectralClusteringを使いこなすためのポイント
今回は、Scikit-learnを使ったSpectralClusteringについて、基本から応用まで徹底的に解説しました。最後に、重要なポイントをまとめておきます。
- SpectralClusteringは「つながり」ベース: データ点間の距離ではなく、関係性(グラフ構造)に着目するため、K-Meansが苦手な複雑な形状(非凸)のデータをうまく分割できます。
- 実装は非常に簡単:
scikit-learnを使えば、数行のコードで実装から可視化まで行えます。 - パラメータ調整が鍵: 性能を最大限に引き出すには、
n_clusters(クラスタ数)、affinity(類似度計算方法)、そしてgamma(影響範囲)といったパラメータの理解と調整が不可欠です。 - 計算コストに注意: SpectralClusteringはデータ間の類似度を総当たりで計算するため、データ数が非常に大きい(数十万件以上)場合は計算に時間がかかることがあります。その点はK-Meansに劣るため、使い分けが必要です。
データ分析の世界では、一つの万能なアルゴリズムは存在しません。それぞれのアルゴリズムの長所と短所を理解し、問題に応じて適切に使い分ける能力が求められます。
ぜひ、あなたのデータセットでもSpectralClusteringを試し、その力を実感してみてください。


コメント