Пользовательский слой для простого экспоненциального сглаживания

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

Я пишу тестовый пользовательский слой, который реализует алгоритм простого экспоненциального сглаживания.
Проблема: когда я его обучаю, коэффициент альфа (сглаживание) всегда сходится к значению 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)

Объяснение исправлений:

  1. Использование правильного предыдущего значения: Мы изменили строчку кода, чтобы в predict_one_step передавать inputs[timestep - 1], что корректно учитывает предыдущее значение на этапе предсказания. Ранее вы использовали текущее значение, что создавало незаслуженно низкие ошибки при обучении, так как новейшие значения не давали возможности слежения за трендом.

  2. Инициализация первого предсказания: Вместо инициализации yhat_previous в теле цикла, мы сделали это до начала цикла, используя значение первого элемента inputs. Это позволяет избежать ошибок при расчете на первом шаге.

  3. Конкатенация предсказаний: Все предсказания собираются в список и затем конкатенируются для возврата на выходе.

Теперь, после внесенных изменений, ваш слой SES должен корректно обучаться, и значение α не должно конвергировать к 1, что позволит ему учитывать как предыдущее значение y, так и предсказанное yhat. Это должно привести к более гармоничному первому шагу предсказания и улучшить общую производительность модели.

Если у вас останутся дополнительные вопросы или понадобится более подробное разъяснение, пожалуйста, дайте знать!

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

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