0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

scipy.optimize.minimizeを使った最適化サンプル

Posted at

閾値の最適化

分類タスク(正例(1) or 負例(0)を予測するタスク)で、ロジスティック回帰モデルなど予測確率を出力するモデルを作成した際、ある閾値以上なら正例(1)を、以下ならば負例(0)として出力を行うことになるが、最適な閾値って??

ということで、scipy.optimize.minimizeを使ったサンプルを纏めておくことにする。
※個人備忘録の為、煩雑でスミマセン(涙)

import pandas as pd
import numpy as np
from sklearn.metrics import f1_score
from scipy.optimize import minimize

# ###################
# サンプルデータの生成
# ###################

# 乱数の初期値を設定 こうしておかないと実行のたびに異なる乱数を出力してしまう。
rand = np.random.RandomState(seed=71)
train_y_prob = np.linspace(0, 1, 10000) # 0~1まで等間隔に10000個の実数を取得

## 真の値(0 or 1)のサンプルデータ生成
#  一様分布から0~1の範囲で値を取得し、上で取得したtrain_y_probとの比較を行い 0 or 1 のデータを生成
train_y = pd.Series(rand.uniform(0, 1, train_y_prob.size) < train_y_prob)

## 予測確率とするサンプルデータの生成
train_pred_prob = np.clip(train_y_prob * np.exp(rand.standard_normal(train_y_prob.size)*0.3), 0, 1)

ちなみに、train_yはこんな感じです。

train_y

0       False
1       False
2       False
3       False
4       False
        ...  
9995     True
9996     True
9997     True
9998     True
9999     True
Length: 10000, dtype: bool

予測確率とみなすサンプルはこんな感じです。

train_pred_prob
array([0.00000000e+00, 1.10648939e-04, 2.29275798e-04, ...,
       9.05431241e-01, 5.25621437e-01, 8.81456054e-01])

まず、閾値を0.5とした場合のf1-scoreは・・・

# 閾値を0.5とすると・・・
init_threshold = 0.5
init_score = f1_score(train_y, train_pred_prob >= init_threshold)
print("閾値0.5の時のf1-score:{}".format(init_score.round(3)))

# >> 閾値0.5の時のf1-score:0.722

「0.722」 になります。

最適化関数

ここでは、f1-scoreが最も大きくなるような threshold(閾値)を探すということで、
「閾値、正解データ(フラグデータ)、予測確率データ」 の3つを引数として受け取り、
f1-scoreを算出し、符号を反転させたデータを返却する関数を作成します。
※最適化にminimize関数を使うので、return 時の符号を反転させています。

# 閾値の最適化関数
#  f1-scoreを元に最適化を行う
#  引数の順番は、最初に最適化される変数がくるようにする?
def f1_optimizer(threshold, train_y, train_pred_prob ):
    return -f1_score(train_y, train_pred_prob >= threshold)

最適化

スタートを0.5に設定 x0で指定
そのほか、train_yとtrain_pred_probは、argsでタプル指定
最適化アルゴリズムはmethodeで指定 ここでは、ネルダーミード法を指定

result = minimize(f1_optimizer, x0 = 0.5, args = (train_y, train_pred_prob), method = "Nelder-Mead")
print(result)

# >>  final_simplex: (array([[0.32324219],
# >>       [0.32319336]]), array([-0.75573177, -0.75573177]))
# >>           fun: -0.7557317703844165
# >>       message: 'Optimization terminated successfully.'
# >>          nfev: 31
# >>           nit: 14
# >>        status: 0
# >>      success: True
# >>             x: array([0.32324219])
print("最適な閾値:{}".format(result["x"].round(3)))
print("最適な閾値でのf1_score:{}".format(f1_score(train_y, train_pred_prob >= result["x"]).round(3)))

# >> 最適な閾値:[0.323]
# >> 最適な閾値でのf1_score:0.756

自分向けの備忘録のため、煩雑でスミマセン(涙)

おしまい

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?