機械学習の評価指標とPythonでの実装【回帰編】

「効果指標入門」を参考に、機械学習における評価指標をまとめました。今回は回帰問題における評価指標を取り上げます。後半ではPythonでの実装例を記載しています。

※内容に間違いがあればご指摘いただけますと幸いです。


# 評価指標とはなにか
機械学習モデルの性能を評価するために使用される「評価指標」とは、モデルがタスクをどれだけ効果的に実行しているかを定量的に測定するための尺度や基準です。

これらの指標は、モデルの訓練やチューニングにおいて不可欠であり、モデルの予測がどれだけ正確で信頼性があるかを評価します。

# 回帰タスク
回帰タスク、二値分類タスク、多クラス分類など、タスクによって使用される評価指標は異なります。

そのうち、今回は回帰タスクを取り上げます。

回帰タスクでは、入力変数（特徴量）と目標変数（予測対象）の間の関係をモデル化し、連続的な数値を予測します。予測対象の変数は実数値で、例えば価格予測、時間予測、数量予測などを行います。

# 回帰における評価指標
回帰問題では、以下のような評価指標が一般的に使用されます。
- 平均絶対誤差(MAE)
- 平均絶対パーセント誤差(MAPE)
- 平均二乗誤差(MSE:Mean Squared Error)
- 二乗平均平方誤差(RMSE)
- 対数平均二乗誤差(RMSLE)
- 決定係数

## 平均絶対誤差(MAE)
平均絶対誤差(MAE)は、予測値と実際の値との差（誤差）の絶対値の平均を取ったものです。数式で表すと以下のようになります。

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

ここで、$y_i$は実際の値、$\hat{y}_i$は予測値、$n$はデータ点の数を表します。

MAEは、絶対値を計算するため、後述のMSE（平均二乗誤差）やRMSE（MSEの平方根）よりも外れ値の影響を受けにくいという利点があります。また、元の数値から単位が変化しないため、シンプルで理解しやすい指標です。

```python:pythonでの算出方法
# 平均絶対誤差(MAE)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(y_test, y_pred)
print(f"MAE: {round(mae, 3)}")
```

## 平均絶対パーセント誤差(MAPE)
MAPE（Mean Absolute Percentage Error）は、予測値と実際の値との差（誤差）の絶対値を実際の値で割ったもの（相対誤差）の平均を取ったものです。

数式で表すと以下のようになります。

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

ここで、$y_i$は実際の値、$\hat{y}_i$は予測値、$n$はデータ点の数を表します。

MAPEは予測誤差の割合の平均を示し、モデルの予測の相対的な精度を評価します。MAPEが低いほど、モデルの性能が高いと言えます。

MAEのような絶対誤差ではなく、相対誤差であるため、スケールが異なるデータの予測（時系列予測など）に対応できます。

注意点として、正解値に0がある場合は割り算ができずにエラーになる問題があります。また、小数点以下で0に近い正解値の場合、極端に大きな評価値になりがちです。

```python:pythonでの算出方法
# 平均絶対パーセント誤差(MAPE)
from sklearn.metrics import mean_absolute_percentage_error
mape = mean_absolute_percentage_error(y_test, y_pred)
print(f"MAPE: {round(mape, 3)}")
```

## 平均二乗誤差(MSE)
MSE（Mean Squared Error）は、予測値と実際の値との差（誤差）を二乗したもの（二乗誤差）をデータ点数で割ったもの（二乗平均誤差）です。数式で表すと以下のようになります。

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

ここで、$y_i$は実際の値、$\hat{y}_i$は予測値、$n$はデータ点の数を表します。

予測誤差が大きいほどMSEは大きくなるため、MSEが小さいほどモデルの予測性能が高いと言えます。

MSEは誤差を二乗するため、大きな誤差を重視する特性があります。一方で、二乗されているので指標の単位がわかりにくいという欠点があります。

```python:pythonでの算出方法
# 平均二乗誤差(MSE)
from sklearn.metrics import mean_squared_error
mse = mean_squared_error(y_test, y_pred, squared=False)
print(f"MSE: {round(mse, 3)}")
```

## 二乗平均平方誤差(RMSE)
RMSE（Root Mean Squared Error）は、予測値と実際の値との差（誤差）を二乗したもの（二乗誤差）をデータ点数で割ったもの（二乗平均誤差）の平方根を取ったものです。数式で表すと以下のようになります。

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

ここで、$y_i$は実際の値、$\hat{y}_i$は予測値、$n$はデータ点の数を表します。

値が小さいほどモデルの予測精度が高いことを示します。MSEの平方根を取るため元の数値と単位が同じになり、解釈がしやすくなっています。

```python:pythonでの算出方法
# 二乗平均平方誤差(RMSE)
from sklearn.metrics import mean_squared_error
rmse = mean_squared_error(y_test, y_pred, squared=True)
print(f"RMSE: {round(rmse, 3)}")
```

## 対数平均二乗誤差(RMSLE)
RMSLE（Root Mean Squared Logarithmic Error）は、予測値と実際の値の対数を取ったものとの差（誤差）を二乗したもの（二乗誤差）をデータ点数で割ったもの（二乗平均誤差）の平方根を取ったものです。数式で表すと以下のようになります。

$$ RMSLE = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(\log(y_i + 1) - \log(\hat{y}_i + 1))^2} $$

ここで、$y_i$は実際の値、$\hat{y}_i$は予測値、$n$はデータ点の数を表します。

RMSLEが小さいほどモデルの予測性能が高いと言えます。

RMSLEは対数を取ることで予測値と実際の値の比率に基づいて誤差を評価するため、予測値と実際の値のスケールが大きく異なる場合でも適切に誤差を評価することができます。特に価格予測などのタスクで有用とされます。

値が0の場合に log(0) となり計算できなくなることを避けるため、予測値と実測値に＋１しています。

```python:pythonでの算出方法
#対数平均二乗誤差(RMSLE)
from sklearn.metrics import mean_squared_log_error
rmsle = mean_squared_log_error(y_test, y_pred, squared=False)
print(f"RMSLE: {round(rmse, 3)}")
```

## 決定係数
決定係数（Coefficient of Determination）は、モデルが実際のデータをどれだけよく説明しているかを評価するための指標です。決定係数は以下の数式で表されます。
```math
 R^2 = 1 - \frac{\sum_{i=1}^{n}(y_i - \hat{y}_i)^2}{\sum_{i=1}^{n}(y_i - \bar{y})^2} 
```

ここで、$y_i$は実際の値、$\hat{y}_i$は予測値、$\bar{y}$は実際の値の平均、$n$はデータ点の数を表します。

決定係数は全体の変動に対する誤差の割合を示しており、0から1の範囲の値を取ります。1に近いほど良いモデルであることを示し、逆に0に近い場合はモデルの説明力が低いことを示します。

説明変数を増やすほど値が1に近づく傾向がある点には注意が必要です。

```python:pythonでの算出方法
#決定係数
from sklearn.metrics import r2_score
r2s = r2_score(y_test, y_pred)
print(f"R2S: {round(r2s, 3)}")
```

# 各指標まとめ
以下に、回帰タスクにおける各評価指標の名称、説明、メリット・デメリットを表にまとめました。

| 評価指標 | 説明 | メリット | デメリット |
|---|---|---|---|
| 平均絶対誤差(MAE) | 予測値と実際の値との絶対誤差の平均 | 予測エラーの大きさを直感的に理解できる | 大きな誤差を重視しない |
| 平均絶対パーセント誤差(MAPE) | 予測値と実際の値との相対的な誤差の平均 | 予測エラーが実際の値に対してどれだけ大きいかをパーセンテージで表せる | 実際の値が0に近い場合、誤差が無限大になる可能性がある |
| 平均二乗誤差(MSE) | 予測値と実際の値との誤差を二乗したものの平均 | 大きな誤差を重視する | 単位が二乗されるため、直感的に理解しにくい |
| 二乗平均平方誤差(RMSE) | MSEの平方根 | 予測エラーの大きさを直感的に理解でき、大きな誤差を重視する | 単位が元の尺度と異なるため、解釈が難しい場合がある |
| 対数平均二乗誤差(RMSLE) | 予測値と実際の値の対数を取ったものの二乗誤差の平均の平方根 | 比率を重視し、絶対的な誤差よりも相対的な誤差を重視する | 対数を取るため、負の予測値を扱うことができない |
| 決定係数 | モデルがデータにどれだけフィットしているかを示す指標 | モデル全体の性能を一つの数値で評価できる | モデルが複雑になるほど1に近づくため、過学習を引き起こす可能性がある |

# Pythonでの実装
Pythonの機械学習ライブラリscikit-learnに用意されている「california housing dataset」(カリフォルニアの住宅価格データセット)」を使用しました。データセットに含まれる様々な変数から住宅価格を予測することを目的に、重回帰と勾配ブースティング(xgboost)でモデル作成を行い、各評価指標での結果を比較しました。

※2023/09/10現在、「ボストン住宅価格データセット」は倫理的な問題から非推奨となっており、今後は「カリフォルニ住宅価格データセット」の使用が推奨されています。

### データの概要

データ数：20640
カラム数：9

| 変数名        | 詳細                                       |
|---------------|--------------------------------------------|
| Medlnc        | 世帯所得の中央値                          |
| HouseAge      | 住宅の築年数                              |
| AveRooms      | 住宅の部屋数の平均                        |
| AveBedrms     | 住宅の寝室数の平均                        |
| Poplation     | 居住人数の合計                            |
| AveOcuup      | 世帯人数の平均                            |
| Latitude      | 各地区における代表地区の緯度            |
| HousePrices   | 各地区における代表地区の住宅価格        |

データの詳細

https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_california_housing.html

### 予測モデルの作成
今回は、解釈のしやすい重回帰分析と予測精度が高いと言われる勾配ブースティング(xgboost)で予測モデルを作成し、結果を比較しました。

```python:予測モデルの作成.py
# 必要ライブラリをインポート
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import xgboost as xgb
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing

# データを読み込み、データの中身とデータ型、欠損を確認
data_housing = fetch_california_housing()

exp_data = pd.DataFrame(data_housing.data, columns=data_housing.feature_names)
tar_data = pd.DataFrame(data_housing.target, columns=['HousingPrices'])
exp_data.head()

print(data.shape) # (20640, 9)
print(data.dtypes) # 全てfloat64
print(data.isnull().sum()) # 全て0

# ヒストグラムの確認
fig, ax = plt.subplots(figsize=(10,8))
plt.tight_layout()
exp_data.hist(bins=20, ax=ax)

# ヒートマップの確認
data = pd.concat([exp_data, tar_data], axis=1)
plt.figure(figsize=(12, 9))
sns.heatmap(data.corr(), annot=True, cmap='Blues', fmt='.2f', linewidths=.5)
plt.savefig('california_housing_heatmap.png')

# 説明変数
exp_vars = ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']
# 目的変数
tar_var = 'HousingPrices'

# 外れ値を除去
for exp_var in exp_vars:
    q_95 = data[exp_var].quantile(0.95)
    print(exp_var, '95%点の分位数', q_95)
    data = data[data[exp_var] < q_95]

# 説明変数と目的変数にデータを分割
X = data[exp_vars]
print(data.shape)
display(X.head())
y = data[[tar_var]]
print(y.shape)
display(y.head())

# 訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
print('X_train', X_train.shape)
print('y_train', y_train.shape)
print('X_test', len(X_test))
print('y_test', len(y_test))

#  X_trainを標準化する
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
print('X_train_scaled', X_train_scaled.shape)
X_train_scaled = pd.DataFrame(X_train_scaled, columns = exp_vars)
display(X_train_scaled.head())

# モデル１（重回帰）の学習
from sklearn.linear_model import LinearRegression
model1 = LinearRegression()
model1.fit(X_train_scaled, y_train)

## 偏回帰係数を計算
###目的に変数に与える影響はMedIncが最も大きいことを確認できる
for xi, wi in zip(exp_vars, model1.coef_[0]):
    print('{0:7s}: {1:6.3f}'.format(xi, wi))

# モデル２（xgboost）の学習
## xgboostのハイパーパラメーター
params = {'early_stopping_rounds': 10,
          'subsample': 0.9,
          'reg_lambda': 0.01,
          'reg_alpha': 0.03,
          'min_child_weight': 8,
          'max_depth': 4,
          'learning_rate': 0.2,
          'gamma': 0.01,
          'colsample_bytree': 1.0}
model2 = xgb.XGBRegressor(params = params)
model2.fit(X_train, y_train, eval_set=[(X_train, y_train,),(X_test, y_test)])


# 予測値を計算
X_test_scaled = scaler.transform(X_test) # テストデータを訓練データから得られた平均と標準偏差で標準化
y_pred_model1 = model1.predict(X_test_scaled) 
y_pred_model2 = model2.predict(X_test)
```

### 評価指標の算出
重回帰のモデル1とxgboostのモデル2の予測結果について、各評価指標の値を算出します。

```python:評価指標の算出.py
# 評価指標の算出
indicators = pd.DataFrame(index = ['model1', 'model2'])

# 平均絶対誤差(MAE)
from sklearn.metrics import mean_absolute_error
mae_model1 = mean_absolute_error(y_test, y_pred_model1)
mae_model2 = mean_absolute_error(y_test, y_pred_model2)
mae = pd.DataFrame([[mae_model1],[mae_model2]], columns=['mae'],index = ['model1', 'model2'])
indicators = pd.merge(indicators, mae, left_index = True, right_index = True)

# 平均絶対パーセント誤差(MAPE)
from sklearn.metrics import mean_absolute_percentage_error
mape_model1 = mean_absolute_percentage_error(y_test, y_pred_model1)
mape_model2 = mean_absolute_percentage_error(y_test, y_pred_model2)
mape = pd.DataFrame([[mape_model1],[mape_model2]], columns=['mape'],index = ['model1', 'model2'])
indicators = pd.merge(indicators, mape, left_index = True, right_index = True)


# 平均二乗誤差(MSE)
from sklearn.metrics import mean_squared_error
mse_model1 = mean_squared_error(y_test, y_pred_model1, squared=False)
mse_model2 = mean_squared_error(y_test, y_pred_model2, squared=False)
mse = pd.DataFrame([[mse_model1],[mse_model2]], columns=['mse'],index = ['model1', 'model2'])
indicators = pd.merge(indicators, mse, left_index = True, right_index = True)

# 二乗平均平方誤差(RMSE)
from sklearn.metrics import mean_squared_error
rmse_model1 = mean_squared_error(y_test, y_pred_model1, squared=True)
rmse_model2 = mean_squared_error(y_test, y_pred_model2, squared=True)
rmse = pd.DataFrame([[rmse_model1],[rmse_model2]], columns=['rmse'],index = ['model1', 'model2'])
indicators = pd.merge(indicators, rmse, left_index = True, right_index = True)


#対数平均二乗誤差(RMSLE)
## 予測値に負の数が含まれると計算できないため例外処理を追加
from sklearn.metrics import mean_squared_log_error
try: 
  rmsle_model1 = mean_squared_log_error(y_test, y_pred_model1, squared=False)
  rmsle_model2 = mean_squared_log_error(y_test, y_pred_model2, squared=False)
except:
  rmsle_model1 = None
  rmsle_model2 = None
rmsle = pd.DataFrame([[rmsle_model1],[rmsle_model2]], columns=['rmsle'],index = ['model1', 'model2'])
indicators = pd.merge(indicators, rmsle, left_index = True, right_index = True)

#決定係数
from sklearn.metrics import r2_score
r2s_model1 = r2_score(y_test, y_pred_model1)
r2s_model2 = r2_score(y_test, y_pred_model2)
r2s = pd.DataFrame([[r2s_model1],[r2s_model2]], columns=['r2s'],index = ['model1', 'model2'])
indicators = pd.merge(indicators, r2s, left_index = True, right_index = True)

print(indicators)
```
### 結果
| モデル  |  MAE | MAPE   | MSE    | RMSE   | RMSLE  | 決定係数 |
|--------|------|--------|--------|--------|--------|----------|
| モデル1 | 0.456 | 0.264  | 0.618  | 0.382  | NaN    | 0.622    |
| モデル2 | 0.303 | 0.164  | 0.448  | 0.201  | NaN    | 0.801    |



結果から、重回帰のモデル1より勾配ブースティング（xgboost)のモデル2のほうが精度良く予測できていることがわかります。


RMSLEはどちらのモデルでも予測結果に負の値が含まれるためか、計算ができていませんでした。




## 参考文献・参考サイト
・評価指標入門
・【Python】scikit-learnのcalifornia housing datasetを使って回帰分析をしてみる
https://zenn.dev/omochimaru/articles/9b289f4a9455b7#%E9%87%8D%E5%9B%9E%E5%B8%B0%E5%88%86%E6%9E%90%E3%81%AE%E6%B5%81%E3%82%8C
・(その4-7) エイムズの住宅価格をXGBoostで予測してみた パート1
https://www.hinomaruc.com/ames-dataset-analytics-4-7-random-forest-part1/