Рассчитать отзыв для каждого класса после каждой эпохи в Tensorflow 2 - PullRequest
3 голосов
/ 30 мая 2019

Я пытаюсь вычислить отзыв в двоичном и мультиклассовом (один с горячим кодированием) сценарии классификации для каждого класса после каждой эпохи в модели, использующей API Keras от Tensorflow 2.например, для бинарной классификации я хотел бы иметь возможность сделать что-то вроде

import tensorflow as tf
model = tf.keras.Sequential()
model.add(...)
model.add(tf.keras.layers.Dense(1))

model.compile(metrics=[binary_recall(label=0), binary_recall(label=1)], ...)
history = model.fit(...)

plt.plot(history.history['binary_recall_0'])
plt.plot(history.history['binary_recall_1'])
plt.show()

или в мультиклассовом сценарии я хотел бы сделать что-то вроде

model = tf.keras.Sequential()
model.add(...)
model.add(tf.keras.layers.Dense(3))

model.compile(metrics=[recall(label=0), recall(label=1), recall(label=2)], ...)
history = model.fit(...)

plt.plot(history.history['recall_0'])
plt.plot(history.history['recall_1'])
plt.plot(history.history['recall_2'])
plt.show()

I'mработая над классификатором для несбалансированного набора данных, и хочу иметь возможность увидеть, в какой момент отзыв моих классов меньшинства начинает ухудшаться.

Я нашел реализацию точности для определенного класса в мульти-классификатор класса здесь https://stackoverflow.com/a/41717938/373655. Я пытаюсь адаптировать это к тому, что мне нужно, но keras.backend все еще довольно чуждо мне, поэтому любая помощь будет принята с благодарностью.

Я тоже неснимите флажок, если я могу использовать Keras metrics (так как они рассчитываются в конце каждой партии и затем усредняются) или мне нужно использовать Keras callbacks (который может выполняться в конце каждой эпохи).Мне кажется, что это не должно иметь никакого значения для отзыва (например, 8/10 == (3/5 + 5/5) / 2), но именно поэтому отзыв был удален в Keras 2, поэтому, возможно, я что-то упускаю (https://github.com/keras-team/keras/issues/5794)

Правка - частичное решение (мультиклассовая классификация) Решение @ mujjiga работает как для бинарной классификации, так и для мультиклассовой классификации, но, как отметило @ P-Gn, тензор потока 10 Recall метрика поддерживает это изкоробка для мультиклассовой классификации, например

from tensorflow.keras.metrics import Recall

model = ...

model.compile(loss='categorical_crossentropy', metrics=[
    Recall(class_id=0, name='recall_0')
    Recall(class_id=1, name='recall_1')
    Recall(class_id=2, name='recall_2')
])

history = model.fit(...)

plt.plot(history.history['recall_2'])
plt.plot(history.history['val_recall_2'])
plt.show()

Ответы [ 3 ]

2 голосов
/ 05 июня 2019

В TF2 tf.keras.metrics.Recall получил член class_id, который позволяет это сделать. Пример использования FashionMNIST:

import tensorflow as tf

(x_train, y_train), _ = tf.keras.datasets.fashion_mnist.load_data()
x_train = x_train[..., None].astype('float32') / 255
y_train = tf.keras.utils.to_categorical(y_train)

input_shape = x_train.shape[1:]
model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(filters=64, kernel_size=2, padding='same', activation='relu', input_shape=input_shape),
  tf.keras.layers.MaxPool2D(pool_size=2),
  tf.keras.layers.Dropout(0.3),

  tf.keras.layers.Conv2D(filters=32, kernel_size=2, padding='same', activation='relu'),
  tf.keras.layers.MaxPool2D(pool_size=2),
  tf.keras.layers.Dropout(0.3),

  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(units=256, activation='relu'),
  tf.keras.layers.Dropout(0.5),
  tf.keras.layers.Dense(units=10, activation='softmax')])

model.compile(loss='categorical_crossentropy', optimizer='Adam',
  metrics=[tf.keras.metrics.Recall(class_id=i) for i in range(10)])
model.fit(x_train, y_train, batch_size=128, epochs=50)

В TF 1.13, tf.keras.metric.Recall не имеет этого аргумента class_id, но его можно добавить с помощью подклассов (что, как ни удивительно, кажется невозможным в альфа-версии TF2).

class Recall(tf.keras.metrics.Recall):

  def __init__(self, *, class_id, **kwargs):
    super().__init__(**kwargs)
    self.class_id= class_id

  def update_state(self, y_true, y_pred, sample_weight=None):
    y_true = y_true[:, self.class_id]
    y_pred = tf.cast(tf.equal(
      tf.math.argmax(y_pred, axis=-1), self.class_id), dtype=tf.float32)
    return super().update_state(y_true, y_pred, sample_weight)
2 голосов
/ 07 июня 2019

Мы можем использовать classification_report sklearn и keras Callback для достижения этой цели.

Пример рабочего кода (с комментариями)

import tensorflow as tf
import keras
from tensorflow.python.keras.layers import Dense, Input
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.callbacks import Callback
from sklearn.metrics import recall_score, classification_report
from sklearn.datasets import make_classification
import numpy as np
import matplotlib.pyplot as plt

# Model -- Binary classifier
binary_model = Sequential()
binary_model.add(Dense(16, input_shape=(2,), activation='relu'))
binary_model.add(Dense(8, activation='relu'))
binary_model.add(Dense(1, activation='sigmoid'))
binary_model.compile('adam', loss='binary_crossentropy')

# Model -- Multiclass classifier
milticlass_model = Sequential()
milticlass_model.add(Dense(16, input_shape=(2,), activation='relu'))
milticlass_model.add(Dense(8, activation='relu'))
milticlass_model.add(Dense(3, activation='softmax'))
milticlass_model.compile('adam', loss='categorical_crossentropy')

# callback to find metrics at epoch end
class Metrics(Callback):
    def __init__(self, x, y):
        self.x = x
        self.y = y if (y.ndim == 1 or y.shape[1] == 1) else np.argmax(y, axis=1)
        self.reports = []

    def on_epoch_end(self, epoch, logs={}):
        y_hat = np.asarray(self.model.predict(self.x))
        y_hat = np.where(y_hat > 0.5, 1, 0) if (y.ndim == 1 or y_hat.shape[1] == 1)  else np.argmax(y_hat, axis=1)
        report = classification_report(self.y,y_hat,output_dict=True)
        self.reports.append(report)
        return

    # Utility method
    def get(self, metrics, of_class):
        return [report[str(of_class)][metrics] for report in self.reports]

# Generate some train data (2 class) and train
x, y = make_classification(n_features=2, n_redundant=0, n_informative=2,
                           random_state=1, n_clusters_per_class=1)
metrics_binary = Metrics(x,y)
binary_model.fit(x, y, epochs=30, callbacks=[metrics_binary])

# Generate some train data (3 class) and train
x, y = make_classification(n_features=2, n_redundant=0, n_informative=2,
                           random_state=1, n_clusters_per_class=1, n_classes=3)
y = keras.utils.to_categorical(y,3)
metrics_milticlass = Metrics(x,y)
milticlass_model.fit(x, y, epochs=30, callbacks=[metrics_milticlass])

# Plotting 
plt.close('all')
plt.plot(metrics_binary.get('recall',0), label='Class 0 recall') 
plt.plot(metrics_binary.get('recall',1), label='Class 1 recall') 

plt.plot(metrics_binary.get('precision',0), label='Class 0 precision') 
plt.plot(metrics_binary.get('precision',1), label='Class 1 precision') 

plt.plot(metrics_binary.get('f1-score',0), label='Class 0 f1-score') 
plt.plot(metrics_binary.get('f1-score',1), label='Class 1 f1-score') 
plt.legend(loc='lower right')
plt.show()

plt.close('all')
for m in ['recall', 'precision', 'f1-score']:
    for c in [0,1,2]:
        plt.plot(metrics_milticlass.get(m,c), label='Class {0} {1}'.format(c,m))

plt.legend(loc='lower right')
plt.show()

Вывод

enter image description here

enter image description here

Преимущества:

  • classification_report предоставляет множество метрик
  • Может вычислять метрики для данных проверки данных поезда, передавая их в Metricsконструктор.
2 голосов
/ 30 мая 2019

Есть несколько способов сделать это, но использование callback кажется лучшим и наиболее kerasy способом сделать это. Один примечание, прежде чем я покажу вам, как:

Мне также неясно, могу ли я использовать метрики Keras (как они рассчитывается в конце каждой партии, а затем усредняется) или, если мне нужно используйте обратные вызовы Keras (которые могут выполняться в конце каждой эпохи).

Это не правда. Обратные вызовы Keras могут использовать следующие методы:

  • on_epoch_begin: вызывается в начале каждой эпохи.
  • on_epoch_end: вызывается в конце каждой эпохи.
  • on_batch_begin: вызывается в начале каждого пакета.
  • on_batch_end: вызывается в конце каждого пакета.
  • on_train_begin: вызывается в начале обучения модели.
  • on_train_end: вызывается в конце обучения модели.

Это верно независимо от того, используете ли вы keras или tf.keras.

Ниже вы можете найти мою реализацию пользовательского обратного вызова.

class RecallHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.recall = {}

    def on_epoch_end(self, epoch, logs={}):
        # Compute and store recall for each class here.
        self.recall[...] = 42

history = RecallHistory()
model.fit(..., callbacks=[history])

print(history.recall)
...