AIって結局何なのかよく分からないので、とりあえず100日間勉強してみた Day36
経緯についてはこちらをご参照ください。
■本日の進捗
●適合率-再現率カーブを理解
■はじめに
引き続き「Pythonではじめる機械学習(オライリー・ジャパン)」で学んでいきます。
前回学んだ適合率と再現率を用いて不確実性を考慮に入れた閾値の決定を学んでいきたいと思います。
■不確実性を考慮したより良いモデルを作る
まずは前回作った疑似医療スクリーニング用データセットを正規化してロジスティック回帰に学習させたモデルを振り返ります。
import numpy as np import pandas as pd 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.preprocessing import StandardScaler from sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay, classification_report n_samples = 500 n_features = 30 X, y = make_classification(n_samples=n_samples, n_features=n_features, n_informative=5, n_redundant=10, n_repeated=5, n_clusters_per_class=1, class_sep=2, flip_y=0.1, random_state=8) feature_names = [f"Feature{i}" for i in range(1, n_features+1)] df = pd.DataFrame(X, columns=feature_names) df['target'] = y X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=8) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) model = LogisticRegression(random_state=8) model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled) accuracy = accuracy_score(y_test, y_pred) print('Accuracy: {:.2f}'.format(accuracy)) report = classification_report(y_test, y_pred, target_names=['Class 0', 'Class 1']) print(report)
Accuracy: 0.98
precision recall f1-score support
Class 0 0.99 0.97 0.98 80
Class 1 0.97 0.99 0.98 70
accuracy 0.98 150
macro avg 0.98 0.98 0.98 150
weighted avg 0.98 0.98 0.98 150
まずは公平性のために交差検証を適用してみます。
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import make_classification from sklearn.model_selection import cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report n_samples = 500 n_features = 30 X, y = make_classification(n_samples=n_samples, n_features=n_features, n_informative=5, n_redundant=10, n_repeated=5, n_clusters_per_class=1, class_sep=2, flip_y=0.1, random_state=8) feature_names = [f"Feature{i}" for i in range(1, n_features+1)] df = pd.DataFrame(X, columns=feature_names) df['target'] = y scaler = StandardScaler() X_scaled = scaler.fit_transform(X) model = LogisticRegression(random_state=8) scores = cross_val_score(model, X_scaled, y, cv=5, scoring='accuracy') print('mean cv score: {:.2f}'.format(np.mean(scores))) model.fit(X_scaled, y) y_pred = model.predict(X_scaled) report = classification_report(y, y_pred, target_names=['Class 0', 'Class 1']) print(report)
mean cv score: 0.95
precision recall f1-score support
Class 0 0.95 0.96 0.96 248
Class 1 0.96 0.95 0.96 252
accuracy 0.96 500
macro avg 0.96 0.96 0.96 500
weighted avg 0.96 0.96 0.96 500
これが現時点のこのモデルの汎化性能と言えます。
今このモデルに必要なのはAccuracyではなく陽性の再現率(Recall)なので、predict_probaを用いて陰性の再現率を犠牲に陽性の再現率を向上させてみます。
import numpy as np import pandas as pd from sklearn.datasets import make_classification from sklearn.model_selection import cross_val_predict from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report n_samples = 500 n_features = 30 X, y = make_classification(n_samples=n_samples, n_features=n_features, n_informative=5, n_redundant=10, n_repeated=5, n_clusters_per_class=1, class_sep=2, flip_y=0.1, random_state=8) feature_names = [f"Feature{i}" for i in range(1, n_features+1)] df = pd.DataFrame(X, columns=feature_names) df['target'] = y scaler = StandardScaler() X_scaled = scaler.fit_transform(X) model = LogisticRegression(random_state=8) y_prob = cross_val_predict(model, X_scaled, y, cv=5, method='predict_proba') threshold = 0.05 y_pred_custom = (y_prob[:, 1] >= threshold).astype(int) report = classification_report(y, y_pred_custom, target_names=['Class 0', 'Class 1']) print(report)
precision recall f1-score support
Class 0 0.95 0.58 0.72 248
Class 1 0.70 0.97 0.81 252
accuracy 0.77 500
macro avg 0.82 0.77 0.76 500
weighted avg 0.82 0.77 0.76 500
■適合率-再現率カーブ
適合率-再現率カーブ(Precision-Recall Curve)とは、分類モデルにおける適合率と再現率のバランスを可視化するためのものです。
先程のモデルは陽性の再現率を2%上げるのに陰性の再現率を38%犠牲にしました。アプリケーションによってはこの2%がクリティカルな場合もあると思うので、一概に悪いことではないのですが、やはり効率が良いとは言えないように思えます。
このモデルで一番おいしいスイートスポットはどこなのでしょうか。また、陽性再現率を上げるためにどこまで妥協できるでしょうか。こういった調整を行うのに良い指標となる表現方法が適合率-再現率カーブで、precision_recall_curveクラスで簡単に実装することが可能です。
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import make_classification from sklearn.model_selection import cross_val_predict from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report, precision_recall_curve, average_precision_score n_samples = 500 n_features = 30 X, y = make_classification(n_samples=n_samples, n_features=n_features, n_informative=5, n_redundant=10, n_repeated=5, n_clusters_per_class=1, class_sep=2, flip_y=0.1, random_state=8) feature_names = [f"Feature{i}" for i in range(1, n_features+1)] df = pd.DataFrame(X, columns=feature_names) df['target'] = y scaler = StandardScaler() X_scaled = scaler.fit_transform(X) model = LogisticRegression(random_state=8) y_prob = cross_val_predict(model, X_scaled, y, cv=5, method='predict_proba') threshold = 0.05 y_pred_custom = (y_prob[:, 1] >= threshold).astype(int) report = classification_report(y, y_pred_custom, target_names=['Class 0', 'Class 1']) print(report) precision, recall, _ = precision_recall_curve(y, y_prob[:, 1]) average_precision = average_precision_score(y, y_prob[:, 1]) plt.figure(figsize=(8, 6)) plt.plot(precision, recall, marker='.', label='Precision-Recall curve') plt.title('Precision-Recall Curve') plt.xlabel('Precision') plt.ylabel('Recall') plt.grid() plt.legend() plt.show()
再現率を1.0まで上げると適合率が0.5まで下がり、再現率0.96辺りが最もバランスが良いということが分かります。
■おわりに
あくまで訓練データの中での話ではありますが、再現率を1.0にするのに適合率も0.5は保っていることは、すべての入力データを陽性と返すモデルと比べれば遥かに有用で、スクリーニングのみに用いるのであれば仕事を大幅に減らせるとも考えられます。
必要なf-値は目的に沿って決める必要がありますが、適合率-再現率カーブは良い指標になりそうです。
■参考文献
- Andreas C. Muller, Sarah Guido. Pythonではじめる機械学習. 中田 秀基 訳. オライリー・ジャパン. 2017. 392p.
- ChatGPT. 4o mini. OpenAI. 2024. https://chatgpt.com/
- API Reference. scikit-learn.org. https://scikit-learn.org/stable/api/index.html