본문 바로가기

Lecture AI/6장.텍스트와 시퀀스를 위한 딥러닝

4. 온도예측 실습과 함께 알아보는 순환 신경망의 고급 사용법

반응형

순환 신경망의 성능과 일반화 능력을 향상시키기 위한 세 가지 고급 기술을 살펴보겠습니다. 

 

  • 순환 드롭아웃(recurrent dropout): 순환 층에서 과대적합을 방지하기 위해 케라스에 내장되어 있는 드롭아웃을 사용합니다.
  • 스태킹 순환 층(stacking recurrent layer): 네트워크의 표현 능력(representational power)을 증가시킵니다. (그 대신 계산 비용이 많이 듭니다.)
  • 양방향 순환 층(bidirectional recurrent layer): 순환 네트워크에 같은 정보를 다른 방향으로 주입하여 정학도를 높이고 기억을 좀 더 오래 유지시킵니다.

 


기온 예측 문제

본 예제에서는 막스 플랑크 생물지구화학 연구소의 지상 관측소에서 수집한 데이터를 대상으로 기온 예측에 대한 문제를 풀어보겠습니다. 데이터는 다음 링크에 존재합니다. 

import os

data_dir = './data/'
fname = os.path.join(data_dir, 'jena_climate_2009_2016.csv')

f = open(fname)
data = f.read()
f.close()

lines = data.split('\n')
header = lines[0].split(',')
lines = lines[1:]

print(header)
print(len(lines))

 

위의 데이터 전체를 넘파이 배열로 바꿉니다.

import numpy as np

float_data = np.zeros((len(lines), len(header) - 1))
for i, line in enumerate(lines):
    values = [float(x) for x in line.split(',')[1:]]
    float_data[i, :] = values

 

온도에 따른 변화 그래프를 그려봅니다.

from matplotlib import pyplot as plt

#온도(섭씨)
temp = float_data[:, 1]
plt.plot(range(len(temp)), temp)
plt.show()

 

다음은 기간을 좁혀서 처음 10일간 온도 데이터를 나타낸 그래프입니다. 이 그래프에서 일별 주기성을 볼 수 있습니다. 특히 마지막 4일간을 보면 확실합니다. 이 데이터는 아주 추운 겨울 중 10일입니다. 

 

지난 몇 달간 데이터를 사용하여 다음 달의 평균 온도를 예측하는 문제는 쉬운 편입니다. 연간 데이터 주기성은 안정적이기 때문입니다. 하지마 하루하루 데이터를 살펴보면 온도 변화는 매우 불안정합니다. 일자별 수준의 시계열 데이터를 예측할 수 있을까요?

plt.plot(range(1440), temp[:1440])
plt.show()

 


데이터 준비

 

lookback타임스텝만큼 이전으로 돌아가서 steps타임스텝마다 샘플링합니다. 이 데이터를 바탕으로 delay타임스텝 이후의 온도를 예측할 수 있을까요?

  • lookback = 1440: 10일 전 데이터로 돌아갑니다.
  • steps = 6: 1시간마다 데이터 포인트 하나를 샘플링합니다.
  • delay = 144: 24시간이 지난 데이터가 타깃이 됩니다.

 

이전에 해야할 작업은 다음과 같습니다. 

  • 신경망에 주입할 수 있는 형태로 데이터를 전처리합니다. 데이터가 이미 수치형이므로 추가적인 벡터화가 필요하지 않습니다. 하지만 데이터에 있는 각 시계열 특성의 범위가 서로 다릅니다. 각 시계열 특성을 개별적으로 정규화하여 비슷한 범위를 가진 작은 값으로 바꾸겠습니다.
  • float_data배열을 받아 과거 데이터의 배치와 미래 타깃 온도를 추출하는 파이썬 제너레이터(generator)를 만듭니다. 이 데이터셋에 있는 샘플은 중복이 많습니다. 모든 샘플을 각기 메모리에 적재하는 것은 낭비가 심하므로 대신에 원본 데이터를 사용하여 그떄그때 배치를 만들겠습니다.

 

각 시계열 특성에 대해 평균을 빼고 표준 편차로 나누어 전처리합니다. 처음 20만 개 타임스텝을 훈련 데이터로 사용할 것이므로 전체 데이터에서 20만 개만 사용하여 평균과 표준 편차를 계산합니다.

mean = float_data[:200000].mean(axis=0)
float_data -= mean
std = float_data[:200000].std(axis=0)
float_data /= std

 

아래의 코드는 여기서 사용할 제너레이터입니다. 이 제너레이터 함수는 (sample, targets) 튜플을 반복적으로 반환합니다. samples는 입력 데이터로 사용할 배치고 targets는 이에 대응되는 타깃 온도의 배열입니다. 이 제너레이터 함수에는 다음 매개변수가 있습니다.

def generator(data, lookback, delay, min_index, max_index,
              shuffle=False, batch_size=128, step=6):
    if max_index is None:
        max_index = len(data) - delay - 1
    i = min_index + lookback
    while 1:
        if shuffle:
            rows = np.random.randint(
                min_index + lookback, max_index, size=batch_size)
        else:
            if i + batch_size >= max_index:
                i = min_index + lookback
            rows = np.arange(i, min(i + batch_size, max_index))
            i += len(rows)

        samples = np.zeros((len(rows),
                           lookback // step,
                           data.shape[-1]))
        targets = np.zeros((len(rows),))
        for j, row in enumerate(rows):
            indices = range(rows[j] - lookback, rows[j], step)
            samples[j] = data[indices]
            targets[j] = data[rows[j] + delay][1]
        yield samples, targets
  • data: 정규화한 부동 소수 데이터로 이루어진 원본 배열
  • lookback: 입력으로 사용하기 위해 거슬러 올라갈 타임스텝
  • delay: 타깃으로 사용할 미래의 타임스텝
  • min_index와 max_index: 추출할 타임스텝의 범위를 지정하기 위한 data배열의 인덱스, 검증 데이터와 테스트 데이터를 분리하는 데 사용합니다. 
  • shuffle: 샘플을 섞을지, 시간 순서대로 추출할지를 결정합니다.
  • bactch_size: 배치의 샘플 수
  • step: 데이터를 샘플링할 타임스탭 간격, 1시간에 하나의 데이터 포인트를 추출하기 위해 6으로 지정하겠습니다.

 

이제 generator 함수를 사용하여 훈련용, 검증용, 테스트용으로 3개의 제너레이터를 만들어 보겠습니다. 각 제너레이터는 원본 데이터에서 다른 시간대를 사용합니다. 훈련 제너레이터는 처음 20만 개 타임스텝을 사용하고, 검증 제너레이터는 그다음 10만 개를 사용하고, 테스트 제너레이터는 나머지를 사용합니다.

lookback = 1440
step = 6
delay = 144
batch_size = 128
train_gen = generator(float_data,
                      lookback=lookback,
                      delay=delay,
                      min_index=0,
                      max_index=200000,
                      shuffle=True,
                      step=step,
                      batch_size=batch_size)
val_gen = generator(float_data,
                    lookback=lookback,
                    delay=delay,
                    min_index=200001,
                    max_index=300000,
                    step=step,
                    batch_size=batch_size)
test_gen = generator(float_data,
                     lookback=lookback,
                     delay=delay,
                     min_index=300001,
                     max_index=None,
                     step=step,
                     batch_size=batch_size)

#전체 검증 세트를 순회하기 위해 val_gen에서 추출할 횟수
val_steps = (300000 - 200001 - lookback)//batch_size

#전체 테스트 세트를 순회하기 위해 test_gen에서 추출할 횟수
test_steps = (len(float_data) - 300001 - lookback)//batch_size

 


상식 수준의 기준점

 

블랙 박스 같은 딥러닝 모델을 사용하여 온도 예측 문제를 풀기 전에 간단한 상식 수준의 해법을 시도해 보겠습니다. 이는 정상 여부 확인을 위한 용도고 고수준 머신 러닝 모델이라면 뛰어넘어야 할 기준점을 만듭니다. 

def evaluate_naive_method():
    batch_maes = []
    for step in range(val_steps):
        samples, targets = next(val_gen)
        preds = samples[:, -1, 1]
        mae = np.mean(np.abs(preds - targets))
        batch_maes.append(mae)
    print(np.mean(batch_maes))

evaluate_naive_method()

 

celsius_mae =  0.29 * std[1]
print(celsius_mae)

 


기본적인 머신 러닝 방법

머신 러닝 모델을 시도하기 전에 상식 수준의 기준점을 세워 놓았습니다. 비슷하게 RNN처럼 복잡하고 연산 비용이 많이 드는 모델을 시도하기 전에 간단하고 손쇱게 만들 수 있는 머신 러닝 모델을 먼저 만든는 것이 좋습니다. 이를 바탕으로 더 복잡한 방법을 도입하는 근거가 마련되고 실제적인 이득도 얻게 될 것입니다. 

 

다음 코드는 데이터를 펼쳐서 2개의 Dense층을 통과시키는 완전 연결 네트워크를 보여 줍니다. 전형적인 회귀 문제이므로 마지막 Dense층에 활성화 함수를 두지 않았습니다. 손실 함수는 MAE입니다. 상식 수준의 방법에서 사용한 것과 동일한 데이터와 지표를 사용했으므로 결과를 바로 비교해 볼 수 있습니다.

 

from keras.models import Sequential
from keras import layers

model = Sequential()
model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1])))
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer="rmsprop", loss='mae')
history = model.fit_generator(train_gen,
                              steps_per_epoch=500,
                              epochs=20,
                              validation_data=val_gen,
                              validation_steps=val_steps)

 

손실 그래프를 그려 보겠습니다.

import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(loss) + 1)

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

 

일부 검증 손실은 학습을 사용하지 않은 기준점에 가깝지만 안정적이지 못합니다. 앞서 기준 모델을 만든 것이 도움이 됩니다. 이 문제는 기준 모델의 성능을 앞지르기가 쉽지 않습니다. 우리가 적용한 상식에는 머신 러닝 모델이 찾지 못한 핵심 정보가 많이 포함되어 있습니다.

 

간단하고 괜찮은 성능을 내는 모델(상식 수준의 기준 모델)이 데이터와 타깃을 매핑할 수 있다면 왜 훈련한 모델은 이를 찾지 못하고 성능이 낮을까요? 훈련 과정이 찾는 것은 이런 간단한 모델이 아니기 때문입니다. 문제 해결을 위해 탐색하는 모델의 공간, 즉 가설 공간은 우리가 매개변수로 설정한 2개의 층을 가진 네트워크의 모든 가능한 가중치 조합입니다. 이 네트워크는 이미 매우 복잡합니다. 복잡한 모델 공간에서 해결책을 탐색할 때 간단하고 괜찮은 성능을 내는 모델은 찾지 못할 수 있습니다. 심지어 기술적으로 보았을 때, 이 가설 공간에 포함되어 있을 때조차도 말이죠.

 

이것이 일반적으로 머신 러닝이 가진 심각한 제약 사항입니다. 학습 알고리즘이 특정한 종류의 간단한 모델을 찾도록 하드코딩되지 않았다면, 모델 파라미터를 학습하는 방법은 간단한 문제를 위한 간략한 해결책을 찾지 못할 수 있습니다.

반응형