Калибровка является важным шагом во многих приложениях машинного обучения, чтобы гарантировать, что предсказанные вероятности классификатора точно отражают истинную вероятность каждого класса. На практике некоторые классификаторы, такие как машины опорных векторов и деревья решений, дают плохо откалиброванные оценки вероятности. Эти неверно откалиброванные вероятности могут отрицательно сказаться на производительности систем принятия решений, приводя к субоптимальным результатам, особенно в задачах, которые основаны на точных оценках вероятности, таких как оценка риска, медицинская диагностика и прогнозирование погоды. Применяя методы калибровки, такие как шкала Платта, изотоническая регрессия или шкала температуры, мы можем настроить выходные вероятности классификатора, чтобы они лучше соответствовали истинным вероятностям класса. В конечном итоге калибровка повышает интерпретируемость и надежность прогнозов модели, позволяя принимать более обоснованные и эффективные решения.
Применить калибровку после тренировки
Калибровка часто применяется после обучения модели, чтобы улучшить ее оценки вероятности. Вот пример проблемы мультиклассовой классификации с использованием набора данных Iris, который применяет калибровку в конце с использованием класса CalibratedClassifierCV
из scikit-learn:
import numpy as np from sklearn.datasets import fetch_openml from sklearn.ensemble import RandomForestClassifier from sklearn.calibration import CalibratedClassifierCV from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, classification_report, log_loss # Load the "mnist_784" dataset (a subset will be used for faster execution) mnist = fetch_openml('mnist_784') X, y = mnist['data'], mnist['target'] # Use a subset of the dataset for faster execution X = X[:10000] y = y[:10000] # Split the dataset into training and test sets X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Train a RandomForest classifier rf = RandomForestClassifier(n_estimators=50, random_state=42) rf.fit(X_train, y_train) # Evaluate the classifier without calibration y_pred = rf.predict(X_test) y_pred_proba = rf.predict_proba(X_test) print("Accuracy without calibration:", accuracy_score(y_test, y_pred)) print("Log loss without calibration:", log_loss(y_test, y_pred_proba)) # Calibrate the classifier using isotonic regression calibrated_rf = CalibratedClassifierCV(rf, method='isotonic', cv=5) calibrated_rf.fit(X_train, y_train) # Evaluate the calibrated classifier y_pred_calibrated = calibrated_rf.predict(X_test) y_pred_proba_calibrated = calibrated_rf.predict_proba(X_test) print("Accuracy with calibration:", accuracy_score(y_test, y_pred_calibrated)) print("Log loss with calibration:", log_loss(y_test, y_pred_proba_calibrated)) Accuracy without calibration: 0.9495 Log loss without calibration: 0.37637322941353757 Accuracy with calibration: 0.9525 Log loss with calibration: 0.18414947220067412
Теперь давайте оценим качество калибровки по шкале Бриера.
Оценка Бриера, также известная как потеря Бриера или правило квадратичной оценки, является мерой точности вероятностных прогнозов в задачах классификации. Это особенно полезно для оценки качества прогнозируемых вероятностей, в отличие от бинарных прогнозов. Оценка Бриера рассчитывается как среднеквадратическая разница между предсказанными вероятностями и фактическими результатами.
Для задачи бинарной классификации с двумя классами, A и B, показатель Бриера можно рассчитать следующим образом:
Оценка Брайера = (1/N) * Σ(Pi — Oi)²
где N — количество выборок, Pi — прогнозируемая вероятность класса A для выборки i, а Oi — фактический результат для выборки i (1, если выборка принадлежит классу A, 0, если она принадлежит классу B).
Оценка Бриера варьируется от 0 до 1, при этом более низкие значения указывают на лучшую калибровку и более высокую точность предсказанных вероятностей. Оценка Брайера, равная 0, означает, что прогнозы идеально откалиброваны, а оценка Бриера, равная 1, означает наихудшую возможную калибровку.
Для задач мультиклассовой классификации оценка Бриера рассчитывается как среднее среднеквадратичное различие между прогнозируемыми вероятностями и фактическими результатами для каждого класса. Вы можете использовать функцию scikit-learn label_binarize
, чтобы преобразовать целевую переменную в двоичный формат, а затем рассчитать оценку Бриера для каждого класса.
# Binarize the true labels y_test_binarized = label_binarize(y_test, classes=np.unique(y_test)) # Calculate the Brier score for each class brier_scores = [ brier_score_loss(y_test_binarized[:, i], y_pred_proba_calibrated[:, i]) for i in range(y_test_binarized.shape[1]) ] # Calculate the average Brier score across all classes average_brier_score = np.mean(brier_scores) print(f"Average Brier score across all classes: {average_brier_score:.4f}") Average Brier score across all classes: 0.0075
Здесь мы использовали «изотонический» метод внутри функции калибровки; CalibratedClassifier
, а какие возможны способы.
Методы калибровки
Существует несколько методов калибровки, которые можно применить к алгоритмам машинного обучения:
- Масштабирование Платта (или метод Платта): этот метод включает обучение модели логистической регрессии на предсказанных вероятностях (или значениях функции принятия решения) и истинных метках. Модель логистической регрессии сопоставляет выходные данные базового классификатора с вероятностями. Он обычно используется с машинами опорных векторов и нейронными сетями.
- Изотоническая регрессия: этот непараметрический метод оценивает кусочно-постоянную неубывающую функцию, которая отображает предсказанные вероятности в калиброванные вероятности. Он может быть более гибким, чем Platt Scaling, но может также соответствовать данным калибровки, особенно если набор данных небольшой.
- Температурное масштабирование: этот метод в основном используется для калибровки нейронных сетей. Это включает в себя изучение одного параметра температуры, который регулирует вывод softmax. Прогнозируемые вероятности делятся на значение температуры, и снова применяется функция softmax.
- Байесовское объединение в квантили (BBQ): этот метод предполагает, что предсказанные вероятности разбиты на ячейки, а истинные вероятности в каждой ячейке подчиняются бета-распределению. BBQ оценивает параметры бета-распределения для каждого бина, используя эмпирические вероятности.
- Калибровка с перекрестной проверкой: во избежание переобучения калибровку можно выполнять с помощью перекрестной проверки. Набор данных разделен на k сгибов, и каждый сгиб используется для калибровки, а другие (k-1) сгибы используются для обучения классификатора.
- Калибровка ансамбля. Методы ансамбля, такие как бэггинг или бустинг, могут использоваться для улучшения калибровки отдельных моделей. Каждая модель в ансамбле калибруется отдельно, а их выходные данные объединяются для получения калиброванного прогноза ансамбля.
- Обучение с учетом затрат: в этом подходе алгоритм обучения модифицируется, чтобы учитывать затраты на неправильную классификацию. Это может помочь улучшить калибровку, сделав классификатор более консервативным в прогнозировании вероятностей.
Эти методы калибровки можно использовать по отдельности или в сочетании для повышения производительности алгоритмов машинного обучения. Выбор метода зависит от конкретного алгоритма и предметной области. Надлежащая оценка и сравнение различных методов калибровки необходимы для определения наиболее подходящего подхода к данной проблеме.
Ниже приведены примеры кода Python, демонстрирующие, как применять каждый метод калибровки с использованием библиотеки scikit-learn для задачи двоичной классификации.
Во-первых, давайте создадим синтетический набор данных и обучим машину опорных векторов (SVM) в качестве базового классификатора:
import numpy as np from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.svm import SVC from sklearn.metrics import brier_score_loss # Create synthetic dataset X, y = make_classification(n_samples=1000, n_features=20, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Train an SVM classifier base_classifier = SVC(kernel='linear', C=1, probability=True) base_classifier.fit(X_train, y_train) # Get predicted probabilities y_pred_probs = base_classifier.predict_proba(X_test)[:, 1] # Evaluate Brier score brier_base = brier_score_loss(y_test, y_pred_probs) print(f"Brier score of base classifier: {brier_base:.4f}") Brier score of base classifier: 0.1090
Теперь применим каждый метод калибровки:
Платт Масштабирование
from sklearn.calibration import CalibratedClassifierCV # Calibrate using Platt Scaling platt_calibrator = CalibratedClassifierCV(base_classifier, method='sigmoid', cv='prefit') platt_calibrator.fit(X_train, y_train) # Get Platt calibrated probabilities y_pred_probs_platt = platt_calibrator.predict_proba(X_test)[:, 1] # Evaluate Brier score brier_platt = brier_score_loss(y_test, y_pred_probs_platt) print(f"Brier score after Platt Scaling: {brier_platt:.4f}"Brier score after Platt Scaling: 0.1091 Brier score after Platt Scaling: 0.1091
Изотоническая регрессия
# Calibrate using Isotonic Regression isotonic_calibrator = CalibratedClassifierCV(base_classifier, method='isotonic', cv='prefit') isotonic_calibrator.fit(X_train, y_train) # Get isotonic calibrated probabilities y_pred_probs_isotonic = isotonic_calibrator.predict_proba(X_test)[:, 1] # Evaluate Brier score brier_isotonic = brier_score_loss(y_test, y_pred_probs_isotonic) print(f"Brier score after Isotonic Regression: {brier_isotonic:.4f}") Brier score after Isotonic Regression: 0.1071
Масштабирование температуры
Этот метод недоступен напрямую в scikit-learn, но может быть реализован следующим образом:
from scipy.optimize import minimize_scalar from sklearn.metrics import log_loss def temperature_scaling(logits, y): def temperature_obj(t): temp_logits = logits / t return log_loss(y, temp_logits) res = minimize_scalar(temperature_obj) return logits / res.x # Get logits for the test set logits = base_classifier.decision_function(X_test) # Calibrate using Temperature Scaling logits_temp_scaled = temperature_scaling(logits, y_test) y_pred_probs_temp_scaled = np.exp(logits_temp_scaled) / (1 + np.exp(logits_temp_scaled)) # Evaluate Brier score brier_temp_scaled = brier_score_loss(y_test, y_pred_probs_temp_scaled) print(f"Brier score after Temperature Scaling: {brier_temp_scaled:.4f}") Brier score after Temperature Scaling: 0.1087
Обратите внимание, что температурное масштабирование больше подходит для нейронных сетей, и здесь оно применяется к SVM в демонстрационных целях.
Байесовское объединение в квантили (BBQ)
Байесовское объединение в квантили (BBQ) и другие методы напрямую не доступны в scikit-learn.
Однако вы можете реализовать его вручную. Вот пример того, как применить калибровку BBQ к тому же синтетическому набору данных и базовому классификатору SVM из предыдущих примеров:
import numpy as np from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.svm import SVC from sklearn.metrics import brier_score_loss from scipy.stats import beta # Create synthetic dataset X, y = make_classification(n_samples=1000, n_features=20, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # Train an SVM classifier base_classifier = SVC(kernel='linear', C=1, probability=True) base_classifier.fit(X_train, y_train) # Get predicted probabilities y_pred_probs = base_classifier.predict_proba(X_test)[:, 1] def bbq_calibration(y_true, y_pred_probs, n_bins=10): bin_edges = np.linspace(0, 1, n_bins + 1) bin_indices = np.digitize(y_pred_probs, bin_edges) - 1 bin_true_probs = np.zeros(n_bins) for i in range(n_bins): mask = bin_indices == i bin_true_probs[i] = np.mean(y_true[mask]) if np.sum(mask) > 0 else 0.5 return bin_true_probs[bin_indices] # Calibrate using Bayesian Binning into Quantiles (BBQ) y_pred_probs_bbq = bbq_calibration(y_test, y_pred_probs) # Evaluate Brier score brier_bbq = brier_score_loss(y_test, y_pred_probs_bbq) print(f"Brier score after Bayesian Binning into Quantiles (BBQ): {brier_bbq:.4f}") Brier score after Bayesian Binning into Quantiles (BBQ): 0.1009
В этом примере показано, как вручную реализовать калибровку барбекю в Python. Обратите внимание, что это простая реализация, которая может быть не такой эффективной и надежной, как библиотечные реализации. На практике вы можете рассмотреть возможность использования библиотеки, которая обеспечивает более полную и эффективную реализацию калибровки BBQ.
Калибровка с перекрестной проверкой:
# Calibrate using cross-validated Platt Scaling platt_calibrator_cv = CalibratedClassifierCV(base_classifier, method='sigmoid', cv=5) platt_calibrator_cv.fit(X_train, y_train) # Get cross-validated Platt calibrated probabilities y_pred_probs_platt_cv = platt_calibrator_cv.predict_proba(X_test)[:, 1] # Evaluate Brier score brier_platt_cv = brier_score_loss(y_test, y_pred_probs_platt_cv) print(f"Brier score after cross-validated Platt Scaling: {brier_platt_cv:.4f}") Brier score after cross-validated Platt Scaling: 0.1094
Калибровка ансамбля:
from sklearn.ensemble import RandomForestClassifier # Train a RandomForest classifier ensemble_classifier = RandomForestClassifier(n_estimators=100, random_state=42) ensemble_classifier.fit(X_train, y_train) # Get predicted probabilities y_pred_probs_ensemble = ensemble_classifier.predict_proba(X_test)[:, 1] # Evaluate Brier score brier_ensemble = brier_score_loss(y_test, y_pred_probs_ensemble) print(f"Brier score of ensemble classifier: {brier_ensemble:.4f}") Brier score of ensemble classifier: 0.0936
Обучение с учетом затрат:
from sklearn.svm import SVC # Define a custom cost matrix cost_matrix = np.array([[0, 1.5], # [0, 1] cost [1, 0]]) # [1, 0] cost # Train a cost-sensitive SVM classifier cs_svm = SVC(kernel='linear', C=1, class_weight={0: cost_matrix[0, 1], 1: cost_matrix[1, 0]}) cs_svm.fit(X_train, y_train) # Get predicted probabilities y_pred_probs_cs = cs_svm.predict_proba(X_test)[:, 1] # Evaluate Brier score brier_cs = brier_score_loss(y_test, y_pred_probs_cs) print(f"Brier score of cost-sensitive classifier: {brier_cs:.4f}") Brier score of cost-sensitive classifier: 0.1105
Приведенные выше фрагменты кода демонстрируют, как применить каждый метод калибровки к его общему варианту использования. Обратите внимание, что выбор метода зависит от конкретного алгоритма и предметной области, и вы должны оценить и сравнить различные методы калибровки, чтобы определить наиболее подходящий подход для данной проблемы.
Вот фрагмент кода Python, который создает интерактивный график, содержащий калибровочные кривые для всех семи методов, упомянутых в предыдущих примерах.
import plotly.graph_objects as go # Create a scatter plot with an ideal calibration curve fig = go.Figure(go.Scatter(x=[0, 1], y=[0, 1], mode='lines', line=dict(dash='dash', color='gray'), name='Ideal Calibration')) colors_list = ['#1f77b4', # muted blue '#ff7f0e', # safety orange '#d62728', # brick red '#9467bd', # muted purple '#8c564b', # chestnut brown '#e377c2', # raspberry yogurt pink '#7f7f7f', # middle gray '#bcbd22', # curry yellow-green '#17becf' # blue-teal ] # Add the calibration curve for each method as invisible traces methods = [('Platt Scaling', y_pred_probs_platt), ('Isotonic Regression', y_pred_probs_isotonic), ('Temperature Scaling', y_pred_probs_temp_scaled), ('BBQ', y_pred_probs_bbq), ('Cross-validated Platt Scaling', y_pred_probs_platt_cv), ('Ensemble Calibration', y_pred_probs_ensemble), ('Cost-sensitive Learning', y_pred_probs_cs) ] L = len(methods) #for method, y_pred_probs_calibrated in methods: # Define the dropdown menu dropdown_menu = [ { "args": [{"visible": [False] * L, "showlegend": [False] * L}], "label": "Select Method", "method": "update", } ] for idx, (method, y_pred_probs_calibrated) in enumerate(methods): fig.add_trace(go.Scatter(x=y_pred_probs, y=y_pred_probs_calibrated, mode='markers', marker=dict(size=6, color=colors_list[idx],opacity=0.5), name=method, visible=False)) visible_list = [False] * L visible_list[(idx+1)%L] = True showlegend_list = [False] * L showlegend_list[(idx+1)%L] = True dropdown_menu.append( { "args": [ { "visible": visible_list, "showlegend": showlegend_list, "annotations": [ { "text": method, "showarrow": False, "xref": "paper", "yref": "paper", "x": 1, "y": 1, "xanchor": "right", "yanchor": "bottom", "font": {"size": 16}, }, { "text": "Ideal Calibration", "showarrow": False, "xref": "paper", "yref": "paper", "x": 1, "y": 1, "xanchor": "right", "yanchor": "bottom", "font":{"size": 16}, } ], } ], "label": method, "method": "update", } ) fig.update_layout( updatemenus=[ go.layout.Updatemenu( buttons=dropdown_menu, showactive=False, direction="down", pad={"r": 10, "t": 10}, x=0.05, xanchor="left", y=1.15, yanchor="top", ) ] ) # Add axis labels fig.update_xaxes(title_text="Predicted Probabilities (Uncalibrated)") fig.update_yaxes(title_text="Predicted Probabilities (Calibrated)") # Show the interactive plot fig.show()
Дайте мне знать, что вы думаете!
👏 Не забудьте поставить лайк этой статье и поделиться ею со своей сетью, чтобы поддержать мою работу! Не стесняйтесь подписываться на мой профиль на Medium, чтобы получать больше информации о машинном обучении и науке о данных. Спасибо за такую поддержку! 🚀