Вопрос или проблема
Я написал метрику для tensorflow, которая представляет собой площадь под кривой точности-отзыва слева от порога отзыва=0.3. Реализация выглядит следующим образом (обратите внимание, что меня интересует только нулевое предсказание последнего временного шага в окне):
def auprc_left_of_recall(y_true, y_pred, recall_threshold=0.3):
y_true = y_true[:, -1, 0]
y_pred = y_pred[:, -1, 0]
recall_threshold = tf.constant(recall_threshold)
indices = tf.argsort(y_pred, direction='DESCENDING')
sorted_y_true = tf.gather(y_true, indices)
cum_true_positives = K.cumsum(sorted_y_true)
cum_false_positives = K.cumsum(1 - sorted_y_true)
total_positives = K.sum(sorted_y_true)
precision = cum_true_positives / (cum_true_positives + cum_false_positives + K.epsilon())
recall = cum_true_positives / (total_positives + K.epsilon())
before_mask = recall < recall_threshold
after_mask = recall >= recall_threshold
idx_before = tf.reduce_max(tf.where(before_mask))
idx_after = tf.reduce_min(tf.where(after_mask))
recall_before = recall[idx_before]
recall_after = recall[idx_after]
precision_before = precision[idx_before]
precision_after = precision[idx_after]
interpolated_precision = precision_before + (precision_after - precision_before) * (recall_threshold - recall_before) / (recall_after
- recall_before)
precision_left = tf.concat([tf.boolean_mask(precision, before_mask), [interpolated_precision]], axis=0)
recall_left = tf.concat([tf.boolean_mask(recall, before_mask), [recall_threshold]], axis=0)
precision_left = tf.concat([[1.0], precision_left], axis=0)
recall_left = tf.concat([[0.0], recall_left], axis=0)
recall_diff = recall_left[1:] - recall_left[:-1]
avg_precision = (precision_left[1:] + precision_left[:-1]) / 2
area = tf.reduce_sum(recall_diff * avg_precision)
return area
Однако, когда агрегировать по партиям через среднее значение по партиям, результат явно не соответствует тому, что я бы ожидал (он работает идеально, если есть одна партия валидации/теста).
Как бы я реализовал метрику, которая оценивает ровно то, что делает метрика выше, но по меткам и предсказаниям всех партий сразу?
.
Ответ или решение
Для оценки метрики, основанной на площади под кривой «Precision-Recall» (PR) на всех партиях данных в TensorFlow, необходимо модифицировать ваш существующий код таким образом, чтобы он учитывал все данные, а не только данные одной партии. Это позволит вам получить адекватное значение вашей метрики при разбиении данных на батчи.
Основные моменты для реализации
-
Сбор данных за все батчи: Вам нужно будет агрегировать предсказания и истинные значения классов по всем батчам в единую структуру. В TensorFlow это можно сделать, используя функцию
tf.TensorArray
или другие подходящие конструкции для хранения. -
Корректное вычисление метрики: Вместо того чтобы вычислять метрику на уровне каждой партии, следует собрать все предсказания и истинные метки в один массив, а затем применять к ним вашу функцию, сохранив логику вычисления площади под кривой PR.
-
Применение на этапе обучения или тестирования: Убедитесь, что ваша метрика обновляется после каждой эпохи или в конце тестирования.
Шаги для модификации кода
Вот пример того, как можно реализовать вашу метрику auprc_left_of_recall
для работы с предсказаниями и истинными значениями всех батчей:
import tensorflow as tf
from tensorflow.keras import backend as K
class AUPRC(tf.keras.metrics.Metric):
def __init__(self, name='auprc', recall_threshold=0.3, **kwargs):
super(AUPRC, self).__init__(name=name, **kwargs)
self.recall_threshold = recall_threshold
self.precisions = self.add_weight(name='precisions', initializer='zeros')
self.recalls = self.add_weight(name='recalls', initializer='zeros')
self.total_count = self.add_weight(name='total_count', initializer='zeros')
def update_state(self, y_true, y_pred, sample_weight=None):
# Сбор всех предсказаний
y_true = y_true[:, -1, 0]
y_pred = y_pred[:, -1, 0]
# выполнение логики из вашей функции
area = self.compute_auprc(y_true, y_pred, self.recall_threshold)
# Обновление состояния
self.precisions.assign_add(area)
self.total_count.assign_add(1)
def result(self):
return self.precisions / self.total_count
def reset_states(self):
self.precisions.assign(0)
self.total_count.assign(0)
def compute_auprc(self, y_true, y_pred, recall_threshold):
# Здесь вставьте вашу реализацию вычисления AUPRC
# ...
return area # возвращаем рассчитанную площадь под кривой
# Пример использования при компиляции модели
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=[AUPRC()])
Пояснение к коду
-
Класс метрики: Определен новый класс
AUPRC
, который наследуется отtf.keras.metrics.Metric
. В данном классе мы храним состояния метрики — сумму значений и количество батчей. -
Обновление состояния: Метод
update_state
агрегирует предсказания и истинные значения для каждой партии и сохраняет их для последующего использования в вычислениях. -
Вычисление результата: Метод
result
возвращает среднее значение площади под кривой PR за все батчи. -
Сброс состояния: Метод
reset_states
сбрасывает значения метрики после завершения одной эпохи или тестирования, чтобы избежать накопления значений с предыдущих эпох.
С помощью этой структуры вы сможете корректно агрегировать оценки по всем батчам и получить точное значение вашей метрики. Это обеспечит более предсказуемые результаты при работе с тестовыми и валидационными данными.