본문 바로가기
Deep Learning/Hands On Machine Learning

15.2 RNN 훈련하기

by 대소기 2021. 11. 21.

 

 

 

BPTT(Backpropagation through time)

* time step으로 네트워크를 펼치고 보통의 역전파를 사용함으로서 RNN을 훈련시키는 방법을 뜻한다. 하지만 RNN 클래스에서는 이러한 펼치는 작업을 따로 하지는 않는다. 다만 역전파 설명을 돕기 위해 펼쳐놓았다고 표현하는 것이다.

* 일반적인 Backpropagation 과정과 동일하게 정방향 계산을 통해서 도출한 output으로 cost함수를 계산하고, 이를 다시 역방향 계산하여 가중치를 update시키는 방식을 동일하게 사용한다.

* 위와 같이 $Y_0, Y_1$을 무시하고 일부 output들만 cost 계산에 사용할 수 있다. output 반영 여부는 network마다 다를 것이다. 예를 들어 sequence to vector network는 마지막 output을 제외한 모든 output은 cost계산에 사용되지 않는다.

 

15.3시계열 예측하기

 

시계열(Time Series)

* 시계열 : 일정 timestep마다 하나 이상의 값을 가진 데이터

* 단변량 시계열(univariate time series) : time step마다 하나의 값을 가지는 데이터. 예) 웹사이트 시간당 접속자 수

* 다변량 시계열(multivariate time series) : time step마다 하나 이상의 값을 가지는 데이터. 예) 여러 지표를 사용한 기업의 분기별 재정 안정성(안정성에 대한 독립변수가 여러 개 있을 것이다)

 

def generate_time_series(batch_size, n_steps):
    freq1, freq2, offsets1, offsets2 = np.random.rand(4, batch_size, 1)
    # time은 (50,) offsets1은 (10000, 1)이다. 브로드캐스팅으로 인해 time이 (10000, 50)으로 취급되어 계산된다.
    series = 0.5 * np.sin((time - offsets1) * (freq1 * 10 + 10))  #   sin 웨이브 1
    series += 0.2 * np.sin((time - offsets2) * (freq2 * 20 + 20)) # + sin 웨이브 2
    series += 0.1 * (np.random.rand(batch_size, n_steps) - 0.5)   # + 잡음
    return series[..., np.newaxis].astype(np.float32) #shape = (10000, 51, 1)
    
# 50번째 데이터까지는 X에 51번째 예측해야될 데이터는 y에 배정
np.random.seed(42)

n_steps = 50
series = generate_time_series(10000, n_steps + 1)
X_train, y_train = series[:7000, :n_steps], series[:7000, -1]
X_valid, y_valid = series[7000:9000, :n_steps], series[7000:9000, -1]
X_test, y_test = series[9000:, :n_steps], series[9000:, -1]

* generate_time_series 함수를 통해 데이터를 생성하고 각 row의 50번째 column까지는 X에 할당, 51번째 row는 target으로 취급해 y에 할당하였다.

* X_valid의 0번째, 1번째, 2번째 데이터를 그려보면 아래와 같다.

 

 

 

* 위 그림은 3개의 단변량 시계열로 각 시계열의 길이는 time step 50개이다. X로 표시된 부분의 값을 예측하는 것이 주어진 시계열의 분석 목표가 될 것이다.

 

15.3.1 기준성능

* 기준 성능은 성능을 평가하기 위한 지표로 모델 생성 이전에 설정해놓는 것이 좋다.

* 기준 성능을 계산하는 방법은 대표적으로 아래 2가지이다.

 

1) 순진한 예측(naive forecasting)

y_pred = X_valid[:, -1]
np.mean(keras.losses.mean_squared_error(y_valid, y_pred)) #0.020211367

* 순진한 예측 방법은 각 시계열의 마지막 값을 그대로 예측 값으로 사용하는 것이다. 

* 위와 같이 X_valid의 각 row들의 마지막 값, 즉 예측 값보다 1 time step 전의 데이터를 예측값으로 사용해 mse를 구하여 기준 성능으로 사용할 수 있다.

* 우리가 앞으로 만들 예측 모델은 mse가 약 0.02보다는 작아야 할 것이다.

 

 

2) Fully Connected Network 사용

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[50, 1]),
    keras.layers.Dense(1)
])

model.compile(loss="mse", optimizer="adam")
history = model.fit(X_train, y_train, epochs=20,
                    validation_data=(X_valid, y_valid))

# Epoch 1/20
# 219/219 [==============================] - 1s 3ms/step - loss: 0.1398 - val_loss: 0.0545
# Epoch 2/20
# 219/219 [==============================] - 0s 690us/step - loss: 0.0443 - val_loss: 0.0266
# Epoch 3/20
# 219/219 [==============================] - 0s 631us/step - loss: 0.0237 - val_loss: 0.0157
# Epoch 4/20
# 219/219 [==============================] - 0s 738us/step - loss: 0.0142 - val_loss: 0.0116
# Epoch 5/20
# 219/219 [==============================] - 0s 740us/step - loss: 0.0110 - val_loss: 0.0098
# Epoch 6/20
# 219/219 [==============================] - 0s 615us/step - loss: 0.0093 - val_loss: 0.0087
# Epoch 7/20
# 219/219 [==============================] - 0s 590us/step - loss: 0.0083 - val_loss: 0.0079
# Epoch 8/20
# 219/219 [==============================] - 0s 581us/step - loss: 0.0074 - val_loss: 0.0071
# Epoch 9/20
# 219/219 [==============================] - 0s 562us/step - loss: 0.0064 - val_loss: 0.0066
# Epoch 10/20
# 219/219 [==============================] - 0s 570us/step - loss: 0.0063 - val_loss: 0.0062
# Epoch 11/20
# 219/219 [==============================] - 0s 576us/step - loss: 0.0059 - val_loss: 0.0057
# Epoch 12/20
# 219/219 [==============================] - 0s 645us/step - loss: 0.0054 - val_loss: 0.0055
# Epoch 13/20
# 219/219 [==============================] - 0s 578us/step - loss: 0.0052 - val_loss: 0.0052
# Epoch 14/20
# 219/219 [==============================] - 0s 596us/step - loss: 0.0050 - val_loss: 0.0049
# Epoch 15/20
# 219/219 [==============================] - 0s 707us/step - loss: 0.0048 - val_loss: 0.0048
# Epoch 16/20
# 219/219 [==============================] - 0s 635us/step - loss: 0.0046 - val_loss: 0.0048
# Epoch 17/20
# 219/219 [==============================] - 0s 604us/step - loss: 0.0046 - val_loss: 0.0045
# Epoch 18/20
# 219/219 [==============================] - 0s 647us/step - loss: 0.0043 - val_loss: 0.0044
# Epoch 19/20
# 219/219 [==============================] - 0s 659us/step - loss: 0.0042 - val_loss: 0.0043
# Epoch 20/20
# 219/219 [==============================] - 0s 769us/step - loss: 0.0043 - val_loss: 0.0042

* 위와 같이 Sequential 모델을 만들어 사용할 수 있다. DNN의 경우 input이 1차원 벡터이기 때문에 Flatten 층을 추가해야 한다.

model.evaluate(X_valid, y_valid)

#63/63 [==============================] - 0s 414us/step - loss: 0.0042
#0.004168087150901556

* naive forecasting 보다는 높은 성능을 보이는 성능지표이다.

 

 

15.3.2 간단한 RNN 구현하기

 

model = keras.models.Sequential([
    keras.layers.SimpleRNN(1, input_shape=[None, 1])
])

optimizer = keras.optimizers.Adam(learning_rate=0.005)
model.compile(loss="mse", optimizer=optimizer)
history = model.fit(X_train, y_train, epochs=20,
                    validation_data=(X_valid, y_valid))

Epoch 1/20
# 219/219 [==============================] - 2s 5ms/step - loss: 0.1554 - val_loss: 0.0489
# Epoch 2/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0409 - val_loss: 0.0296
# Epoch 3/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0277 - val_loss: 0.0218
# Epoch 4/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0208 - val_loss: 0.0177
# Epoch 5/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0174 - val_loss: 0.0151
# Epoch 6/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0146 - val_loss: 0.0134
# Epoch 7/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0138 - val_loss: 0.0123
# Epoch 8/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0128 - val_loss: 0.0116
# Epoch 9/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0118 - val_loss: 0.0112
# Epoch 10/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0117 - val_loss: 0.0110
# Epoch 11/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0112 - val_loss: 0.0109
# Epoch 12/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0115 - val_loss: 0.0109
# Epoch 13/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0114 - val_loss: 0.0109
# Epoch 14/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0114 - val_loss: 0.0109
# Epoch 15/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0113 - val_loss: 0.0109
# Epoch 16/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0114 - val_loss: 0.0109
# Epoch 17/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0114 - val_loss: 0.0109
# Epoch 18/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0115 - val_loss: 0.0109
# Epoch 19/20
# 219/219 [==============================] - 1s 5ms/step - loss: 0.0115 - val_loss: 0.0109
# Epoch 20/20
# 219/219 [==============================] - 1s 4ms/step - loss: 0.0116 - val_loss: 0.0109

* Simple RNN모델을 통해 예측을 해 보았다.

* RNN은 어떤 sequence의 길이도 처리할 수 있기 때문에(RNN 뉴런이 반복적으로 입력을 받는다고 생각하면 이해하기 쉽다) input shape의 첫 번째 차원을 None으로 지정해도 된다.

* 보통 SimpleRNN모델의 activation function은 hyperbolic tangent를 사용한다.

* 위와 같은 간단한 RNN모델의 경우 입력 가중치, 은닉 상태(hidden state), 편향(bias) 총 3개의 가중치가 존재하게 된다.

* 위 RNN모델의 경우 cost계산에 쓰이는 output은 가장 마지막에 도출되는 output  $y_49$가 cost계산에 사용된다(이 층은 $y_49$ 를 출력한다).

 

RNN과 다른 시계열 예측 방법

* 시계열 예측에는 weighted moving average, autoregressive intergrated moving average, ARIMA 등의 모델이 사용될 수 있다. 이런 모델들이 trend와 seasonality를 제거하고 훈련을 한 다음 마지막 최종 예측 때 다시 trend와 seasonality를 더하는 것과는 달리 RNN은 trend와 seasonality와 관련된 작업을 할 필요가 없다. 

 

 

15.3.3 심층 RNN

* 위 그림과 같이 셀을 여러 층으로 쌓는 것을 심층 RNN이라고 한다.

 

심층 RNN 구현

 

np.random.seed(42)
tf.random.set_seed(42)

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20, return_sequences=True),
    keras.layers.SimpleRNN(1)
])

model.compile(loss="mse", optimizer="adam")
history = model.fit(X_train, y_train, epochs=20,
                    validation_data=(X_valid, y_valid))

# Epoch 1/20
# 219/219 [==============================] - 5s 17ms/step - loss: 0.1324 - val_loss: 0.0090
# Epoch 2/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0078 - val_loss: 0.0065
# Epoch 3/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0057 - val_loss: 0.0045
# Epoch 4/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0045 - val_loss: 0.0040
# Epoch 5/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0044 - val_loss: 0.0040
# Epoch 6/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0038 - val_loss: 0.0036
# Epoch 7/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0036 - val_loss: 0.0040
# Epoch 8/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0038 - val_loss: 0.0033
# Epoch 9/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0037 - val_loss: 0.0032
# Epoch 10/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0035 - val_loss: 0.0031
# Epoch 11/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0034 - val_loss: 0.0030
# Epoch 12/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0033 - val_loss: 0.0031
# Epoch 13/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0034 - val_loss: 0.0031
# Epoch 14/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0034 - val_loss: 0.0032
# Epoch 15/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0034 - val_loss: 0.0033
# Epoch 16/20
# 219/219 [==============================] - 3s 15ms/step - loss: 0.0037 - val_loss: 0.0030
# Epoch 17/20
# 219/219 [==============================] - 3s 14ms/step - loss: 0.0034 - val_loss: 0.0029
# Epoch 18/20
# 219/219 [==============================] - 3s 14ms/step - loss: 0.0031 - val_loss: 0.0030
# Epoch 19/20
# 219/219 [==============================] - 3s 14ms/step - loss: 0.0032 - val_loss: 0.0029
# Epoch 20/20
# 219/219 [==============================] - 3s 14ms/step - loss: 0.0033 - val_loss: 0.0029

* SimpleRNN층을 위와 같이 쌓으면 심층 RNN을 구현할 수 있다.

* 마지막 출력만 관심 대상일 경우엔 마지막 층에서는 return_sequence=True로 설정하지 않지만, 더 낮은 층에서는 설정해줘야 한다. 만약 return_sequence가 True가 아닐 경우 모든 time step에 대한 출력을 담은 3D 배열이 아니라 마지막 time step에 대한 출력만 담은2D배열이 출력되고 다음 순환 층이 3D 형태로 이전 층의 sequence를 받지 못하기 때문에 작동하지 못한다.

 

 

마지막 층이 Dense 층인 심층 RNN

* 위 심층 RNN은 univariate data를 예측하기 위해 만들어졌다. 때문에 마지막 RNN layer의 unit이 1개인데, 사실 이 마지막 layer가 꼭 RNN layer일 필요는 없다. 마지막 layer의 hidden state는 다음 layer로 전달될 일이 없기 때문에 굳이 RNN으로 구현하지 않아도 된다.

* 또한 RNN layer의 activation function은 hyperbolic tangent (tanh)이기 때문에 값이 -1부터 1까지로 제한된다.

* 위 두 가지의 이유로 인해 마지막 layer는 RNN보다 Dense로 구현하는 것이 더 유용하다. 

np.random.seed(42)
tf.random.set_seed(42)

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20),
    keras.layers.Dense(1)
])

model.compile(loss="mse", optimizer="adam")
history = model.fit(X_train, y_train, epochs=20,
                    validation_data=(X_valid, y_valid))

# Epoch 1/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0566 - val_loss: 0.0052
# Epoch 2/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0048 - val_loss: 0.0036
# Epoch 3/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0036 - val_loss: 0.0031
# Epoch 4/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0033 - val_loss: 0.0033
# Epoch 5/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0033 - val_loss: 0.0034
# Epoch 6/20
# 219/219 [==============================] - 3s 11ms/step - loss: 0.0031 - val_loss: 0.0029
# Epoch 7/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0030 - val_loss: 0.0034
# Epoch 8/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0033 - val_loss: 0.0028
# Epoch 9/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0031 - val_loss: 0.0028
# Epoch 10/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0029 - val_loss: 0.0029
# Epoch 11/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0029 - val_loss: 0.0027
# Epoch 12/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0029 - val_loss: 0.0031
# Epoch 13/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0029 - val_loss: 0.0031
# Epoch 14/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0031 - val_loss: 0.0030
# Epoch 15/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0030 - val_loss: 0.0030
# Epoch 16/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0030 - val_loss: 0.0027
# Epoch 17/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0030 - val_loss: 0.0028
# Epoch 18/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0029 - val_loss: 0.0027
# Epoch 19/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0029 - val_loss: 0.0028
# Epoch 20/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0030 - val_loss: 0.0026

 

15.3.4 여러 타임 스텝 앞을 예측하기

* 여러 time step을 예측하는 것은 여러가지 방법이 있다.

 

1) 예측값을 실제 값 처럼 취급해서 순차적으로 예측

* 우리가 지금까지 사용했던 모델을 그대로 사용하는 방식이다.

* 예측하려는 n step 중 가장 가까운 시점의  1 step 값을 예측하고, 이를 다시 input으로 넣어 그 다음 2 step 을 예측하는 방법이다. 이를 반복하면 n step까지 예측하는 것이 가능하다.

* 하지만 이 방법은 오차가 누적되기 때문에 정확도가 떨어진다는 단점이 있다.

 

n_steps=50
series = generate_time_series(1, n_steps + 10) #총 60개 data 생성
X_new, Y_new = series[:, :n_steps], series[:, n_steps:]
X = X_new
for step_ahead in range(10):
    #위에서 사용했던 심층 RNN 모델을 그대로 사용한다.
    y_pred_one = model.predict(X[:, step_ahead:])[:, np.newaxis, :] 
    print(y_pred_one)
    X = np.concatenate([X, y_pred_one], axis=1)

Y_pred = X[:, n_steps:]

 

 

2) 여러 step의 값을 한 번에 예측

* 예를 들어 10개의 step을 예측하고 싶다면 10개의 step을 한 번에 예측하는 방법이다.

* Sequence to Vector 모델을 이용하고, 예측값을 1개가 아닌 10개를 출력하도록 하기 위해서는 데이터셋의 target(y 값)을 10개의 값이 담긴 행 vector들로 구성된 matrix로 조정해줘야 한다.

n_steps = 50
series = generate_time_series(10000, n_steps + 10)
X_train, Y_train = series[:7000, :n_steps], series[:7000, -10:, 0]
X_valid, Y_valid = series[7000:9000, :n_steps], series[7000:9000, -10:, 0]
X_test, Y_test = series[9000:, :n_steps], series[9000:, -10:, 0]

* 그리고 RNN 모델의 마지막 층의 unit을 10개로 설정해줘야 한다.

 

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20),
    keras.layers.Dense(10)
])

model.compile(loss="mse", optimizer="adam")
history = model.fit(X_train, Y_train, epochs=20,
                    validation_data=(X_valid, Y_valid))
                    
# Epoch 1/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.1216 - val_loss: 0.0317
# Epoch 2/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0294 - val_loss: 0.0200
# Epoch 3/20
# 219/219 [==============================] - 3s 11ms/step - loss: 0.0198 - val_loss: 0.0160
# Epoch 4/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0162 - val_loss: 0.0144
# Epoch 5/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0144 - val_loss: 0.0118
# Epoch 6/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0127 - val_loss: 0.0112
# Epoch 7/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0119 - val_loss: 0.0110
# Epoch 8/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0114 - val_loss: 0.0103
# Epoch 9/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0110 - val_loss: 0.0112
# Epoch 10/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0118 - val_loss: 0.0100
# Epoch 11/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0109 - val_loss: 0.0103
# Epoch 12/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0104 - val_loss: 0.0096
# Epoch 13/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0103 - val_loss: 0.0100
# Epoch 14/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0101 - val_loss: 0.0103
# Epoch 15/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0095 - val_loss: 0.0107
# Epoch 16/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0095 - val_loss: 0.0089
# Epoch 17/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0092 - val_loss: 0.0111
# Epoch 18/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0098 - val_loss: 0.0094
# Epoch 19/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0090 - val_loss: 0.0083
# Epoch 20/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0092 - val_loss: 0.0085

 

* 첫 번째 방법보다는 더 좋은 성능을 보여주지만, 다음 소개할 마지막 방법의 성능이 가장 좋다.

 

3) 모든 time step에서 다음 값 10개 예측

* 기존의 sequence to vector model을 sequence to sequence model로 변경하면 모든 time step에서 다음 값 10개를 예측하는 것이 가능해진다.

* 이 경우 마지막 output만이 아니라 모든 time step의 output들이 cost에 반영되어 계산된다. 이는 더 많은 error gradients들이 모델을 시간에 상관없이 흐르게 된다는 것을 뜻하는데, 이로 인해 훈련이 안정되고, 속도가 올라갈 것이다.

* 먼저 dataset을 준비한다.

n_steps = 50
series = generate_time_series(10000, n_steps + 10)
X_train = series[:7000, :n_steps]
X_valid = series[7000:9000, :n_steps]
X_test = series[9000:, :n_steps]
Y = np.empty((10000, n_steps, 10)) # 50 by 10 matrix 가 10000개
for step_ahead in range(1, 10 + 1):
    Y[:, :, step_ahead - 1] = series[:, :, step_ahead:step_ahead + n_steps, 0]
Y_train = Y[:7000]
Y_valid = Y[7000:9000]
Y_test = Y[9000:]

* dataset의 target은 입력에 포함된 값을 가지게 된다. X_train의 shape은 (7000, 50, 1)이다. 그리고 Y_train의 shape은 (7000, 50, 10)이다. Y_train이 이런 shape을 가지게 된 이유는 X_train의 0번째 입력에 대한 target은 1~10까지 timestep의 예측 값이기 때문이다. 더 풀어 설명하자면 X_train 의 데이터는 7000개의 50x1의 time series vector이고, 각 vector 안에 있는 50개의 값들은 각각 target을 가지게되는데, 이 target이 다음 10 time step의 예측 값이 되는 것이다.

* dataset을 만들었으니 이제 model을 생성해보자.

np.random.seed(42)
tf.random.set_seed(42)

model = keras.models.Sequential([
    keras.layers.SimpleRNN(20, return_sequences=True, input_shape=[None, 1]),
    keras.layers.SimpleRNN(20, return_sequences=True),
    keras.layers.TimeDistributed(keras.layers.Dense(10))
])

def last_time_step_mse(Y_true, Y_pred):
    return keras.metrics.mean_squared_error(Y_true[:, -1], Y_pred[:, -1])

model.compile(loss="mse", optimizer=keras.optimizers.Adam(learning_rate=0.01), metrics=[last_time_step_mse])
history = model.fit(X_train, Y_train, epochs=20,
                    validation_data=(X_valid, Y_valid))
                    
# Epoch 1/20
# 219/219 [==============================] - 4s 12ms/step - loss: 0.0705 - last_time_step_mse: 0.0621 - val_loss: 0.0429 - val_last_time_step_mse: 0.0324
# Epoch 2/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0413 - last_time_step_mse: 0.0301 - val_loss: 0.0366 - val_last_time_step_mse: 0.0264
# Epoch 3/20
# 219/219 [==============================] - 3s 11ms/step - loss: 0.0333 - last_time_step_mse: 0.0223 - val_loss: 0.0343 - val_last_time_step_mse: 0.0244
# Epoch 4/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0306 - last_time_step_mse: 0.0200 - val_loss: 0.0284 - val_last_time_step_mse: 0.0164
# Epoch 5/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0281 - last_time_step_mse: 0.0167 - val_loss: 0.0282 - val_last_time_step_mse: 0.0196
# Epoch 6/20
# 219/219 [==============================] - 3s 11ms/step - loss: 0.0259 - last_time_step_mse: 0.0137 - val_loss: 0.0215 - val_last_time_step_mse: 0.0081
# Epoch 7/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0234 - last_time_step_mse: 0.0105 - val_loss: 0.0220 - val_last_time_step_mse: 0.0089
# Epoch 8/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0216 - last_time_step_mse: 0.0085 - val_loss: 0.0217 - val_last_time_step_mse: 0.0091
# Epoch 9/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0214 - last_time_step_mse: 0.0089 - val_loss: 0.0202 - val_last_time_step_mse: 0.0074
# Epoch 10/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0210 - last_time_step_mse: 0.0085 - val_loss: 0.0211 - val_last_time_step_mse: 0.0086
# Epoch 11/20
# 219/219 [==============================] - 3s 11ms/step - loss: 0.0203 - last_time_step_mse: 0.0078 - val_loss: 0.0201 - val_last_time_step_mse: 0.0078
# Epoch 12/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0203 - last_time_step_mse: 0.0079 - val_loss: 0.0194 - val_last_time_step_mse: 0.0073
# Epoch 13/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0198 - last_time_step_mse: 0.0074 - val_loss: 0.0209 - val_last_time_step_mse: 0.0085
# Epoch 14/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0197 - last_time_step_mse: 0.0073 - val_loss: 0.0189 - val_last_time_step_mse: 0.0067
# Epoch 15/20
# 219/219 [==============================] - 3s 12ms/step - loss: 0.0192 - last_time_step_mse: 0.0072 - val_loss: 0.0182 - val_last_time_step_mse: 0.0066
# Epoch 16/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0187 - last_time_step_mse: 0.0066 - val_loss: 0.0196 - val_last_time_step_mse: 0.0095
# Epoch 17/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0187 - last_time_step_mse: 0.0067 - val_loss: 0.0185 - val_last_time_step_mse: 0.0072
# Epoch 18/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0186 - last_time_step_mse: 0.0067 - val_loss: 0.0179 - val_last_time_step_mse: 0.0064
# Epoch 19/20
# 219/219 [==============================] - 3s 11ms/step - loss: 0.0185 - last_time_step_mse: 0.0068 - val_loss: 0.0172 - val_last_time_step_mse: 0.0058
# Epoch 20/20
# 219/219 [==============================] - 2s 11ms/step - loss: 0.0181 - last_time_step_mse: 0.0066 - val_loss: 0.0205 - val_last_time_step_mse: 0.0096

* 기존 모델을 sequence to sequence model로 바꾸기 위해서는 모든 순환 층, 마지막 dense층에도 return_sequence=True를 지정해줘야 한다. 그리고 모든 time step의 출력을 Dense에 적용해야 한다. 이를 위해 TimeDistributed layer를 사용하여 모든 time step에 대해 Dense를 적용한다.

* TimeDistributed를 적용하면, 각 time step을 별개의 sample처럼 다루도록 입력의 크기를 바꿔 이를 효과적으로 수행한다. 자세히 설명하자면 입력을 [배치 크기, 타입 스텝 수, 입력 차원]에서 [배치 크기 x 타입 스텝 수, 입력 차원] 크기로 바꾼다. 그리고 출력시엔 역으로 바꿔준다.

* 평가시 마지막 출력에 대한 MSE만을 계산하면 되기 때문에(훈련 동안에는 모든 출력을 사용하지만) 사용자 지표를 만들어서(last_time_step_mse) metrics에 적용하였다.