機械学習で使ういろんな評価指標 (Metrics)

# 

機械学習で作成したモデルの良し悪しを比較するためにさまざまな評価指標があります。すぐ忘れるのでここにメモします。
今回の実験ではサンプルとして [Boston Housing：ボストンの住宅価格 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2006/24/news033.html) を使います。実はこのデータセットは人種差別の要素があるらしく最近非推奨になりましたが、データ量が少なく練習するのに楽なので勉強用として使わせていただきます。以下のライブラリを利用します。


```python
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score, root_mean_squared_error, mean_absolute_percentage_error
```

昔は Scikit-Learn の `load_boston()` 関数でダウンロードできたましたが、非推奨になったので手動でデータセットを作ります。


```python
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data"
column_names = [
    "CRIM", "ZN", "INDUS", "CHAS", "NOX", "RM", "AGE", "DIS", "RAD", "TAX", 
    "PTRATIO", "B", "LSTAT", "PRICE"
]
data = pd.read_csv(url, sep=r'\s+', names=column_names)

print("データの概要:")
data.head()
```

    データの概要:





<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>CRIM</th>
      <th>ZN</th>
      <th>INDUS</th>
      <th>CHAS</th>
      <th>NOX</th>
      <th>RM</th>
      <th>AGE</th>
      <th>DIS</th>
      <th>RAD</th>
      <th>TAX</th>
      <th>PTRATIO</th>
      <th>B</th>
      <th>LSTAT</th>
      <th>PRICE</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0.00632</td>
      <td>18.0</td>
      <td>2.31</td>
      <td>0</td>
      <td>0.538</td>
      <td>6.575</td>
      <td>65.2</td>
      <td>4.0900</td>
      <td>1</td>
      <td>296.0</td>
      <td>15.3</td>
      <td>396.90</td>
      <td>4.98</td>
      <td>24.0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0.02731</td>
      <td>0.0</td>
      <td>7.07</td>
      <td>0</td>
      <td>0.469</td>
      <td>6.421</td>
      <td>78.9</td>
      <td>4.9671</td>
      <td>2</td>
      <td>242.0</td>
      <td>17.8</td>
      <td>396.90</td>
      <td>9.14</td>
      <td>21.6</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0.02729</td>
      <td>0.0</td>
      <td>7.07</td>
      <td>0</td>
      <td>0.469</td>
      <td>7.185</td>
      <td>61.1</td>
      <td>4.9671</td>
      <td>2</td>
      <td>242.0</td>
      <td>17.8</td>
      <td>392.83</td>
      <td>4.03</td>
      <td>34.7</td>
    </tr>
    <tr>
      <th>3</th>
      <td>0.03237</td>
      <td>0.0</td>
      <td>2.18</td>
      <td>0</td>
      <td>0.458</td>
      <td>6.998</td>
      <td>45.8</td>
      <td>6.0622</td>
      <td>3</td>
      <td>222.0</td>
      <td>18.7</td>
      <td>394.63</td>
      <td>2.94</td>
      <td>33.4</td>
    </tr>
    <tr>
      <th>4</th>
      <td>0.06905</td>
      <td>0.0</td>
      <td>2.18</td>
      <td>0</td>
      <td>0.458</td>
      <td>7.147</td>
      <td>54.2</td>
      <td>6.0622</td>
      <td>3</td>
      <td>222.0</td>
      <td>18.7</td>
      <td>396.90</td>
      <td>5.33</td>
      <td>36.2</td>
    </tr>
  </tbody>
</table>
</div>



それぞれの列の詳しい説明は先ほどの [Boston Housing：ボストンの住宅価格 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2006/24/news033.html) を参照ください。CRIM(犯罪率)や ZN(25,000平方フィートを超える広い家の割合) といった入力から、最後の列の PRICE(価格 x 1000ドル) を当てるというのが目的です。

本題ではないのでモデルの作成方法はさらっと書いて、正解の価格 (y_test) と予想した価格 (y_pred) の違いを散布図にして感じを見てみます。


```python
# 説明変数と目的変数の設定
X = data.drop('PRICE', axis=1)
y = data['PRICE']

# 訓練データとテストデータの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 線形回帰モデルの構築
model = LinearRegression()
model.fit(X_train, y_train)

# 予測
y_pred = model.predict(X_test)

# 実測値でソートして後の評価を見やすくする。
results = pd.DataFrame({'y_test': y_test, 'y_pred': y_pred})
results.sort_values('y_test', inplace=True)
results.reset_index(inplace=True)

# 結果の表示
print("予測値と正解値:")
results.plot.scatter(x='y_test', xlabel='y_test(Actual)', y='y_pred', ylabel='y_pred(Predicted)')
```

    予測値と正解値:

![output_5_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/e0078d1e-cbce-5d09-3780-d620515349d4.png)
    
かりに予測が完璧だと y_pred(予測値) == y_test(正解値) なのでナナメ 45 度の直線になるはずです。まあまあ予測できていると思います。
この予測できてる具合を測るために評価指標 (metrics) があります。sklearn では関数一つで評価指標を計算できるようになっています。
ただ、このままだと数値の意味がよくわからないので自分で実装しながらいろいろな評価指標の意味を確認します。


```python
# モデルの評価
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = root_mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
mape = mean_absolute_percentage_error(y_test, y_pred) * 100

print("\nモデルの評価:")
print(f"残差の絶対値の平均 (MAE): {mae:.2f}")
print(f"MAPE 平均絶対パーセント誤差 (%): {mape:.2f}")

print(f"平均二乗誤差 (MSE): {mse:.2f}")
print(f"ルート平均二乗誤差 (RMSE): {rmse:.2f}")
print(f"決定係数 (R^2): {r2:.2f}")
```

    
    モデルの評価:
    残差の絶対値の平均 (MAE): 3.19
    MAPE 平均絶対パーセント誤差 (%): 16.87
    平均二乗誤差 (MSE): 24.29
    ルート平均二乗誤差 (RMSE): 4.93
    決定係数 (R^2): 0.67


## MAE 平均絶対誤差 とは?

一番簡単な評価指標は MAE (平均絶対誤差)です。これは単に予測値と正解値の差分を平均したものです。予測値と正解値のどちらが大きくても良いように絶対値を平均します。

$$
MAE = \frac{1}{n}\sum_{i=1}^{n}|予測値_i - 正解値_i|
$$

差分は小さい方がよいので、値が小さい方が正確です。この例だと、平均して 3.19 (x1000 ドル) の誤差がある事になります。実際の値と評価指標の関係を掴むためにグラフにしてみました。

* y_test: 正解値。この値でソート済み。一番上に表示されます。
* -y_pred: 見やすいように予測値をマイナスにして一番下に上下反転してに表示します。
* err: y_test - y_pred: 正解値と予測値の差分です。ゼロ近辺を上下に細かく動きます。
* abs_err: 差分の絶対値です。ゼロの上をギザギザしています。
* mean_abs_err: 差分の絶対値を平均したものです。abs_err の真ん中あたりの水平な線です。


```python
err = results.y_test - results.y_pred
abs_err = err.abs()
mean_abs_err = abs_err.sum() / len(abs_err)
print(f"残差の絶対値の平均 (MAE): {mean_abs_err:.2f}")
pd.DataFrame({"MAE": mean_abs_err, "err": err, "abs_err": abs_err, "y_test": results.y_test, "-y_pred": -results.y_pred}).plot.line(grid=True)
```

    残差の絶対値の平均 (MAE): 3.19

![output_9_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/c07a9cc2-4036-c698-b651-242d1aa1f22c.png)

## MAPE 平均絶対パーセント誤差 とは?

平均絶対誤差では単に予測値と正解値の差分を平均しましたが、値が大きいほど誤差も大きくなるような場合(普通そうなると思いますが)、誤差ではなく正解値に対する誤差の割合(絶対パーセント誤差)を指標にしたくなります。割合を求めるためには誤差の絶対値(abs_err)を正解値(y_test)で割れば良いです。これを平均化したものを絶対平均パーセント誤差 (MAPE) と呼びます。この例だと、絶対平均パーセント誤差は 0.1687 なので、正解値に比べて平均で 17% 程度の誤差がある事になります。これも小さい方が良い成績です。

$$
MAPE = \frac{1}{n}\sum_{i=1}^{n}|\frac{予測値_i - 正解値_i}{正解値_i}|
$$

絶対パーセント誤差は割合なので、正解値を同じグラフに入れると収まりが悪いので絶パーセント誤差とその平均だけをグラフに描きました。正解値が大きくなるほど誤差の影響が少なくなっている事がわかります。


```python
abs_per_err = abs_err / results.y_test
pd.DataFrame({"MAPE": abs_per_err.mean(), "abs_per_err": abs_per_err}).plot.line()
print(f"MAPE 平均絶対パーセント誤差 (%): {abs_per_err.mean() * 100:.2f}")
```

    MAPE 平均絶対パーセント誤差 (%): 16.87

![output_11_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/681a1a34-c21c-4ab5-db1e-889b90307d0e.png)

# MSE 平均二乗誤差 / RMSE 平均二乗誤差の平方根 とは?

平均二乗誤差(MSE) は平均絶対誤差と同じく誤差の平均を使った評価指標で、機械学習のモデル作成によく使われます。学習時にできるだけこの値が小さくなるように調整するのです。平均二乗誤差では、絶対値の代わりに二乗を使います。二乗を使う理由は以下です。

* 差分を正の数値にしたい。 (絶対値を使う理由と同じ)
* 大きい誤差に対してペナルティを課したい。機械学習で素早くモデルを調整するために、大きい誤差を素早く調整できる。
* 絶対値と違って二乗は微分可能。微分というのはグラフの傾きの事ですが、誤差の傾きよってモデルをどっち向きにどれくらい調整したら良いか決めます。

$$
MSE = \frac{1}{n}\sum_{i=1}^{n}(予測値_i - 正解値_i)^2
$$

評価指標の感触を掴むために、この値の平方根を取った RMSE (Root MSE) というのもよく使われます。平方根を取ると、この場合のモデルは平均して 4.93 (x1000 ドル)の誤差がある事がわかります。

これも縦軸が正解値や予測値と離れていてグラフにしずらいですが、無理やりプロットすると誤差の大きさが誇張されて指標に影響されている事がわかります。


```python
sq_err = (results.y_pred - results.y_test)**2
mean_sq_err = sq_err.sum() / len(sq_err)

print(f"MSE 平均二乗誤差 (%): {mean_sq_err:.2f}")
print(f"RMSE 平均二乗誤差の平方根 (%): {np.sqrt(mean_sq_err):.2f}")
pd.DataFrame({
    "MSE": mean_sq_err,
    "RMSE": np.sqrt(mean_sq_err),
    "sq_err": sq_err,
    "y_test": results.y_test,
    "-y_pred": -results.y_pred
}).plot.line()
```

    MSE 平均二乗誤差 (%): 24.29
    RMSE 平均二乗誤差の平方根 (%): 4.93

![output_13_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/bf35cba7-b746-8c78-529d-5443ff0eaf9c.png)

## R2 決定係数 とは?

まだ良くわからないので後で調べます。少々お待ちください。

[決定係数R2を超わかりやすく解説【統計学入門16】](https://datawokagaku.com/r_squared/) とか分かりやすそう。


```python
sum_sq = (results.y_test - results.y_test.mean())**2
r2 = 1 - sq_err.sum() / sum_sq.sum()
print(f"決定係数 (R^2): {r2:.2f}")
pd.DataFrame({"R^2": r2, "sum_sq": sum_sq, "sq_err": sq_err, "y_test": results.y_test, "-y_pred": -results.y_pred}).plot.line()
```

    決定係数 (R^2): 0.67

![output_15_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35350/9f5cf5ca-2af5-cf59-6795-24ef3c5b1a3f.png)

## 参考

* [Boston Housing：ボストンの住宅価格（部屋数や犯罪率などの13項目）の表形式データセット：AI・機械学習のデータセット辞典 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2006/24/news033.html)
* [回帰モデルの評価指標を一挙に解説(MSE, RMSE, MAE, R-Squared等)【機械学習入門10】](https://datawokagaku.com/reg_metrics/)
* [［損失関数／評価関数］平均絶対誤差（MAE：Mean Absolute Error）／L1損失（L1 Loss）とは？：AI・機械学習の用語辞典 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2105/19/news022.html)
* [［評価関数］平均絶対パーセント誤差（MAPE：Mean Absolute Percentage Error）とは？：AI・機械学習の用語辞典 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2106/09/news028.html)
* [［損失関数／評価関数］平均二乗誤差（MSE：Mean Squared Error）／RMSE（MSEの平方根）とは？：AI・機械学習の用語辞典 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2105/24/news019.html)
* [［評価関数］決定係数（Coefficient of Determination）R2とは？：AI・機械学習の用語辞典 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2108/25/news033.html)
* [Chat GPT 質問の記録](https://chatgpt.com/share/68333bc7-c376-41d1-b5b7-87b82ad6e006)