【機械学習の初学者向け】F1スコアについて理解しよう!
はじめに
機械学習モデルの良し悪しを評価する指標にF1スコアがあります。
機械学習を勉強して初めて知る人も多いと思いますが、
初学者にとっては、分かりにくい指標ではないかと思います。
私自身も初学者の領域ですが、
F1スコアについては、よく分かっていませんでした。
このたび、金融データ活用推進協会が主催する
機械学習コンペ「第2回金融データ活用チャレンジ」に参加したところ、
評価指標が「Mean F1スコア」とのこと!
F1スコアすらも分からないのに、そこにMeanが加わるとは!!
これは学びの良い機会だと考え、
F1スコアについて初歩的なところから学び直しました。
この記事では、
「第2回金融データ活用チャレンジ」での予測データや自分が試したことを踏まえながら
・F1スコアがどういうものか、
・MeanF1スコアとはなにモノか、
・スコアアップを図る方法、など
初学者がF1スコアを理解できるよう、分かりやすく解説していきます。
◾️金融データ活用チャレンジの概要はこちら↓
★まずは、混同行列を理解する
F1スコアを理解する前に
欠かせないのが混同行列です。
まずは、これを正しく理解しましょう。
混同行列とは、
モデルの予測値が陽性か陰性か
実際の値が陽性か陰性か
を表にしたものです。
これらの組み合わせには
以下の❶〜❹の4パターンがあります。
つぎからは、混同行列を使って
いくつかの指標について見ていきましょう。
★正解率について理解する
もっともシンプルな正解率について考えます。
具体的な数字を入れて見ていきましょう。
正解率とは、以下の式で表されます。
正しく予測した数値を全体の数値で割ったものになります。
分かりやすい指標ですね。
具体的な数値に当てはめると、
正解率=(5+100)÷(5+10+10+100)=0.84
正解率84%、まあまあ高い数値です。
素人感覚ですと、
「モデルの精度として十分じゃね?」となりますが、
そうはなりません。
なぜでしょうか?
このデータは、
実際の陽性の数が15、陰性の数が110と
陰性に偏った不均衡データになります。
125のデータ全てについて、
モデルが陰性と予測した場合でも
正解率を算出すると、
110÷125=88%となってしまいます。
つまり、陽性を正しく予測しなくても
高い正解率が出てしまうのです。
このように、
陽性と陰性の数が不均衡な場合においては、
正解率だけ見ていると
モデルの精度を見誤ります。
そこで大事になってくるのが
これから説明する適合率、再現率になります。
★次に、適合率と再現率を理解する
適合率と再現率は
混同行列の数値を使って計算できます。
どこの行と列を計算しているのか?というと
以下の通りになります。
◾️適合率 Precision
モデルが陽性と判断し、実際に陽性だった割合をいいます。
数式で表すと以下の通りになります。
◾️再現率 Recall
実際に陽性だったもののうち、モデルが陽性と判断した割合をいいます。
数式で表すと以下の通りになります。
◾️適合率が重視される時
スパムメールの検出モデルを想像してみてください。
これは❸の方を減らしたいですよね
実際は重要なメール(陰性)なのに、スパムメール(陽性)として検出され読めなくなる場合、業務への影響は大きいでしょう。
逆にこのスパムメールが見逃され手元に届いたとしても、開封しなければいいだけですから、業務への影響は大きくありません。
適合率は誤検出をなくしたい時に重視されます
◾️再現率が重視される時
コロナ検査における陽性・陰性の判定を想像してみてください。
この場合は、❹の方を減らしたいですよね。
実際は陽性の患者を陰性として判定してしまう方が、患者本人にとっても、社会的にもリスクが高いからです。
再現率は見逃しをなくしたい時に重視されます。
ちなみに、適合率と再現率はトレードオフの関係にあり、
どちらかを高めようとすると、
どちらかが低くなります。
★そして、F1スコアを理解する
さて、ここからが本丸のF1スコアになります。
F1スコアは、
適合率と再現率のバランスを重視した指標になります。
具体的には、
適合率と再現率の調和平均がF1スコアとなります。
◾️具体的に計算してみよう!
以下は、第2回金融データ活用チャレンジで、
実際に自分の作ったモデルが算出した
混同行列になります。
なお、混同行列を描画するPythonコードは以下の通りです
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(df["MIS_Status"], df["pred"] >=0.5)
plt.figure(figsize=(5, 5))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues")
plt.title(f'Confusion Matrix')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()
◾️陽性1に対するF1スコア
多数クラスの陽性1に対してF1を計算してみましょう。
適合率は、37257/(37257+3513)=91.4%
再現率は、37257/(37257+510)=98.6%
F1スコアは、(2*0.914*0.986)/(0.914+0.986)=94.8%
と、かなり高い数値になりました。
めでたし、めでたし….
とはならなかったのです。
★Mean F1スコアとはなんぞや
金融データ活用チャレンジの評価指標は
単純なF1スコアではなく、
MeanF1スコアと呼ばれるものでした。
(別名、マクロF1スコア)
計算式はこうです
さきほどのF1スコアは、
陽性1(債務を完済した者)というクラスに対してものでした。
しかし、クラスには、
陰性0(債務不履行になった者)というクラスも存在します。
金融のタスクを考えるうえでは、1より0の方が重要そうですよね。
MeanF1スコアは、
こうしたクラスごとのF1スコアを計算し、
それらを平均せよというものでした。
MeanF1スコアを求めるPythonコードは下の通りになります
from sklearn.metrics import f1_score
score = f1_score(df["MIS_Status"], df["pred"] >= 0.5, average = "macro")
◾️陰性0に対するF1スコア
さきほどの混同行列に基づき、
クラス0についてのF1スコアを計算してみましょう。
適合率は、1027/(1027+510)=66.8%
再現率は、1027/(1027+3513)=22.6%
F1スコアは、(2*0.668*0.226)/(0.668+0.226)=33.8%
となり、めちゃくちゃ低い数値になりました。
クラス0については、特にRecallがうまく計算できていないことが
よく分かりますね。
◾️Mean F1スコア
クラス1とクラス0のF1スコアから
MeanF1スコアを求めると。。。
0.643となりました。
クラス1のF1スコアが高くても、
クラス0のF1スコアが低いとこうなりますよね。
今回のような不均衡データの場合は、
特に数が少ないクラスのF1についても、
よく見ていく必要がありそうです。
★閾値を理解しスコアアップを図る
機械学習のモデルは、陽性である確率を計算します。
確率は0〜1の値をとる連続値です。
F1スコアが評価指標になっている場合には、
その連続値である確率を
陽性1か陰性0かの二値に分けてやる必要があります。
そして、、、
どの確率以上を陽性=1とし、
どの確率未満を陰性=0とするかの分かれ目は、
人間が決める必要があります。
この陽性と陰性の分かれ目となる基準を閾値(Threshold)と呼びます。
一般的に、閾値は0.5とすることが多いです。
先ほどのモデルの予測も、
実は、閾値0.5として計算したものでした。
F1スコアの場合、
閾値の違いでスコアが大きく動くことがありますので、
ちゃんと考える必要があります。
◾️閾値を変えることで何が変わる?
以下は、実際にモデルが計算した
予測値の確率分布になります。
閾値を0.5とするか、0.6とするかで
陽性1の予測数が変わることがわかりますね。
一般的には0.5を閾値とすることが多いですが、
0.5を閾値とすることが必ずしもベストではありません。
陽性クラスの場合、
閾値を0にすれば、
全て陽性判定となるため、再現率は100%になりますが、
閾値が0.5だった場合に比べて、
適合率は下がります。
逆に閾値を1にすれば、
全て陰性判定となるため、陽性クラスの再現率と適合率は
0%になります。
陰性クラスの場合、逆のことが起きますね。
それでは最適な閾値はどうやって求めたら良いのでしょう?
◾️最適な閾値を求めよう!
閾値を細かく動かすことで、
MeanF1スコアが最大となる閾値を
解析的に求めるのが手っ取り早いです。
具体的には、
閾値を0→1に向かって0.001ずつ上げ、
閾値ごとのMeanF1スコアを計算して、
どの閾値の時にMeanF1スコアが最大になるかを計算します。
こういうことはPythonが得意です。
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import f1_score
# 実際の値と予測値を設定
y_true = df['MIS_Status']
y_scores = df['pred']
# 閾値のリストを生成
thresholds = np.arange(0, 1.001, 0.001)
# 各閾値でのF1スコアを計算
f1_scores = []
for v in thresholds:
score = f1_score(y_true, y_scores >= v, average='macro')
f1_scores.append(score)
# F1スコアが最大の場合の閾値を見つける
best_score = max(f1_scores)
best_v = thresholds[f1_scores.index(best_score)]
# F1スコアを閾値に対してプロット
plt.figure(figsize=(8, 6))
plt.plot(thresholds, f1_scores, label='Mean F1 Score')
plt.xlabel('Threshold')
plt.ylabel('Mean F1 Score')
plt.title('Mean F1 Score by Threshold')
plt.axvline(x=best_v, color='red', linestyle='--', label=f'Best Threshold = {best_v}')
plt.legend()
plt.grid(True)
plt.show()
print(f"Best Mean F1 Score: {best_score}, Best Threshold: {best_v}")
MeanF1スコアが
閾値0.5の場合、0.643でしたが、
閾値を調整した場合、0.677まで上がりました!!
これを可視化すると以下のグラフの通りになります
このグラフからも、閾値を調整すると
MeanF1スコアが変わることがわかると思います。
さいごに
機械学習コンペにおいては、
与えられた評価指標の性質を理解し、
評価指標に合わせてモデルの予測値を最適化していくことが
重要だと思います。
この記事を通して、
機械学習初学者の方のF1スコアへの理解が
少しでも深まればと思います。
私自身、昨年開催された第1回金融データ活用チャレンジに続いての
2回目の参加でしたが、今回も大いに勉強になり、
機械学習のスキルが格段にアップしたことを実感しています。
来年もぜひ参加したいです!!
ちなみに、第1回の参加感想は↓
よろしければサポートお願いします! いただいたサポートはクリエイターとしての活動費に使わせていただきます!