[初級編]LLMへ至る道~評価指標ってなに?~[5日目]

2023.12.05

みなさんこんにちは!クルトンです。

前回までのブログでは、機械学習の中身としてニューラルネットワークというものがあり、数値計算を何度も繰り返して出力をするというものを確認してきました。

今回は評価指標について確認していきます。「機械学習モデルが良い感じの出力をするようになったので予測させてみたけれども、性能を数値で理解するには?」という、学習がひと段落した後のお話です。

どういう評価指標があるか確認してみよう

まずはどういうものがあるか、箇条書きで書いてみます。

  • 再現率(recall)
  • 適合率(precision)
  • f1値
  • ROC曲線
  • PR曲線

いっぱいあるように見えますが、それぞれ使い所が違います。 順番にそれぞれを理解していきましょう。

前提部分

評価指標は出力されている結果をある観点から分類して、それら分類結果の割合を求めることで確認できるものです。

まずはどのような観点で分類しているのか、下の図をご覧ください。

road-to-llm-advent-calendar-2023-05-01

上記の表は正と負を予測する機械学習モデルにおいて、予測値(推論結果)と実際のデータはどうだったか、というそれぞれに対して分類されているものです。

文章にすると以下のようになります。

  • 予測が正の場合
    • 実データが正のデータ(True Positive)
    • 実データが負のデータ(False Positive)
  • 予測が負の場合
    • 実データが負のデータ(True Negative)
    • 実データが正のデータ(False Negative)

正と負を予測する機械学習モデルと言われると分かりづらいかと思いますので、「画像を見て猫(正のデータ)かどうかを判定する」モデルだと捉えてください。 そうすると、上記の表は下のように書き換えられます。

road-to-llm-advent-calendar-2023-05-02

予想と実際の画像が当てはまっている場合は を、予想と実際の画像が違う場合は × を表では記載しています。

また、文章については以下のように書き換えられます。

  • 予測がの場合
    • 実データが猫画像(True Positive)
    • 実データがライオンの画像(False Positive)
  • 予測がライオンの画像の場合
    • 実データがライオンの画像(True Negative)
    • 実データが猫画像(False Negative)

上記のように分類したものを 混同行列 と呼びます。

つまり推論結果と、その結果に対して当たり外れがあるので合計4つに推論結果を分類できます。

ちなみにPythonコードで書くと以下のようになります。実行環境はGoogle Colab(ランタイムはCPU)です。

from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

X, y = make_classification(n_samples=2023, n_classes=2, random_state=42) # ダミーデータ作成(正と負の2種類の分類のためn_classes=2としている)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 学習用のデータとモデルの性能評価のためのデータに分ける

model = LogisticRegression() # 正と負の2種類で分類するのに使われるモデルを呼び出し
model.fit(X_train, y_train) # ダミーデータを使って学習

y_pred = model.predict(X_test) # 推論結果を取得

cm = confusion_matrix(y_test, y_pred) # 混同行列の計算

# 混同行列を見やすさのためにヒートマップで可視化
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=['cat', 'lion'], yticklabels=['cat', 'lion'])
plt.xlabel('Predicted Data')
plt.ylabel('True Data')
plt.title('Confusion Matrix Graph')
plt.show()

# # 正解率を計算して表示
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy:{accuracy}")

road-to-llm-advent-calendar-2023-05-03

再現率(recall)と適合率(precision)

再現率と適合率はトレードオフな関係ですので、一緒に確認していきます。

再現率(recall)

再現率というのは以下の数式で求められるものです。

再現率 = True Positive True Positive + False Negative

数式だけだと分かりづらいかもしれないので、これも動物の画像に変えて見てしまいましょう。

road-to-llm-advent-calendar-2023-05-04

白塗りの実際の画像に注目してください。どれも実際のデータとしては、猫画像(正のデータ)であるものです。 そして、分母には黒塗りの予測部分で合っているものと外れているものがありますよね。

何を表しているかというと、全ての猫画像(正のデータ)のうち、どれだけ予測が当てはまっているかという割合を確認しているものになります。

Pythonコードだと以下のようにすると簡単に出力が得られます。

from sklearn.metrics import recall_score
recall_score(y_test, y_pred) # 実際の値、予測した値の順番で値を渡す

ちなみに再現率の割合を高めるにはどうしたら良いでしょうか?極端な事を言えば、全ての画像を猫(正のデータ)であると予測した場合は、割合としては100%という値になります。

適合率(precision)

次に適合率を見ていきます。以下の数式で表されるものです。

適合率 = True Positive True Positive + False Positive

再現率と違うのは一箇所だけで、False Positiveと書かれている部分だけになります。

言葉だけだと分かりづらいかもしれないので、これも動物の画像に変えて見てしまいましょう。

road-to-llm-advent-calendar-2023-05-05

黒塗りの画像に注目してください。全て猫画像(正のデータ)と予測したものですよね。次に分母を見てみると、白塗りの実データは猫画像(正のデータ)とライオンの画像(負のデータ)があります。

つまり適合率というのは、予測した猫画像(正のデータ)のうち、どれだけ当たっているかというものになります。

Pythonコードだと以下のようにすると簡単に出力が得られます。

from sklearn.metrics import precision_score
precision_score(y_test, y_pred) # 実際の値、予測した値の順番で値を渡す

ちなみに再現率と同じく、適合率を100%にする方法があります。 それは予測を1つだけして、それが当てはまった場合です。もちろん1つだけでなく、3つや4つでもいいです。全ての予測が合っている場合なら100%です。

で、冒頭の再現率と適合率がトレードオフってどういうこと?

改めて整理して、それぞれを一言で表すと以下のようになります。

  • 再現率
    • 正の実データ(猫画像)のうち、どれだけ予測できているか
  • 適合率
    • 正のデータ(猫画像)と予測したもののうち、どれだけ正の実データ(猫画像)であったか

上記を見比べると、言葉の順番が違う事に気づきます。再現率は実データに注目しています。適合率は予測に注目しています。

そして、どちらも正確性を確認しています。

つまり、再現率は実データが予測でどれだけ拾えているか という事を表しており、適合率は予測がどれだけ実データと当てはまっているかという事を確認しています。

以上の事から、トレードオフであるというのは次のような事を言っています。

  • 再現率を高めたい場合は出来るだけ実データのうち予測したい対象(例えば正のデータ)であると予測すれば良いが、そうすると、予測の正確性(適合率)が下がってしまう
  • 予測の正確性(適合率)を高めたい場合は予測対象の中で確実に当てはまるものだけ予測すれば(例えば正のデータだと言えば)良いが、そうすると実データの一部しか拾えない事態を招くので再現率が下がる

どちらを重視するかによって、使い分けが発生しそうですよね。 実際その通りでどちらを重視するかは状況によって異なります。以下に一例を書いてみます。

  • 自社製品で出荷済みの商品で、人に悪影響を及ぼす可能性が確認できたので商品をお客様から回収したい
    • 少しでも疑わしい製品(実際の製品)を回収したいので、再現率(recall) を重視する
    • イメージ的には車の回収などでは実際にリコールと呼ばれていますよね。
  • 感染症で、検査キットで感染しているか正確に判断をしたい
    • 健康な人であるのに間違って感染したと判断したくない、つまり正確に感染しているかどうかを判断したいので、 適合率(precision) を重視する
    • コロナに感染しているかどうかを判断する時にも検査キットの正確性がどれだけであるか、など話題になりましたね。

状況によって使い分けたら良い指標なのですが、どちらも重視したいぞ!という時に使えるのが次に紹介するf1値です。

f1値

再現率と適合率それぞれはトレードオフの関係にあります。しかし両者をバランスよく評価する事で機械学習モデルの性能評価をしたい場合はf1値を使うと良いです。(f1値はF-Scoreや1の部分を無くしてf値と呼ばれる事もあります。)

f1値の式は以下のようなものです。

f1値 = 2 1 再現率 + 1 適合率 = 2 × 再現率 × 適合率 再現率 + 適合率

上記の計算式は調和平均というもので、小学生の時に実は皆さん習った事がある概念です。よくある算数の問題でいうと「家から塾へ向かうときは3km/hで10分、塾から家に帰るときは6km/hで5分で移動した場合に、平均速度はどれくらいであるか?」のような問題です。

簡単にいうと、再現率と適合率の平均をとっているものがf1値になります。

Pythonで簡単に計算できるようにしているライブラリがあります。実際の計算にはそちらを使う事も可能です。

from sklearn.metrics import f1_score
f1_score(y_test, y_pred) # 実際の値、予測した値の順番で値を渡す

他にもf0.5値(適合率重視)やf2値(再現率重視)というものがあります。よければ検索してみてください。

ROC曲線とPR曲線

ROC曲線とPR曲線というのは、それぞれ一定の基準を持って曲線を描き、曲線の下にある図形の面積を求めるものです。

曲線の下の図形のことをArea Under the Curveの略でAUCと言います。AUCは最大値を1とするもので、機械学習モデルの予測性能が良ければ良いほど1に近い値を表します。 図形の面積を求めるには積分が必要なのですが、Pythonコードだと簡単に計算できるメソッドが用意されているので今回は意識する必要はないです。

曲線の描き方としては、以下の手順です。

  1. データのそれぞれに対して正のデータである確率を算出します。
  2. 何%以上の確率であれば正のデータであるか決定します。これを閾値と言います。
  3. 決定した数値(閾値)以上のものを正のデータであると予測結果にして、実際のデータと比較して合っているか間違っているか判定します。これでTrue Positiveなどが計算可能になります。
  4. それぞれの曲線で使われている指標の定義式に合わせて計算します。
  5. 図にプロットしていきます。
  6. 2~5を何度か繰り返した後に線で点と点を繋げます。(線は曲線やジグザクしたものになりやすいです。)

road-to-llm-advent-calendar-2023-05-06

AUCを求める時と同様で、曲線を描く場合についてもPythonコードでは既に用意されているメソッドを呼び出せばすぐに実行できます。

では具体的にそれぞれ確認してみましょう。

ROC曲線について

まずはROC曲線についてです。

次のPythonコードでグラフを出力します。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

X, y = make_classification(n_samples=2023, n_classes=2, random_state=42) # ダミーデータ作成(正と負の2種類の分類のためn_classes=2としている)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 学習用のデータとモデルの性能評価のためのデータに分ける

model = LogisticRegression() # 正と負の2種類で分類するのに使われるモデルを呼び出し
model.fit(X_train, y_train) # ダミーデータを使って学習


# 予測確率を取得
y_scores = model.predict_proba(X_test)[:, 1]

# ROC曲線の計算
fpr, tpr, _ = roc_curve(y_test, y_scores)
roc_auc = auc(fpr, tpr)

# ROC曲線の描画
plt.figure(figsize=(8, 8))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.4f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc='lower right')
plt.grid(True)

plt.show()

road-to-llm-advent-calendar-2023-05-07

縦軸がTrue Positive Rateで、横軸がFalse Positive Rateです。 True Positive Rateと英語で書いていますが要は再現率です。

False Positive Rateは猫画像の判定の例でいうところの、猫画像と判定したものの実際はライオンの画像だったという間違いの割合を表しています。つまりFalse Positive Rateが大きいほど、予測性能が悪い事を表しています。

True Positive Rate(再現率)を100%まで高めるには全てのデータを正のデータ(猫画像)とすれば良いのですが、False Positive Rateも同時に確認する事で機械学習モデルの評価をしているグラフになります。

AUCが約90%の数値(図中のキャプションを確認)なので、まずまずの性能のようです。

補足事項として、ROC曲線は複数の機械学習モデルを同時に描く事で、視覚的にも機械学習モデル同士の性能差を比較する事が可能です。

ちなみにROCはReceiver Operator Characteristics Curveという英語の略称です。

PR曲線について

続いてPR曲線についてです。 次のPythonコードでグラフを出力します。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import precision_recall_curve, auc
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression


X, y = make_classification(n_samples=2023, n_classes=2, random_state=42) # ダミーデータ作成(正と負の2種類の分類のためn_classes=2としている)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 学習用のデータとモデルの性能評価のためのデータに分ける

model = LogisticRegression() # 正と負の2種類で分類するのに使われるモデルを呼び出し
model.fit(X_train, y_train) # ダミーデータを使って学習


# 予測確率を取得
y_scores = model.predict_proba(X_test)[:, 1]

# PR曲線の計算
precision, recall, _ = precision_recall_curve(y_test, y_scores)
pr_auc = auc(recall, precision)

# PR曲線の描画
plt.figure(figsize=(8, 8))
plt.plot(recall, precision, color='green', lw=2, label=f'PR curve (AUC = {pr_auc:.4f})')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall (PR) Curve')
plt.legend(loc='upper right')
plt.grid(True)

plt.show()

road-to-llm-advent-calendar-2023-05-08

縦軸がPrecision、つまり適合率です。横軸がRecall、つまり再現率のことです。 ちなみにPR曲線のPがPrecisionのことで、RがRecallのことです。

正と負のデータ数の割合のバランスが極端に異なるものを不均衡なデータと言いますが、不均衡なデータにおいてはROC曲線よりもPR曲線を使う方が一般的です。 なぜかというと負のデータが極端に少ない場合は、正のデータと予測して間違う場合が少ないためFalse Positive Rateが大きくなりづらい、つまりROC曲線では(PR曲線と比べると)正確な機械学習モデルの評価がしづらいためです。

ちなみにROC曲線同様にPR曲線についても、複数の機械学習モデルを同時に描く事で、視覚的にも機械学習モデル同士の性能差を比較する事が可能です。

終わりに

今回は学習が終わった後にするものとして、機械学習モデルの性能チェックするための評価指標についてご説明しました。

これで学習から推論までの基本的な流れについてはイメージで理解できるようになりましたでしょうか。

明日以降は、いよいよ日本語や英語など人の使う言葉をコンピュータが理解するためにやっている事についてご説明していきます!

本日はここまで。明日もよければご覧ください!

参考にしたサイト