Но если вы хотите знать, как это сделать для задач мультикласса, то нам нужно создать собственные метрики.
Во-первых, я должен сказать, что считаю, что нет смысла иметь эти метрики в категориальной задаче (среди многих только один правильный класс - 'softmax'
+ 'categorical_crossentropy'
). Такая проблема не является «бинарной», так что на самом деле нет «положительной» и «отрицательной», а одна правильная среди многих.
Если вы рассматриваете его как отдельные классы и рассматриваете каждый из них как двоичный класс, вы получите что-то вроде:
- Каждый раз, когда модель получает правильный класс, это означает 1 TP + 4 TN (обратите внимание, сколько истинных негативов есть, потому что есть больше, чем просто два результата)
- Каждый раз, когда модель получает неправильный класс, это означает 1 FP + 1 FN + 3 TN
Если сложить эти цифры, они просто не имеют особого смысла. (Возможно, мне не хватает какого-то специального метода для их вычисления для категориальной задачи ... и все же вы можете использовать все, что ниже, для этого, зная выше).
Теперь, с другой стороны, Вы можете получить хорошие метрики для нескольких двоичных классов (где каждый класс независим от других, и более одного класса может быть правильным - 'sigmoid'
+ 'binary_crossentropy'
).
В этом случае вы можете следовать двум подходам:
- Получить метрики "для класса"
- Как-то усреднить метрики всех классов (вы можете увидеть некоторые типы средних значений здесь в документации sklearn )
Метрики для класса
Они соответствуют среднему режиму 'binary'
в документации sklearn.
Альтернатива 1:
У каждого класса в качестве отдельного выхода модели при компиляции установите все эти метрики для каждого из выходов. Tensorflow будет видеть каждый вывод в отдельности и вычислять все без проблем.
Альтернатива 2:
Это должно быть сделано в виде отдельного показателя c для каждого класса. Таким образом, мы можем создать оболочку для этого, учитывая индекс класса.
Я приведу несколько примеров. Обратите внимание, что ни один из них не может быть «разреженным», поскольку более одного класса может быть правильным, поэтому в этом случае наземные данные true
будут иметь форму (samples, classes)
, как и прогнозируемые значения pred
.
Для каждого метри c вы создаете обертку, подобную этой:
#class getter
def get_class(true, pred, index):
#get the class
true = true[:, index]
pred = pred[:, index]
#round pred - you can choose different thresholds
pred = K.cast(K.greater(pred, 0.5), K.floatx())
return true, pred
#class wrapper
def some_metric_per_class(class_index):
def the_actual_metric(true, pred):
true, pred = get_class(class_index)
return calculations
return the_actual_metric
Обертку, подобную этой, можно использовать следующим образом:
metrics = [some_metric_per_class(i) for i in range(n_classes)]
metrics += [some_other_metric_per_class(i) for i in range(n_classes)]
model.compile(metrics = metrics, ...)
Здесь
Теперь у каждой из следующих метрик должна быть своя собственная оболочка (которую я здесь не написал, чтобы избежать ненужного повторения):
def TP(true, pred):
true, pred = get_class(class_index)
return K.sum(true * pred)
def FP(true, pred):
true, pred = get_class(class_index)
return K.sum(pred * (1 - true))
def TN(true, pred):
true, pred = get_class(class_index)
return K.sum((1-true) * (1-pred))
def FN(true, pred):
true, pred = get_class(class_index)
return K.sum((1-pred) * true)
def precision(true, pred):
true, pred = get_class(class_index)
TP = K.sum(true * pred)
TP_and_FP = K.sum(pred)
return K.switch(K.equal(TP_and_FP, 0), 1, TP / TP_and_FP)
def recall(true, pred):
true, pred = get_class(class_index)
TP = K.sum(true * pred)
TP_and_FN = K.sum(true)
return K.switch(K.equal(TP_and_FN, 0), 1, TP / TP_and_FN)
def AUC(true, pred):
true, pred = get_class(class_index)
#We want strictly 1D arrays - cannot have (batch, 1), for instance
true= K.flatten(true)
pred = K.flatten(pred)
#total number of elements in this batch
totalCount = K.shape(true)[0]
#sorting the prediction values in descending order
values, indices = tf.nn.top_k(pred, k = totalCount)
#sorting the ground truth values based on the predictions above
sortedTrue = K.gather(true, indices)
#getting the ground negative elements (already sorted above)
negatives = 1 - sortedTrue
#the true positive count per threshold
TPCurve = K.cumsum(sortedTrue)
#area under the curve
auc = K.sum(TPCurve * negatives)
#normalizing the result between 0 and 1
totalCount = K.cast(totalCount, K.floatx())
positiveCount = K.sum(true)
negativeCount = totalCount - positiveCount
totalArea = positiveCount * negativeCount
return auc / totalArea
Объяснение о AU C
Важно : точность, отзыв и AU C не будут точными значениями, поскольку Keras рассчитывает показатели по партиям, а затем усредняет результаты для каждого пакета.
Усредненные метрики
Они, вероятно, имеют смысл только с "точностью" и "отзывом". Так что я делаю это для этих двоих.
Здесь нет необходимости ни в обертках, ни в отдельных выходах. Данные true
и pred
такие же, как в предыдущих примерах, с формой (samples, classes)
.
Здесь мы используем одни и те же вычисления, но теперь мы держим все классы вместе и решаем, как их усреднить.
def base_metrics(true, pred):
#round pred - you can choose different thresholds
pred = K.cast(K.greater(pred, 0.5), K.floatx())
TP = K.sum(true * pred, axis=0)
TP_and_FP = K.sum(pred, axis=0)
TP_and_FN = K.sum(true, axis=0)
return TP, TP_and_FP, TP_and_FN
def precision_micro(true, pred):
TP, TP_FP, TP_FN = base_metrics(true, pred)
TP = K.sum(TP)
TP_FP = K.sum(TP_FP)
return K.switch(K.equal(TP_FP, 0), 1, TP / TP_FP)
def precision_macro(true, pred):
TP, TP_FP, TP_FN = base_metrics(true, pred)
precision = K.switch(K.equal(TP_FP, 0), 1, TP / TP_FP)
return K.mean(precision)
Вы можете сделать то же самое для recall_micro
и recall_macro
, но используя TP_FN
вместо TP_FP
.