t-SNE(t-distributed Stochastic Neighbor Embedding)は、高次元データを2次元や3次元に圧縮して可視化するための次元削減手法です。PCAとは異なり、データの局所的な構造(近いデータ点同士の関係)を保存することに特化しており、クラスタ構造の可視化に優れた性能を発揮します。
Laurens van der MaatenとGeoffrey Hintonによって2008年に提案され、現在では機械学習のデータ探索や結果の可視化で広く利用されています。
本記事の内容
- t-SNEのアルゴリズムと理論
- 高次元空間と低次元空間での確率分布の構成
- KLダイバージェンスによる最適化
- PCAとの比較とPythonでの実装
前提知識
この記事を読む前に、以下の概念を押さえておくと理解が深まります。
- 確率分布の基礎
- KLダイバージェンス(Kullback-Leibler divergence)
t-SNEのアルゴリズム
t-SNEは以下の2つのステップで構成されます。
Step 1: 高次元空間での類似度
高次元空間上のデータ点 $\bm{x}_i$ と $\bm{x}_j$ の間の条件付き確率を、ガウス分布を用いて定義します。
$$ p_{j|i} = \frac{\exp(-\|\bm{x}_i – \bm{x}_j\|^2 / 2\sigma_i^2)}{\sum_{k \neq i}\exp(-\|\bm{x}_i – \bm{x}_k\|^2 / 2\sigma_i^2)} $$
$p_{j|i}$ は「$\bm{x}_i$ の近傍として $\bm{x}_j$ が選ばれる確率」を表します。$\sigma_i$ は各データ点のバンド幅で、perplexityパラメータによって制御されます。
対称化した同時確率は、
$$ p_{ij} = \frac{p_{j|i} + p_{i|j}}{2n} $$
Step 2: 低次元空間での類似度
低次元空間(2次元)上のデータ点 $\bm{y}_i$ と $\bm{y}_j$ の間の類似度を、t分布(自由度1、すなわちコーシー分布) を用いて定義します。
$$ q_{ij} = \frac{(1 + \|\bm{y}_i – \bm{y}_j\|^2)^{-1}}{\sum_{k \neq l}(1 + \|\bm{y}_k – \bm{y}_l\|^2)^{-1}} $$
t分布を使う理由は、ガウス分布と比べて裾が重いため、低次元空間で遠くに配置されたデータ点のペナルティが小さくなり、クラウディング問題(異なるクラスタが重なる問題)を緩和できるからです。
最適化
$p_{ij}$ と $q_{ij}$ のKLダイバージェンスを最小化することで、低次元の座標 $\bm{y}_i$ を求めます。
$$ C = \text{KL}(P \| Q) = \sum_{i \neq j} p_{ij} \log \frac{p_{ij}}{q_{ij}} $$
勾配は以下のようになります。
$$ \frac{\partial C}{\partial \bm{y}_i} = 4 \sum_{j} (p_{ij} – q_{ij})(1 + \|\bm{y}_i – \bm{y}_j\|^2)^{-1}(\bm{y}_i – \bm{y}_j) $$
この勾配を用いて、勾配降下法で $\bm{y}_i$ を更新します。
Perplexityパラメータ
Perplexityは、各データ点の「有効な近傍の数」を制御するパラメータです。
$$ \text{Perplexity}(P_i) = 2^{H(P_i)} $$
ここで $H(P_i) = -\sum_j p_{j|i}\log_2 p_{j|i}$ はエントロピーです。
- Perplexityが小さい: 局所的な構造を重視(近傍が少ない)
- Perplexityが大きい: 大域的な構造も考慮(近傍が多い)
一般的に5から50の範囲で設定し、30がデフォルト値として広く使われています。
PCAとt-SNEの比較
| PCA | t-SNE | |
|---|---|---|
| 線形/非線形 | 線形 | 非線形 |
| 保存する構造 | 大域的な分散 | 局所的な類似度 |
| 計算コスト | $O(nd^2)$ | $O(n^2)$(Barnes-Hutで$O(n\log n)$) |
| 決定的/確率的 | 決定的 | 確率的(初期値依存) |
| 逆変換 | 可能 | 不可能 |
Pythonでの実装
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
# --- データの準備(手書き数字: 64次元)---
digits = load_digits()
X = digits.data # (1797, 64)
y = digits.target
print(f"データサイズ: {X.shape}")
print(f"クラス数: {len(np.unique(y))}")
# --- t-SNE ---
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne = tsne.fit_transform(X)
# --- PCA ---
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
# --- 可視化: PCA vs t-SNE ---
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
ax1 = axes[0]
scatter1 = ax1.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='tab10',
s=10, alpha=0.7)
ax1.set_xlabel('PC 1')
ax1.set_ylabel('PC 2')
ax1.set_title(f'PCA (Variance Ratio: {pca.explained_variance_ratio_.sum():.2%})')
plt.colorbar(scatter1, ax=ax1, label='Digit')
ax1.grid(True, alpha=0.3)
ax2 = axes[1]
scatter2 = ax2.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10',
s=10, alpha=0.7)
ax2.set_xlabel('t-SNE 1')
ax2.set_ylabel('t-SNE 2')
ax2.set_title('t-SNE (perplexity=30)')
plt.colorbar(scatter2, ax=ax2, label='Digit')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# --- Perplexityの影響 ---
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
perplexities = [5, 30, 100]
for ax, perp in zip(axes, perplexities):
tsne_p = TSNE(n_components=2, random_state=42, perplexity=perp)
X_p = tsne_p.fit_transform(X)
scatter = ax.scatter(X_p[:, 0], X_p[:, 1], c=y, cmap='tab10', s=10, alpha=0.7)
ax.set_title(f'Perplexity = {perp}')
ax.set_xlabel('t-SNE 1')
ax.set_ylabel('t-SNE 2')
ax.grid(True, alpha=0.3)
plt.suptitle('Effect of Perplexity on t-SNE', fontsize=14)
plt.tight_layout()
plt.show()
PCAでは数字ごとのクラスタが重なりがちですが、t-SNEでは各数字が明確に分離されたクラスタを形成していることが確認できます。Perplexityの値によってクラスタの形状が変わるため、複数の値で試すことが推奨されます。
まとめ
本記事では、t-SNEの理論と使い方について解説しました。
- t-SNEは高次元データの局所的な構造を保存して低次元に可視化する手法
- 高次元空間ではガウス分布、低次元空間ではt分布で類似度を定義し、KLダイバージェンスを最小化する
- t分布の重い裾によりクラウディング問題が緩和され、クラスタが明確に分離される
- Perplexityパラメータは近傍の数を制御し、結果に大きな影響を与える
次のステップとして、以下の記事も参考にしてください。