CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: scikit-learn の cross_validate() 関数で独自の評価指標を計算する

今回は scikit-learncross_validate() 関数で、組み込みでは用意されていないような評価指標を計算する方法について書く。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.14.3
BuildVersion:   18D109
$ python -V               
Python 3.7.2

下準備

下準備として、事前に scikit-learn をインストールしておく。

$ pip install scikit-learn

cross_validate() 関数について

まずは cross_validate() 関数のおさらいから。 この関数は、その名の通り機械学習モデルの汎化性能を Cross Validation (交差検証) で評価するためにある。

次のサンプルコードでは、Breast Cancer データセットをランダムフォレストで学習させた場合の汎化性能を Stratified k-Fold CV で評価している。 cross_validate() 関数は、一度に複数の評価指標を計算できる点も特徴的。 以下では、組み込みで用意されている Accuracy, Precision, Recall, F1-Value について計算している。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_validate


def main():
    # データセットを読み込む
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target

    # モデルを用意する
    clf = RandomForestClassifier(n_estimators=100, random_state=42)

    # Stratified k-Fold で汎化性能を評価する
    skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
    # 評価する指標
    score_funcs = [
        'accuracy',
        'precision',
        'recall',
        'f1',
    ]
    # Cross Validation で検証する
    scores = cross_validate(clf, X, y, cv=skf, scoring=score_funcs)
    # 得られた指標を出力する
    print('accuracy:', scores['test_accuracy'].mean())
    print('precision:', scores['test_precision'].mean())
    print('recall:', scores['test_recall'].mean())
    print('f1:', scores['test_f1'].mean())


if __name__ == '__main__':
    main()

上記の実行結果は次の通り。

$ python cv.py 
accuracy: 0.9631088880338592
precision: 0.961883227291678
recall: 0.9805164319248826
f1: 0.9708395567654284

独自の評価指標を組み込む

続いて本題の独自の評価指標を組み込む方法について。 独自の評価指標を計算したいときは、真の値と推定した値を引数として受け取る関数を定義する。 そして、関数では引数を元に評価指標を計算して返す。

以下のサンプルコードでは、手始めに精度 (Accuracy) を計算する関数 accuracy_score を定義している。 精度 (Accuracy) の計算は組み込みでも用意されているけど、まずはそれと差異が出ないことを比較できるようにするため。 ポイントとして、定義した関数は make_scorer() 関数でラップする必要がある。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_validate
from sklearn.metrics import make_scorer


def accuracy_score(y_true, y_pred):
    """オリジナルの評価関数 (ただし単なる精度の計算)"""
    accuracy = sum(y_true == y_pred) / len(y_true)
    return accuracy


def main():
    dataset = datasets.load_breast_cancer()
    X, y = dataset.data, dataset.target

    clf = RandomForestClassifier(n_estimators=100, random_state=42)

    skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

    score_funcs = {
        'builtin_accuracy': 'accuracy',
        # オリジナルの評価関数を登録する
        'custom_accuracy': make_scorer(accuracy_score),  # make_scorer() でラップする
    }

    scores = cross_validate(clf, X, y, cv=skf, scoring=score_funcs)
    print('built-in accuracy:', scores['test_builtin_accuracy'].mean())
    print('custom accuracy:', scores['test_custom_accuracy'].mean())


if __name__ == '__main__':
    main()

上記の実行結果は次の通り。 組み込みの精度 (Accuracy) 計算と同じ値が得られていることが分かる。

$ python acc.py 
built-in accuracy: 0.9631088880338592
custom accuracy: 0.9631088880338592

回帰問題でもやってみる

先ほどは分類問題だったので、念のため次は回帰問題でも確認しておこう。

以下のサンプルコードでは Boston データセットを ElasticNet 回帰で処理している。 独自の評価指標を計算する関数は rmse_score() という関数を用意した。 これは、文字通り RMSE (Root Mean Square Error: 平均二乗誤差平方根) を計算する関数になっている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import math

from sklearn import datasets
from sklearn.linear_model import ElasticNet
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_validate
from sklearn.metrics import mean_squared_error
from sklearn.metrics import make_scorer


def rmse_score(y_true, y_pred):
    """RMSE (Root Mean Square Error: 平均二乗誤差平方根) を計算する関数"""
    mse = mean_squared_error(y_true, y_pred)
    rmse = math.sqrt(mse)
    return rmse


def main():
    # Boston データセットを使った回帰問題
    dataset = datasets.load_boston()
    X, y = dataset.data, dataset.target

    # ElasticNet 回帰
    reg = ElasticNet(random_state=42)

    kf = KFold(n_splits=5, shuffle=True, random_state=42)

    score_funcs = {
        'rmse': make_scorer(rmse_score),
    }

    scores = cross_validate(reg, X, y, cv=kf, scoring=score_funcs)
    mean_rmse = scores['test_rmse'].mean()
    print('RMSE:', mean_rmse)


if __name__ == '__main__':
    main()

上記の実行結果は次の通り。

$ python rmse.py   
RMSE: 5.274746876673534

ちゃんと計算できているようだ。

めでたしめでたし。

機械学習のための特徴量エンジニアリング ―その原理とPythonによる実践

機械学習のための特徴量エンジニアリング ―その原理とPythonによる実践

  • 作者: Alice Zheng,Amanda Casari,株式会社ホクソエム
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2019/02/23
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る