Machine Learning: 次元削減 — PCAとt-SNEによる高次元データ可視化ガイド

最終更新:2026-08-26

高次元空間では、すべての点が互いに遠く離れています。次元削減は単なる圧縮ではなく、データの真の構造を明らかにするための手段です。

1. 学習内容


2. 現役データサイエンティストの体験談

(1) 課題:50次元のユーザー行動データは見えない・理解できない

Bobは50個のユーザー行動特徴量(セッション時間、クリック数、カテゴリごとのエンゲージメント率など)を収集しましたが、ユーザーがどのように分布しているかを直感的に把握する手段がありませんでした。テーブルは読み切れず、散布図は2次元が限界で、クラスタリング結果の検証も困難でした。高次元データは人間の直感にとってブラックボックスです。

(2) 解決策:PCA + t-SNE

PCAは分散を最大限に保持しながらデータを2次元に圧縮し、t-SNEは局所的な構造を2D平面にマッピングします。これにより、高次元データを実際に「見える」ものにできます。

PYTHON
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler

X_scaled = StandardScaler().fit_transform(X_50d)

# PCA: fast, preserves global variance
X_pca = PCA(n_components=2).fit_transform(X_scaled)

# t-SNE: slow, preserves local structure
X_tsne = TSNE(n_components=2, perplexity=30).fit_transform(X_scaled)

(3) 成果:2Dプロットで3つの自然なユーザーグループが一目で判明

次元削減後、Bobは3つの明確なユーザーグループ(高価値ユーザー、アクティブユーザー、休眠ユーザー)を一目で確認できました。これはクラスタリング結果ともよく一致しており、ビジネスチームは数秒でデータ分布を理解できました。


3. 次元の呪い

(1) 高次元空間における直感に反する現象

▶ サンプル:次元の呪い実験

PYTHON
import numpy as np

rng = np.random.default_rng(42)

for dim in [2, 10, 50, 100, 500]:
    n = 1000
    X = rng.uniform(0, 1, (n, dim))

    # Compute pairwise distances
    from sklearn.metrics import pairwise_distances
    dists = pairwise_distances(X)

    np.fill_diagonal(dists, np.inf)
    min_dist = dists.min()
    max_dist = dists.max()
    ratio = (max_dist - min_dist) / max_dist

    print(f"Dim={dim:4d}: min={min_dist:.3f}, max={max_dist:.3f}, "
          f"relative_range={ratio:.4f}")

出力:

TEXT 📖 参照専用
# Runs successfully
次元数 最小距離 最大距離 相対範囲 意味
2 0.02 1.41 0.99 距離は意味を持つ
10 0.78 1.87 0.58 距離が崩壊し始める
50 2.42 3.28 0.26 距離はほぼ無意味
100 3.54 4.05 0.13 すべての距離が収束
500 8.11 8.47 0.04 完全に崩壊

4. PCA(主成分分析)

(1) PCAの仕組み

PCAは固有値分解を用いて分散が最大となる方向(主成分)を見つけ、データを低次元空間に射影します。

100%
graph TB
    INPUT[Standardized Data] --> COV[Compute Covariance Matrix]
    COV --> EIG[Eigenvalue Decomposition]
    EIG --> SELECT[Select Top K Eigenvectors]
    SELECT --> PROJECT[Project Data to K Dimensions]
    PROJECT --> OUTPUT[Low-Dimensional Representation]

▶ サンプル:PCAによる次元削減と寄与率

PYTHON
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np

X, y = load_iris(return_X_y=True)

# Standardize first (PCA is sensitive to scale)
X_scaled = StandardScaler().fit_transform(X)

# PCA with all components to see explained variance
pca_full = PCA()
pca_full.fit(X_scaled)

print("Explained variance ratio:")
for i, (evr, cum) in enumerate(zip(pca_full.explained_variance_ratio_,
                                     pca_full.explained_variance_ratio_.cumsum())):
    print(f"  PC{i+1}: {evr:.3f} (cumulative: {cum:.3f})")

# Reduce to 2D
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(f"\n2D PCA preserves {pca.explained_variance_ratio_.sum():.1%} variance")

# Visualize
fig, ax = plt.subplots(figsize=(8, 6))
for target in np.unique(y):
    mask = y == target
    ax.scatter(X_pca[mask, 0], X_pca[mask, 1], label=load_iris().target_names[target], s=40)
ax.set_xlabel(f"PC1 ({pca.explained_variance_ratio_[0]:.1%})")
ax.set_ylabel(f"PC2 ({pca.explained_variance_ratio_[1]:.1%})")
ax.set_title("PCA of Iris Dataset")
ax.legend()
plt.tight_layout()
plt.savefig("pca_iris.png", dpi=150)

出力:

TEXT 📖 参照専用
Explained variance ratio:

(2) 保持する成分数の選び方

▶ サンプル:累積寄与率プロット

PYTHON
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(42)
n, p = 500, 50
X = rng.standard_normal((n, p))
# Make first 5 dimensions have more signal
X[:, :5] *= 3

X_scaled = StandardScaler().fit_transform(X)
pca = PCA().fit(X_scaled)

fig, ax = plt.subplots(figsize=(10, 5))
cumvar = pca.explained_variance_ratio_.cumsum()
ax.plot(range(1, len(cumvar)+1), cumvar, "b-o", markersize=3)
ax.axhline(0.95, color="red", linestyle="--", label="95% variance")
ax.axhline(0.90, color="orange", linestyle="--", label="90% variance")
ax.set_xlabel("Number of Components")
ax.set_ylabel("Cumulative Explained Variance")
ax.set_title("PCA Explained Variance")
ax.legend()
n_95 = (cumvar < 0.95).sum() + 1
print(f"Components for 95% variance: {n_95}")
plt.tight_layout()
plt.savefig("pca_variance.png", dpi=150)

出力:

TEXT 📖 参照専用
# Runs successfully
しきい値 意味 典型的な結果
90% 主要なシグナルを保持 5〜15成分(50次元の場合)
95% より多くの詳細を保持 10〜25成分(50次元の場合)
99% ほぼ無損失 20〜40成分(50次元の場合)

5. t-SNE多様体可視化

(1) t-SNEの仕組み

t-SNEは、高次元空間と低次元空間におけるペアワイズ類似度のKLダイバージェンスを最小化することで、局所的な近傍関係を保持します。

▶ サンプル:t-SNE可視化

PYTHON
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
import numpy as np

X, y = load_digits(return_X_y=True)
X_scaled = StandardScaler().fit_transform(X)

# t-SNE with different perplexity
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

for ax, perplexity in zip(axes, [5, 30, 50]):
    tsne = TSNE(n_components=2, perplexity=perplexity, random_state=42, init="pca")
    X_tsne = tsne.fit_transform(X_scaled)

    scatter = ax.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap="tab10", s=5, alpha=0.7)
    ax.set_title(f"t-SNE (perplexity={perplexity})")
    ax.set_xlabel("t-SNE 1")
    ax.set_ylabel("t-SNE 2")

plt.tight_layout()
plt.savefig("tsne_perplexity.png", dpi=150)

出力:

TEXT 📖 参照専用
# Runs successfully

(2) t-SNEの注意点

考慮事項 備考
perplexity 通常は5〜50。小さい値 → 近傍に注目、大きい値 → グローバルな構造に注目
乱数シード シードが異なると結果も変わるため、random_stateを固定する
下流タスクへの利用不可 t-SNEは距離を保持しないため、モデルの特徴量入力には不向き
計算が遅い 大規模データセット(1万件以上)では、まずPCAで削減してからt-SNEを実行する
クラスタサイズに意味がない t-SNEはクラスタの相対的なサイズを誇張または縮小することがある

▶ サンプル:PCA前処理によるt-SNEの高速化

PYTHON
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import numpy as np

rng = np.random.default_rng(42)
X = rng.standard_normal((5000, 50))

# Direct t-SNE (slow)
# tsne = TSNE(n_components=2, random_state=42)
# X_tsne = tsne.fit_transform(X)  # Very slow on 5000x50!

# Best practice: PCA first, then t-SNE
X_scaled = StandardScaler().fit_transform(X)
X_pca_30 = PCA(n_components=30).fit_transform(X_scaled)  # Fast PCA to 30D
X_tsne = TSNE(n_components=2, perplexity=30, random_state=42,
              init="pca", learning_rate="auto").fit_transform(X_pca_30)

print(f"PCA→t-SNE shape: {X_tsne.shape}")

出力:

TEXT 📖 参照専用
# Runs successfully

6. 実践:ECサイトユーザー行動の次元削減

▶ サンプル:50次元のユーザー特徴量 → 2D可視化

PYTHON
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(42)
n = 3000

# Generate 50-dim user behavior data with 3 natural groups
group1 = rng.normal(loc=2, scale=1, size=(800, 50))   # High-value users
group2 = rng.normal(loc=0, scale=1.5, size=(1200, 50)) # Regular users
group3 = rng.normal(loc=-2, scale=0.8, size=(1000, 50)) # Dormant users
X = np.vstack([group1, group2, group3])

# Add noise dimensions
X[:, 20:] += rng.normal(0, 3, (n, 30))

# Standardize
X_scaled = StandardScaler().fit_transform(X)

# PCA to 2D
X_pca = PCA(n_components=2).fit_transform(X_scaled)

# t-SNE to 2D (with PCA preprocessing)
X_pca30 = PCA(n_components=30).fit_transform(X_scaled)
X_tsne = TSNE(n_components=2, perplexity=30, random_state=42,
              init="pca", learning_rate="auto").fit_transform(X_pca30)

# K-Means labels for reference
labels = KMeans(n_clusters=3, random_state=42, n_init=10).fit_predict(X_scaled)

# Visualize
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

for ax, X_2d, title in [(axes[0], X_pca, "PCA"), (axes[1], X_tsne, "t-SNE")]:
    scatter = ax.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap="Set1", s=8, alpha=0.6)
    ax.set_title(title)
    ax.set_xlabel(f"{title} 1")
    ax.set_ylabel(f"{title} 2")

plt.tight_layout()
plt.savefig("user_behavior_dim_reduction.png", dpi=150)

出力:

TEXT 📖 参照専用
# Runs successfully

(1) PCA vs t-SNE vs UMAP

観点 PCA t-SNE UMAP
保持するもの グローバルな分散 局所的な近傍 グローバル + 局所的
速度 高速(1秒未満) 低速(数分) 中程度(数秒)
再現性 決定的 シード依存 シード依存
解釈性 高い(寄与率) 低い 中程度
下流タスクでの利用 ✅ 特徴量として使用可能 ❌ 可視化専用 ⚠️ 検証が必要
新規データのマッピング ✅ transform ❌ 再実行が必要 ✅ transform

❓ よくある質問

Q PCAで削減したデータをモデルの学習に使えますか?
A はい、使えます。PCAの主成分はモデルの入力として使用でき、特に高次元・少サンプルのシナリオで有用です。ただし、PCAは訓練データに対してfitし、テストデータにはtransformのみを適用してください。
Q なぜt-SNEはモデルの特徴量として使えないのですか?
A t-SNEはグローバルな距離関係を保持せず、新規データを変換することもできません(毎回ゼロから再計算する必要があります)。可視化専用であり、モデルの入力には適していません。
Q perplexityパラメータはどう選べばよいですか?
A 一般的な範囲は5〜50です。小規模データセット(約1千件)では5〜20、大規模データ(1万件以上)では30〜50を使用します。いくつかの値を試して、クラスタが最も明確に現れるものを選んでください。
Q PCAには標準化が必要ですか?
A 必須です。PCAは分散を最大化するため、特徴量のスケールが異なる場合(例:年齢と収入)、分散の大きい特徴量が主成分を支配してしまいます。PCAの前には必ずStandardScalerを実行してください。
Q UMAPはt-SNEより何が優れているのですか?
A 3つの利点があります。1)10〜100倍高速であること、2)グローバルな構造をよりよく保持すること、3)新規データの変換に対応していることです。ただし、pip install umap-learnで別途インストールが必要です。
Q 次元削減でどの程度の情報が失われますか?
A 保持する成分数によります。分散の95%を保持すれば、失われる情報は約5%にとどまり、失われる部分はノイズである可能性もあります。cumsum(explained_variance_ratio_)で確認して判断してください。

📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):IrisデータセットにPCAを適用して2次元に削減し、散布図を描いて寄与率を注記してください。ヒント:PCA(n_components=2) + scatter。
  2. 応用(難易度 ⭐⭐)load_digitsデータ(64次元)に対し、まずPCAで30次元に削減してからt-SNEで2次元に削減してください。直接t-SNEを実行した場合とPCA + t-SNEの場合の実行時間と可視化品質を比較してください。ヒント:time.time()で計測。
  3. 発展(難易度 ⭐⭐⭐):3つのユーザーグループを含む50次元データを生成し、PCA、t-SNE、UMAPそれぞれで2次元に削減して、K-Meansクラスタリング(ARI/NMI)で評価し、どの手法が元のグループ構造を最もよく保持しているかを判定してください。ヒント:sklearn.metrics.adjusted_rand_score

← 前へ:アンサンブル学習 | 次へ:NLP基礎 →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%