Я использую 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:
Я не уверен в следующих моментах и был бы признателен за разъяснения:
Если ваша исходная задача состоит в том, чтобы «предсказать вектор оценок (0–1)», почему вы хотите обратиться к ней как к порядковой классификации, а не к регрессии, например, используя сигмоид, как вы предложили? Это потому, что вы не получили приемлемых результатов с помощью регрессионной/сигмовидной структуры?
@today спасибо за ваш ответ, я пытаюсь предсказать одну отметку времени в будущем. IE допустим, что последовательность состоит из 100 меток времени, я хочу использовать первые 50, чтобы предсказать последнюю (метка времени 100)
@SaTa спасибо за ваш ответ, я просто хочу посмотреть, смогу ли я подойти к проблеме с другой стороны.
Давайте разделим цель на две подцели, сначала мы рассмотрим цель, концепцию, математические детали 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
во взвешенной каппе, мы также учитывать зависимость между соседними классами
Вот две часто используемые системы взвешивания:
Где |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
Разница между категориальными, порядковыми и числовыми переменными
Вау, большое спасибо за этот подробный ответ, спасибо за это, заслуженная награда!
Рад написать это и спасибо за ваше одобрение, я также узнал кое-что, пока писал его, хорошего дня :)
Чтобы убедиться, что я понимаю вашу первоначальную проблему (поскольку вы дали несколько описаний с некоторыми несоответствиями), позвольте мне дать описание: в основном, в проблеме, над которой вы работаете, каждый входной образец представляет собой временной ряд формы
(seq_len, seq_features)
, и вам интересно прогнозировать оценку для каждого временного шага входной выборки (выход формы(seq_len,)
); однако сами оценки для вас не важны, и ваша главная задача — найти правильный порядок (или ранжирование) временных шагов в каждой входной выборке. Это правильное описание или я что-то упустил?