Scikit-LearnやKerasやその他いろいろと最後にシグモイド関数を使って分類する事は2値分類(陽性か陰性)では多いです。
ですが、2値分類において大体の場合しきい値は0.5以上だと1(一般に陽性)で0.5未満だと0(一般に陰性)とすることが初期設定では多いです。
ただ、もちろんそれに合わせて回帰しているのでその考え方自体は間違っていないのですが、時として他の値をしきい値にすることでより汎化する事もあります。
そこで以下の指標を基に実際にロジスティック回帰のしきい値を変えて汎化能力を考察してみましょう。
- 正解率
- 再現率
陽性・陰性のデータが実際に陽性・陰性と判断された割合 - 適合率
陽性・陰性と判断されたデータが実際に陽性・陰性だった割合 - f1
再現率と適合率の調和平均
コーディング
ライブラリのインポート
ロジスティック回帰を分かりやすくするためここではstatsmodelsを使用します。
from sklearn.metrics import classification_report
import statsmodels.api as sm
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
データの読み込み
ここでは分かりやすく陰性と陽性を扱いたいのでScikit-Learnにあります乳がんのデータセットを使います。あらかじめCSVにしているのでそれを読み込みます(目的変数の名前が当時解析できなかったので「y」としています)。
df = pd.read_csv("breast_cancer.csv")
df.head()
目的変数と説明変数で分ける
ここではstatsmodelsを使用しているため何もしないと切片が出来ないため「add_constatnt」を行いcontantという説明変数を作り「1」で埋めます。
※なお、本当は自由に値を最後に入れてテスト(シミュレーション)をする場合は変数間の相関、つまり多重共線性を考慮しないといけないのでVIFを本当なら計算して因子を選択しましょう。
x = sm.add_constant(df.drop("y", axis=1))
y = df["y"]
モデルの生成と回帰
実際にロジスティック回帰を作成して説明変数をテストデータとして値を出力させます。
model = sm.Logit(y, x).fit_regularized()
y_pred_proba = model.predict(x)
y_pred_proba
するとこのように出力されます。
0 6.312334e-55
1 1.152860e-32
2 2.268552e-41
3 4.608393e-20
4 9.211627e-26
...
564 6.152497e-61
565 1.854253e-35
566 6.885977e-10
567 1.064712e-47
568 1.000000e+00
Length: 569, dtype: float64
しきい値をいじって判別
ここからが本題で、ロジスティック回帰におけるシグモイド関数のしきい値を0.5以外で設定した時にどのように精度が変動するかを見ていきます。
acc = []
rcl0 = []
rcl1 = []
pcs0 = []
pcs1 = []
f10 = []
f11 = []
x = []
for i in range(1, 10):
y_pred = np.where(y_pred_proba.values >= i/10, 1, 0)
rep = classification_report(y, y_pred, output_dict=True)
acc.append(rep["accuracy"])
rcl0.append(rep["0.0"]["recall"])
rcl1.append(rep["1.0"]["recall"])
pcs0.append(rep["0.0"]["precision"])
pcs1.append(rep["1.0"]["precision"])
f10.append(rep["0.0"]["f1-score"])
f11.append(rep["1.0"]["f1-score"])
x.append(i/10)
plt.plot(x, acc, marker="x", label="accuracy")
plt.plot(x, rcl0, marker="x", label="recall 0")
plt.plot(x, rcl1, marker="x", label="recall 1")
plt.plot(x, pcs0, marker="x", label="precision 0")
plt.plot(x, pcs1, marker="x", label="precision 1")
plt.plot(x, f10, marker="x", label="f1 0")
plt.plot(x, f11, marker="x", label="f1 1")
plt.legend()
plt.show()
これによって以下のような折れ線グラフが出力されます。
(横軸:しきい値、縦軸:精度)
これらを総合的に見るとしきい値はどの値が適切かを考察できます。
例えばこのグラフから陰性の適合率は値が大きくなるにつれて下がりますが、陽性の適合率は陰性の適合率ほどの変動はありません。
正解率やf1に着目すると0.3と0.4が適切になります。
その他にもいろいろ考察できますが、何が言いたいかというと正解率単体に着目していると予測のバランス(陰性と陽性の正誤の比率)が悪くなったり、陰性と陽性の識別はトレードオフだったり案外f1(F値)が再現率と適合率のバランスにおいてその辺役に立つなどがあります。
いずれにせよ、別に本来のしきい値の0.5でも問題ないんですけどね、本当は。ただ、一度こういう実験をしてみるのも大切かなと思った次第です。