テキトーに予測したベースラインを作成するDummyRegressorとDummyRegressorを試す
DummyClassifier/DummyRegressorとは
過去記事(3. ベースラインを定める)にも書いたが、機械学習モデルを作成した際の評価は何かしらのベースラインと比較しなければその値が良いのか悪いのかわからない。
単純な比較対象として、「テキトーに予測した」結果と比較することが往々にしてある。
この「テキトー」は、分類モデルの場合はTrainデータのラベル比率をもとに確率でラベルを決めたり、常に最頻値を出力することが多い。
イメージとしては、0/1の2値分類において、「頑張って作った機械学習モデル vs 90%は0なので90%の確率で0って言うだけのモデル」で後者と対して精度が変わらないなら機械学習モデル使う意味無いよね?みたいなイメージ。 どんだけ精度が悪くてもこれよりは高くないと意味ねぇ 、っていう意味での「ベースライン」 。
なお、このあたりの「テキトーなベースライン」の意義の詳細は以下の本のどこかに書いていた気がする。
scikit-learnとTensorFlowによる実践機械学習
- 作者:Aurélien Géron
- 発売日: 2018/04/26
- メディア: 単行本(ソフトカバー)
この「テキトーなベースライン」は、sklearn.dummy
のDummyClassifier
とDummyRegressor
で簡単に作ることができる。
DummyClassifier
DummyClassifier
は分類モデル用のベースラインを作成する。
引数strategy
を変えることで、Trainで学習したラベルをもとに
stratified
: ラベルの比率(確率分布)をもとに予測(0, 1, 2が 3:5:1の比率で存在する場合、50%の確率で0が予測値として出力most_frequent
: 最頻値を常に予測値として出力prior
: 予測値はmost_frequent
と同じだが、予測確率がラベルの比率と同じ結果になる(most_frequent
は常に0か1)。uniform
: 完全にランダムに予測した値を返す(一様乱数)。例えば2値分類なら各ラベルになる確率はどれも50%だし、4値分類ならそれぞれ25%。constant
: 指定した値を予測値として常に返す
実践
データとしてはiris
データを使う。
import pandas as pd from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.svm import SVC from sklearn.metrics import accuracy_score import random # データ準備 iris = datasets.load_iris() X = pd.DataFrame(iris.data, columns=iris.feature_names) y = iris.target_names[iris.target] # train, testを分ける X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, train_size = 0.8, shuffle = True) X.head()
このデータに対して学習と予測をおこない、評価したところ精度0.93
のモデルなようだ。
# SVMでTrain clf_svm = SVC(probability=True) clf_svm.fit(X_train, y_train) # 予測および評価 y_pred = clf_svm.predict(X_test) accuracy_score(y_test, y_pred) # => 0.9333333333333333
次に、本題のDummyClassifier
を使った「テキトーなベースライン」を作成する。
作り方は、他のsklearn
モデルと同様の流れでおこなうことができる。
なお、形式上fit
で学習データX, yを渡しているが前述のように、確率分布などに沿った値を返すだけなのでラベルyの中身は使っているが、特徴量Xは使われていない。
# DummyClassifierで各クラス割合(確率分布)に伴う予測結果を返す clf_dummy = DummyClassifier(strategy = 'stratified') # デフォルト # モデル学習 # X_trainを渡しているが、中身を使ってない clf_dummy.fit(X_train, y_train) # 各クラス割合に伴って予測 y_pred_dummy = clf_dummy.predict(X_test)
このときのy_train
から読み取った各ラベルの比率はsatose : versicolor : virginica = 0.325 : 0.35833333 : 0.31666667
となっている。そのため、strategy = 'stratified
より、この確率に沿って予測がおこなわれている。
なお、class_prior_
を使うとそのモデルでの確率分布を見ることができる。
# y_train自体から確認 # Train用のyのラベルは以下の比率 values, count = np.unique(y_train, return_counts=True) ratio = count / np.sum(count) display(values, ratio) # => array(['setosa', 'versicolor', 'virginica'], dtype='<U10') # => array([0.325 , 0.35833333, 0.31666667]) # fit済モデルから確認 clf_dummy.class_prior_ # => array([0.325 , 0.35833333, 0.31666667])
ではここで、先程clf_dummy
で予測された結果y_pred
を見ると、以下のような出力比となっている*1 。
# 予測されたyのラベルは以下の比率 values, count = np.unique(y_pred_dummy, return_counts=True) ratio = count / np.sum(count) display(values, ratio) # => array(['setosa', 'versicolor', 'virginica'], dtype='<U10') # => array([0.16666667, 0.46666667, 0.36666667])
これは、先程見たy_train
のラベル比率と違うように見えるが120インスタンスの予測結果なので、インスタンス数を増やすと大数の法則より実際の比率に近くなることから、確率分布だということがわかる。
# yを100000つ生成したいので、テキトーにXを100000つ生成 # 大数の法則より y_trainの分布に近づく val_100000 = [[random.uniform(0, 5)] * 4 for i in range(100000)] X_dummy = pd.DataFrame(val_100000, columns=iris.feature_names) y_pred_dummy = clf_dummy.predict(X_dummy) # 予測されたyのラベルは以下の比率 values, count = np.unique(y_pred_dummy, return_counts=True) ratio = count / np.sum(count) display(values, ratio) # => array(['setosa', 'versicolor', 'virginica'], dtype='<U10') # => array([0.32518, 0.35839, 0.31643])
前述のように予測結果は確率で出力されるゆえに、実際に使用する際は1回だけだと偏りが出ることもあるので複数回おこなった平均スコアを出してベースラインとする。
# 確率なので安定化させるために10000回施行をおこなった平均を使う scores = [] for _ in range(10000): # DummyClassifierで各クラス割合(確率分布)に伴う予測 # satose : versicolor : virginica = 0.325 : 0.358 : 0.317 clf_dummy = DummyClassifier() # モデル学習 # X_trainは中身を使ってない clf_dummy.fit(X_train, y_train) # 各クラス割合に伴って予測 y_pred_dummy = clf_dummy.predict(X_test) scores.append(accuracy_score(y_test, y_pred_dummy)) np.mean(scores) # => 0.32887999999999995
このとき、比率に応じてテキトーに出力したモデルの精度は0.33
ほどとなる。このことからSVMモデルの0.933
は使うに値するモデルだということがわかる。
なお、最頻値の場合の精度は0.233
となった。
# 確率なので安定化させるために10000回施行をおこなった平均を使う scores = [] for _ in range(10000): # DummyClassifierで最頻値を予測として返す。以下より、常に`versicolor`として予測。 # satose : versicolor : virginica = 0.325 : 0.358 : 0.317 clf_dummy = DummyClassifier(strategy='prior') # モデル学習 # X_trainは中身を使ってない clf_dummy.fit(X_train, y_train) # 各クラス割合に伴って予測 y_pred_dummy = clf_dummy.predict(X_test) scores.append(accuracy_score(y_test, y_pred_dummy)) np.mean(scores) # 0.23333333333333334
DummyRegressor
次に、連続値予測モデルについて同様にベースラインを作れるDummyRegressor
を考える。
こちらもstrategy
を変えることで、Trainしたy(train_y)に応じた以下を出力することができる。
mean
: 常にtrain_yの平均値を出力median
: 常にtrain_yの中央値を出力quantile
: 常にtrain_yの任意の分位点を出力constant
: 常に任意の値を出力
DummyRegressor
のように確率をもとにせず、常に同じ値を出力するのでloopを回してscore
の安定をはかる必要はない。
実践
データとしてボストン住宅価格を用いる。
from sklearn.datasets import load_boston dsn = load_boston() X = pd.DataFrame(dsn.data, columns=dsn.feature_names) y = pd.DataFrame(dsn.target, columns=['target']) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, train_size = 0.8, shuffle = True) np.mean(y_train.values) # => 22.98193069306931
y_train
の平均値は22.98
となっている。
回帰モデルを用いたscore結果(決定係数)は0.63
になった。
from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error model_ols = LinearRegression() model_ols.fit(X_train, y_train) y_pred = model_ols.predict(X_test) # RMSE np.sqrt(mean_squared_error(y_test, y_pred)) # => 4.786190310043946
次にDummyRegressor
を用いる。このとき、y_train
の平均値は22.98
なので常に22.98
を予測値として返すモデルを評価することになる。
from sklearn.dummy import DummyRegressor model_dummy = DummyRegressor(strategy='mean') model_dummy.fit(X_train, y_train) y_pred = model_dummy.predict(X_test) # RMSE np.sqrt(mean_squared_error(y_test, y_pred)) # => 8.179532051041386
ここから、LinearRegression
の場合はRMSEが4.79
、テキトーに平均値を返すモデルは8.18
なのでLinearRegression
は半分近く誤差が少ないモデルなので使用する価値はある。
ちなみに、DummyRegressorではちゃんと予測結果は常に22.98
となっている
参考
*1:確率分布なので人によって値は違う