損失関数の全体像 — MSE・交差エントロピー・ヒンジ損失の使い分け

機械学習モデルを訓練するとは、何を最小化しているのでしょうか。勾配降下法でパラメータを更新するとき、その勾配は「何の」勾配なのでしょうか。

答えは損失関数(loss function)です。損失関数はモデルの予測と真の値の「ずれ」を数値化するもので、この値を最小化するようにパラメータを調整するのが機械学習の訓練プロセスです。

しかし、「ずれ」の測り方は一つではありません。二乗誤差で測るのか、絶対値誤差で測るのか、あるいは確率的に測るのか——損失関数の選択はモデルの性質を根本的に変えます。

損失関数を理解すると、以下のような判断が可能になります。

  • 回帰問題でMSEを使うべきかMAEを使うべきか: 外れ値に頑健なモデルが欲しい場合の選択
  • なぜ分類にはMSEではなく交差エントロピーを使うのか: 勾配消失の問題と確率的解釈
  • SVMがなぜヒンジ損失を使うのか: マージン最大化との関係
  • カスタム損失関数の設計: ビジネス上の非対称コストの反映

本記事の内容

  • 損失関数の役割と基本的な性質
  • 回帰の損失関数(MSE, MAE, Huber)
  • 分類の損失関数(交差エントロピー, ヒンジ損失)
  • 各損失関数の数学的導出と確率的解釈
  • Pythonによる比較と可視化

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

損失関数とは

モデル訓練における役割

損失関数(loss function)とは、モデルの予測 $\hat{y}$ と真の値 $y$ を受け取り、その「ずれの大きさ」を非負の実数で返す関数です。

$$ L(y, \hat{y}) \geq 0 $$

$L = 0$ は予測が完璧であることを意味し、$L$ が大きいほど予測が悪いことを表します。

訓練データ全体に対する損失の平均をコスト関数(cost function)または経験リスク(empirical risk)と呼びます。

$$ J(\bm{w}) = \frac{1}{n}\sum_{i=1}^n L(y_i, \hat{f}(\bm{x}_i; \bm{w})) $$

機械学習の訓練とは、このコスト関数 $J(\bm{w})$ を最小化するパラメータ $\bm{w}^*$ を見つけることです。

損失関数に求められる性質

良い損失関数は以下の性質を持ちます。

微分可能性: 勾配降下法で最適化するため、パラメータに関して微分可能(または劣微分可能)であることが望ましいです。

凸性: 損失関数が凸であれば、局所最適解が大域最適解と一致するため、最適化が容易になります。

課題への適合性: 回帰なのか分類なのか、外れ値に頑健であるべきか、誤分類のコストは対称か——問題の性質に適した損失関数を選ぶ必要があります。

まず、回帰問題で使われる損失関数から見ていきましょう。

回帰の損失関数

平均二乗誤差(MSE)

最もよく使われる回帰の損失関数は平均二乗誤差(Mean Squared Error, MSE)です。

予測値 $\hat{y}$ と真の値 $y$ の差の二乗を損失として定義します。

$$ \begin{equation} L_{\text{MSE}}(y, \hat{y}) = (y – \hat{y})^2 $$

コスト関数は

$$ J_{\text{MSE}} = \frac{1}{n}\sum_{i=1}^n (y_i – \hat{y}_i)^2 \end{equation} $$

性質: – 微分可能で、勾配が $\frac{\partial L}{\partial \hat{y}} = -2(y – \hat{y})$ と計算しやすい – 厳密な凸関数 – 大きな誤差に対して二乗のペナルティを課すため、外れ値に敏感

MSEの確率的解釈

MSEがなぜ自然な損失関数なのかは、最尤推定の観点から理解できます。

データの生成過程として $y_i = f(\bm{x}_i) + \varepsilon_i$、$\varepsilon_i \sim N(0, \sigma^2)$ を仮定すると、尤度は

$$ p(y_i | \bm{x}_i, \bm{w}) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(y_i – \hat{f}(\bm{x}_i; \bm{w}))^2}{2\sigma^2}\right) $$

対数尤度を最大化すると

$$ \max_{\bm{w}} \sum_{i=1}^n \ln p(y_i | \bm{x}_i, \bm{w}) = \max_{\bm{w}} \sum_{i=1}^n \left[-\frac{(y_i – \hat{y}_i)^2}{2\sigma^2} – \frac{1}{2}\ln(2\pi\sigma^2)\right] $$

$\sigma^2$ と定数項を無視すると、これは $\sum (y_i – \hat{y}_i)^2$ の最小化と等価です。つまり、ノイズが正規分布に従うとき、MSEの最小化は最尤推定と一致するのです。

平均絶対誤差(MAE)

平均絶対誤差(Mean Absolute Error, MAE)は、予測値と真の値の差の絶対値を損失とします。

$$ \begin{equation} L_{\text{MAE}}(y, \hat{y}) = |y – \hat{y}| \end{equation} $$

性質: – $\hat{y} = y$ で微分不可能(劣微分は定義可能) – 凸関数 – 大きな誤差に対しても線形のペナルティなので、外れ値に頑健

MAEの最小化は、ノイズがラプラス分布に従う場合の最尤推定に対応します。ラプラス分布 $p(\varepsilon) \propto \exp(-|\varepsilon|/b)$ の対数尤度を最大化すると、$\sum |y_i – \hat{y}_i|$ の最小化になるからです。

また、MSEの最適解は平均(mean)ですが、MAEの最適解は中央値(median)です。中央値は外れ値の影響を受けにくいため、MAEが外れ値に頑健な理由がここにあります。

Huber損失

Huber損失はMSEとMAEのいいとこ取りをした損失関数で、小さな誤差にはMSEのように振る舞い、大きな誤差にはMAEのように振る舞います。

$$ \begin{equation} L_{\delta}(y, \hat{y}) = \begin{cases} \frac{1}{2}(y – \hat{y})^2 & \text{if } |y – \hat{y}| \leq \delta \\ \delta|y – \hat{y}| – \frac{1}{2}\delta^2 & \text{otherwise} \end{cases} \end{equation} $$

パラメータ $\delta$ は二乗損失と絶対値損失の切り替え点を制御します。$\delta$ が大きいほどMSEに近づき、小さいほどMAEに近づきます。

Huber損失は至る所で微分可能(MAEと異なり $\hat{y} = y$ でも微分可能)であり、外れ値に対しても線形のペナルティに留まります。

これら3つの回帰損失関数を比較してみましょう。

import numpy as np
import matplotlib.pyplot as plt

# 残差の範囲
residual = np.linspace(-4, 4, 500)

# 各損失関数
mse = residual ** 2
mae = np.abs(residual)

delta = 1.0
huber = np.where(np.abs(residual) <= delta,
                 0.5 * residual ** 2,
                 delta * np.abs(residual) - 0.5 * delta ** 2)

fig, axes = plt.subplots(1, 2, figsize=(14, 5.5))

# (a) 損失関数の比較
ax = axes[0]
ax.plot(residual, mse, "b-", linewidth=2.5, label="MSE $(y - \hat{y})^2$")
ax.plot(residual, mae, "r-", linewidth=2.5, label=r"MAE $|y - \hat{y}|$")
ax.plot(residual, huber, "g-", linewidth=2.5, label=rf"Huber ($\delta$={delta})")

ax.set_xlabel(r"Residual $(y - \hat{y})$", fontsize=13)
ax.set_ylabel("Loss", fontsize=13)
ax.set_title("Regression Loss Functions", fontsize=14)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_ylim(-0.5, 10)

# (b) 勾配の比較
ax = axes[1]
grad_mse = -2 * residual  # dL/d(y_hat) = -2(y - y_hat)
grad_mae = -np.sign(residual)
grad_huber = np.where(np.abs(residual) <= delta,
                      -residual,
                      -delta * np.sign(residual))

ax.plot(residual, grad_mse, "b-", linewidth=2.5, label="MSE gradient")
ax.plot(residual, grad_mae, "r-", linewidth=2.5, label="MAE gradient")
ax.plot(residual, grad_huber, "g-", linewidth=2.5, label="Huber gradient")

ax.set_xlabel(r"Residual $(y - \hat{y})$", fontsize=13)
ax.set_ylabel(r"$\partial L / \partial \hat{y}$", fontsize=13)
ax.set_title("Gradients of Regression Loss Functions", fontsize=14)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig("regression_losses.png", dpi=150, bbox_inches="tight")
plt.show()

この比較グラフから、各損失関数の特性が明確に読み取れます。

  1. 左図(損失関数): MSE(青)は残差の二乗に比例して急激に増加するため、大きな外れ値に強いペナルティを課します。MAE(赤)は残差に対して線形に増加するだけなので、外れ値の影響が抑えられます。Huber損失(緑)は残差が小さいときはMSEに、大きいときはMAEに一致し、両者の良い性質を兼ね備えています

  2. 右図(勾配): MSEの勾配は残差に比例して大きくなるため、外れ値に対して過剰に反応します。MAEの勾配は常に $\pm 1$ の一定値をとり、残差の大きさによらない安定した更新を行いますが、原点で不連続です。Huber損失の勾配は原点付近で滑らかに変化し、遠方では一定値にクリップされます

回帰の損失関数を理解したところで、次に分類問題の損失関数に進みましょう。分類では予測が確率やスコアの形をとるため、損失関数の設計がより巧妙になります。

分類の損失関数

なぜ分類にMSEを使わないのか

二値分類で真のラベルが $y \in \{0, 1\}$、モデルの出力がシグモイド関数を通した確率 $\hat{p} = \sigma(\bm{w}^T\bm{x})$ とします。MSEを使うと

$$ L_{\text{MSE}} = (y – \hat{p})^2 $$

この損失のシグモイド出力に対する勾配は

$$ \frac{\partial L}{\partial \bm{w}} = -2(y – \hat{p})\hat{p}(1 – \hat{p})\bm{x} $$

ここで $\hat{p}(1 – \hat{p})$ というシグモイドの微分が含まれています。$\hat{p}$ が0や1に近いとき(つまりモデルが確信を持っているとき)、この項がほぼ0になり、勾配が消失します。

これは深刻な問題です。モデルが間違った方向に確信を持っている場合(例: $y = 1$ なのに $\hat{p} \approx 0$)、本来は大きな修正が必要なのに、勾配がほぼ0で学習が進みません。

この問題を解決するのが、交差エントロピー損失です。

二値交差エントロピー損失

二値交差エントロピー(Binary Cross-Entropy, BCE)は、真のラベル $y \in \{0, 1\}$ とモデルの予測確率 $\hat{p}$ の間の「確率分布の距離」を測ります。

$$ \begin{equation} L_{\text{BCE}}(y, \hat{p}) = -\left[y \ln \hat{p} + (1 – y) \ln(1 – \hat{p})\right] \end{equation} $$

この損失関数の直感的な意味を考えてみましょう。

  • $y = 1$ のとき: $L = -\ln \hat{p}$。$\hat{p}$ が1に近いほど損失は小さく、0に近いほど損失は $\infty$ に発散する
  • $y = 0$ のとき: $L = -\ln(1 – \hat{p})$。$\hat{p}$ が0に近いほど損失は小さく、1に近いほど発散する

つまり、間違った確信に対して非常に大きなペナルティを課すのが交差エントロピーの特徴です。

交差エントロピーの確率的解釈

交差エントロピーは、ベルヌーイ分布の負の対数尤度と一致します。

$y \sim \text{Bernoulli}(\hat{p})$ と仮定すると

$$ p(y | \hat{p}) = \hat{p}^y (1 – \hat{p})^{1-y} $$

負の対数尤度は

$$ -\ln p(y | \hat{p}) = -[y \ln \hat{p} + (1-y) \ln(1-\hat{p})] = L_{\text{BCE}} $$

つまり、交差エントロピーの最小化はベルヌーイ分布の最尤推定と一致するのです。MSEが正規分布の最尤推定に対応していたのと同様の関係です。

交差エントロピーの勾配

シグモイド出力 $\hat{p} = \sigma(z)$($z = \bm{w}^T\bm{x}$)を用いたときの勾配は

$$ \frac{\partial L_{\text{BCE}}}{\partial z} = \hat{p} – y $$

驚くほどシンプルです。そしてMSEのときの勾配と異なり、$\hat{p}(1-\hat{p})$ の項が相殺されているため、$\hat{p}$ が0や1に近くても勾配が消失しません。これが交差エントロピーが分類に適している最大の理由です。

多クラス交差エントロピー

$K$ クラスの分類では、真のラベルをone-hotベクトル $\bm{y} = (y_1, \ldots, y_K)$、予測確率をソフトマックス出力 $\hat{\bm{p}} = (\hat{p}_1, \ldots, \hat{p}_K)$ とすると

$$ \begin{equation} L_{\text{CE}}(\bm{y}, \hat{\bm{p}}) = -\sum_{k=1}^K y_k \ln \hat{p}_k \end{equation} $$

$y_k$ は正解クラスでのみ1で他は0なので、正解クラス $c$ について $L = -\ln \hat{p}_c$ となります。正解クラスに割り当てた確率が大きいほど損失は小さくなります。

多クラス交差エントロピーはカテゴリカル分布の負の対数尤度に対応し、やはり最尤推定と一致します。

次に、SVMで使われるヒンジ損失について見ていきましょう。

ヒンジ損失

ヒンジ損失(hinge loss)はサポートベクターマシン(SVM)で使われる損失関数です。ラベルを $y \in \{-1, +1\}$、モデルの出力を $\hat{y} = \bm{w}^T\bm{x}$ とすると

$$ \begin{equation} L_{\text{hinge}}(y, \hat{y}) = \max(0, 1 – y\hat{y}) \end{equation} $$

$y\hat{y}$ はマージンと呼ばれ、分類が正しいとき正、間違っているとき負になります。

  • $y\hat{y} \geq 1$: 正しい分類かつ十分なマージン → 損失は0
  • $0 < y\hat{y} < 1$: 正しい分類だがマージン不足 → 損失あり
  • $y\hat{y} \leq 0$: 誤分類 → 損失は $1 – y\hat{y} \geq 1$

ヒンジ損失の重要な特徴は、十分に正しく分類されたサンプル($y\hat{y} \geq 1$)には損失0を与える点です。つまり、「正しいが確信が低い」サンプルと「間違っている」サンプルだけが損失に寄与します。これがスパースな解(サポートベクター)を生み出す理由です。

0-1損失との関係

本来、分類で最小化したいのは0-1損失(誤分類率)です。

$$ L_{0\text{-}1}(y, \hat{y}) = \mathbb{1}[y\hat{y} \leq 0] = \begin{cases} 0 & y\hat{y} > 0 \\ 1 & y\hat{y} \leq 0 \end{cases} $$

しかし、0-1損失は微分不可能(階段関数)で最適化が困難です。交差エントロピーやヒンジ損失は0-1損失の凸上界(convex surrogate)であり、これらを最小化することで間接的に誤分類率を低減します。

import numpy as np
import matplotlib.pyplot as plt

margin = np.linspace(-3, 3, 500)  # y * f(x)

# 各損失関数
zero_one = (margin <= 0).astype(float)
hinge = np.maximum(0, 1 - margin)
logistic = np.log(1 + np.exp(-margin))  # log loss
exponential = np.exp(-margin)

fig, ax = plt.subplots(figsize=(10, 6))

ax.plot(margin, zero_one, "k-", linewidth=2.5, label="0-1 Loss")
ax.plot(margin, hinge, "b-", linewidth=2.5, label="Hinge Loss")
ax.plot(margin, logistic, "r-", linewidth=2.5, label="Logistic Loss (Cross-Entropy)")
ax.plot(margin, exponential, "g-", linewidth=2.5, label="Exponential Loss")

ax.axvline(0, color="gray", linestyle=":", linewidth=1, alpha=0.5)
ax.axvline(1, color="gray", linestyle=":", linewidth=1, alpha=0.5)

ax.set_xlabel(r"Margin $y \cdot f(x)$", fontsize=13)
ax.set_ylabel("Loss", fontsize=13)
ax.set_title("Classification Loss Functions as Convex Surrogates of 0-1 Loss", fontsize=14)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_ylim(-0.2, 5)
ax.set_xlim(-3, 3)

ax.annotate("Misclassified", xy=(-1.5, 0.1), fontsize=11, color="darkred")
ax.annotate("Correct", xy=(1.5, 0.1), fontsize=11, color="darkgreen")

plt.tight_layout()
plt.savefig("classification_losses.png", dpi=150, bbox_inches="tight")
plt.show()

このグラフから、各分類損失関数と0-1損失の関係が読み取れます。

  1. 0-1損失(黒): マージンが0を境に0または1の値をとる階段関数です。最適化が困難ですが、これが本来最小化したい損失です

  2. ヒンジ損失(青): 0-1損失の凸上界で、マージンが1以上で0になります。マージン1未満では線形に増加し、SVMのマージン最大化に対応します。$y\hat{y} = 1$ で微分不可能ですが劣微分は定義できます

  3. ロジスティック損失(赤): 交差エントロピーをマージンの形で表したもの $\ln(1 + e^{-y\hat{y}})$ です。至る所で滑らかに微分可能で、マージンが大きくても0にはならず、指数的に減衰します。これは「正しい分類にもさらに確信を持つ」よう学習が進むことを意味します

  4. 指数損失(緑): AdaBoostで使われる損失で、誤分類に対して指数的に大きなペナルティを課します。外れ値や誤ラベルに非常に敏感です

これらの損失関数はすべて0-1損失の上界であり、それぞれ異なる特性を持っています。次に、これらの損失関数の選択がモデルの学習結果にどのような影響を与えるか、実験で確認しましょう。

損失関数の選択がモデルに与える影響

回帰における外れ値の影響

外れ値が存在するデータに対して、MSE、MAE、Huber損失がそれぞれどのような回帰直線を学習するか比較します。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression, HuberRegressor
from sklearn.base import BaseEstimator, RegressorMixin

np.random.seed(42)

# データ生成(外れ値付き)
n = 50
x = np.linspace(0, 10, n)
y_true = 2 * x + 3
y = y_true + np.random.normal(0, 1.5, n)

# 外れ値の追加
outlier_idx = [5, 15, 25, 35, 45]
y[outlier_idx] += np.array([15, -20, 18, -15, 22])

X = x.reshape(-1, 1)

# MSE(最小二乗法)
model_mse = LinearRegression().fit(X, y)

# Huber
model_huber = HuberRegressor(epsilon=1.35).fit(X, y)

# MAE(QuantileRegressorで中央値回帰)
from sklearn.linear_model import QuantileRegressor
model_mae = QuantileRegressor(quantile=0.5, alpha=0.0, solver="highs").fit(X, y)

fig, ax = plt.subplots(figsize=(10, 6))

# データ点
normal_mask = np.ones(n, dtype=bool)
normal_mask[outlier_idx] = False
ax.scatter(x[normal_mask], y[normal_mask], color="steelblue", s=40, alpha=0.7, label="Normal data")
ax.scatter(x[~normal_mask], y[~normal_mask], color="red", s=80, marker="x",
           linewidths=2, label="Outliers")

# 真の直線
ax.plot(x, y_true, "k--", linewidth=2, alpha=0.5, label="True line: y = 2x + 3")

# 各モデルの予測
x_plot = np.linspace(0, 10, 100).reshape(-1, 1)
ax.plot(x_plot, model_mse.predict(x_plot), "b-", linewidth=2.5,
        label=f"MSE: y = {model_mse.coef_[0]:.2f}x + {model_mse.intercept_:.2f}")
ax.plot(x_plot, model_huber.predict(x_plot), "g-", linewidth=2.5,
        label=f"Huber: y = {model_huber.coef_[0]:.2f}x + {model_huber.intercept_:.2f}")
ax.plot(x_plot, model_mae.predict(x_plot), "r-", linewidth=2.5,
        label=f"MAE: y = {model_mae.coef_[0]:.2f}x + {model_mae.intercept_:.2f}")

ax.set_xlabel("x", fontsize=13)
ax.set_ylabel("y", fontsize=13)
ax.set_title("Effect of Loss Function on Linear Regression with Outliers", fontsize=14)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig("loss_outlier_comparison.png", dpi=150, bbox_inches="tight")
plt.show()

このグラフから、損失関数の選択が外れ値への頑健性に直接影響することが読み取れます。

  1. MSE(青線): 外れ値に引っ張られて、真の直線(黒破線)から大きくずれています。二乗損失は大きな残差に対して過剰なペナルティを課すため、外れ値に合わせようとしてモデル全体が歪んでしまいます

  2. Huber(緑線): 真の直線にかなり近い回帰直線を学習しています。小さな残差にはMSEと同様に二乗ペナルティを課しつつ、大きな残差(外れ値)には線形ペナルティに切り替えるため、外れ値の影響が抑制されています

  3. MAE(赤線): 最も真の直線に近い回帰直線を学習しています。中央値回帰に対応するため、外れ値に対して最も頑健ですが、通常のデータ点に対する効率(推定量の分散)はMSEよりやや劣ります

分類におけるMSE vs 交差エントロピー

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

# シグモイド関数
def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-np.clip(z, -500, 500)))

# 2次元データの生成
n = 200
X_pos = np.random.randn(n // 2, 2) + np.array([2, 2])
X_neg = np.random.randn(n // 2, 2) + np.array([-1, -1])
X = np.vstack([X_pos, X_neg])
y = np.hstack([np.ones(n // 2), np.zeros(n // 2)])

# 勾配降下法によるロジスティック回帰
def train_logistic(X, y, loss_type="bce", lr=0.01, epochs=1000):
    X_aug = np.hstack([X, np.ones((len(X), 1))])  # バイアス追加
    w = np.zeros(X_aug.shape[1])
    losses = []

    for epoch in range(epochs):
        z = X_aug @ w
        p = sigmoid(z)

        if loss_type == "bce":
            # 交差エントロピー
            loss = -np.mean(y * np.log(p + 1e-15) + (1 - y) * np.log(1 - p + 1e-15))
            grad = X_aug.T @ (p - y) / len(y)
        else:
            # MSE
            loss = np.mean((y - p) ** 2)
            grad = -2 * X_aug.T @ ((y - p) * p * (1 - p)) / len(y)

        w -= lr * grad
        losses.append(loss)

    return w, losses

w_bce, losses_bce = train_logistic(X, y, "bce", lr=0.1, epochs=500)
w_mse, losses_mse = train_logistic(X, y, "mse", lr=0.1, epochs=500)

fig, axes = plt.subplots(1, 2, figsize=(14, 5.5))

# (a) 学習曲線の比較
ax = axes[0]
ax.plot(losses_bce, "b-", linewidth=2, label="Cross-Entropy Loss")
ax.plot(losses_mse, "r-", linewidth=2, label="MSE Loss")
ax.set_xlabel("Epoch", fontsize=12)
ax.set_ylabel("Loss", fontsize=12)
ax.set_title("Training Loss: Cross-Entropy vs MSE", fontsize=13)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)

# (b) 決定境界の比較
ax = axes[1]
ax.scatter(X_pos[:, 0], X_pos[:, 1], color="blue", s=20, alpha=0.5, label="Class 1")
ax.scatter(X_neg[:, 0], X_neg[:, 1], color="red", s=20, alpha=0.5, label="Class 0")

x_bound = np.linspace(-4, 5, 100)
# 決定境界: w0*x + w1*y + w2 = 0 → y = -(w0*x + w2)/w1
if abs(w_bce[1]) > 1e-10:
    y_bound_bce = -(w_bce[0] * x_bound + w_bce[2]) / w_bce[1]
    ax.plot(x_bound, y_bound_bce, "b-", linewidth=2.5, label="CE boundary")
if abs(w_mse[1]) > 1e-10:
    y_bound_mse = -(w_mse[0] * x_bound + w_mse[2]) / w_mse[1]
    ax.plot(x_bound, y_bound_mse, "r--", linewidth=2.5, label="MSE boundary")

ax.set_xlabel("$x_1$", fontsize=12)
ax.set_ylabel("$x_2$", fontsize=12)
ax.set_title("Decision Boundary Comparison", fontsize=13)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_xlim(-4, 5)
ax.set_ylim(-4, 5)

plt.tight_layout()
plt.savefig("bce_vs_mse_classification.png", dpi=150, bbox_inches="tight")
plt.show()

この実験結果から、分類における損失関数の選択の重要性が確認できます。

  1. 左図(学習曲線): 交差エントロピー(青)の方がMSE(赤)よりも速く収束しています。これは交差エントロピーの勾配が $\hat{p} – y$ という単純な形であり、シグモイドの飽和による勾配消失がないためです。MSEは学習初期の損失減少がやや遅く、これがシグモイド微分 $\hat{p}(1-\hat{p})$ による勾配の抑制を反映しています

  2. 右図(決定境界): 両方の損失関数とも最終的にはデータをうまく分離する決定境界を学習していますが、交差エントロピーの方がより安定した境界を見つけています。特に学習率や初期値に対する感度が異なり、MSEは学習が不安定になりやすい傾向があります

損失関数の選択ガイド

ここまでの議論を踏まえて、実務での損失関数の選択指針をまとめます。

タスク 推奨損失関数 理由
回帰(外れ値なし) MSE 最尤推定と一致、微分可能、効率的
回帰(外れ値あり) Huber / MAE 外れ値への頑健性
二値分類 二値交差エントロピー 勾配消失なし、確率的解釈
多クラス分類 交差エントロピー 確率的解釈、ソフトマックスとの相性
SVM ヒンジ損失 マージン最大化、スパースな解
ランキング ペアワイズ損失/リストワイズ損失 順序関係の学習

カスタム損失関数の設計原則

実務では、ビジネス要件に合わせてカスタム損失関数を設計することもあります。例えば、予測が過大な場合と過小な場合でコストが異なる場合(在庫管理など)には、非対称な損失関数が適切です。

カスタム損失関数を設計する際の注意点:

  1. 微分可能性: 勾配降下法で最適化できるよう、(少なくとも劣微分が定義できるよう)設計する
  2. 凸性: 可能であれば凸関数にして最適化を容易にする
  3. スケール: 他の正則化項や学習率との相性を考慮する

まとめ

本記事では、機械学習における主要な損失関数の理論と使い分けについて解説しました。

  • MSEはノイズが正規分布に従う場合の最尤推定に対応し、外れ値に敏感だが効率的な回帰損失関数である
  • MAEは中央値回帰に対応し、外れ値に頑健だが原点で微分不可能という欠点がある
  • Huber損失はMSEとMAEの利点を組み合わせ、パラメータ $\delta$ で両者のバランスを制御する
  • 交差エントロピーはベルヌーイ/カテゴリカル分布の負の対数尤度に対応し、シグモイドとの組み合わせで勾配消失が起きないため、分類の標準的な損失関数である
  • ヒンジ損失はSVMのマージン最大化に対応し、十分に正しく分類されたサンプルには損失0を与えることでスパースな解を実現する
  • すべての分類損失関数は0-1損失の凸上界であり、それぞれ異なるトレードオフを持つ

損失関数の選択はモデルの学習結果を根本的に左右する重要な設計決定です。次のステップとして、以下の記事も参考にしてください。