CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: scikit-learn の Dummy{Classifier,Regressor} を試してみる

つい最近 scikit-learn に DummyClassifier と DummyRegressor という実装があることを知ったので試してみた。 これらの実装は、説明変数の内容は使わず、主に目的変数の内容を代わりに使って、その名の通りダミーの結果を返す。 特定のデータセットと評価指標を使ったときの、最低ラインの性能を確認するのに便利そう。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.14.5
BuildVersion:   18F132
$ python -V
Python 3.7.3

下準備

まずは scikit-learn をインストールしておく。

$ pip install scikit-learn

DummyClassifier

DummyClassifier は、その名の通りダミーの分類器となる。 使い方は一般的な scikit-learn の分類器と何ら違いはない。 違いがあるとすれば与えた教師データに含まれる説明変数を学習しないという点。

教師データの目的変数の確率分布を再現する

動作を確認するためのサンプルコードとして以下を用意した。 デフォルトでは、教師データに含まれる目的変数の確率分布を再現するように動作する。

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

import numpy as np
from sklearn.dummy import DummyClassifier


def main():
    # ダミーの分類器 (デフォルトの strategy は 'stratified')
    clf = DummyClassifier()

    # 教師データの説明変数 (無視される)
    X_train = np.arange(6)
    # 教師データの目的変数
    y_train = np.array([0, 1, 2, 2, 1, 2])

    # モデルを教師データで学習する
    clf.fit(X_train, y_train)

    # 検証データの説明変数を元に推論する
    X_test = np.arange(6, 12)
    y_pred = clf.predict(X_test)
    y_pred_proba = clf.predict_proba(X_test)

    # 推論結果 (教師データのクラスの確率分布を元にランダム)
    print('Prediction:', y_pred)
    print('Prediction (probability):')
    print(y_pred_proba)

    # 各クラスの頻度
    print('Class probabilities:', clf.class_prior_)


if __name__ == '__main__':
    main()

上記を実行してみよう。 教師データとして与えた目的変数は 0 が 1 件、1 が 2 件、2 が 3 件だった。 得られる結果は毎回異なるものの、今回は 0 が 1 件、1 が 1 件、2 が 4 件と、わずかに異なっている。 また、predict_proba() メソッドから得られる確率は一つが 1.0 で残りが 0.0 となっている。

$ python dummy.py 
Prediction: [2 2 1 2 2 0]
Prediction (probability):
[[1. 0. 0.]
 [0. 0. 1.]
 [0. 0. 1.]
 [0. 0. 1.]
 [0. 0. 1.]
 [0. 0. 1.]]
Class probabilities: [0.16666667 0.33333333 0.5       ]

試しに、もっとたくさんの検証用データを予測させてみよう。 数が多くなれば、大数の法則で教師データの確率分布に近づくはずだ。

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

import numpy as np
from sklearn.dummy import DummyClassifier


def main():
    clf = DummyClassifier()

    X_train = np.arange(6)
    y_train = np.array([0, 1, 2, 2, 1, 2])

    clf.fit(X_train, y_train)

    # 大数の法則
    X_test = np.arange(6, 100000)
    y_pred = clf.predict(X_test)

    # 推論した結果の頻度を確認する
    values, count = np.unique(y_pred, return_counts=True)
    print('Prediction frequency', count / np.sum(count))

    # 教師データの頻度
    print('Class probabilities:', clf.class_prior_)


if __name__ == '__main__':
    main()

上記を実行してみる。 今度は理論値にかなり近い結果が得られた。

$ python dummy.py
Prediction frequency [0.16611997 0.33404004 0.49983999]
Class probabilities: [0.16666667 0.33333333 0.5       ]

最頻値を常に返す

DummyClassifier は、返す値を色々とカスタマイズできる。 例えば、最頻値を常に返したいときはインスタンス化するときの strategy オプションに 'most_frequent' を指定する。

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

import numpy as np
from sklearn.dummy import DummyClassifier


def main():
    clf = DummyClassifier(strategy='most_frequent')

    X_train = np.arange(6)
    y_train = np.array([0, 1, 2, 2, 1, 2])

    clf.fit(X_train, y_train)

    X_test = np.arange(6, 12)
    y_pred = clf.predict(X_test)
    y_pred_proba = clf.predict_proba(X_test)

    # 推論結果 (常に最も多いクラスを返す)
    print('Prediction:', y_pred)
    print('Prediction (probability):')
    print(y_pred_proba)


if __name__ == '__main__':
    main()

上記を実行してみよう。 たしかに、最頻値である 2 が常に返されるようになっている。

$ python dummy.py 
Prediction: [2 2 2 2 2 2]
Prediction (probability):
[[0. 0. 1.]
 [0. 0. 1.]
 [0. 0. 1.]
 [0. 0. 1.]
 [0. 0. 1.]
 [0. 0. 1.]]

最頻値を常に返す (確率については教師データに準拠する)

'most_frequent' に近いものの、確率の返し方が異なるのが 'prior' となる。 こちらでは predict_proba() メソッドの返す結果が、元の確率分布にもとづいたものになる。

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

import numpy as np
from sklearn.dummy import DummyClassifier


def main():
    clf = DummyClassifier(strategy='prior')

    X_train = np.arange(6)
    # 目的変数
    y_train = np.array([0, 1, 2, 2, 1, 2])

    clf.fit(X_train, y_train)

    X_test = np.arange(6, 12)
    y_pred = clf.predict(X_test)
    y_pred_proba = clf.predict_proba(X_test)

    # 推論結果 (常に最も多いクラスを返す)
    print('Prediction:', y_pred)
    print('Prediction (probability):')
    print(y_pred_proba)


if __name__ == '__main__':
    main()

上記を実行してみよう。 たしかに predict() メソッドが最頻値を返す点は変わらないものの、predict_proba() の結果が変わっている。

$ python dummy.py 
Prediction: [2 2 2 2 2 2]
Prediction (probability):
[[0.16666667 0.33333333 0.5       ]
 [0.16666667 0.33333333 0.5       ]
 [0.16666667 0.33333333 0.5       ]
 [0.16666667 0.33333333 0.5       ]
 [0.16666667 0.33333333 0.5       ]
 [0.16666667 0.33333333 0.5       ]]

指定した固定値を返す

任意の定数を返したいときは 'constant' を指定する。 以下では例として常に 1 を返している。

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

import numpy as np
from sklearn.dummy import DummyClassifier


def main():
    clf = DummyClassifier(strategy='constant', constant=1)

    X_train = np.arange(6)
    y_train = np.array([0, 1, 2, 2, 1, 2])

    clf.fit(X_train, y_train)

    X_test = np.arange(6, 12)
    y_pred = clf.predict(X_test)
    y_pred_proba = clf.predict_proba(X_test)

    # 推論結果 (ユーザが指定したクラスを常に返す)
    print('Prediction:', y_pred)
    print('Prediction (probability):')
    print(y_pred_proba)


if __name__ == '__main__':
    main()

上記を実行してみよう。 たしかに、常に 1 が返っている。

$ python dummy.py 
Prediction: [1 1 1 1 1 1]
Prediction (probability):
[[0. 1. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 1. 0.]
 [0. 1. 0.]]

ランダム (一様分布) に返す

また、目的変数の元の確率分布に依存せず、一様分布にしたいときは 'uniform' を指定する。

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

import numpy as np
from sklearn.dummy import DummyClassifier


def main():
    clf = DummyClassifier(strategy='uniform')

    X_train = np.arange(6)
    y_train = np.array([0, 1, 2, 2, 1, 2])

    clf.fit(X_train, y_train)

    X_test = np.arange(6, 12)
    y_pred = clf.predict(X_test)
    y_pred_proba = clf.predict_proba(X_test)

    # 推論結果 (クラスを一様分布として返す)
    print('Prediction:', y_pred)
    print('Prediction (probability):')
    print(y_pred_proba)


if __name__ == '__main__':
    main()

上記を実行してみよう。 predict() の結果は試行回数が少ないので偏ってしまっているが predict_proba() は全てのクラスが均等な値になっている。

$ python dummy.py 
Prediction: [2 2 2 2 0 0]
Prediction (probability):
[[0.33333333 0.33333333 0.33333333]
 [0.33333333 0.33333333 0.33333333]
 [0.33333333 0.33333333 0.33333333]
 [0.33333333 0.33333333 0.33333333]
 [0.33333333 0.33333333 0.33333333]
 [0.33333333 0.33333333 0.33333333]]

DummyRegressor

同様に回帰問題であれば DummyRegressor を用いる。

平均値を常に返す

DummyRegressor はデフォルトで目的変数の平均値を常に返すようになっている。

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

import numpy as np
from sklearn.dummy import DummyRegressor


def main():
    # ダミーの回帰 (デフォルトの strategy は 'mean' なので平均を返す)
    reg = DummyRegressor()

    # 教師データの説明変数 (無視される)
    X_train = np.arange(6)
    # 教師データの目的変数
    y_train = np.array([-1.0, 0.0, 1.0, 2.0, 4.0, 8.0])

    reg.fit(X_train, y_train)

    X_test = np.arange(6, 12)
    y_pred = reg.predict(X_test)

    # 推論した結果の頻度を確認する
    print('Prediction', y_pred)


if __name__ == '__main__':
    main()

上記を実行してみよう。

$ python dummy.py 
Prediction [2.33333333 2.33333333 2.33333333 2.33333333 2.33333333 2.33333333]

中央値を常に返す

DummyRegressor も返す値をカスタマイズできる。 例えば中央値を返したいのであれば 'median' を指定する。

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

import numpy as np
from sklearn.dummy import DummyRegressor


def main():
    # ダミーの回帰 (常に中央値を返す)
    reg = DummyRegressor(strategy='median')

    X_train = np.arange(6)
    y_train = np.array([-1.0, 0.0, 1.0, 2.0, 4.0, 8.0])

    reg.fit(X_train, y_train)

    X_test = np.arange(6, 12)
    y_pred = reg.predict(X_test)

    print('Prediction', y_pred)


if __name__ == '__main__':
    main()

上記を実行してみよう。 ちゃんと中央値を返している。

$ python dummy.py 
Prediction [1.5 1.5 1.5 1.5 1.5 1.5]

指定した値を常に返す

特定の値を返したいときは 'constant' を指定する。 以下では例として -999 を返している。

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

import numpy as np
from sklearn.dummy import DummyRegressor


def main():
    # ダミーの回帰 (常に指定した値を返す)
    reg = DummyRegressor(strategy='constant', constant=-999)

    X_train = np.arange(6)
    y_train = np.array([-1.0, 0.0, 1.0, 2.0, 4.0, 8.0])

    reg.fit(X_train, y_train)

    X_test = np.arange(6, 12)
    y_pred = reg.predict(X_test)

    print('Prediction', y_pred)


if __name__ == '__main__':
    main()

上記を実行してみよう。 ちゃんと指定した値を返している。

$ python dummy.py 
Prediction [-999 -999 -999 -999 -999 -999]

まとめ

機械学習において最低ラインの性能というのは使うデータセットと評価指標に依存する。 得られた結果がどれくらい良いものなのかを検討する上で、最低ラインの性能は確認しておきたいもの。 そんなとき Dummy{Classifier,Regressor} を使うと、既存のデータパイプラインのコードを流用して確認できるので便利そうだ。