Как реализовать пользовательскую функцию потерь с Keras для разреженного набора данных

Вопрос или проблема

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

def sparse_penalty_logits(y_true, y_pred):
    penalty = 10
    if y_true != 0:
        loss = -penalty*K.sum((y_true*K.log(y_pred) + (1 - y_true)*K.log(1 - y_pred)))
    else:
        loss = -K.sum((y_true*K.log(y_pred) + (1 - y_true)*K.log(1 - y_pred)))

    return loss

Это правильно? (Я также пробовал это с tensorflow). Каждый раз, когда я запускаю это, я получаю либо много NaN‘ов в качестве потерь, либо предсказания, которые вовсе не бинарные. Мне интересно, делаю ли я что-то неправильно при настройке модели, потому что binary_crossentropy также работает неправильно. Моя модель выглядит примерно так (цели представлены столбцом с 0 и 1):

model = Sequential()
model.add(Dense(100, activation = 'relu', input_shape = (train.shape[1],)))
model.add(Dense(100, activation = 'relu'))
model.add(Dense(100, activation = 'relu'))
model.add(Dense(1, activation = 'sigmoid'))

model.compile(optimizer="adam", loss = sparse_penalty_logits)

Если я запускаю это, как я уже сказал, я получаю очень странные результаты (боже, мне кажется, я все сильно испортил…):

Вовсе не бинарно. Боже, как же я все испортил...

Судя по упомянутым проблемам, с которыми вы сталкиваетесь, это похоже на проблему взрыва градиентов. Проблема взрыва градиентов может быть идентифицирована по:

  • Модель не может получить traction на ваших обучающих данных (например, плохие потери).
  • Модель неустойчивая, что приводит к большим изменениям потерь от обновления к обновлению.
  • Модель теряет NaN в процессе обучения.

Более подробно о проблеме взрыва градиентов можно узнать в этой статье

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

Ответ или решение

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

Исправление функции потерь

Ваш код для функции потерь имеет несколько проблем:

  1. Условия сравнения: Вы используете оператор != для y_true, что не совсем корректно, так как y_true – это массив значений. Вам нужно использовать K.any(y_true) или K.sum(y_true) для определения, содержит ли ваш выбор 1 (что означает, что запрос был выполнен).

  2. Безопасное логарифмирование: Функция K.log может возвращать NaN, если аргумент равен 0. Чтобы избежать этого, используйте K.clip для ограничения значений y_pred в диапазоне (epsilon, 1-epsilon).

  3. Общая формула потерь: Убедитесь, что ваша формула для вычисления потерь учитывает вероятность для каждого примера.

Вот исправленный вариант вашей функции потерь:

import keras.backend as K

def sparse_penalty_logits(y_true, y_pred):
    epsilon = K.epsilon()  # Для предотвращения деления на ноль
    y_pred = K.clip(y_pred, epsilon, 1 - epsilon)  # Ограничиваем y_pred
    penalty = 10

    # Вычисляем стандартную бинарную кросс-энтропию
    binary_crossentropy = -K.sum(y_true * K.log(y_pred) + (1 - y_true) * K.log(1 - y_pred), axis=1)

    # Добавляем штраф (penalty) в зависимости от наличия 1 в y_true
    loss = K.switch(K.greater(K.sum(y_true), 0), penalty * binary_crossentropy, binary_crossentropy)

    return K.mean(loss)

Проблемы с взрывом градиентов

Если вы столкнулись с NaN-значениями, это может быть связано со взрывом градиентов. Чтобы справиться с этой проблемой, вы можете использовать метод отсечения градиентов. Это можно сделать, добавив аргумент clipnorm или clipvalue в оптимизатор. Например:

from keras.optimizers import Adam

optimizer = Adam(clipnorm=1.0)  # Или clipvalue=0.5 в зависимости от вашей задачи
model.compile(optimizer=optimizer, loss=sparse_penalty_logits)

Общие рекомендации по модели

  1. Инициализация весов: Убедитесь, что вы используете хорошую инициализацию весов, чтобы избежать проблем с производительностью модели.

  2. Регуляризация: Рассмотрите возможность применения методов регуляризации, таких как Dropout, чтобы избежать переобучения, особенно когда ваши данные разрежены.

  3. Нормализация данных: Убедитесь, что ваши входные данные нормализованы. Не забудьте масштабировать ваши признаки, если это необходимо.

  4. Мониторинг процесса обучения: Используйте функции обратного вызова, чтобы отслеживать процесс обучения и предотвращать переобучение.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...