Метрики оценки модели в сценариях классификации с несколькими метками

искусственный интеллект
Метрики оценки модели в сценариях классификации с несколькими метками

Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность

предисловие

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

Далее давайте углубимся в то, что такое классификация с несколькими метками, на всякий случай, если вам это нужно. Если у нас есть данные о характеристиках собаки, мы можем предсказать, к какой породе и категории домашних животных она относится.

В случае обнаружения объектов классификация по нескольким меткам предоставляет нам список всех объектов на изображении, как показано на рисунке ниже. Мы видим, что классификатор обнаружил на изображении 3 объекта. Если общее количество обучающих объектов равно 4, его можно выразить в виде следующего списка[1 0 1 1](Соответствующий объект[狗、人、自行车、卡车]). Эта классификация называется классификацией с несколькими метками.

image.png

Разница между классификацией Multiclass и классификацией Multilabel:

  • Мультикатегориальная классификация:Классификационные задачи с более чем двумя классами. Мультиклассовая классификация предполагает, что каждый образец относится к одной и только одной этикетке, например, фрукт может быть яблоком или апельсином, но не тем и другим одновременно.
  • Многоуровневая классификация:Назначьте одну или несколько меток каждому образцу. Например, новость может относиться как к спорту, так и к развлечениям.

Метрики оценки классификации с несколькими метками

Наиболее распространенные метрики, подходящие для классификации по нескольким меткам, следующие:

  1. Precision at k
  2. Avg precision at k
  3. Mean avg precision at k
  4. Sampled F1 Score
  5. Log Loss

Рассмотрим детали этих показателей.

Precision at k (P@K)

Учитывая список фактических и прогнозируемых классов, Precision@K определяется как количество правильных прогнозов с учетом только k лучших элементов, деленное на k лучших элементов каждого прогнозируемого класса. Диапазон значений от 0 до 1.

Пример кода выглядит следующим образом:

def patk(actual, pred, k):
    #we return 0 if k is 0 because 
    #   we can't divide the no of common values by 0 
    if k == 0:
        return 0

    #taking only the top k predictions in a class 
    k_pred = pred[:k]

    # taking the set of the actual values 
    actual_set = set(actual)
    print(list(actual_set))
    # taking the set of the predicted values 
    pred_set = set(k_pred)
    print(list(pred_set))
    
    # 求预测值与真实值得交集
    common_values = actual_set.intersection(pred_set)
    print(common_values)

    return len(common_values)/len(pred[:k])

# defining the values of the actual and the predicted class
y_true = [1 ,2, 0]
y_pred = [1, 1, 0]

if __name__ == "__main__":
    print(patk(y_true, y_pred,3))

Результаты приведены ниже:

K:3,真实值:{0, 1, 2}, 预测值:{0, 1}, 交集:{0, 1}, P@k:0.6666666666666666
0.6666666666666666

Average Precision at K (AP@K)

Он определяется как среднее значение всех Precision@K для k = от 1 до k. Для большей ясности давайте посмотрим на код. Диапазон значений от 0 до 1.

import numpy as np

def apatk(acutal, pred, k):
    #creating a list for storing the values of precision for each k 
    precision_ = []
    for i in range(1, k+1):
        #calculating the precision at different values of k 
        #      and appending them to the list 
        precision_.append(patk(acutal, pred, i))

    #return 0 if there are no values in the list
    if len(precision_) == 0:
        return 0 

    #returning the average of all the precision values
    return np.mean(precision_)

#defining the values of the actual and the predicted class
y_true = [[1,2,0,1], [0,4], [3], [1,2]]
y_pred = [[1,1,0,1], [1,4], [2], [1,3]]

if __name__ == "__main__":
    for i in range(len(y_true)):
        for j in range(1, 4):
            print(
                f"""
                y_true = {y_true[i]}
                y_pred = {y_pred[i]}
                AP@{j} = {apatk(y_true[i], y_pred[i], k=j)}
                """
            )
        print("-----------")
            

Результаты приведены ниже:

K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0

                y_true = [1, 2, 0, 1]
                y_pred = [1, 1, 0, 1]
                AP@1 = 1.0
                
K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:0.5

                y_true = [1, 2, 0, 1]
                y_pred = [1, 1, 0, 1]
                AP@2 = 0.75
                
K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:0.5
K:3,真实值:{0, 1, 2}, 预测值:{0, 1}, 交集:{0, 1}, P@k:0.6666666666666666

                y_true = [1, 2, 0, 1]
                y_pred = [1, 1, 0, 1]
                AP@3 = 0.7222222222222222
                
-----------
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0

                y_true = [0, 4]
                y_pred = [1, 4]
                AP@1 = 0.0
                
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0
K:2,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5

                y_true = [0, 4]
                y_pred = [1, 4]
                AP@2 = 0.25
                
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0
K:2,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5
K:3,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5

                y_true = [0, 4]
                y_pred = [1, 4]
                AP@3 = 0.3333333333333333
                
-----------
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0

                y_true = [3]
                y_pred = [2]
                AP@1 = 0.0
                
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:2,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0

                y_true = [3]
                y_pred = [2]
                AP@2 = 0.0
                
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:2,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:3,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0

                y_true = [3]
                y_pred = [2]
                AP@3 = 0.0
                
-----------
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0

                y_true = [1, 2]
                y_pred = [1, 3]
                AP@1 = 1.0
                
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5

                y_true = [1, 2]
                y_pred = [1, 3]
                AP@2 = 0.75
                
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5
K:3,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5

                y_true = [1, 2]
                y_pred = [1, 3]
                AP@3 = 0.6666666666666666
                
-----------

Mean avg precision at k (MAP@K)

Среднее значение всех значений AP@k по всем обучающим данным называется MAP@k. Это помогает нам точно представлять точность всех данных прогноза. Диапазон значений от 0 до 1.

AP@k измеряет, насколько хороша изученная модель в каждой категории, а MAP@k измеряет, насколько хороша изученная модель во всех категориях. Пример кода выглядит следующим образом:

import numpy as np

def mapk(acutal, pred, k):

    #creating a list for storing the Average Precision Values
    average_precision = []
    #interating through the whole data and calculating the apk for each 
    for i in range(len(acutal)):
        ap = apatk(acutal[i], pred[i], k)
        print(f"AP@k: {ap}")
        average_precision.append(ap)

    #returning the mean of all the data
    return np.mean(average_precision)

#defining the values of the actual and the predicted class
y_true = [[1,2,0,1], [0,4], [3], [1,2]]
y_pred = [[1,1,0,1], [1,4], [2], [1,3]]

if __name__ == "__main__":
    print(mapk(y_true, y_pred,3))

Результаты приведены ниже:

K:1,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{0, 1, 2}, 预测值:{1}, 交集:{1}, P@k:0.5
K:3,真实值:{0, 1, 2}, 预测值:{0, 1}, 交集:{0, 1}, P@k:0.6666666666666666
AP@k: 0.7222222222222222
K:1,真实值:{0, 4}, 预测值:{1}, 交集:set(), P@k:0.0
K:2,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5
K:3,真实值:{0, 4}, 预测值:{1, 4}, 交集:{4}, P@k:0.5
AP@k: 0.3333333333333333
K:1,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:2,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
K:3,真实值:{3}, 预测值:{2}, 交集:set(), P@k:0.0
AP@k: 0.0
K:1,真实值:{1, 2}, 预测值:{1}, 交集:{1}, P@k:1.0
K:2,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5
K:3,真实值:{1, 2}, 预测值:{1, 3}, 交集:{1}, P@k:0.5
AP@k: 0.6666666666666666
0.4305555555555556

Оценка здесь плохая, потому что в наборе прогнозов много ошибок.

Sampled F1 Score

Метрика сначала вычисляет оценку F1 для каждого экземпляра в данных, а затем вычисляет среднее значение оценок F1.

Мы будем использовать реализацию sklearn в коде. Пожалуйста, обратитесь к документации для расчета очков F1.здесь. Диапазон значений от 0 до 1.

Сначала мы преобразуем данные в0-1формате, а затем рассчитайте для него балл F1.

Пример кода выглядит следующим образом:

from sklearn.metrics import f1_score
from sklearn.preprocessing import MultiLabelBinarizer

def f1_sampled(actual, pred):
    # converting the multi-label classification to a binary output
    mlb = MultiLabelBinarizer()
    actual = mlb.fit_transform(actual)
    pred = mlb.fit_transform(pred)
    print(f"多标签二值化后的标签值:{mlb.classes_}")
    print(f"真实值:\n{actual}  \n预测值:\n{pred}")
    # fitting the data for calculating the f1 score 
    f1 = f1_score(actual, pred, average = "samples")
    return f1

# defining the values of the actual and the predicted class
# 总共有五个类别
y_true = [[1,2,0,1], [0,4], [3], [1,2]]
y_pred = [[1,1,0,1], [1,4], [2], [1,3]]

if __name__ == "__main__":
    print(f1_sampled(y_true, y_pred))

Результаты приведены ниже:

多标签二值化后的标签值:[0 1 2 3 4]
真实值:
[[1 1 1 0 0]
 [1 0 0 0 1]
 [0 0 0 1 0]
 [0 1 1 0 0]]  
预测值:
[[1 1 0 0 0]
 [0 1 0 0 1]
 [0 0 1 0 0]
 [0 1 0 1 0]]
0.45

Мы знаем, что оценка F1 находится между 0 и 1, здесь мы получаем оценку 0,45. Это связано с тем, что набор прогнозов не является хорошим. Если бы у нас был лучший набор прогнозов, значение было бы ближе к 1.

Log Loss

Логарифмическая потеря, китайское название - логарифмическая потеря, также известная как логистическая потеря или перекрестная энтропийная потеря.

Эта мера потери ошибок на самом деле является методом, используемым в логистической регрессии для измерения ошибки между прогнозируемой вероятностью и истинной меткой.

Его формула:

loss(x,y)=1C*iy[i]*log(1(1+exp(x[i])))+(1y[i])*log(exp(x[i])(1+exp(x[i])))loss(x, y) = - \frac{1}{C} * \sum_i y[i] * \log( \frac{1}{(1 + \exp(-x[i]))}) + (1-y[i]) * \log\left(\frac{\exp(-x[i])}{(1 + \exp(-x[i]))}\right)

вiе{0,  ,  x.nElement()1},y[i]е{0,  1}.i \in \left\{0, \;\cdots , \;\text{x.nElement}() - 1\right\}, y[i] \in \left\{0, \;1\right\ }.

В формуле x эквивалентно прогнозируемому значению, а y эквивалентно фактическому значению.

Во-первых, вам нужно бинаризировать метки, а затем использовать логарифмическую потерю для каждого столбца. Наконец, вы можете взять среднее значение потерь журнала в каждом столбце, которое также известно как среднее значение потерь журнала столбца. Конечно, вы можете сделать это и другими способами.

Пример кода выглядит следующим образом:

import numpy as np

# y_test = np.random.randint(0,2,(3,4))
# y_pred = np.random.random((3,4))
y_test = np.array([[1, 1, 0, 0], [0, 1, 0, 1]])
y_pred = np.array([[0.2, 0.5, 0, 0], [0.1, 0.5, 0, 0.8]])
print(f"{y_test}\n{y_pred}")

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

def log_loss(y_test,y_pred):
    y_test = y_test.astype(np.float16)
    y_pred = y_pred.astype(np.float16)
    N,M = y_test.shape
    print(f"列-M:{M}, 行-N:{N}")
    a=[]
    for m in range(M):
        loss=0
        for i in range(N):
            loss -= (y_test[i,m]*np.log(sigmoid(y_pred[i,m])))+((1.0-y_test[i,m])*np.log(1.0-sigmoid(y_pred[i,m])))
        loss = loss/N
        a.append(round(loss,8))
    print(f"每一列的损失如下:{a}")
    return np.mean(a)
a = log_loss(y_test,y_pred)
print(a)

Результаты приведены ниже:

[[1 1 0 0]
 [0 1 0 1]]
[[0.2 0.5 0.  0. ]
 [0.1 0.5 0.  0.8]]
列-M:4, 行-N:2
每一列的损失如下:[0.67131506, 0.47402386, 0.69314718, 0.53217012]
0.592664055

Приведенный выше код предназначен для нахождения логарифмических потерь для каждого столбца, мы также можем найти логарифмические потери для каждой выборки (строки), и результаты согласуются. Конкретный код выглядит следующим образом:

import numpy as np

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

def compute_loss_v1(y_true, y_pred):
    t_loss = y_true * np.log(sigmoid(y_pred)) + \
             (1 - y_true) * np.log(1 - sigmoid(y_pred))  # [batch_size,num_class]
    loss = t_loss.mean(axis=-1)  # 得到每个样本的损失值
    print(f"每个样本的损失如下:{loss}")
    return -loss.mean()  # 返回整体样本的损失均值(或其他)

if __name__ == '__main__':
    y_true = np.array([[1, 1, 0, 0], [0, 1, 0, 1]])
    y_pred = np.array([[0.2, 0.5, 0, 0], [0.1, 0.5, 0, 0.8]])
    print(compute_loss_v1(y_true, y_pred)) # 0.5926

Результаты приведены ниже:

每个样本的损失如下:[-0.61462755 -0.57068037]
0.5926539631803737

Соответствующий код реализации PyTorch выглядит следующим образом:

import torch
import torch.nn as nn

y_true = torch.tensor([[1, 1, 0, 0], [0, 1, 0, 1]],dtype=torch.int16)
y_pred = torch.tensor([[0.2, 0.5, 0, 0], [0.1, 0.5, 0, 0.8]],dtype=torch.float32)
loss = nn.MultiLabelSoftMarginLoss(reduction='mean')
print(loss(y_pred, y_true)) # 0.5926

Результаты приведены ниже:

tensor(0.5927)

Суммировать

Для сценариев классификации с несколькими метками мы обычно используем MAP@K , Sampled F1 Score или Log Loss, чтобы установить метрики оценки для вашей модели.

Справочная документация