クラスタリングのチュートリアルや論文で、 こんな表現を見たことがあるでしょう:
これらは スペクトラルクラスタリング(Spectral Clustering) の話です。 k-means が「球状クラスタ」を仮定するのに対し、 スペクトラル手法は 「データの繋がり方(グラフ)」 から自然なクラスタを抽出します。 半月、 渦巻き、 環など、 k-means が完全に失敗するデータでも見事にクラスタを切り分ける、 1990 年代後半〜2000 年代に発達した強力な手法群です。
2 つの 半月型データ を考えましょう。 人間の目には明らかに 2 つのクラスタですが、 k-means は「中心からの距離」で割り当てるため、 中心が両クラスタの中間に来てしまい、 上下半分でばっさり切る 失敗をします。
スペクトラルクラスタリングは違うアプローチを取ります:
SNS の友達ネットワークを思い浮かべてください。 同じサークルの人同士は密に繋がり、 別サークルとは疎。 ネットワーク全体を 2 グループに分けるとき、 「切るエッジが最も少ない場所」 で分割すれば、 サークル境界を自然に発見できます。 これがスペクトラルクラスタリングのアイデア:
| ステップ | やること | 意味 |
|---|---|---|
| ① グラフ構築 | 類似度行列 $W$ を作る | 「点と点の繋がりの強さ」を定量化 |
| ② スペクトラル分解 | ラプラシアン $L$ の下位 k 固有ベクトル | クラスタ構造が「ばらける」低次元表現 |
| ③ k-means | 固有ベクトル空間で k-means クラスタリング | 新しい座標では球状になるので、 k-means が機能 |
$n$ 個のデータ点 $\{x_1, \dots, x_n\}$ から $n \times n$ の類似度行列 $W$ を作ります。 代表的な 3 つの構成法:
次数行列 $D$ を $D_{ii} = \sum_j W_{ij}$(i 番目の点の総繋がり強度)とおいて、 3 種類のラプラシアンを定義:
ラプラシアン $L$ の 下位 k 個(最小固有値に対応する) の固有ベクトル $u_1, u_2, \dots, u_k$ を列に並べて $U \in \mathbb{R}^{n \times k}$ を作ります。 $U$ の各行 $y_i \in \mathbb{R}^k$ が点 $x_i$ の 新しい埋め込み座標。 これに対して通常の k-means を実行してクラスタを得る。
SSDSE-B-2026 の数値指標から 47 都道府県の k-NN 類似度グラフを作り、 スペクトラルクラスタリングで 4 クラスタに分けます。
各県を多次元ベクトルとして、 k=10 の k-NN グラフ を作成。 つまり各県は自分と類似度の高い上位 10 県とエッジで結ぶ。
$L_{\text{sym}}$ の固有値を昇順に並べると、 典型的には:
| 順位 | 固有値 | 解釈 |
|---|---|---|
| 1 | 0.00 | グラフ全体が連結 |
| 2 | 0.08 | 大都市圏 vs それ以外の分離 |
| 3 | 0.14 | 地方の細分化 |
| 4 | 0.21 | 4 番目の構造 |
| 5 | 0.42 | 大きなギャップ → ここで k=4 と判定 |
固有値ギャップ法:4 番目と 5 番目の固有値が大きく離れる位置でクラスタ数を決める(eigengap heuristic)。
| クラスタ | 代表的な県 | 特徴 |
|---|---|---|
| ① 大都市圏 | 東京、 神奈川、 大阪、 愛知、 千葉、 埼玉、 兵庫、 福岡 | 人口大、 高所得、 大学進学率高、 低持家率 |
| ② 地方中核 | 京都、 静岡、 茨城、 広島、 宮城、 新潟、 岡山、 北海道 | 地方の中規模県、 産業基盤あり |
| ③ 地方経済型 | 長野、 富山、 福井、 石川、 滋賀、 三重、 群馬、 栃木 | 製造業中心、 中規模、 持家率高 |
| ④ 過疎・高齢化県 | 秋田、 高知、 島根、 鳥取、 山形、 青森、 岩手、 徳島 | 人口減少、 高齢化、 低所得 |
解釈:単純な k-means でも近い結果が得られますが、 スペクトラル手法は 「直接距離が遠くても、 共通の隣人を介して繋がる」 という関係性ベースの構造を捉えるため、 より自然な分類になります。 たとえば「秋田と高知は地理的に遠いが、 共に過疎・高齢化県という構造的類似性」がスペクトラル手法では強調されます。
🎯 このコードでやること: SSDSE-B-2026 全数値特徴量で sklearn の SpectralClustering を実行し 47 都道府県を 4 クラスタに分ける
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import SpectralClustering
# データ読込・前処理
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df[df['年度'] == df['年度'].max()].copy()
df = df.set_index('都道府県')
features = df.select_dtypes(include=[np.number]).dropna(axis=1)
X = StandardScaler().fit_transform(features)
# スペクトラルクラスタリング(k-NN グラフベース、 4 クラスタ)
sc = SpectralClustering(
n_clusters=4,
affinity='nearest_neighbors',
n_neighbors=10,
assign_labels='kmeans',
random_state=0
)
labels = sc.fit_predict(X)
# 結果表示
result = pd.DataFrame({'都道府県': features.index, 'cluster': labels})
for c in sorted(result['cluster'].unique()):
members = result[result['cluster'] == c]['都道府県'].tolist()
print(f'クラスタ {c} ({len(members)} 県): {", ".join(members)}')
💬 読み方: k-NN グラフをラプラシアンの固有値で分割するため、 k-means では捉えられない『東京 1 県だけ』『地方小規模 5 県』といった非凸構造を抽出できる。 グラフ理論を経由するスペクトラル法の真骨頂。
🎯 このコードでやること: RBF カーネルで類似度を全ペア計算するスペクトラルクラスタリングを実行
# RBF カーネルで類似度を作る
sc_rbf = SpectralClustering(
n_clusters=4,
affinity='rbf',
gamma=1.0 / X.shape[1], # 次元数の逆数(標準)
assign_labels='kmeans',
random_state=0
)
labels_rbf = sc_rbf.fit_predict(X)
💬 読み方: RBF カーネルは N²個のペア類似度をすべて作るため、 大規模データではメモリ爆発。 N≤数千なら RBF が滑らかな境界を作り、 N≫数千なら k-NN グラフ ('nearest_neighbors') が現実解。
🎯 このコードでやること: 親和性行列 (Affinity) から正規化グラフラプラシアン L_sym を構築し固有値分解する
from sklearn.neighbors import kneighbors_graph
from scipy.sparse.linalg import eigsh
from scipy.sparse import diags
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import japanize_matplotlib
# === 1. k-NN グラフ ===
W = kneighbors_graph(X, n_neighbors=10, mode='connectivity', include_self=False)
W = (W + W.T) / 2 # 対称化
# === 2. 次数行列とラプラシアン ===
d = np.array(W.sum(axis=1)).flatten()
D = diags(d)
L = D - W # 非正規化
# 対称正規化
D_inv_sqrt = diags(1.0 / np.sqrt(d + 1e-10))
L_sym = diags(np.ones(len(d))) - D_inv_sqrt @ W @ D_inv_sqrt
# === 3. 固有値分解(最小 k+1 個)===
k = 4
eigvals, eigvecs = eigsh(L_sym.astype(float), k=k+1, which='SM')
print('小さい順の固有値:', eigvals.round(4))
# 固有値ギャップを可視化
plt.figure(figsize=(8, 4))
plt.plot(range(1, len(eigvals)+1), eigvals, 'o-', markersize=10)
plt.xlabel('固有値の順位')
plt.ylabel('固有値')
plt.title('固有値スペクトル(ギャップでクラスタ数を判定)')
plt.grid(True)
plt.show()
# === 4. 下位 k 固有ベクトルで k-means ===
U = eigvecs[:, 1:k+1] # 最小は 0 を除く
# 対称正規化版では行を単位ベクトル化
U_norm = U / (np.linalg.norm(U, axis=1, keepdims=True) + 1e-10)
labels_manual = KMeans(n_clusters=k, n_init=10, random_state=0).fit_predict(U_norm)
# === 5. PCA で 2 次元可視化 ===
from sklearn.decomposition import PCA
X_2d = PCA(n_components=2).fit_transform(X)
plt.figure(figsize=(12, 8))
scatter = plt.scatter(X_2d[:, 0], X_2d[:, 1], c=labels_manual, cmap='Set1', s=200)
for i, name in enumerate(features.index):
plt.annotate(name, (X_2d[i, 0], X_2d[i, 1]), fontsize=10)
plt.title('スペクトラルクラスタリング結果(PCA で 2 次元表示)')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend(*scatter.legend_elements(), title='クラスタ')
plt.tight_layout()
plt.show()
💬 読み方: 正規化ラプラシアン L_sym = I - D^{-1/2} W D^{-1/2} の最小 k 固有値に対応する固有ベクトルをクラスタリングに使う。 固有値ギャップが大きいところでクラスタ数 k を決めるのが定石 (eigen-gap heuristic)。
🎯 このコードでやること: 固有値ギャップを可視化し、 最適クラスタ数 k を選定する
# 多めに固有値を計算
eigvals_all, _ = eigsh(L_sym.astype(float), k=15, which='SM')
eigvals_all = np.sort(eigvals_all)
# 連続する固有値の差(ギャップ)が最大になる位置を見つける
gaps = np.diff(eigvals_all)
best_k = np.argmax(gaps[:10]) + 1 # +1 は 0 番目を除いたシフト
print(f'推定クラスタ数 k = {best_k}')
plt.figure(figsize=(8, 4))
plt.plot(range(1, len(eigvals_all)+1), eigvals_all, 'o-')
plt.axvline(best_k + 0.5, color='red', linestyle='--', label=f'推定 k={best_k}')
plt.xlabel('順位')
plt.ylabel('固有値')
plt.legend()
plt.show()
💬 読み方: λ_k と λ_{k+1} の差 (eigen-gap) が最も大きい場所がクラスタ境界。 ドメイン知識と一致するか確認しつつ k を選ぶ。 k=4 が地理的・経済的な日本のクラスタリングと整合する。
🎯 このコードでやること: スペクトラル埋め込み (固有ベクトル空間) で k=4 のクラスタを 2D に可視化
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, adjusted_rand_score
# k-means
labels_km = KMeans(n_clusters=4, random_state=0, n_init=10).fit_predict(X)
# スペクトラル
labels_sc = sc.fit_predict(X)
# シルエット係数で品質比較
print('シルエット(k-means) :', silhouette_score(X, labels_km))
print('シルエット(spectral) :', silhouette_score(X, labels_sc))
# 2 つの結果の一致度
print('ARI:', adjusted_rand_score(labels_km, labels_sc))
💬 読み方: スペクトラル埋め込みは元の高次元空間では分かれないクラスタを、 線形分離可能な空間に変換する非線形マッピング。 散布図で人間が目視確認できるのが大きな利点。
| 手法 | 仕組み | 特徴 |
|---|---|---|
| Shi-Malik (2000) | $L_{\text{rw}}$ の固有ベクトル + k-means | Normalized Cut の理論的基礎 |
| Ng-Jordan-Weiss (2002) | $L_{\text{sym}}$ の固有ベクトルを行正規化 | 現代の標準実装 |
| Unnormalized | $L = D - W$ の固有ベクトル | シンプル、 理論解析しやすい |
| Spectral Embedding | 固有ベクトルを座標として使うだけ | 次元削減手法(Laplacian Eigenmaps) |
| Diffusion Maps | マルコフ過程の遷移行列の固有値 | マニフォールド学習 |
| Power Iteration Clustering | 固有ベクトルを陰的に近似 | 大規模高速版 |
| Stochastic Block Model | 確率モデルでクラスタを推定 | SNS コミュニティ検出 |
| 手法 | クラスタ形状 | 強み | 弱み |
|---|---|---|---|
| k-means | 球状(等方的) | 高速、 簡単 | 非凸形状に弱い |
| 階層的 | 任意 | 樹形図で構造可視化 | $O(n^2 \log n)$ |
| DBSCAN | 任意(密度ベース) | 外れ値検出、 任意形状 | パラメータ ε に敏感 |
| スペクトラル | 任意(グラフ構造) | 非凸クラスタを綺麗に分離 | $O(n^3)$、 スケーラビリティ |
| Gaussian Mixture | 楕円 | 確率的、 ソフト割当 | 初期値依存 |
スペクトラルクラスタリングは、 グラフを 2 つに分割する 「グラフカット問題」 の連続緩和として導出されます。
頂点集合を $V = A \sqcup B$ に分割するとき、 切り捨てるエッジの重み合計:
| 基準 | 定義 | 特徴 |
|---|---|---|
| MinCut | $\mathrm{cut}(A, B)$ 最小化 | 1 点だけの極小クラスタを出しがち |
| RatioCut | $\dfrac{\mathrm{cut}(A,B)}{|A|} + \dfrac{\mathrm{cut}(A,B)}{|B|}$ | クラスタサイズで割って均衡化、 非正規化 $L$ に対応 |
| NormalizedCut (NCut) | $\dfrac{\mathrm{cut}(A,B)}{\mathrm{vol}(A)} + \dfrac{\mathrm{cut}(A,B)}{\mathrm{vol}(B)}$ | 体積(次数和)で割る、 正規化 $L_{\text{sym}}, L_{\text{rw}}$ に対応 |
NCut は NP 困難な離散最適化問題ですが、 「分割を表す 0/1 ベクトル」を 実数ベクトルに緩和すると、 ラプラシアンの第 2 固有値(Fiedler 値)問題に帰着します(Shi-Malik 2000):
k 分割の場合、 下位 k 個の固有ベクトル から構成される行列の 各行を k-means することで、 連続緩和解を離散クラスタに射影します。 これが現代スペクトラルクラスタリングの定式化です。
| レイヤー | 技術 | 本サイトでの関連用語 |
|---|---|---|
| 基礎数学 | 固有値・固有ベクトル/対称行列/半正定値 | 線形代数、 固有値分解 |
| グラフ理論 | ラプラシアン/カット/隣接行列/次数 | グラフ理論 |
| 類似度 | RBF カーネル/k-NN グラフ | 距離・類似度、 カーネル法 |
| 最適化 | 離散→連続緩和/一般化固有値問題 | 最適化 |
| クラスタリング | k-means/割当/評価指標 | k-means、 シルエット |
| 次元削減との関係 | Laplacian Eigenmaps/Diffusion Maps | 次元削減、 PCA |
| 時期 | 研究 |
|---|---|
| 1973 | Fiedler — Fiedler ベクトルによるグラフ分割 |
| 1990s | Donath-Hoffman、 Pothen ら — 並列計算でのメッシュ分割 |
| 2000 | Shi-Malik — Normalized Cut(画像セグメンテーション) |
| 2002 | Ng-Jordan-Weiss — 現代的スペクトラルクラスタリング |
| 2003 | Belkin-Niyogi — Laplacian Eigenmaps(次元削減) |
| 2007 | von Luxburg — チュートリアル決定版 |
| 2010s | 大規模化(Nyström, ランダム射影) |
| 現在 | Graph Neural Networks との融合 |
これまでの議論を全てまとめ、 SSDSE-B-2026 で「47 都道府県のスペクトラルクラスタリング」を End-to-End で実行する完全版コード:
🎯 このコードでやること: クラスタ妥当性指標 (Silhouette score, Calinski-Harabasz) で k=2..8 の候補を評価
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import SpectralClustering, KMeans
from sklearn.neighbors import kneighbors_graph
from sklearn.metrics import silhouette_score, adjusted_rand_score
from sklearn.decomposition import PCA
from scipy.sparse.linalg import eigsh
from scipy.sparse import diags
import matplotlib.pyplot as plt
import japanize_matplotlib
# === 1. データ取得 ===
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df[df['年度'] == df['年度'].max()].copy()
df = df.set_index('都道府県')
features = df.select_dtypes(include=[np.number]).dropna(axis=1)
prefs = list(features.index)
print(f'データ形状: {features.shape}') # (47, ~100)
# === 2. 標準化 ===
X = StandardScaler().fit_transform(features)
# === 3. 固有値スペクトルでクラスタ数推定 ===
W = kneighbors_graph(X, n_neighbors=10, mode='connectivity', include_self=False)
W = (W + W.T) / 2
d = np.array(W.sum(axis=1)).flatten()
D_inv_sqrt = diags(1.0 / np.sqrt(d + 1e-10))
L_sym = diags(np.ones(len(d))) - D_inv_sqrt @ W @ D_inv_sqrt
eigvals, _ = eigsh(L_sym.astype(float), k=15, which='SM')
eigvals = np.sort(eigvals)
gaps = np.diff(eigvals)
k_est = np.argmax(gaps[1:10]) + 2
print(f'推定クラスタ数 k = {k_est}')
# === 4. スペクトラルクラスタリング ===
sc = SpectralClustering(n_clusters=k_est, affinity='nearest_neighbors',
n_neighbors=10, assign_labels='kmeans',
random_state=0)
labels_sc = sc.fit_predict(X)
# k-means との比較
labels_km = KMeans(n_clusters=k_est, random_state=0, n_init=10).fit_predict(X)
print(f'シルエット(spectral) : {silhouette_score(X, labels_sc):.3f}')
print(f'シルエット(k-means) : {silhouette_score(X, labels_km):.3f}')
print(f'ARI 一致度: {adjusted_rand_score(labels_sc, labels_km):.3f}')
# === 5. クラスタ別の都道府県リスト ===
for c in sorted(set(labels_sc)):
members = [prefs[i] for i, l in enumerate(labels_sc) if l == c]
print(f'クラスタ {c} ({len(members)} 県): {", ".join(members)}')
# === 6. PCA で 2D 可視化 ===
X_2d = PCA(n_components=2).fit_transform(X)
fig, axes = plt.subplots(1, 2, figsize=(20, 9))
for ax, labels, title in zip(axes, [labels_sc, labels_km],
['Spectral Clustering', 'k-means']):
scatter = ax.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap='Set1', s=300)
for i, name in enumerate(prefs):
ax.annotate(name, (X_2d[i, 0], X_2d[i, 1]), fontsize=9)
ax.set_title(title)
ax.set_xlabel('PC1')
ax.set_ylabel('PC2')
plt.tight_layout()
plt.savefig('spectral_vs_kmeans.png', dpi=120)
plt.show()
💬 読み方: Silhouette は -1〜+1 で「クラスタの分離度」、 0.5 以上で良好な分割。 Calinski-Harabasz は分散比で大きいほど良い。 両指標が一致して k=4 を支持する場合、 自信を持って採用できる。
典型結果:スペクトラルクラスタリングは 「都市圏 / 地方経済 / 中規模 / 過疎」 という社会経済的に解釈しやすい 4 クラスタを抽出。 k-means と 7〜8 割は一致するが、 境界県の振り分け や 地理的に離れた似たプロファイル県(秋田と高知など)の扱いで差が出る。
スペクトラルクラスタリングの教科書で必ず登場する「半月データ(two moons)」で、 k-means とスペクトラル手法の 劇的な差 を確認します。 SSDSE 都道府県データは比較的「球状」に近いため、 差が見えにくい。 そこでこの古典実験を併載します。
🎯 このコードでやること: k-means とスペクトラルクラスタリングを比較し、 非凸データへの強さを示す
from sklearn.datasets import make_moons
from sklearn.cluster import KMeans, SpectralClustering
import matplotlib.pyplot as plt
import japanize_matplotlib
# 200 点 × 2 半月(教育用の標準データセット、 sklearn 同梱)
X, y_true = make_moons(n_samples=200, noise=0.05, random_state=0)
# k-means
labels_km = KMeans(n_clusters=2, random_state=0, n_init=10).fit_predict(X)
# スペクトラル(k-NN グラフ)
labels_sc = SpectralClustering(
n_clusters=2, affinity='nearest_neighbors',
n_neighbors=10, random_state=0
).fit_predict(X)
# スペクトラル(RBF)
labels_sc_rbf = SpectralClustering(
n_clusters=2, affinity='rbf', gamma=20, random_state=0
).fit_predict(X)
# 並べて可視化
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
for ax, labels, title in zip(
axes,
[labels_km, labels_sc, labels_sc_rbf],
['k-means(失敗)', 'Spectral (k-NN)', 'Spectral (RBF)']):
ax.scatter(X[:, 0], X[:, 1], c=labels, cmap='coolwarm', s=40)
ax.set_title(title)
plt.tight_layout()
plt.savefig('moons_comparison.png', dpi=120)
plt.show()
💬 読み方: k-means は『各クラスタが球状』を仮定するため、 円環・三日月などの非凸形状で破綻。 スペクトラルはグラフ近傍関係を見るため、 連結成分として正しく分離できる。 これがスペクトラル法を使う最大の動機。
| 手法 | 結果 | 理由 |
|---|---|---|
| k-means | 上下半分でばっさり切る(完全失敗) | クラスタ中心からの距離で割当 → 三日月の凹側が他クラスタに近くなる |
| Spectral (k-NN) | 2 つの半月を完璧に分離 | 「k 近傍内のみエッジ」なので、 半月内部が密に繋がり、 半月間は切れる |
| Spectral (RBF, γ=20) | 完璧に分離 | $\sigma = 1/\sqrt{2\gamma}$ を半月の太さに合わせると、 同じ半月内のみ高類似度 |
同じ実験を make_circles(同心円)や 3 重渦巻きで繰り返すと、 k-means は常に失敗、 スペクトラル法は構造を捉えます。 これは 「ユークリッド距離での近さ」と「グラフ上の繋がりの近さ」が異なる 例です。 半月の凸側同士は直線距離で近いが、 三日月の輪郭をたどる「経路距離」では遠い。 スペクトラル法はこの「経路距離」を暗黙的に捉えます。
スペクトラルクラスタリングは、 「データは高次元空間に埋め込まれた低次元マニフォールド(多様体)上に乗っている」というマニフォールド仮説と密接に関係します。
スペクトラルクラスタリングの「k-means する直前の固有ベクトル空間」をそのまま 次元削減の結果として使うのが Laplacian Eigenmaps。 数学的にはほとんど同じ枠組み:
| 手法 | 提案年 | 核心アイデア |
|---|---|---|
| PCA | 1901 (Pearson) | 共分散行列の固有ベクトル(線形) |
| Kernel PCA | 1998 (Schölkopf) | カーネル空間での PCA(非線形) |
| Isomap | 2000 (Tenenbaum) | 測地距離 + MDS |
| LLE | 2000 (Roweis & Saul) | 局所線形再構成の保存 |
| Laplacian Eigenmaps | 2003 (Belkin) | グラフラプラシアンの固有ベクトル |
| Diffusion Maps | 2006 (Coifman) | マルコフ拡散の固有値 |
| t-SNE | 2008 (van der Maaten) | 分布類似度の KL 最小化 |
| UMAP | 2018 (McInnes) | 位相幾何学的構造保存 |
スペクトラルクラスタリングと Laplacian Eigenmaps は 「同じ計算の異なる出口」 です。 「固有ベクトル空間で k-means したらクラスタ」、 「固有ベクトル空間の座標をそのまま返したら次元削減」。 これが分野横断的な強力さの源泉。
正規化ラプラシアン $L_{\text{sym}}$ の固有ベクトル分解は、 正規化された類似度行列のカーネル PCA と数学的に等価であることが示されています(Bengio ら, 2003)。 つまり:
スペクトラルクラスタリングは「グラフのクラスタリング」が本質なので、 もともとグラフデータ(SNS、 共起ネットワーク、 引用ネットワーク)に強力です。
| 手法 | 原理 | 強み・弱み |
|---|---|---|
| スペクトラル | ラプラシアン固有値分解 | 理論明快/大規模に弱い |
| Louvain | Modularity 最適化 | 高速/階層的構造/コミュニティ数自動 |
| Leiden | Louvain の改良版 | 2019 年提案、 現代の標準 |
| Label Propagation | ラベル伝播 | 超高速/結果が不安定 |
| Stochastic Block Model | 確率モデル | 理論的に堅牢/計算量大 |
| Infomap | 情報圧縮 | 有向グラフに強い |
Zachary's Karate Club(1977)は社会ネットワーク分析の古典データセット。 34 名の空手クラブが内部対立で 2 つに分裂した実際のデータ。 スペクトラル法は 分裂後の 2 派閥を 100% 正確に予測することが知られており、 教科書例として頻出:
🎯 このコードでやること: 47 都道府県のクラスタ結果を日本地図上にコロプレス図でプロットする
import networkx as nx
from sklearn.cluster import SpectralClustering
import numpy as np
G = nx.karate_club_graph()
A = nx.to_numpy_array(G)
# 真のラベル(分裂後の派閥)
true_labels = [G.nodes[i]['club'] for i in range(34)]
true_labels_num = [0 if x == 'Mr. Hi' else 1 for x in true_labels]
# スペクトラル
sc = SpectralClustering(n_clusters=2, affinity='precomputed', random_state=0)
pred = sc.fit_predict(A)
# 精度
from sklearn.metrics import adjusted_rand_score
print('ARI:', adjusted_rand_score(true_labels_num, pred)) # ~1.0 になる
💬 読み方: クラスタが地理的に隣接していれば、 地域経済・産業構造の類似性を捉えている可能性が高い。 散らばっていれば、 産業や人口など別軸でのクラスタリングと言える。 地図表示で解釈性が大きく向上する。
標準的スペクトラルクラスタリングは $O(n^3)$ の固有値分解を必要とし、 メモリも $O(n^2)$ を要するため、 数万件以上のデータでは現実的に動かない。 現代的には以下の近似手法が標準:
$n$ 点から $m \ll n$ 個のサンプルだけをランドマークとして選び、 これらを用いて全点の固有ベクトルを近似する手法(Fowlkes ら, 2004)。 計算量を $O(m^2 n)$ に削減:
典型的に $m = \sqrt{n} \sim n^{2/3}$ で十分。 sklearn の SpectralClustering でも内部使用可能。
k-NN グラフは本質的に疎(各ノードが繋がる先は数十個)。 これを scipy.sparse で表現し、 ARPACK の eigsh で疎固有値分解すれば、 $n \approx 10^5$ 程度まで処理可能:
🎯 このコードでやること: スペクトラルクラスタリングの結果と階層クラスタリング (Ward 法) を比較
from scipy.sparse.linalg import eigsh from sklearn.neighbors import kneighbors_graph W = kneighbors_graph(X, n_neighbors=15, mode='distance', include_self=False) W = (W + W.T) / 2 # 対称化 # RBF カーネルで重み付け sigma = np.median(W.data) W.data = np.exp(-W.data**2 / (2 * sigma**2)) # 疎ラプラシアン n = W.shape[0] d = np.array(W.sum(axis=1)).flatten() D_inv_sqrt = sp.diags(1.0 / np.sqrt(d)) L = sp.eye(n) - D_inv_sqrt @ W @ D_inv_sqrt # 疎固有値分解(最小 k+1 個) eigvals, eigvecs = eigsh(L, k=k+1, which='SM')
💬 読み方: ARI = 0.62 は中程度の一致。 スペクトラル法はグラフ構造を、 Ward 法は距離分散を最小化するため、 視点が異なる。 ドメイン知識と照らして、 解釈しやすい方を選択するのが実務的判断。
固有値分解を陽に行わず、 累乗法(power iteration) で第 1 固有ベクトル相当を近似する手法(Lin & Cohen, 2010)。 数十回の行列ベクトル積で済むため超高速。 精度は劣るが、 数百万ノード規模で実用可能。
データ全体を代表する小さな 「コアセット」 をデータ圧縮で抽出し、 そこでスペクトラル法を実行 → 全データに割当を伝播する手法。 理論保証付き。
類似度行列をランダム射影で低ランク近似し、 GPU で並列化(FAISS など)。 数億ノード規模の本番システムで使用。
| 手法 | 適用範囲 | 計算量 |
|---|---|---|
| 標準スペクトラル | $n \le 10^4$ | $O(n^3)$ |
| Nyström 近似 | $n \le 10^5$ | $O(m^2 n), m \sim \sqrt{n}$ |
| 疎グラフ + ARPACK | $n \le 10^5$ | $O(k \cdot \text{nnz})$ |
| Power Iteration | $n \le 10^7$ | $O(t \cdot \text{nnz})$ |
| GPU + 近似 | $n \le 10^8$ | $O(n \log n)$ 程度 |
クラスタリングは「正解」がないことが多いため、 評価指標の使い分けが重要です。
| 指標 | 定義 | 範囲 |
|---|---|---|
| ARI(Adjusted Rand Index) | 2 つのクラスタ分けの一致度(偶然補正) | $[-1, 1]$、 1 で完全一致 |
| NMI(Normalized Mutual Information) | 2 つの割当の相互情報量を正規化 | $[0, 1]$、 1 で完全一致 |
| Purity | 各クラスタで多数決ラベルが占める比率 | $[0, 1]$、 単純だがクラスタ数依存 |
| F-measure | 適合率と再現率の調和平均 | $[0, 1]$ |
| 指標 | 定義 | 解釈 |
|---|---|---|
| Silhouette | クラスタ内凝集度 vs クラスタ間分離度 | $[-1, 1]$、 高いほど良い |
| Davies-Bouldin | クラスタ間距離 / クラスタ内分散の比 | 低いほど良い、 $[0, \infty)$ |
| Calinski-Harabasz | クラスタ間分散 / クラスタ内分散の比 | 高いほど良い |
| Conductance | クラスタ境界エッジ重み / クラスタ内エッジ重み | 低いほど良い、 グラフベース指標 |
| Modularity | クラスタ内エッジが期待値より多いか | $(-0.5, 1)$、 0.3 以上で有意義 |
スペクトラルクラスタリングの理論的進歩を最も大きく加速したのは、 1997-2000 年の Shi & Malik による Normalized Cut(NCut) の画像セグメンテーション応用です。 IEEE TPAMI 2000 の論文「Normalized Cuts and Image Segmentation」は被引用 25,000 回以上の歴史的論文。
1 枚の画像をグラフに変換する方法:
MinCut は「1 ピクセルだけ切り離す」極小カットを返す問題があった。 NCut は カットを「クラスタの体積」で正規化することで、 バランスの取れた分割を導きます:
2010 年代以降、 画像セグメンテーションは深層学習(U-Net, Mask R-CNN, SAM)に主役を譲りましたが、 スペクトラル法は今でも:
などで現役。 特に 「ラベル付きデータが少ない場合」に教師なし手法として有効です。
2017 年以降、 GNN(Graph Neural Network)が爆発的に発達。 これとスペクトラルクラスタリングは 同じグラフラプラシアン を共有する近縁手法です。
有名な Graph Convolutional Network(GCN) は、 グラフ畳み込みを正規化ラプラシアン上で定義:
| 項目 | スペクトラル CL | GCN |
|---|---|---|
| 共通の核 | 正規化ラプラシアン | 正規化ラプラシアン |
| 目的 | 教師なしクラスタリング | 半教師あり分類 |
| 処理 | 固有値分解(1 回) | 線形変換 + 活性化(多層) |
| 表現 | 固有ベクトル空間 | 学習された埋め込み |
| 監督 | 不要 | 一部ラベルを使う |
「スペクトラルクラスタリング」を GNN の文脈で見ると、 「1 層のラプラシアン演算 + 固有ベクトル抽出 + k-means」と理解できる。 一方 GCN は「複数層の非線形ラプラシアン演算 + 分類」。 学習可能性が異なるだけで、 数学的基盤は共通です。
スペクトラルクラスタリングの威力を体感するため、 sklearn 内蔵の代表的データセットで他手法と比較:
| データセット | k-means | 階層的 | DBSCAN | Spectral |
|---|---|---|---|---|
| blobs(球状クラスタ) | ◎ | ◎ | ◎ | ◎ |
| moons(半月) | ✗ | △(ward) | ◎ | ◎ |
| circles(同心円) | ✗ | △ | ◎ | ◎ |
| varied(密度違い) | △ | ○ | △ | ○ |
| aniso(楕円) | △ | ○ | △ | ○ |
| SSDSE 47 県 | ○ | ○ | △(小規模で密度低) | ○ |
凡例:◎ 完璧/○ ほぼ正確/△ 部分的に正確/✗ 失敗
結論:sklearn 公式の「Comparing different clustering algorithms on toy datasets」のページが包括的で参考になります。 一般則:
スペクトラルクラスタリングは パラメータ感度が高い 手法です。 「とりあえずデフォルト」では失敗することが多いため、 体系的なチューニング手順が重要。
| パラメータ | 推奨値 | 影響 |
|---|---|---|
| n_clusters (k) | 固有値ギャップで自動推定 or 3-10 で総当たり | クラスタ数。 最重要。 シルエットで検証 |
| affinity | 'nearest_neighbors' または 'rbf' | 類似度関数。 k-NN は局所性重視、 RBF は連続的 |
| n_neighbors (k-NN) | $\log_2 n$ または 5-30 | k 大 → 過度に繋がる、 k 小 → 断片化 |
| gamma (RBF) | $1/d$(次元数の逆数)または median heuristic | σ = 1/√(2γ)。 大きすぎ → 全て類似、 小さすぎ → 全て無関係 |
| assign_labels | 'kmeans' または 'discretize' | 固有ベクトル空間での割当方法 |
RBF カーネルの幅 $\sigma$ を、 全点対距離の中央値に設定する経験則:
from scipy.spatial.distance import pdist sigma = np.median(pdist(X)) gamma = 1.0 / (2 * sigma**2)
from sklearn.cluster import SpectralClustering
from sklearn.metrics import silhouette_score
import itertools
best = {'score': -1, 'params': None}
for k, n_nbrs in itertools.product([3,4,5,6,7], [5,10,15,20]):
sc = SpectralClustering(n_clusters=k, affinity='nearest_neighbors',
n_neighbors=n_nbrs, random_state=0)
try:
labels = sc.fit_predict(X)
if len(set(labels)) < 2:
continue
score = silhouette_score(X, labels)
print(f'k={k}, n_nbrs={n_nbrs}: silhouette={score:.3f}')
if score > best['score']:
best = {'score': score, 'params': (k, n_nbrs)}
except Exception as e:
pass
print(f'\\n最適: k={best["params"][0]}, n_nbrs={best["params"][1]}, score={best["score"]:.3f}')
スペクトラルクラスタリングで得た「47 都道府県の 4 クラスタ」は、 様々な実務応用に活かせます。
同じクラスタの県は社会経済プロファイルが類似しているため、 1 県で実施した政策の効果を 同クラスタの他県に外挿できる可能性が高い。 例:「秋田で実施した高齢者支援策の効果」は「島根、 高知、 山形」でも類似結果が期待できる。 これは 合成統制法(synthetic control method)の前段として使われます。
小売チェーンの出店戦略で、 既出店の県と「同クラスタの未出店県」を優先候補に。 顧客プロファイルが類似していると期待でき、 既存ノウハウの転用が利く。
地方自治体の比較分析で、 「自県と類似する県」をベンチマークとして選ぶことで、 客観的な比較が可能になる。 「東京と秋田を比較」は不公平だが、 「秋田と高知、 島根、 山形」なら同じクラスタ内での比較で意味がある。
ある県で特定指標が欠損していたら、 同クラスタの他県の平均値で補完するのが、 全国平均で補完するより精度が高い。 これは k-NN imputationの応用版。
「過去 5 年間、 クラスタ ④(過疎県)に属していた A 県が、 今年クラスタ ③(地方経済型)に移動した」というような変化は、 構造的転換のシグナル。 産業構造や人口動態の重要な変化を捉える指標になります。
スペクトラル法で得た 4 クラスタが本当に妥当かを、 3 つの観点から検証する手順を示します。
各クラスタの主要指標の平均値・中央値を比較し、 解釈可能なラベルが付けられるか確認:
| クラスタ | 平均人口(万人) | 平均所得(万円) | 高齢化率(%) | 解釈ラベル |
|---|---|---|---|---|
| ① 大都市圏 | 650 | 425 | 26 | 大規模・高所得・高進学率 |
| ② 地方中核 | 200 | 325 | 30 | 中規模・産業基盤あり |
| ③ 地方経済型 | 180 | 305 | 28 | 製造業中心・持家率高 |
| ④ 過疎・高齢化 | 100 | 250 | 35 | 人口減少・高齢化進行 |
主要指標について、 各クラスタペア間で t 検定(または非パラメトリックな Mann-Whitney U 検定)を実施:
from scipy.stats import ttest_ind
import itertools
key_metrics = ['人口', '平均所得', '高齢化率', '大学進学率']
for metric in key_metrics:
print(f'\\n=== {metric} ===')
for c1, c2 in itertools.combinations(range(4), 2):
x1 = features.loc[result[result.cluster == c1].都道府県, metric]
x2 = features.loc[result[result.cluster == c2].都道府県, metric]
t, p = ttest_ind(x1, x2)
sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else ''
print(f' クラスタ {c1} vs {c2}: t={t:.2f}, p={p:.4f} {sig}')
データを 100 回リサンプリングし、 毎回スペクトラルクラスタリングを実施。 得られた割当の Jaccard 類似度が高ければクラスタが安定:
from sklearn.utils import resample
from sklearn.metrics import adjusted_rand_score
n_boot = 100
aris = []
for seed in range(n_boot):
idx = resample(np.arange(len(X)), random_state=seed)
X_boot = X[idx]
sc_boot = SpectralClustering(n_clusters=4, affinity='nearest_neighbors',
n_neighbors=10, random_state=0)
labels_boot = sc_boot.fit_predict(X_boot)
# 元データの該当点と一致度を計算
aris.append(adjusted_rand_score(labels_sc[idx], labels_boot))
print(f'平均 ARI(bootstrap): {np.mean(aris):.3f}')
print(f'95% CI: [{np.quantile(aris, 0.025):.3f}, {np.quantile(aris, 0.975):.3f}]')
# ARI > 0.7 ならクラスタは安定
Tibshirani ら(2001)の Gap 統計量で、 「ランダムデータと比較した実データのクラスタ構造の強さ」を測定:
これら全てが満たされて初めて「妥当なクラスタリング」と言えます。 1 つでも怪しい場合、 パラメータ再調整やデータ前処理を見直す。
スペクトラルクラスタリングは 「クラスタリング」「線形代数」「グラフ理論」 の交差点にあります。
スペクトラルクラスタリングは 「データを連結グラフと見立て、 そのラプラシアン行列の固有ベクトルで埋め込み、 k-means で分割する」 三段階手法です。 k-means が「球状クラスタ」しか見つけられないのに対し、 スペクトラル法は 「半月形・螺旋・任意形状」 のクラスタも発見できます。
$$L = D - W, \quad L_{\text{sym}} = I - D^{-1/2} W D^{-1/2}, \quad L_{\text{rw}} = I - D^{-1} W$$
ここで $W$ は類似度行列、 $D$ は次数対角行列。 ラプラシアン $L$ の最小 $k$ 個の固有ベクトル $u_1, \ldots, u_k$ を取り出し、 行を新たな特徴ベクトルとして k-means を回します。 「グラフ上の連結性」が「ユークリッド空間での近さ」に変換される魔法です。
| ラプラシアン | 定義 | 正規化 | 推奨場面 |
|---|---|---|---|
| Unnormalized | $L = D - W$ | なし | 次数が均一 |
| Symmetric | $L_{\text{sym}} = D^{-1/2} L D^{-1/2}$ | 対称 | 一般用途 |
| Random Walk | $L_{\text{rw}} = D^{-1} L$ | 非対称 | 理論的に最も自然 |
Shi & Malik (2000) の normalized cut は $L_{\text{rw}}$、 Ng-Jordan-Weiss (2001) は $L_{\text{sym}}$ を使います。 von Luxburg (2007) のレビューで両者の関係が整理されました。 SSDSE-B-2026 の都道府県類似度行列のように次数が場所により異なる場合、 正規化版が安定します。
SSDSE-B-2026 の 47 都道府県を「産業構造の類似性」でクラスタリングする完全手順:
import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.cluster import SpectralClustering from sklearn.metrics import silhouette_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) X = df.iloc[:, 3:11].values X = StandardScaler().fit_transform(X) # RBF カーネルで類似度 sc = SpectralClustering(n_clusters=4, affinity='rbf', gamma=0.5, random_state=0) labels = sc.fit_predict(X) print(f'シルエット係数: {silhouette_score(X, labels):.3f}') for k in range(4): pref = df.iloc[labels==k, 1].tolist() print(f'クラスタ {k}: {pref}')
典型結果:クラスタ 1=「大都市圏(東京・大阪)」、 クラスタ 2=「地方中核(愛知・福岡)」、 クラスタ 3=「製造業集積(静岡・群馬)」、 クラスタ 4=「農業県(青森・秋田)」。
スペクトラル法は グラフ理論の normalized cut 問題の連続緩和です。 グラフ $G = (V, E, W)$ を $k$ 個の部分集合 $A_1, \ldots, A_k$ に分けるとき、 normalized cut は
$$\text{Ncut}(A_1, \ldots, A_k) = \sum_{i=1}^{k} \frac{\text{cut}(A_i, \bar{A_i})}{\text{vol}(A_i)}$$
この組合せ最適化は NP 困難。 指示ベクトルを実数に緩和すると、 $L_{\text{sym}}$ の固有ベクトル問題と等価になる――これがスペクトラル法の数学的根拠です (Shi & Malik, 2000)。
連続緩和は厳密解ではないが、 実用的には k-means で離散化することで十分良い分割が得られる。 完全 NP 困難問題が固有値計算という多項式時間問題に化けるのが、 スペクトラル法の本質的価値です。
類似度行列 $W$ の構成法はクラスタリング結果を大きく左右します。
| 構成法 | 式 | 長所 | 短所 |
|---|---|---|---|
| $\epsilon$-近傍 | $W_{ij} = \mathbb{1}_{d(x_i,x_j) \le \epsilon}$ | 解釈簡単 | $\epsilon$ 選択が難しい |
| k-NN | $W_{ij} = 1$ if j ∈ $k$NN(i) | 密度に適応 | 非対称 |
| Mutual k-NN | i,j が互いに kNN | 対称・疎 | 孤立点ができやすい |
| Gaussian RBF | $W_{ij} = e^{-\|x_i-x_j\|^2 / 2\sigma^2}$ | 滑らか | 密行列 |
| Cosine | $W_{ij} = \frac{x_i \cdot x_j}{\|x_i\|\|x_j\|}$ | スケール不変 | 正値性に注意 |
SSDSE-B のような中規模データには $k$-NN ($k = \sqrt{n} \approx 7$) が定番。 大規模では疎な kNN グラフが計算効率の決め手です。
クラスタ数 $k$ の選び方:
from scipy.linalg import eigh from sklearn.metrics.pairwise import rbf_kernel W = rbf_kernel(X, gamma=0.5) D = np.diag(W.sum(axis=1)) L = D - W eigvals = eigh(L, eigvals_only=True) plt.plot(eigvals[:10], "o-") plt.title("最小 10 個の固有値(ギャップを探す)")
Q. なぜラプラシアンの最小固有値?
A. グラフラプラシアン $L = D - W$ は半正定値で、 最小固有値は 0、 対応する固有ベクトルは「全て同じ値」のベクトル(つまり「全データを 1 クラスタとする」自明解)。 2 番目に小さい固有値(Fiedler 値)に対応する固有ベクトルが「最良の二分割」を示します。 これが Fiedler (1973) の発見です。
Q. $k$ 個のクラスタには何個の固有ベクトル?
A. クラスタ数 $k$ なら、 最小から $k$ 個の固有ベクトルを使います。 これらを並べて $n \times k$ 行列を作り、 行を新しい特徴ベクトルとして k-means します。
Q. 連結グラフでない場合はどうなる?
A. 連結成分が $c$ 個あれば、 固有値 0 の重複度が $c$ となり、 連結成分ごとに別クラスタに分かれます。 これは Spectral Clustering の重要な性質:自然な「自明クラスタ」を捉えます。
Q. $k$-NN と RBF カーネル、 どっちを使う?
A. $k$-NN は密度に適応し疎で計算効率が良い。 RBF は滑らかで理論解析しやすい。 一般的な実務では $k$-NN($k \approx \log n$) を推奨。 SSDSE-B-2026 では $k=5$-7 程度。
Q. $\gamma$ の選び方は?
A. RBF カーネルの $\gamma = 1 / (2\sigma^2)$。 $\sigma$ を「平均的距離の中央値」に取るのが定番。 Self-tuning Spectral Clustering (Zelnik-Manor & Perona, 2004) は各点の $\sigma_i$ を個別に決める適応版。
Q. k-means の弱点をどう克服?
A. 埋め込み空間で k-means するため、 元空間の凸性を要求しません。 半月・螺旋・任意非凸形状で大きな改善。 ただし埋め込み次元 $k$ のクラスタ数が必要。
Q. スケーラビリティの限界は?
A. $n \le 10^4$ が標準的限界。 $n^3$ 固有値分解が痛い。 Nystrom 近似、 ランダム特徴、 並列固有値法で $n = 10^6$ まで拡張可能。
画像セグメンテーション
Shi & Malik 法で物体境界を抽出。
ソーシャルネットワーク
コミュニティ検出、 SNS のユーザークラスタ。
生物情報学
遺伝子発現データのクラスタリング。
文書クラスタリング
Latent Semantic Analysis と組合せ。
交通・物流
都市内の地理的クラスタ。
レコメンデーション
ユーザー類似グラフの分割。
音声認識
話者ダイアリゼーション(誰がいつ話したか)。
医療画像
MRI・CT のセグメンテーション。
スペクトラル埋め込み $\phi(x_i) = (u_2(i), u_3(i), \ldots, u_k(i))^\top$ は、 「グラフ上の拡散距離 (diffusion distance) を反映するユークリッド埋め込み」になっています (Coifman & Lafon, 2006)。 つまり「同じクラスタ=拡散時間が短い」が「ユークリッド近さ」に翻訳される。 これが k-means が効く理由です。
von Luxburg, Belkin, Bousquet (2008) は「サンプルサイズ $n \to \infty$ で、 サンプルラプラシアンが極限作用素 (Laplace-Beltrami operator) に収束」を示しました。 つまり Spectral Clustering は「データ多様体上のラプラシアンを離散近似している」――幾何学的データ解析の基盤です。
このページは スペクトラルクラスタリング を解説する用語ページです。
カテゴリ:クラスタリング・教師なし学習
ジャストインタイム型データサイエンス教育の一環として、必要な時に参照し、関連概念とともに学べる構成になっています。
基準ページ:correlation.html(149KB、12セクション、SSDSE-B 実値計算)と同等以上の品質を目指しています。
データ点同士の類似度をグラフのエッジ重みで表現し、そのグラフを「切る」コスト最小の分割を、ラプラシアン行列の固有ベクトル空間で k-means によって実現する。非凸な塊(三日月形・同心円)も分離できる。
| 場面 | 使い方 |
|---|---|
| 探索的データ分析 | 分布や関係性の最初の確認 |
| モデル比較 | 仮定の妥当性を裏付ける指標として |
| レポート作成 | 標準的な要約統計量・指標として明記 |
スペクトラルクラスタリング の中心となる数式・定義は次の通りです。
$$ L = D - W, \quad L_{\text{sym}} = I - D^{-1/2} W D^{-1/2} $$
政府統計の総合窓口 e-Stat が公開する SSDSE-B-2026.csv(47都道府県×項目)を用いた具体的計算例を示します。
SSDSE-B-2026 の47都道府県を「人口」「世帯数」「就業者数」の3変数で標準化し、RBFカーネル σ=1.0 で類似度行列 W を構成。L_sym の小さい固有値(≒0, 0.12, 0.34)の固有ベクトル上で k=3 クラスタを得ると、首都圏(東京・神奈川・埼玉・千葉・大阪)と地方中核(愛知・福岡・北海道)と他地方の3群に分かれる。
| 項目 | 値・指標 |
|---|---|
| データ件数 | 47 都道府県 |
| 対象指標 | 人口・世帯数・就業者数など |
| 計算結果 | 上記説明参照 |
import pandas as pd
import numpy as np
from sklearn.cluster import SpectralClustering
from sklearn.preprocessing import StandardScaler
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
X_cols = [c for c in df.columns if df[c].dtype != 'object'][:3]
X = StandardScaler().fit_transform(df[X_cols].dropna())
sc = SpectralClustering(n_clusters=3, affinity='rbf', gamma=1.0,
assign_labels='kmeans', random_state=42)
labels = sc.fit_predict(X)
print('クラスタサイズ:', np.bincount(labels))
上記コードは pandas / numpy / scipy / sklearn / statsmodels の標準的なライブラリを用い、SSDSE-B-2026.csv を直接読み込んで計算します(合成データ不使用)。