Понимание WeightedKappaLoss с использованием Keras

Я использую Keras, чтобы попытаться предсказать вектор оценок (0-1), используя последовательность событий.

Например, X — это последовательность из 3 векторов, состоящая из 6 признаков каждый, а y — это вектор из 3 оценок:

X
[
  [1,2,3,4,5,6], <--- dummy data
  [1,2,3,4,5,6],
  [1,2,3,4,5,6]
]

y
[0.34 ,0.12 ,0.46] <--- dummy data

Я хочу решить проблему как порядковую классификацию, поэтому, если фактические значения [0.5,0.5,0.5], прогноз [0.49,0.49,0.49] лучше, чем [0.3,0.3,0.3]. Мое первоначальное решение состояло в том, чтобы использовать sigmoid активацию на моем последнем слое и mse в качестве функции потерь, поэтому выходной сигнал находится в диапазоне от 0 до 1 для каждого из выходных нейронов:

def get_model(num_samples, num_features, output_size):
    opt = Adam()
    model = Sequential()
    
    model.add(LSTM(config['lstm_neurons'], activation=config['lstm_activation'], input_shape=(num_samples, num_features)))
    model.add(Dropout(config['dropout_rate']))

    for layer in config['dense_layers']:
      model.add(Dense(layer['neurons'], activation=layer['activation']))

    model.add(Dense(output_size, activation='sigmoid'))
    model.compile(loss='mse', optimizer=opt, metrics=['mae', 'mse'])

    return model

Моя цель - понять использование WeightedKappaLoss и реализовать его на моих реальных данных. Я создал этот Colab, чтобы поиграть с идеей. В Colab мои данные представляют собой последовательность в форме (5000,3,3), а мои цели в форме (5000, 4) представляют 1 из 4 возможных классов.

Я хочу, чтобы модель понимала, что ей нужно обрезать число с плавающей запятой X, чтобы предсказать правильный класс y:

[[3.49877793, 3.65873511, 3.20218196],
 [3.20258153, 3.7578669 , 3.83365481],
 [3.9579924 , 3.41765455, 3.89652426]], ----> y is 3 [0,0,1,0]

[[1.74290875, 1.41573056, 1.31195701],
 [1.89952004, 1.95459796, 1.93148095],
 [1.18668981, 1.98982041, 1.89025326]], ----> y is 1 [1,0,0,0]

Код новой модели:

def get_model(num_samples, num_features, output_size):
    opt = Adam(learning_rate=config['learning_rate'])
    model = Sequential()
    
    model.add(LSTM(config['lstm_neurons'], activation=config['lstm_activation'], input_shape=(num_samples, num_features)))
    model.add(Dropout(config['dropout_rate']))

    for layer in config['dense_layers']:
      model.add(Dense(layer['neurons'], activation=layer['activation']))

    model.add(Dense(output_size, activation='softmax'))
    model.compile(loss=tfa.losses.WeightedKappaLoss(num_classes=4), optimizer=opt, metrics=[tfa.metrics.CohenKappa(num_classes=4)])

    return model

При подгонке модели я вижу следующие показатели на TensorBoard:

Я не уверен в следующих моментах и ​​был бы признателен за разъяснения:

  • Я правильно его использую?
  • В моей исходной задаче я предсказываю 3 балла, в отличие от примера Colab, где я предсказываю только 1. Если я использую WeightedKappaLoss, означает ли это, что мне нужно преобразовать каждый из баллов в вектор? 100 однократное кодирование?
  • Есть ли способ использовать WeightedKappaLoss для исходных оценок с плавающей запятой без преобразования в проблему классификации?

Чтобы убедиться, что я понимаю вашу первоначальную проблему (поскольку вы дали несколько описаний с некоторыми несоответствиями), позвольте мне дать описание: в основном, в проблеме, над которой вы работаете, каждый входной образец представляет собой временной ряд формы (seq_len, seq_features), и вам интересно прогнозировать оценку для каждого временного шага входной выборки (выход формы (seq_len,)); однако сами оценки для вас не важны, и ваша главная задача — найти правильный порядок (или ранжирование) временных шагов в каждой входной выборке. Это правильное описание или я что-то упустил?

today 18.12.2020 20:24

Если ваша исходная задача состоит в том, чтобы «предсказать вектор оценок (0–1)», почему вы хотите обратиться к ней как к порядковой классификации, а не к регрессии, например, используя сигмоид, как вы предложили? Это потому, что вы не получили приемлемых результатов с помощью регрессионной/сигмовидной структуры?

SaTa 20.12.2020 01:27

@today спасибо за ваш ответ, я пытаюсь предсказать одну отметку времени в будущем. IE допустим, что последовательность состоит из 100 меток времени, я хочу использовать первые 50, чтобы предсказать последнюю (метка времени 100)

Shlomi Schwartz 20.12.2020 22:23

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

Shlomi Schwartz 20.12.2020 22:24
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
6
4
1 367
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Давайте разделим цель на две подцели, сначала мы рассмотрим цель, концепцию, математические детали Weighted Kappa, после чего подведем итоги, на что следует обратить внимание, когда мы пытаемся использовать WeightedKappaLoss в тензорном потоке.

PS: вы можете пропустить часть понимания, если вас интересует только использование


Подробное объяснение Взвешенной Каппы

Поскольку взвешенную каппу можно рассматривать как каппу Коэна + веса, поэтому нам нужно сначала понять каппу Коэна.

Пример каппы Коэна

Предположим, у нас есть два классификатора (A и B), пытающихся классифицировать 50 утверждений по двум категориям (Истинно и Ложно), как они классифицируют эти утверждения относительно друг друга в таблице непредвиденных обстоятельств:

         B
         True False
A True   20   5     25 statements A think is true
  False  10   15    25 statements A think is false
         30 statements B think is true
              20 statements B think is false

Теперь предположим, что мы хотим знать: насколько достоверны предсказания, сделанные А и В?

Что мы можем сделать, так это просто взять процент классифицированных утверждений, которые A и B согласуются друг с другом, то есть долю наблюдаемого согласия обозначить как Po, поэтому:

Po = (20 + 15) / 50 = 0.7

Но это проблематично, потому что существует вероятность того, что A и B согласуются друг с другом случайным образом, то есть доля ожидаемого совпадения шансов обозначается как Pe, если мы используем наблюдаемый процент как ожидаемую вероятность, тогда:

Pe = (probability statement A think is true) * (probability statement B think is true) +
     (probability statement A think is false) * (probability statement B think is false) 
   = (25 / 50) * (30 / 50) + 
     (25 / 50) * (20 / 50)
   = 0.5

Коэффициент каппа Коэна обозначается как K, который включает Po и Pe, чтобы дать нам более надежный прогноз о надежности сделанных прогнозов A и B:

K = (Po - Pe) / (1 - Pe) = 1 - (1 - Po) / (1 - Pe) = 1 - (1 - 0.7) / (1 - 0.5) = 0.4

Мы можем видеть, чем больше A и B согласуются друг с другом (Po выше) и меньше они согласуются из-за случайности (Pe ниже), тем больше каппа Коэна «думает», что результат надежен.

Теперь предположим, что A - это метки (основная истина) утверждений, тогда K говорит нам, насколько надежен прогноз B, то есть насколько прогноз согласуется с метками, если принять во внимание случайный случай.

Гири для каппы Коэна

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

                                    classifier 2
                       class.1  class.2  class... class.k  Sum over row
               class.1   n11      n12      ...      n1k      n1+  
               class.2   n21      n22      ...      n2k      n2+  
classifier 1   class...  ...      ...      ...      ...      ...  
               class.k   nk1      nk2      ...      nkk      nk+  
       Sum over column   n+1      n+2      ...      n+k      N   # total sum of all table cells

Ячейки таблицы содержат количество перекрестно классифицированных категорий, обозначаемых как nij, i,j для индекса строки и столбца соответственно.

Учтите, что порядковые классы k отделены от двух категориальных классов, например, разделены 1, 0 на пять классов 1, 0.75, 0.5, 0.25, 0, которые имеют плавный упорядоченный переход, мы не можем сказать, что классы независимы, за исключением первого и последнего класса, например very good, good, normal, bad, very bad, very good и good не являются независимыми и good должен быть ближе к bad, чем к very bad

Поскольку соседние классы взаимозависимы, то для расчета количества, связанного с согласием, нам необходимо определить эту зависимость, т.е. Веса обозначаются как Wij, он присваивается каждой ячейке в таблице сопряженности, значение веса (в диапазоне [0, 1]) зависит от того, насколько близки два класса

Теперь давайте посмотрим на формулу Po и Pe во взвешенной каппе:

И формула Po и Pe в каппе Коэна:

Мы можем видеть, что формула Po и Pe в каппе Коэна является частным случаем формулы во взвешенной каппе, где weight = 1 присваивается всем диагональным ячейкам, а вес = 0 в другом месте, когда мы вычисляем K (коэффициент каппы Коэна) с использованием формулы Po и Pe во взвешенной каппе, мы также учитывать зависимость между соседними классами

Вот две часто используемые системы взвешивания:

  1. Линейный вес:

  1. Квадратичный вес:

Где |i-j| — расстояние между классами, а k — количество классов.

Взвешенная каппа-потеря

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

Формула взвешенной потери Каппа определяется по формуле:

Просто возьмите формулу отрицательного коэффициента Каппа Коэна и избавьтесь от константы -1, затем примените к ней натуральный логарифм, где dij = |i-j| для линейного веса, dij = (|i-j|)^2 для квадратичного веса

Ниже приведен исходный код взвешенной потери каппа, написанный с помощью tensroflow, как вы можете видеть, он просто реализует формулу взвешенной потери каппа выше:

import warnings
from typing import Optional

import tensorflow as tf
from typeguard import typechecked

from tensorflow_addons.utils.types import Number

class WeightedKappaLoss(tf.keras.losses.Loss):
    @typechecked
    def __init__(
        self,
        num_classes: int,
        weightage: Optional[str] = "quadratic",
        name: Optional[str] = "cohen_kappa_loss",
        epsilon: Optional[Number] = 1e-6,
        dtype: Optional[tf.DType] = tf.float32,
        reduction: str = tf.keras.losses.Reduction.NONE,
    ):
        super().__init__(name=name, reduction=reduction)
        warnings.warn(
            "The data type for `WeightedKappaLoss` defaults to "
            "`tf.keras.backend.floatx()`."
            "The argument `dtype` will be removed in Addons `0.12`.",
            DeprecationWarning,
        )
        if weightage not in ("linear", "quadratic"):
            raise ValueError("Unknown kappa weighting type.")

        self.weightage = weightage
        self.num_classes = num_classes
        self.epsilon = epsilon or tf.keras.backend.epsilon()
        label_vec = tf.range(num_classes, dtype=tf.keras.backend.floatx())
        self.row_label_vec = tf.reshape(label_vec, [1, num_classes])
        self.col_label_vec = tf.reshape(label_vec, [num_classes, 1])
        col_mat = tf.tile(self.col_label_vec, [1, num_classes])
        row_mat = tf.tile(self.row_label_vec, [num_classes, 1])
        if weightage == "linear":
            self.weight_mat = tf.abs(col_mat - row_mat)
        else:
            self.weight_mat = (col_mat - row_mat) ** 2

    def call(self, y_true, y_pred):
        y_true = tf.cast(y_true, dtype=self.col_label_vec.dtype)
        y_pred = tf.cast(y_pred, dtype=self.weight_mat.dtype)
        batch_size = tf.shape(y_true)[0]
        cat_labels = tf.matmul(y_true, self.col_label_vec)
        cat_label_mat = tf.tile(cat_labels, [1, self.num_classes])
        row_label_mat = tf.tile(self.row_label_vec, [batch_size, 1])
        if self.weightage == "linear":
            weight = tf.abs(cat_label_mat - row_label_mat)
        else:
            weight = (cat_label_mat - row_label_mat) ** 2
        numerator = tf.reduce_sum(weight * y_pred)
        label_dist = tf.reduce_sum(y_true, axis=0, keepdims=True)
        pred_dist = tf.reduce_sum(y_pred, axis=0, keepdims=True)
        w_pred_dist = tf.matmul(self.weight_mat, pred_dist, transpose_b=True)
        denominator = tf.reduce_sum(tf.matmul(label_dist, w_pred_dist))
        denominator /= tf.cast(batch_size, dtype=denominator.dtype)
        loss = tf.math.divide_no_nan(numerator, denominator)
        return tf.math.log(loss + self.epsilon)

    def get_config(self):
        config = {
            "num_classes": self.num_classes,
            "weightage": self.weightage,
            "epsilon": self.epsilon,
        }
        base_config = super().get_config()
        return {**base_config, **config}

Использование взвешенных потерь Каппа

Мы можем использовать взвешенную каппа-потерю всякий раз, когда мы можем преобразовать нашу проблему в задачи порядковой классификации, т. е. классы образуют плавный упорядоченный переход, а соседние классы взаимозависимы, например ранжирование чего-либо с помощью very good, good, normal, bad, very bad, а выходные данные модели должны быть похожими на Softmax результаты.

Мы не можем использовать взвешенную каппа-потерю, когда пытаемся предсказать вектор оценок (0-1), даже если они могут суммироваться с 1, поскольку веса в каждом элементе вектора разные, и эта потеря не спрашивает, насколько отличается значение путем вычитания , но спросите, сколько будет число путем умножения, например:

import tensorflow as tf
from tensorflow_addons.losses import WeightedKappaLoss

y_true = tf.constant([[0.1, 0.2, 0.6, 0.1], [0.1, 0.5, 0.3, 0.1],
                      [0.8, 0.05, 0.05, 0.1], [0.01, 0.09, 0.1, 0.8]])
y_pred_0 = tf.constant([[0.1, 0.2, 0.6, 0.1], [0.1, 0.5, 0.3, 0.1],
                      [0.8, 0.05, 0.05, 0.1], [0.01, 0.09, 0.1, 0.8]])
y_pred_1 = tf.constant([[0.0, 0.1, 0.9, 0.0], [0.1, 0.5, 0.3, 0.1],
                      [0.8, 0.05, 0.05, 0.1], [0.01, 0.09, 0.1, 0.8]])

kappa_loss = WeightedKappaLoss(weightage='linear', num_classes=4)
loss_0 = kappa_loss(y_true, y_pred_0)
loss_1 = kappa_loss(y_true, y_pred_1)
print('Loss_0: {}, loss_1: {}'.format(loss_0.numpy(), loss_1.numpy()))

Выходы:

# y_pred_0 equal to y_true yet loss_1 is smaller than loss_0
Loss_0: -0.7053321599960327, loss_1: -0.8015820980072021

Ваш код в Colab работает правильно в контексте задач порядковой классификации, так как функция, которую вы формируете X->Y, очень проста (целое число X равно индексу Y + 1), поэтому модель обучается достаточно быстро и точно, как мы можно увидеть K (коэффициент каппа Коэна) до 1.0, а взвешенная каппа-потеря падает ниже -13.0 (что на практике обычно минимально, что мы можем ожидать)

Таким образом, вы можете использовать взвешенную каппа-потерю, если только вы не можете преобразовать свою проблему в задачи порядковой классификации, которые имеют метки одним горячим способом, если вы можете и пытаетесь решить проблемы LTR (обучение для ранжирования), то вы можете проверить это руководство по внедрению ListNet и это руководство по tensorflow_ranking для лучшего результата, в противном случае вам не следует использовать взвешенную потерю каппы, если вы можете только преобразовать свою проблему в проблемы регрессии, тогда вы должны сделать то же самое, что и ваше исходное решение


Ссылка:

Каппа Коэна в Википедии

Взвешенная каппа в R: для двух порядковых переменных

исходный код WeightedKappaLoss в tensroflow-addons

Документация tfa.losses.WeightedKappaLoss

Разница между категориальными, порядковыми и числовыми переменными

Вау, большое спасибо за этот подробный ответ, спасибо за это, заслуженная награда!

Shlomi Schwartz 20.12.2020 22:15

Рад написать это и спасибо за ваше одобрение, я также узнал кое-что, пока писал его, хорошего дня :)

Matrix Adventurer 21.12.2020 01:30

Другие вопросы по теме