Вопрос или проблема
Я пишу тестовый пользовательский слой, который реализует алгоритм простого экспоненциального сглаживания.
Проблема: когда я его обучаю, коэффициент альфа (сглаживание) всегда сходится к значению 1. Это означает, что прогноз на один шаг вперед всегда принимает предыдущее фактическое значение.
Скорее всего, я упускаю что-то очевидное, но не смог это понять.
Есть идеи? Спасибо.
Код:
from tensorflow.keras.layers import Layer
class SES(Layer):
def __init__(self, dtype=tf.float32):
super(SES, self).__init__()
def build(self, input_shape):
alpha_init = tf.keras.initializers.random_uniform(minval=0., maxval=1.)
self.alpha = tf.Variable(name="alpha", initial_value=alpha_init(shape=(1,),dtype="float32"),
constraint=tf.keras.constraints.min_max_norm(0,1),trainable=True)
def call(self, inputs):
'''Формула SES: yhat (один шаг вперед) = альфа*y_предыдущий (т.е. фактический_предыдущий) + (1-альфа)* yhat_предыдущий'''
def predict_one_step(y_previous, alpha, yhat_previous):
yhat = (alpha*y_previous) + ((1-alpha)*yhat_previous)
return yhat #прогноз на один шаг вперед
predictions = []
for timestep in range(inputs.shape[0]):
if timestep == 0:
yhat_previous = inputs[timestep]
yhat = predict_one_step(inputs[timestep], self.alpha, yhat_previous)
yhat_previous = yhat
predictions.append(yhat)
return tf.concat(predictions, axis=-1)
--------------------- Потеря во время обучения SES --------------------
Потеря на эпохе 000: 0.439, альфа: 0.250
Потеря на эпохе 020: 0.226, альфа: 0.433
Потеря на эпохе 040: 0.129, альфа: 0.581
Потеря на эпохе 060: 0.069, альфа: 0.705
Потеря на эпохе 080: 0.031, альфа: 0.810
Потеря на эпохе 100: 0.011, альфа: 0.892
Потеря на эпохе 120: 0.003, альфа: 0.949
Потеря на эпохе 140: 0.000, альфа: 0.981
Потеря на эпохе 160: 0.000, альфа: 0.995
Потеря на эпохе 180: 0.000, альфа: 1.000
Потеря на эпохе 200: 0.000, альфа: 1.000
Потеря на эпохе 220: 0.000, альфа: 1.000
Потеря на эпохе 240: 0.000, альфа: 1.000
Потеря на эпохе 260: 0.000, альфа: 1.000
Потеря на эпохе 280: 0.000, альфа: 1.000
Потеря на эпохе 300: 0.000, альфа: 1.000
Потеря на эпохе 320: 0.000, альфа: 1.000
Потеря на эпохе 340: 0.000, альфа: 1.000
Потеря на эпохе 360: 0.000, альфа: 1.000
Потеря на эпохе 380: 0.000, альфа: 1.000
Потеря на эпохе 400: 0.000, альфа: 1.000
Потеря на эпохе 420: 0.000, альфа: 1.000
Потеря на эпохе 440: 0.000, альфа: 1.000
Потеря на эпохе 460: 0.000, альфа: 1.000
Потеря на эпохе 480: 0.000, альфа: 1.000
Итоговая потеря: 0.000
альфа = 1.000
Проблема в том, что вы используете inputs[timestep]
вместо inputs[timestep - 1]
в качестве y_previous
в методе call
, именно поэтому потеря равна нулю, когда alpha
равно одному. Это было исправлено в коде ниже.
import tensorflow as tf
from tensorflow.keras.layers import Layer
class SES(Layer):
def __init__(self, dtype=tf.float32):
super(SES, self).__init__()
def build(self, input_shape):
alpha_init = tf.keras.initializers.random_uniform(minval=0., maxval=1.)
self.alpha = tf.Variable(
name="alpha",
initial_value=alpha_init(shape=(1,), dtype="float32"),
constraint=tf.keras.constraints.min_max_norm(0, 1),
trainable=True
)
def call(self, inputs):
def predict_one_step(y_previous, alpha, yhat_previous):
return alpha * y_previous + (1 - alpha) * yhat_previous
### оригинал
#yhat_previous = inputs[0]
#predictions = [yhat_previous]
### предлагаемая модификация ###
yhat_previous = [inputs[0]]
predictions = [tf.convert_to_tensor(yhat_previous, dtype="float32")]
for timestep in range(1, inputs.shape[0]):
yhat = predict_one_step(inputs[timestep - 1], self.alpha, yhat_previous)
yhat_previous = yhat
predictions.append(yhat)
return tf.concat(predictions, axis=-1)
Ответ или решение
Конечно, давайте рассмотрим вашу реализацию пользовательского слоя для Простого Экспоненциального Сглаживания (SES) и исправим возникшую проблему.
В вашем первоначальном коде основная ошибка заключается в том, что вы используете текущие входные данные inputs[timestep]
вместо предыдущих значений inputs[timestep - 1]
для y_previous
в методе call
. Это приводит к тому, что предсказания становятся слишком близкими к текущим входным значениям, особенно когда α (коэффициент сглаживания) стремится к 1. Посмотрим на исправленный код:
import tensorflow as tf
from tensorflow.keras.layers import Layer
class SES(Layer):
def __init__(self, dtype=tf.float32):
super(SES, self).__init__()
def build(self, input_shape):
alpha_init = tf.keras.initializers.random_uniform(minval=0., maxval=1.)
self.alpha = tf.Variable(
name="alpha",
initial_value=alpha_init(shape=(1,), dtype="float32"),
constraint=tf.keras.constraints.min_max_norm(0, 1),
trainable=True
)
def call(self, inputs):
def predict_one_step(y_previous, alpha, yhat_previous):
return alpha * y_previous + (1 - alpha) * yhat_previous
# Инициализация предсказания для первого шага
yhat_previous = inputs[0]
predictions = [tf.convert_to_tensor(yhat_previous, dtype="float32")]
# Начиная с 1, так как 0-й шаг уже обработан
for timestep in range(1, inputs.shape[0]):
yhat = predict_one_step(inputs[timestep - 1], self.alpha, yhat_previous)
yhat_previous = yhat
predictions.append(yhat)
return tf.concat(predictions, axis=-1)
Объяснение исправлений:
-
Использование правильного предыдущего значения: Мы изменили строчку кода, чтобы в
predict_one_step
передаватьinputs[timestep - 1]
, что корректно учитывает предыдущее значение на этапе предсказания. Ранее вы использовали текущее значение, что создавало незаслуженно низкие ошибки при обучении, так как новейшие значения не давали возможности слежения за трендом. -
Инициализация первого предсказания: Вместо инициализации
yhat_previous
в теле цикла, мы сделали это до начала цикла, используя значение первого элементаinputs
. Это позволяет избежать ошибок при расчете на первом шаге. -
Конкатенация предсказаний: Все предсказания собираются в список и затем конкатенируются для возврата на выходе.
Теперь, после внесенных изменений, ваш слой SES должен корректно обучаться, и значение α не должно конвергировать к 1, что позволит ему учитывать как предыдущее значение y
, так и предсказанное yhat
. Это должно привести к более гармоничному первому шагу предсказания и улучшить общую производительность модели.
Если у вас останутся дополнительные вопросы или понадобится более подробное разъяснение, пожалуйста, дайте знать!