Вопрос или проблема
Мой набор данных состоит из бездействующей системы, которая в некоторые моменты времени получает запросы. Я пытаюсь предсказать эти моменты с помощью часов. Поскольку запросы распределены разряженно (я заставил их длиться некоторое время, чтобы они не были слишком разряженными), я хотел создать новую функцию потерь, которая бы накладывала штраф на модель, если она выдает предсказание нуля для всего. Моя попытка реализации – это просто штраф для стандартных логитов:
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.
Исправление функции потерь
Ваш код для функции потерь имеет несколько проблем:
-
Условия сравнения: Вы используете оператор
!=
дляy_true
, что не совсем корректно, так какy_true
– это массив значений. Вам нужно использоватьK.any(y_true)
илиK.sum(y_true)
для определения, содержит ли ваш выбор1
(что означает, что запрос был выполнен). -
Безопасное логарифмирование: Функция
K.log
может возвращать NaN, если аргумент равен 0. Чтобы избежать этого, используйтеK.clip
для ограничения значенийy_pred
в диапазоне (epsilon, 1-epsilon). -
Общая формула потерь: Убедитесь, что ваша формула для вычисления потерь учитывает вероятность для каждого примера.
Вот исправленный вариант вашей функции потерь:
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)
Общие рекомендации по модели
-
Инициализация весов: Убедитесь, что вы используете хорошую инициализацию весов, чтобы избежать проблем с производительностью модели.
-
Регуляризация: Рассмотрите возможность применения методов регуляризации, таких как Dropout, чтобы избежать переобучения, особенно когда ваши данные разрежены.
-
Нормализация данных: Убедитесь, что ваши входные данные нормализованы. Не забудьте масштабировать ваши признаки, если это необходимо.
-
Мониторинг процесса обучения: Используйте функции обратного вызова, чтобы отслеживать процесс обучения и предотвращать переобучение.
Заключение
Таким образом, исправленная функция потерь и методы управления градиентами помогут избежать проблем с NaN и улучшат производительность вашей модели. Экспериментируйте с различными гиперпараметрами и следите за процессом обучения, чтобы достичь наилучших результатов на ваших разреженных данных.