K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

線形判別分析 (LDA)

中級

02.2.4

線形判別分析 (LDA)

最終更新 2020-03-11 読了時間 4 分
まとめ
線形判別分析(LDA)は、「同じクラスの点はまとまり、違うクラスの平均は離れる」方向 \(\mathbf{w}\) を 1 本選び、その方向に点を射影して分類する手法です。選ぶ基準はクラス間分散とクラス内分散の比 \(J(\mathbf{w})\) で、各クラスが同じ共分散のガウス分布に従うと仮定すると、この方向はベイズ最適な境界と一致します。決定境界は \(\mathbf{w}^\top \mathbf{x} + b = 0\) という直線(3 次元なら平面)になり、LinearDiscriminantAnalysis の coef_ と intercept_ から読み取れます。読み終えると、transform で得た 1 次元の射影値のヒストグラムから「2 クラスがどれだけ重なっているか」を判断できるようになります。

LDA の数式はロジスティック回帰と同じ「直線で分ける」形なので、先に ロジスティック回帰 で \(\mathbf{w}^\top \mathbf{x} + b\) の読み方を押さえておくと、違いが「直線の選び方」だけであることが分かります。

直感 #

2 クラスの点が散らばった平面に、1 本の数直線(ものさし)を置いて、全部の点をそこへ垂直に落とすと考えます。ものさしの向きが悪いと 2 クラスの影が重なりますが、向きがよければ影は左右にきれいに分かれます。LDA が探すのはこの「いちばん影が分かれる向き」です。具体的には、2 クラスの平均の影の距離を大きく、各クラスの影の広がりを小さくする向き \(\mathbf{w}\) を選びます。射影した 1 次元の値をしきい値で切れば分類になり、2 次元に戻すとしきい値は直線 \(\mathbf{w}^\top \mathbf{x} + b = 0\) です。

式で書くと何を最大にしているか #

「影が分かれる向き」を式にしたのが次の比です。2 クラスの場合、射影方向 \(\mathbf{w}\) は

$$ J(\mathbf{w}) = \frac{\mathbf{w}^\top \mathbf{S}_B \mathbf{w}}{\mathbf{w}^\top \mathbf{S}_W \mathbf{w}} $$

を最大化することで求めます。分子の \(\mathbf{S}_B\) はクラス間散布行列で、2 クラスの平均がどれだけ離れているかを表し、分母の \(\mathbf{S}_W\) はクラス内散布行列で、各クラスの点が平均のまわりにどれだけ散らばっているかを表します。図でいうと、分子は 2 つの影の中心の距離、分母は影それぞれの幅です。多クラスでは最大でクラス数マイナス 1 個の射影方向が得られ、3 クラスなら 2 次元の散布図として可視化できます。

Pythonで境界と射影を描く #

次のコードは make_blobs で作った 2 クラス 200 点に LinearDiscriminantAnalysis を当て、決定境界の直線と、transform で 1 次元に射影した値のヒストグラムを描きます。ヒストグラムの 2 つの山が重なる部分が、直線では分けられない点です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from __future__ import annotations

import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.metrics import accuracy_score

def run_lda_demo(
    n_samples: int = 200,
    random_state: int = 42,
    title_boundary: str = "LDA の決定境界",
    title_projection: str = "LDA による一次元射影",
    xlabel: str = "特徴量1",
    ylabel: str = "特徴量2",
    hist_xlabel: str = "射影後の特徴量",
    class0_label: str = "クラス0",
    class1_label: str = "クラス1",
) -> dict[str, float]:
    """Train LDA on synthetic blobs and plot boundary plus projection."""
    X, y = make_blobs(
        n_samples=n_samples,
        centers=2,
        n_features=2,
        cluster_std=2.0,
        random_state=random_state,
    )

    clf = LinearDiscriminantAnalysis(store_covariance=True)
    clf.fit(X, y)

    accuracy = float(accuracy_score(y, clf.predict(X)))
    w = clf.coef_[0]
    b = float(clf.intercept_[0])

    xs = np.linspace(X[:, 0].min() - 1, X[:, 0].max() + 1, 300)
    ys_boundary = -(w[0] / w[1]) * xs - b / w[1]

    fig, ax = plt.subplots(figsize=(7, 6))
    ax.set_title(title_boundary)
    ax.scatter(X[:, 0], X[:, 1], c=y, cmap="coolwarm", edgecolor="k", alpha=0.8)
    ax.plot(xs, ys_boundary, "k--", lw=1.2, label="w^T x + b = 0")
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.legend(loc="best")
    ax.grid(alpha=0.25)
    fig.tight_layout()
    plt.show()

    X_proj = clf.transform(X)[:, 0]

    fig, ax = plt.subplots(figsize=(8, 4))
    ax.set_title(title_projection)
    ax.hist(X_proj[y == 0], bins=20, alpha=0.7, label=class0_label)
    ax.hist(X_proj[y == 1], bins=20, alpha=0.7, label=class1_label)
    ax.set_xlabel(hist_xlabel)
    ax.legend(loc="best")
    ax.grid(alpha=0.25)
    fig.tight_layout()
    plt.show()

    return {"accuracy": accuracy}

metrics = run_lda_demo(
    title_boundary="LDA の決定境界",
    title_projection="LDA による一次元射影",
    xlabel="特徴量1",
    ylabel="特徴量2",
    hist_xlabel="射影後の特徴量",
    class0_label="クラス0",
    class1_label="クラス1",
)
print(f"訓練精度: {metrics['accuracy']:.3f}")

transform を呼ぶと射影されたデータを直接取得できますの図

実行結果の読み方 #

1 枚目の破線 w^T x + b = 0 が決定境界で、2 クラスの平均を結ぶ線分を、共分散を考慮した角度で横切っています。2 枚目のヒストグラムは同じデータを \(\mathbf{w}\) の方向から見たもので、山と山の間にあるしきい値が 1 枚目の破線に対応します。print される訓練精度が 1.0 でないのは、cluster_std=2.0 のせいで 2 つの山の裾が重なっているからです。

正則化と決定境界 #

正則化パラメータを変えると LDA の決定境界がどう変化するか確認できます。

使いどころと落とし穴 #

LDA が向くのは、各クラスがほぼ同じ広がりの楕円状に分布し、サンプル数が特徴量数の 10 倍以上あるデータです。クラスごとに広がりが大きく違う(たとえば一方の cluster_std が 0.5、他方が 3.0)場合は共通共分散の仮定が破れ、QuadraticDiscriminantAnalysis のほうが境界を正しく曲げられます。特徴量が 500 を超えてサンプル数に近づくと共分散行列が不安定になるので、solver="lsqr", shrinkage="auto" で縮小推定を入れます。PCA との違いは、PCA がラベルを見ずに分散の大きい方向を選ぶのに対し、LDA はラベルを使って「クラスが分かれる方向」を選ぶ点です。

まとめ #

LDA は、クラス間分散とクラス内分散の比 \(J(\mathbf{w})\) を最大にする 1 本の方向 \(\mathbf{w}\) を求め、そこへ射影した値をしきい値で切って分類します。境界は \(\mathbf{w}^\top \mathbf{x} + b = 0\) の直線で、coef_ と intercept_ から式を書き下せます。transform で得る射影値は最大 \(K-1\) 次元なので、3 クラスのデータを 2 次元の散布図にする次元削減としても使えます。各クラスの共分散が等しいという仮定が崩れたら QDA を、特徴量が多すぎるなら shrinkage を検討してください。

参考文献 #

  • Fisher, R. A. (1936). The Use of Multiple Measurements in Taxonomic Problems. Annals of Eugenics, 7(2), 179 E88.
  • Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning. Springer.