Многошаговые прогнозы данных заводского производства с использованием модели Seq2Seq Encoder-Decoder с вниманием

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

Я пытаюсь использовать модель Seq2Seq для прогнозирования данных производства на фабрике, используя модель Encoder-Decoder с добавлением механизма внимания. Я немного застрял, так как выход модели, похоже, является константой и имеет такую же длину последовательности, что и вход, тогда как на самом деле я хотел бы иметь возможность указать, что, скажем, хочу прогнозировать на 3 (или любое другое количество) месяцев вперед.

Вот 2 диаграммы архитектуры Seq2Seq и механизма внимания, которые я хочу построить:

Архитектура Seq2Seq

Внимание

Цель
Насколько я понимаю, я хочу прогнозировать объем производства определенного материала с этой фабрики на будущее. Таким образом, его размерность равна $1$ и это, конечно, целое число.

Энкодер
Энкодер принимает на вход последовательность длиной $168$, где каждый вход представляет собой данные за $20$ предыдущих дней, а также $37$ фабричных характеристик, таких как количество работников и т.д.

Декодер
Здесь я запутался и сталкиваюсь с проблемами в своем коде. Опять же, насколько я понимаю, декодер должен принимать на вход уровни производства за предыдущие временные шаги (что означает размерность $1$), а также предыдущие скрытое состояние и ячейку.

Код

class EncoderRNN(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, p):
        super(EncoderRNN, self).__init__()

        self.lstm = nn.LSTM(input_size, hidden_size,
                            num_layers, dropout = p, 
                            bidirectional = True)

        self.fc_hidden = nn.Linear(hidden_size*2, hidden_size) 
        self.fc_cell = nn.Linear(hidden_size*2, hidden_size)

    def forward(self, input):
        print(f"Форма входа энкодера: {input.shape}")

        encoder_states, (hidden, cell_state) = self.lstm(input)

        print(f"Скрытое состояние энкодера: {hidden.shape}")
        print(f"Состояние ячейки энкодера: {cell_state.shape}")

        hidden = self.fc_hidden(torch.cat((hidden[0:1], hidden[1:2]), dim = 2))
        cell = self.fc_cell(torch.cat((cell_state[0:1], cell_state[1:2]), dim = 2))

        print(f"Скрытое состояние энкодера: {hidden.shape}")
        print(f"Состояние ячейки энкодера: {cell.shape}")

        return encoder_states, hidden, cell

class Decoder_LSTMwAttention(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, output_size, p):
        super(Decoder_LSTMwAttention, self).__init__()

        self.rnn = nn.LSTM(hidden_size*2 + input_size, hidden_size,
                           num_layers)

        self.energy = nn.Linear(hidden_size * 3, 1)
        self.fc = nn.Linear(hidden_size, output_size)
        self.softmax = nn.Softmax(dim=0)
        self.dropout = nn.Dropout(p)
        self.relu = nn.ReLU()  

        self.attention_combine = nn.Linear(hidden_size, hidden_size)

    def forward(self, input, encoder_states, hidden, cell):
        input = input.unsqueeze(0)
        input = input.unsqueeze(0)

        input = self.dropout(input)

        sequence_length = encoder_states.shape[0]
        h_reshaped = hidden.repeat(sequence_length, 1, 1)

        concatenated = torch.cat((h_reshaped, encoder_states), dim = 2)
        print(f"Размер сопряженного: {concatenated.shape}")

        energy = self.relu(self.energy(concatenated))
        attention = self.softmax(energy)
        attention = attention.permute(1, 0, 2)

        encoder_states = encoder_states.permute(1, 0, 2)

        context_vector = torch.einsum("snk,snl->knl", attention, encoder_states)

        rnn_input = torch.cat((context_vector, input), dim = 2)

        output, (hidden, cell) = self.rnn(rnn_input, hidden, cell)

        output = self.fc(output).squeeze(0)

        return output, hidden, cell

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder):
        super(Seq2Seq, self).__init__()
        self.encoder = encoder
        self.decoder = decoder

    def forward(self, source, target, teacher_force_ratio=0.5):
        batch_size = source.shape[1]
        target_len = target.shape[0]

        outputs = torch.zeros(target_len, batch_size).to(device)
        encoder_states, hidden, cell = self.encoder(source)

        x = target[0]

        for t in range(1, target_len):
            output, hidden, cell = self.decoder(x, encoder_states, hidden, cell)

            outputs[t] = output

            best_guess = output.argmax(1)

            x = target[t] if random.random() < teacher_force_ratio else best_guess

        return outputs

Режим обучения

def Seq2seq_trainer(model, optimizer, train_input, train_target,
                  test_input, test_target, criterion, num_epochs):

    train_losses = np.zeros(num_epochs)
    validation_losses = np.zeros(num_epochs)

    for it in range(num_epochs):
        optimizer.zero_grad()

        outputs = model(train_input, train_target)  
        loss = criterion(outputs, train_target)

        loss.backward()

        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1)

        optimizer.step()

        train_losses[it] = loss.item()

        test_outputs = model(test_input, test_target)
        validation_loss = loss_function(test_outputs, test_target)
        validation_losses[it] = validation_loss.item()

        if (it + 1) % 25 == 0:
            print(f'Эпоха {it+1}/{num_epochs}, Обучающая потеря: {loss.item():.4f}, Потеря валидации: {validation_loss.item():.4f}')

    return train_losses, validation_losses

Результаты, которые я получаю

Проблема, похоже, в том, что декодер предсказывает одно и то же значение каждый раз и не улавливает шум в данных.

введите описание изображения здесь

Разбирая ряд вопросов, во-первых

Я хочу прогнозировать на 3 месяца вперед.

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

Декодер должен принимать уровни производства за предыдущие временные шаги на вход (что означает размерность 1), а также предыдущее скрытое состояние и состояние ячейки.

Я думаю, что вы смешиваете архитектуру пуллинга внимания с концепциями самовнимания/архитектуры трансформеров.

  1. В типичном RNN с энкодером/декодером декодер должен принимать скрытое состояние, выходящее из каждой LSTM-ячейки/временного шага. Если вы не пытаетесь использовать более экспериментальный выход, состояние ячейки, а также входные данные не должны передаваться декодеру. Если вы используете внимание Лунга/аддитивный пуллинг внимания, то снова только скрытое состояние необходимо для его вычисления. Я не уверен, что ваш метод внимания полностью правильный. Я вставляю ниже пример того, как ваш декодер с аддитивным вниманием должен выглядеть:
class AttnDecoderRNN(nn.Module):
    """
    Courtesy of https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html
    """

    def __init__(self, hidden_size, output_size, dropout_p=0.1, max_length=MAX_LENGTH):
        super(AttnDecoderRNN, self).__init__()
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.dropout_p = dropout_p
        self.max_length = max_length

        self.attn = nn.Linear(self.hidden_size * 2, self.max_length)
        self.attn_combine = nn.Linear(self.hidden_size * 2, self.hidden_size)
        self.dropout = nn.Dropout(self.dropout_p)
        self.gru = nn.GRU(self.hidden_size, self.hidden_size)
        self.out = nn.Linear(self.hidden_size, self.output_size)

    def forward(self, input, hidden, encoder_outputs):

        attn_weights = F.softmax(
            self.attn(torch.cat((embedded[0], hidden[0]), 1)), dim=1)
        attn_applied = torch.bmm(attn_weights.unsqueeze(0),
                                 encoder_outputs.unsqueeze(0))

        output = torch.cat((embedded[0], attn_applied[0]), 1)
        output = self.attn_combine(output).unsqueeze(0)

        output = F.relu(output)
        output, hidden = self.gru(output, hidden)

        output = F.log_softmax(self.out(output[0]), dim=1)
        return output, hidden, attn_weights

    def initHidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)
  1. Правильно, вы утверждаете, что входные данные и скрытое состояние должны передаваться декодеру, и я полагаю, вы имеете в виду архитектуру самовнимания. Для этой модели нет единиц декодера/декодера, но каждый вход абстрагируется в латентные пространства запроса, ключа и значения, как на следующей диаграмме:
    введите описание изображения здесь
    Вы можете прочитать больше об архитектуре самовнимания здесь https://towardsdatascience.com/self-attention-5b95ea164f61, прежде чем решите, как хотите продолжать решать вашу проблему.

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

Прогнозирование данных о производстве на заводе с помощью модели Seq2Seq Encoder-Decoder с механизмом внимания

Вы столкнулись с задачей многослойного прогнозирования данных о производстве на заводе, используя архитектуру Seq2Seq с механизмом внимания. Ваша цель — предсказать объем производства в течение следующего времени, например, на три месяца вперед. При этом важно правильно настроить модель, чтобы избежать постоянных значений в выходных данных, которые не отражают реальную динамику.

Основные элементы модели

  1. Цель прогноза: Необходимо предсказать объем производства, который является одномерным значением (целым числом).

  2. Энкодер: На вход энкодера подается последовательность длиной 168, включающая данные за последние 20 дней и 37 параметров, касающихся производственного процесса (например, количество работников).

  3. Декодер: Здесь происходят основные сложности. Декодер должен принимать предыдущие уровни производства как входные данные, а также скрытые и ячеистые состояния из энкодера.

Предложенные изменения

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

  2. Замены в строках кода:

    • В вашем текущем подходе вы используете "teacher forcing", что хорошо, но необходимо убедиться, что декодер получает на вход именно те значения, которые он должен предсказывать.
    • Вы должны структурировать декодер так, чтобы он генерировал последовательность выходных значений (например, три шага), а не одномерный список, который повторяется для каждого временного шага.
  3. Обновление функции forward: Чтобы декодер предсказывал значения на три месяца вперед, можно изменить цикл в методе forward класса Seq2Seq, чтобы он генерировал прогноз на несколько временных шагов:

def forward(self, source, target, teacher_force_ratio=0.5):
    batch_size = source.shape[1]
    target_len = <number_of_months>  # Укажите количество шагов, на которые хотите прогнозировать
    outputs = torch.zeros(target_len, batch_size, self.decoder.output_size).to(device)

    encoder_states, hidden, cell = self.encoder(source)

    # Начальное значение для декодера (например, токен начала последовательности)
    x = target[0]

    for t in range(target_len):
        output, hidden, cell = self.decoder(x, encoder_states, hidden, cell)

        # Сохраняем предсказание для текущего времени
        outputs[t] = output

        # Получаем лучшее предсказание
        best_guess = output.argmax(1)

        # Используем teacher forcing с вероятностью teacher_force_ratio
        x = target[t] if random.random() < teacher_force_ratio else best_guess

    return outputs

Проблемы с предсказанием постоянного значения

Проблема с предсказанием постоянного значения может быть вызвана несколькими факторами:

  • Проблемы с данными: Проверьте масштабирование и нормализацию входных данных. Перепроверьте, что данные поступают правильно и не содержат аномалий.

  • Переобучение или недообучение: Если ваша модель слишком сложная или слишком простая, она может не распознавать зависимости. Это может быть решено регулированием гиперпараметров или изменением архитектуры модели.

  • Коэффициенты потерь: Следите за значениями функции потерь при обучении. Если значения остаются слишком высокими или не меняются, это может указывать на проблемы в настройках модели или данных.

Заключение

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

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

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