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

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

言語 JA

ソフトマックス回帰

中級

02.2.2

ソフトマックス回帰

最終更新 2020-02-12 読了時間 3 分
まとめ
ソフトマックス回帰は、ロジスティック回帰を 3 クラス以上に広げた手法で、クラスごとに線形スコア \(\mathbf{w}_k^\top \mathbf{x} + b_k\) を計算し、\(\exp\) をとって総和で割ることで「全クラスの確率」を一度に出します。出力は 0 以上 1 以下で総和が 1 なので、確率として読めるだけでなく、誤分類コストを掛けた期待損失の計算にもそのまま使えます。学習はクロスエントロピー損失 \(L = -\sum \log P(y = y_i \mid \mathbf{x}_i)\) の最小化で、正解クラスの確率を 1 に近づける方向に全クラスの係数を同時に動かします。scikit-learn では LogisticRegression(multi_class="multinomial") がこの計算を行い、決定境界はクラスどうしの境目ごとに直線になります。

ソフトマックス回帰はクラス数 \(K = 2\) のときにシグモイドと一致するので、先に ロジスティック回帰 で \(\sigma(z)\) とクロスエントロピーを押さえておくと、このページの式が「その \(K\) クラス版」だと分かります。

直感 #

手書き数字の画像が 0〜9 のどれかを答える場面を考えます。二値分類ではシグモイド関数が「クラス 1 の確率」を 1 つ返しますが、ここでは 10 クラスぶんの確率を同時に知りたい。ソフトマックス回帰はクラスごとに線形スコア(たとえば 2.0、0.5、−1.0)を出し、それぞれを \(e^{2.0} = 7.4\)、\(e^{0.5} = 1.6\)、\(e^{-1.0} = 0.4\) と指数関数で正の数に変え、合計 9.4 で割って 0.79、0.17、0.04 という確率分布にします。指数関数のおかげでスコアの高いクラスが強調され、低いクラスは 0 近くまで抑えられます。

式で書くと確率と損失はどうなるか #

「指数をとって総和で割る」を式にすると、クラス数を \(K\)、クラス \(k\) の重みベクトルとバイアスをそれぞれ \(\mathbf{w}_k\)、\(b_k\) として

$$ P(y = k \mid \mathbf{x}) = \frac{\exp\left(\mathbf{w}_k^\top \mathbf{x} + b_k\right)} {\sum_{j=1}^{K} \exp\left(\mathbf{w}_j^\top \mathbf{x} + b_j\right)} $$

で確率が得られます。分母が全クラス共通なので、\(K\) 個の確率を足すと必ず 1 になります。目的関数はクロスエントロピー損失

$$ L = - \sum_{i=1}^{n} \sum_{k=1}^{K} \mathbb{1}(y_i = k) \log P(y = k \mid \mathbf{x}_i) $$

です。\(\mathbb{1}(y_i = k)\) は正解クラスだけ 1 になるので、各サンプルで「正解クラスに割り当てた確率の対数」だけが残り、その確率を 1 に近づけるほど損失が 0 に近づきます。重みに正則化項を加えると過学習を抑えられ、LogisticRegression では C がその強さ(罰則の逆数)です。

Pythonで 3 クラスの決定領域を描く #

次のコードは make_classification で 3 クラス 300 点の 2 次元データを作り、LogisticRegression(multi_class="multinomial") で学習して、平面上の各点がどのクラスに分類されるかを 3 色で塗ります。クラスどうしの境目は、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
from __future__ import annotations

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

def run_softmax_regression_demo(
    n_samples: int = 300,
    n_classes: int = 3,
    random_state: int = 42,
    label_title: str = "ソフトマックス回帰の決定領域",
    xlabel: str = "特徴量1",
    ylabel: str = "特徴量2",
) -> dict[str, float]:
    """Train a softmax regression model and visualise decision regions."""
    X, y = make_classification(
        n_samples=n_samples,
        n_features=2,
        n_informative=2,
        n_redundant=0,
        n_clusters_per_class=1,
        n_classes=n_classes,
        random_state=random_state,
    )

    clf = LogisticRegression(multi_class="multinomial", solver="lbfgs")
    clf.fit(X, y)

    accuracy = float(accuracy_score(y, clf.predict(X)))

    x1_min, x1_max = X[:, 0].min() - 1.0, X[:, 0].max() + 1.0
    x2_min, x2_max = X[:, 1].min() - 1.0, X[:, 1].max() + 1.0
    grid_x1, grid_x2 = np.meshgrid(
        np.linspace(x1_min, x1_max, 400),
        np.linspace(x2_min, x2_max, 400),
    )
    grid_points = np.c_[grid_x1.ravel(), grid_x2.ravel()]
    preds = clf.predict(grid_points).reshape(grid_x1.shape)

    cmap = ListedColormap(["#ff9896", "#98df8a", "#aec7e8", "#f7b6d2", "#c5b0d5"])
    fig, ax = plt.subplots(figsize=(7, 6))
    ax.contourf(
        grid_x1,
        grid_x2,
        preds,
        alpha=0.3,
        cmap=cmap,
        levels=np.arange(-0.5, n_classes + 0.5, 1),
    )
    scatter = ax.scatter(X[:, 0], X[:, 1], c=y, edgecolor="k", cmap=cmap)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_title(label_title)
    legend = ax.legend(*scatter.legend_elements(), title="class", loc="best")
    ax.add_artist(legend)
    fig.tight_layout()
    plt.show()

    return {"accuracy": accuracy}

metrics = run_softmax_regression_demo(
    label_title="ソフトマックス回帰の決定領域",
    xlabel="特徴量1",
    ylabel="特徴量2",
)
print(f"訓練精度: {metrics['accuracy']:.3f}")

ソフトマックス回帰の決定領域

実行結果の読み方 #

背景の 3 色がそれぞれのクラスに分類される領域で、境目はすべて直線です。これは 2 つのクラス \(j, k\) の確率が等しい条件 \(\mathbf{w}_j^\top \mathbf{x} + b_j = \mathbf{w}_k^\top \mathbf{x} + b_k\) が \(\mathbf{x}\) の一次式になるためです。3 本の境界線は 1 点で交わります。そこが 3 クラスの確率が同率 1/3 になる点です。print される訓練精度が 1.0 でないなら、塊の縁が重なる部分の点が背景と違う色で描かれているはずです。

ソフトマックス回帰の決定境界は線形です。クラス間の境界が非線形な場合は、多項式特徴量を追加するか、SVMやニューラルネットワークなどの非線形モデルを検討してください。

scikit-learnのLogisticRegressionはデフォルトでsolver="lbfgs"を使い安定して収束します。特徴量が非常に多い場合はsolver="saga"とL1正則化(penalty="l1")を組み合わせると、不要な特徴量の重みを0にして解釈性を高められます。

温度と決定境界 #

温度パラメータを変えるとソフトマックスの決定境界の鋭さがどう変化するか確認できます。

使いどころと落とし穴 #

ソフトマックス回帰は、3 クラス以上の分類で「全クラスの確率が欲しい」「係数でどの特徴量が効くか説明したい」ときの最初の候補です。苦手なのはクラスの境目が曲がったデータです。SVM の RBF カーネルか、\(x_1^2\) や \(x_1 x_2\) を足した多項式特徴量に切り替えます。間違えやすいのは multi_class="ovr" との混同で、一対多方式では 3 つの二値分類器の確率を別々に出すため総和が 1 にならず、predict_proba を確率として足し引きすると矛盾します。クラスに「星 1、星 2、星 3」のような順序があるなら、順序を無視するソフトマックスではなく 順序回帰 のほうが「隣のクラスへの誤り」を軽く扱えます。

まとめ #

ソフトマックス回帰は、クラスごとの線形スコア \(\mathbf{w}_k^\top \mathbf{x} + b_k\) を \(\exp\) で正の数にし、総和で割って \(K\) 個の確率を同時に出す多クラス分類です。学習はクロスエントロピー損失の最小化で、正解クラスの確率を 1 に近づける方向に全クラスの係数を一度に更新します。決定境界はクラスの組ごとに直線です。make_classification の 3 クラスデータでは、3 本の直線が 1 点で交わる図になりました。境界が曲がるデータには非線形モデルを、順序のあるクラスには順序回帰を、特徴量が数千あるなら solver="saga" と penalty="l1" を選んでください。

参考文献 #

  • Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer.
  • Murphy, K. P. (2012). Machine Learning: A Probabilistic Perspective. MIT Press.