본문 바로가기
딥러닝으로 하루하루 씹어먹기

LSTM으로 수요 예측하기 - 5장 (K-Fold 사용)

by Yoo Sung Hyun 2021. 4. 12.
728x90

1. 시간별로 데이터 전처리 / Not Scaled

2. 일반 lstm 3레이어 정도(?)

3. 이후 data도 중요하다고 생각듬

4. BI-LSTM 사용

5. 데이터가 너무 없어 K-Fold 적용

6. Layer만 주구장창 쌓다가 AutoEncoder 활용

7. 데이터 Scaled 적용

8. Loss Function Custom

9. 1차 마무리

  - 여기 사이에는 BERT가 포함되어있다. (Google Source를 다 까봤는데, 이건 언제 또 정리할까...) -

10. Attention 적용

  => Dual-Stage Attention RNN (DA-RNN)

  => Luong Attention (Dot-Product Attention)

  => Bahdanau Attention                                         => Attention은 Time-Series Forecasting Regression 문제에서 적합하지 않은 것으로 판단된다. (추후에 추가 포스팅 할 예정.)

 

BI-LSTM을 사용하기로 한 이후에, 단순히 레이어 수와 units 등으로 조절하면서, trainable parameter 개수 정도나 수정하여, 모델을 개선할 수 있을 것이라 생각했다.

하지만 현실은 그렇게 녹록치 않은 법. 분명히 한계는 존재했고 그를 타파해야만 했다.

 

그나마 여기부터는 좀 소스가 남아있다... 뭐 이런식으로 짰었던것 같음.

stateful은 썼다가 시간대비 학습율이 좋지못해 나중가서 뺐던 걸로 기억

model = Sequential()
model.add(Bidirectional(LSTM(64, return_sequences=True, stateful=True), batch_input_shape=(batch_size, past_history,x_concat_data.shape[2])))
model.add(BatchNormalization())
model.add(Bidirectional(LSTM(64, return_sequences=True, stateful=True)))
model.add(BatchNormalization())
model.add(Bidirectional(LSTM(64, return_sequences=True, stateful=True)))
model.add(BatchNormalization())
model.add(Bidirectional(LSTM(64, return_sequences=True, stateful=True)))
model.add(TimeDistributed(Dense(64, activation=LeakyReLU())))
# model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(TimeDistributed(Dense(64, activation=LeakyReLU())))
# model.add(Dropout(0.2))
model.add(BatchNormalization())

model.add(TimeDistributed(Dense(future_target)))

model.summary()

사실 이쯤에서 슬슬 Encoder-Decoder 형태의 Seq2Seq 형태로 구현을 했어야 했는가에 대한 고민을 좀 하기 시작했다.

하지만, 가지고있는 현업 데이터도 많지 않았었기에, (3년치 24시간 데이터정도.) 일단 가장 쉬운 방법부터 접근해보자 라고 생각했다. (valid 셋까지 다 활용할 수 있게 학습시켜보자!!)

 

K-Fold를 도입하기 전에는 참 여러가지 방식으로 데이터를 학습시켜봤는데,

예를들면,

2018->2019->2020 이렇게 예측하게 학습을 시킨다거나

2018 1분기 -> 2018 2분기 -> .... 이렇게 학습을 시킨다거나

2018 상반기 -> 2018 하반기 -> 2019 상반기 -> ... 이렇게 학습을 시킨다거나

20180101 -> 20180102 -> 20180103 -> ... 이렇게 학습을 시킨다거나

아주 여러가지 방식으로 학습을 시키고 있었다. (이거말고도 진짜 엄청많음)

 

이 예측 target에 대한 문제는, 현업이 정확하게 어떤 기간의 데이터를 언제 제공받고싶은지에 대한 것이 명확했으면(현업 요구사항이 명확했으면), 그렇게 제공이 가능했을테지만, 현재 여기 현업은 직급있는 누군가가 경험에 의해서 수요를 예측하다보니, 요구사항이 그냥 '되는데로 주세요.' 와 같이 상당히 두루뭉술하였다.

 

때문에, 내가 생각한 것은, 그러면 '가장 정확도가 높은 기간으로 선별해서 적당히 제공기간 GAP이 존재하도록 줘야겠다.' 라는 판단이었고, 때문에 여러가지 데이터 패턴 케이스를 전부 돌려볼 수 밖에 없었다. (다행이 작업환경 하드웨어 스펙이 좋아서, 여러 모델을 병렬적으로 마구 돌려볼 수 있었다.)

 

대략 6~10월 사이에 증가함

또한 내가 판단하기에, 상단에 그래프만봐도, 뭔가 뚜렷한 패턴이 잘 보이지 않았거니와, ARIMA 등을 이용하여 시계열 분리를 좀 해서 분석해볼까도 싶었지만은, 모델을 뽑아내기에도 급급한 일정이라, 복잡하게 EDA를 해볼 겨를이 없었다.

 

그나마도 연도별 기간으로 봤을때 6~10월사이(휴가시즌)에 일반적으로 증가하는 양상을 보였으며, 때문에 내 생각에는,

 가급적이면 1년치씩 데이터를 전부 한번씩은 train에 활용하면 좋을 것 같다는 생각이 들었다.

 

그래서 k-fold를 사용하려고 했는데, 그러려면 몇가지 유의사항이 존재할 것 같았다.

1. shuffle을 하지 않더라도, 기간별로 짤려서 순서상 학습이 될 수 있으니, train set의 feature/label을 구성하는데 있어, shuffle의 영향이 없도록 설계.

  -> 실제로 1,2,3월이 valid로 사용되면 4~12월까지 train하고 1,2,3월로 valid하게 된다.

      4~12월의 기간을 학습한 것이, 1,2,3월을 valid한 score로 적용되었을때, 문제가 없겠는가?

  => 사실 곰곰히 생각해보면, 문제 한개와 정답한개를 한 보자기에 넣고 그 보자기를 넣은 항아리가 여러개 있다고 가정할때, 그 항아리들을 섞는다고해서 보자기 안에 문제와 정답이 뒤섞이지는 않는다. 그래서 문제가 없다.

  => 만일 계절성이 큰 영향을 미친다면, 4~12월의 데이터의 계절성으로 1,2,3월을 설명할 수는 없게 될지도 모른다. (이부분에 대해서는, 초반에 MAE나 RMSE를 loss로 사용하다가, 추후에 MASE를 조금 변형해서 사용하게되는 계기가 된다. 다다음장에서 설명.)

2. 필요 메모리와 학습시간이 증대하는 것을 trade-off 할만한 의미있는 작업인가?

  => 이건 해봐야 아는건데, 필자는 의미있었다.

 

추가적으로도 더 있었던것 같은데, 작년에 작업했던거라 기억이 가물가물하다....ㅋㅋ 필자는 사실 1번에 대한걸 가장 많이 고민하고 진행했다. (왜냐면 시계열 데이터인데 shuffle이 되면 쓰나....)

 

K-Fold야 데이터 구성에 대한 고민의 시간이 길었을 뿐, 소스는 매우 간략하기 때문에,

def train(model, custom_loss, x_concat_data, y_concat_data, epoch=200, batch_size=24, n_splits=5, kf_shuffle=True):
        from sklearn.model_selection import KFold
        skf = KFold(n_splits=n_splits, shuffle=kf_shuffle)
                    
        # 계산과 수행
        with tf.device('/gpu:0'):
            accuracy = []
            model.compile(loss='mean_absolute_percentage_error', optimizer=Adam(lr=0.001))
            for train, validation in skf.split(x_concat_data, y_concat_data):
                print('train valid rate :', len(train),len(validation))
                model.fit(x_concat_data[train], y_concat_data[train], batch_size=batch_size, epochs=epoch, verbose=2, shuffle=True)

                score = model.evaluate(x_concat_data[validation], y_concat_data[validation], batch_size=batch_size)
                accuracy.append(score)


            print('\nK-fold cross validation score: {}'.format(accuracy))

        return model, accuracy

해당 부분은 혹시나 문제될 수 있어 몇가지 소스들은 실제 적용된것과 다르게 수정해놓았다. 그대로 사용하면 잘 안되거나, 기대한 동작이 안될 수 있음에 참고하자.

 

뭐 이런식으로 간단하게 짜줬다.

 

내 기억에 K-Fold는 소스는 간단하니 그냥 가볍게 넣고 돌려봤고, 결과는 조금 더 잘 맞았던것 같다. 근데도 역시 만족스럽지는 않아서, VAE까지 전부 적용시켜놓고, 거기부터는 그래프도 좀 결과가 남겨놓았던 것 같다.

(왜냐면, 이쯤부터 좀 팔아먹을 수 있을 각이 보였으니까...)

 

지금까지는 대부분 말로 설명하기 바빴는데, 이제 다음 장으로 넘어갈 수록 좀 더 유의미한 소스와 결과들을 확인할 수 있을 것 같다!

728x90

댓글