SciPy minimize を使ったモデルのしきい値最適化
Posted on 2019/10/13 in 機械学習 , Updated on: 2019/10/13
はじめに¶
2値分類問題において、正負のラベルを提出する評価指標では、モデルで予測確率を出力し、あるしきい値以上の値を正例として出力する必要がある。このしきい値を求める方法として、scipy.optimize
の minimize
関数を利用して最適なしきい値を求めることができる。
参考 : Kaggleで勝つデータ分析の技術
本記事では、上記書籍内で紹介されている手法を図を交えて、理解することを目的とする。
import numpy as np
import pandas as pd
# 描画用ライブラリのインポート
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('darkgrid')
%matplotlib inline
サンプルデータ生成¶
まず、サンプルデータを作成する。サンプルの数は 400個で、各サンプルの正負ラベル(1 or 0)は true_y
として与えられていたとする。また、それらのサンプルの正である予測確率を pred_y
と計算できたとする。
# 乱数シード固定
rand = np.random.RandomState(seed=1)
# 0 ~ 1で等間隔に 400分割 (データ作成用)
prob_y = np.linspace(0, 1, 400)
# true_y は、0~1で一用乱数を400個生成し、それぞれが prob_y より
# 小さいものを負例(= 0)、大きいものを正例(= 1)
true_y = pd.Series(rand.uniform(0.0, 1.0, prob_y.size) < prob_y)
# exponential 関数をノイズとして prob_y に載せて、0 ~ 1の範囲でクリッピング
pred_y = np.clip(prob_y * np.exp(rand.standard_normal(prob_y.shape) * 0.3), 0.0, 1.0)
サンプルデータの可視化¶
まず、ture_y
を描画する。ある特徴量1(x軸)に対して、正負 (0 or 1) をプロットしている。
plt.scatter(prob_y, true_y, edgecolors='green', c='yellow', s=30)
plt.ylabel('True labels')
plt.xlabel('Feature1')
plt.show()
つぎに、モデルにて出力したとする pred_y
を可視化する。x軸に置いている特徴量1が大きくなるにつれて、このモデルは、正例である確率が高いと計算していることがわかる。これらのプロットは単なる確率であり、分類問題では、正負を分類する必要がある。ここで、例としてこの分類のしきい値を単純に、0.5 としたとする。(赤点線)
plt.scatter(prob_y, pred_y, edgecolors='blue', c='yellow', s=20)
plt.hlines(xmin=0, xmax=1, y=0.5, linestyles='--', colors=['red'],
label='init threshold')
plt.ylabel('Predicted labels probability')
plt.xlabel('Feature 1')
plt.legend(facecolor='white')
plt.show()
この分類問題においての評価指標は、F1スコアであったとすると、このF1スコアを最大化するしきい値を求めることが必要になる。適当に選択したしきい値 0.5 の時の F1スコアを求めると、下記のように 0.68 という値だった。
from sklearn.metrics import f1_score
# 設定しきい値
init_threshold = 0.5
# しきい値以上の時のサンプルを正として予測した結果と、真の値で F1_score を算出
init_score = f1_score(true_y, pred_y >= init_threshold)
print('When threshold is {}, f1_score is {}.'.format(init_threshold,
round(init_score,2)))
最適しきい値の探索¶
scipy.optimize
の minimize
メソッドを利用して最適なしきい値を求める。まず、minimize
によって最小化したい目的関数を設定する。ここでは、最大化したい F1_score に -1 を乗算した関数を設定する。
# 本来求めたい f1_score を負の値として返す、最適化の目的関数を設定。
def f1_opt(x):
return -f1_score(true_y, pred_y >= x)
次に、minimize
メソッドを定義する。第一引数として、最小化したい関数、第二引数として、探索を開始する値を渡し、第三引数に使用するアルゴリズムを渡す。ここでは、勾配情報を使用しない(目的関数が微分可能でなくてもよい)Nelder-Meadを使用する。
from scipy.optimize import minimize
result = minimize(f1_opt, x0=np.array([0.5]), method='Nelder-Mead')
print(result)
上記の結果より、しきい値(x)は、0.284375 を選択すると、F1_score が最適化できることがわかった。このしきい値を使って、F1_score を算出してみる。
best_threshold = result['x'].item()
best_score = f1_score(train_y, train_pred_prob >= best_threshold)
print('When threshold is {}, f1_score is {}.'.format(round(best_threshold,2), round(best_score,2)))
しきい値0.5 の時の F1スコアは 0.68 だったのに対し、最適化したしきい値でのスコアは 0.76 と確かに改善していることがわかる。最後に、最適なしきい値をグラフに描画する。
plt.scatter(prob_y, pred_y, edgecolors='blue', c='yellow', s=20)
plt.hlines(xmin=0, xmax=1, y=0.5, linestyles='--', colors=['red'],
label='init threshold : 0.5', linewidth=2)
plt.hlines(xmin=0, xmax=1, y=best_threshold, linestyles='-',
colors=['green'], label='best threshold : 0.28', linewidth=2)
plt.ylabel('Predicted labels probability')
plt.xlabel('Samples')
plt.legend(facecolor='white')
plt.show()