RMSEとは?回帰モデルの評価指標を理解する

RMSE(Root Mean Square Error, 二乗平均平方根誤差)は、回帰モデルの予測精度を評価する最も基本的な指標の1つです。予測値と実際の値の誤差を二乗して平均し、その平方根を取ることで、目的変数と同じ単位で誤差を表現できます。

深層学習の損失関数(MSE)としても広く利用されており、機械学習を学ぶ上で必ず押さえておくべき概念です。

本記事の内容

  • RMSE、MSE、MAEの定義と導出
  • 各指標の性質と使い分け
  • Pythonでの計算と回帰モデルの評価

RMSEの定義

$n$ 個のデータ $(y_i, \hat{y}_i)$ について、RMSE は以下で定義されます。

$$ \text{RMSE} = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(y_i – \hat{y}_i)^2} $$

ここで $y_i$ は実際の値、$\hat{y}_i$ はモデルの予測値です。

関連する評価指標

MSE (Mean Squared Error, 平均二乗誤差)

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

RMSEとの関係は $\text{RMSE} = \sqrt{\text{MSE}}$ です。MSEは微分が容易なため、最適化の目的関数として利用されます。

MAE (Mean Absolute Error, 平均絶対誤差)

$$ \text{MAE} = \frac{1}{n}\sum_{i=1}^{n}|y_i – \hat{y}_i| $$

MAPE (Mean Absolute Percentage Error, 平均絶対パーセント誤差)

$$ \text{MAPE} = \frac{1}{n}\sum_{i=1}^{n}\left|\frac{y_i – \hat{y}_i}{y_i}\right| \times 100 $$

MSEとMAEの数学的な違い

MSE(およびRMSE)は誤差を二乗するため、大きな誤差に対して非常に大きなペナルティを与えます。一方、MAEは誤差の絶対値を取るため、外れ値の影響を受けにくい性質があります。

これを統計的に言うと、MSEを最小化する予測値は条件付き期待値(平均値)であり、MAEを最小化する予測値は条件付き中央値です。

$$ \hat{y}_{\text{MSE}} = E[y|\bm{x}] \quad (\text{条件付き平均}) $$

$$ \hat{y}_{\text{MAE}} = \text{median}(y|\bm{x}) \quad (\text{条件付き中央値}) $$

外れ値が多いデータではMAE、正規分布に近いデータではRMSEが適しています。

MSEの微分

MSEが損失関数として好まれる理由の1つは微分の容易さです。

$$ \frac{\partial}{\partial \hat{y}_i} \text{MSE} = \frac{2}{n}(\hat{y}_i – y_i) $$

この勾配は線形であり、勾配降下法による最適化が安定して進みます。一方、MAEの勾配は、

$$ \frac{\partial}{\partial \hat{y}_i} \text{MAE} = \frac{1}{n}\text{sign}(\hat{y}_i – y_i) $$

となり、$\hat{y}_i = y_i$ で不連続(微分不可能)です。

Pythonでの実装

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

np.random.seed(42)

# --- スクラッチ実装 ---
def rmse(y_true, y_pred):
    """RMSE"""
    return np.sqrt(np.mean((y_true - y_pred) ** 2))

def mse(y_true, y_pred):
    """MSE"""
    return np.mean((y_true - y_pred) ** 2)

def mae(y_true, y_pred):
    """MAE"""
    return np.mean(np.abs(y_true - y_pred))

# --- データ生成 ---
n = 100
X = np.random.uniform(0, 10, n).reshape(-1, 1)
y_true = 2 * np.sin(X.ravel()) + X.ravel() * 0.5
y = y_true + np.random.randn(n) * 1.0

# 外れ値を追加
n_outliers = 5
outlier_idx = np.random.choice(n, n_outliers, replace=False)
y_with_outliers = y.copy()
y_with_outliers[outlier_idx] += np.random.randn(n_outliers) * 10

# --- モデルの学習と評価 ---
poly = PolynomialFeatures(degree=3)
X_poly = poly.fit_transform(X)

# 外れ値なし
model_clean = LinearRegression().fit(X_poly, y)
y_pred_clean = model_clean.predict(X_poly)

# 外れ値あり
model_outlier = LinearRegression().fit(X_poly, y_with_outliers)
y_pred_outlier = model_outlier.predict(X_poly)

print("=== 外れ値なしのデータ ===")
print(f"RMSE: {rmse(y, y_pred_clean):.4f}")
print(f"MSE:  {mse(y, y_pred_clean):.4f}")
print(f"MAE:  {mae(y, y_pred_clean):.4f}")
print(f"R^2:  {r2_score(y, y_pred_clean):.4f}")

print(f"\n=== 外れ値ありのデータ ===")
print(f"RMSE: {rmse(y_with_outliers, y_pred_outlier):.4f}")
print(f"MSE:  {mse(y_with_outliers, y_pred_outlier):.4f}")
print(f"MAE:  {mae(y_with_outliers, y_pred_outlier):.4f}")
print(f"R^2:  {r2_score(y_with_outliers, y_pred_outlier):.4f}")

# --- 可視化 ---
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 外れ値なし
X_plot = np.linspace(0, 10, 200).reshape(-1, 1)
X_plot_poly = poly.transform(X_plot)

ax1 = axes[0]
ax1.scatter(X, y, c='blue', s=20, alpha=0.5, label='Data')
ax1.plot(X_plot, model_clean.predict(X_plot_poly), 'r-', label='Fit')
ax1.set_title(f'Without Outliers (RMSE={rmse(y, y_pred_clean):.3f})')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 外れ値あり
ax2 = axes[1]
ax2.scatter(X, y_with_outliers, c='blue', s=20, alpha=0.5, label='Data')
ax2.scatter(X[outlier_idx], y_with_outliers[outlier_idx],
            c='red', s=60, marker='x', label='Outliers', zorder=5)
ax2.plot(X_plot, model_outlier.predict(X_plot_poly), 'r-', label='Fit')
ax2.set_title(f'With Outliers (RMSE={rmse(y_with_outliers, y_pred_outlier):.3f})')
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.legend()
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# --- 各指標の誤差分布に対する感度 ---
errors = np.linspace(-5, 5, 200)
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(errors, errors**2, 'b-', label='Squared Error (MSE)')
ax.plot(errors, np.abs(errors), 'r-', label='Absolute Error (MAE)')
ax.set_xlabel('Error ($y - \\hat{y}$)')
ax.set_ylabel('Loss')
ax.set_title('MSE vs MAE: Sensitivity to Large Errors')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

外れ値がある場合、RMSEは大きく増加しますがMAEの増加は比較的穏やかです。これがMSEの「外れ値に敏感」という性質を示しています。

まとめ

本記事では、RMSEを中心に回帰モデルの評価指標について解説しました。

  • RMSEは目的変数と同じ単位で誤差を表現でき、直感的に理解しやすい
  • MSEは微分が容易なため損失関数として利用され、RMSEはその平方根
  • MAEは外れ値にロバストだが、原点で微分不可能
  • データの特性(外れ値の有無、分布の形状)に応じて適切な指標を選択することが重要

次のステップとして、以下の記事も参考にしてください。