시계열 예측에서 lstm encoder-decoder 모델 출력값이 수렴하는 경우

안녕하세요
시계열 예측 문제에서 lstm encoder-decoder 모델을 사용하려고 합니다.
데이터는 슬라이딩 윈도우로 5일 학습, 2일 예측. 이런 방식으로 구성되고 단변량 데이터입니다.

image

하지만, 위 사진처럼 예측값들이 하나로 수렴됩니다.
이런 문제는 어떤 상황이라 할 수 있나요?

모델 코드는 아래와 같습니다.

class lstm_encoder(nn.Module):
  def __init__(self, input_size, hidden_size, num_layers = 1):
      super(lstm_encoder, self).__init__()
      self.input_size = input_size
      self.hidden_size = hidden_size
      self.num_layers = num_layers

      self.lstm = nn.LSTM(input_size = input_size, hidden_size = hidden_size, num_layers = num_layers, batch_first=True)

  def forward(self, x_input):
      lstm_out, self.hidden = self.lstm(x_input)
      return lstm_out, self.hidden

class lstm_decoder(nn.Module):
  def __init__(self, input_size, hidden_size, num_layers = 1):
      super(lstm_decoder, self).__init__()
      self.input_size = input_size
      self.hidden_size = hidden_size
      self.num_layers = num_layers

      self.lstm = nn.LSTM(input_size = input_size, hidden_size = hidden_size,num_layers = num_layers, batch_first=True)
      self.linear = nn.Linear(hidden_size, input_size)           

  def forward(self, x_input, encoder_hidden_states):
      lstm_out, self.hidden = self.lstm(x_input.unsqueeze(-1), encoder_hidden_states)
      output = self.linear(lstm_out)
      
      return output, self.hidden


class lstm_encoder_decoder(nn.Module):
  def __init__(self, input_size, hidden_size):
      super(lstm_encoder_decoder, self).__init__()

      self.input_size = input_size
      self.hidden_size = hidden_size

      self.encoder = lstm_encoder(input_size = input_size, hidden_size = hidden_size)
      self.decoder = lstm_decoder(input_size = input_size, hidden_size = hidden_size)

  def forward(self, inputs, targets, target_len, teacher_forcing_ratio):
      batch_size = inputs.shape[0]
      input_size = inputs.shape[2]

      outputs = torch.zeros(batch_size, target_len, input_size).to(DEVICE)

      _, hidden = self.encoder(inputs)
      decoder_input = inputs[:,-1, :]
      
      for t in range(target_len): 
          out, hidden = self.decoder(decoder_input, hidden)
          out =  out.squeeze(1)
          if random.random() < teacher_forcing_ratio:
              decoder_input = targets[:, t, :]
          else:
              decoder_input = out
          outputs[:,t,:] = out
      
      return outputs.squeeze()

  def predict(self, inputs, target_len):
      inputs = inputs
      self.eval()
      batch_size = inputs.shape[0]
      input_size = inputs.shape[2]
      outputs = torch.zeros(batch_size, target_len, input_size).to(DEVICE)
      _, hidden = self.encoder(inputs)
      decoder_input = inputs[:,-1, :]
      for t in range(target_len): 
          out, hidden = self.decoder(decoder_input, hidden)
          out =  out.squeeze(1)
          decoder_input = out
          outputs[:,t,:] = out
      return outputs[:,:,0]

제가 참조한 코드는 여기입니다.

좋아요 1

코드 자체가 오류가 있을 것으로 생각되지는 않는데요
이런 경우에 데이터가 부족이 문제인가? 문제가 자체가 어려운가?
학습 파라미터가 잘못되었나 등의 생각해야할 문제들이 아주 많습니다.

학습은 잘 수렴되었는지 학습 데이터에서도 해당 현상이 발생하는지 확인이 먼저 필요합니다.
그리고 문제가 너무 어렵다면 예측하려는 시간 폭을 줄여 보시는 것도 동작자체를 확인하는데는 좋을 것 같습니다.
발생할 수 있는 문제점이 많기 때문에 차분하게 변수를 줄여 나가는 접근이 필요할 것 같습니다.

추가로 시계열데이터 예측은 사실 쉽지 않은 문제이기, 예측이 어려운 상황에서 예측기가 평균값만을 출력하려는 경향이 있습니다. 평균값을 출력하면 MSE는 어느정도 맞출 수 있기 때문입니다. MSE 외에 다른 Loss도 추가해서 학습을 하는 것도 추천드립니다.

좋아요 3

어려운 질문이었는데, 정말 감사합니다.

학습데이터도 똑같은 현상이 보이네요.
폭을 줄인다는 것이,
예를들면, 분단위 데이터를 시간 당 최고값을 고른다. 이렇게 생각하면 될까요?

제가 이해하기에 @Sung_Sue_Hwang 님께서 말씀하신 시간 폭은 예측하려는 time step의 수를 말씀하시는 것 같습니다.

예를 들어서, 1시간 간격으로 수집한 데이터가 있을 때, 지금 구성하신 구조가 지난 5일치의 데이터를 가지고 앞으로 2일치의 데이터를 예측한다면 입력은 5 * 24 = 120, 출력은 2 * 24 = 48일 것입니다. 이 때 2일치가 아니라 12시간 또는 6시간과 같이 예측하려는 time step을 줄이는 것을 말씀하셨던 것 같습니다.


더불어 MSE에 대해서도 의견을 주셨는데, 시계열예측 모델에서는 MAE나 MAPE와 같은 loss function을 사용해보시면 어떨까요?

MAE(Mean Absolute Error)는 MSE와 다르게 절대값만 취하므로 예측값이 평균값만 출력하려는 경향이 다소 적을 것으로 추측됩니다. 정답과 예측값의 차이에 대해서 정답에 대한 비율만큼을 오차로 갖는 MAPE(Mean Absolute Percentage Error)도 마찬가지이구요. (| y - y_pred | / | y |와 같이 계산합니다.)


그 외에도 전처리에 대한 부분도 고려해보셔야 할 것 같은데요, 혹시 MinMaxScaler 외에 다른 방식도 고려해보셨을까요? 여러가지 시도 후에도 잘 안되신다면 모델을 바꿔보시는 것도 좋은 방법일 것 같습니다. :smiley:

좋아요 4