본문 바로가기
Study/Deep learning

Deep Learning(5)-RNN

by 왕방개 2024. 3. 22.

1.RNN

0) CNN 개념 정리 및 차이점

Batch Normalization: 최대한 정규분포와 비슷한 그래프로 만들어서 local minimum에 빠지는 경우의 수를 제거

Input, Flatten :  첫번째꺼는 input , Flatten은 1차원 구조로 만들어줌. Dense는 1차원으로 펼치는 거 밖에 안되서 Flatten을 진행해야함. 특히 이미지 학습할때 중요

CNN: 커널이라는 개념을 활용해서 데이터를 바라볼 때 이미지를 1차원으로 펼치지 말고, 2차원으로 두고 한 kernel size 로 학습시킴. kernel size을 몇칸씩 이동할래가 stride. padding은 양끝단에 있는 애들은 한번만 들어가지만, padding='same'을 활용하면 똑같이 학습이 가능함. 그럼 padding ='VALID' 은 언제 사용하냐 양 끝단의 데이터가 필요없다로 할때는 사용. 예를 들어 MNIST는 양끝단의 의미가 없음

Pooling:Dropout 과 비슷한 개념으로. 모아서 하나로 만들겠다. (2,2) 행렬안에서 MaxPooling이나 AveragePooling 두가지 존재. 일반적으로 MaxPooling을 사용하는게 좋다 알려지나, Max는 75%의 정보량을 없애는거 떄문에 위험

CNN은 이전 뉴런에 대한 기억이 전혀 없어서 연속적으로 어떤 일을 할 때는 의미가없음. 이를 위해 만든게 RNN.

옆으로 넘어가는 개념. 

RNN부터 Output을 sequence층으로 만들어 줄 수 있음. 진짜 예측된 값 10개를 제공.

 

1)순차 데이터 

=>순차 데이터는 텍스트나 시계열 데이터 와 같이 순서에 의미가 있음

=>Fashion MNIST 와 같은 이미지는 데이터를 신경망에 전달할 때 랜덤하게 섞은 후 훈련 세트 와 검증 세트로 나누고 순서와 상관없이 전달

=>"I am a boy"은 쉽게 이해해도 "boy am a I"는 말이 되지 않으며 일별 온도를 기록한 데이터에서 날짜 순서를 뒤죽박죽 섞는다면 내일의 온도를 쉽게 예상하기 어려움

=>텍스트 데이터는 단어의 순서가 중요한 순차 데이터인데 이런 데이터는 순서를 유지하면서 신경망에 주입해야함

=>'별로지만 추천해요'에서 별로지만을 기억하고 있어야 이 댓글을 무조건 긍정적이라고 판단하지 않음

=>Dense 이나 CNN 은 이런 기억 장치가 없기 떄문에 하나의 샘플을 사용하여 정방향 계산을 수행하고 나면 그 샘플은 버려지고 다음 샘플을 처리할때 재사용하지 않음.이렇게 입력 데이터의 흐름이 앞으로만 전달되는 신경망을 피드 포워드 신경망(FFNN - Feed Forward Neural Network)이라고 합니다.

=>신경망이 이전에 처리했던 샘플을 재사용을 하기 위해서는 데이터의 흐름이 앞으로만 전달되어서는 곤란하고 이전 데이터가 신경망층에 순환을 할 필요가 있는데 이런 신경망이 순환 신경망(RNN)

 

2)RNN

=>RNN(Recurrent Neural Network- 순환 신경망)은 미래를 예측할 수 있는 네트워크

=>RNN은 시계열 데이터를 분석해서 주식 가격 같은 것을 예측해 언제 사고 팔지 알려 줄 수 있으며 자율 주행 시스템에서는 차의 이동 경로를 예측하고 사고를 피하도록 도울 수 있음

=>RNN은 다른 네트워크처럼 고정 길이 입력이 아닌 임의 길이를 가진 시퀀스를 다룰 수 있는데 도디어 샘플이나, 자동 번역같은 자연어 처리에 매우 유용

 

3)순환 뉴런 층 과 순환 층
=>Dense 나 CNN 은 활성화 신호가 입력 층에서 출력 층 한 방향으로 흐르는 피드 포워드 신경망에 초점을 맞추었는데 RNN 에서는 피드 포워드 신경망 과 유사하지만 뒤쪽으로 순환하는 연결도 가지고 있다는 것이 다름
=>입력을 받아서 출력을 만들고 자신에게도 출력을 보내는 뉴런 하나로 구성된 계층

 

 

 
예를 들어 함께 성장하는 커뮤니티를 만들고 싶다 라는 문장이 있다면 이전에 배운 완전 연결 신경망(FC)이나 합성곱(CNN) 신경망에서는 순서 상관없이 데이터를 입력(Input)받지만 순환 신경망(RNN)에서는 데이터 순서를 유지하면서 차례대로 입력하는데 각 단어는 순차적으로 하나씩 모델에 반복적으로 입력
 

RNN 모델에서 문장을 각 단어 별로 구분하여 순서대로 입력되고 순환 신경망의 입력 값은 우리가 가지고 있는 데이터 뿐 아니라 RNN을 거쳐 나온 상태(State)값도 입력으로 활용되므로 입력 1뿐만 아니라 RNN을 거쳐 나온 출력 값인 상태를 입력2로 함께 사용되는 것이 특징인데 이 과정을 반복하기 때문에 순환한다는 뜻에서 순환신경망이라고 부름

 

지금까지는 활성화 신호가 입력 층에서 출력 층 한 방향으로만 흐르는 피드 포워드 신경망에 초점을 맞추었는데 순환 신경망은 피드 포워드 신경망과 매우 비슷하지만 뒤쪽으로 순환하는 연결도 있다는 점이 다름

 

왼쪽의 그림처럼 입력을 받아 출력을 만들고 자신에게도 출력을 보내는 뉴런 하나로 구성된 가장 간단한 RNN

 

순환 뉴런 과 순환 층
Time Step(Frame) 마다 recurrent neuron 물론 x(t) 이전 Time Step의 출력인 y(t-1)을 입력으로 받음
첫 번째 Time Step에서는 이전 출력이 없으므로 일반적으로 0으로 설정
작은 네트워크를 이전 그림처럼 시간을 축으로 하여 표현할 수 있는데 이를 시간에 따라 네트워크를 펼쳤다고 표현했다고 하는데 동일한 뉴런을 Time Step 마다 하나씩 표현
순환 뉴런으로 된 층은 쉽게 만들 수 있는데 아래 그림처럼 Time Step t 마다 모든 뉴런은 입력 벡터 X(t)와 이전 Time Step의 출력 벡터 Y(t-1)을 받는데 이제 입력과 출력이 모두 벡터가 되는데 뉴런이 하나일 때는 출력이 스칼라

 

4)입력 시퀀스 와 출력 시퀀스 유형
=>One to One: 하나의 입력 과 하나의 출력으로 가장 기본적인 RNN 구조
=>One to Many: 하나의 데이터를 입력했는데 여러 개의 출력이 만들어지는 경우
대표적인 경우가 이미지를 입력했는데 이미지를 설명하는 문장이 만들어지는 형태
=>Many to One: 여러 개의 데이터를 입력했는데 1개의 출력이 만들어지는 경우
대표적인 경우가 감성 분석: 문장을 입력하면 긍정 이나 부정 하나를 리턴하고 문장의 카테고리 분류 등
=>Many to Many: 여러 개의 데이터를 입력하고 출력이 여러 개 만들어지는 것
기계 번역이나 챗봇, 품사 예측 등
줄거리를 입력했을 때 요약

 

 

5)RNN 훈련
=>타임 스텝으로 네트워크를 펼치고 보통의 역전파를 사용하는 방식
=>최대 타임 스텝을 설정해서 출력 시퀀스가 평가됨
이전의 Dense 는 무조건 결과 1개 와 만 비교하지만 RNN 에서는 최대 타임 스텝을 설정해서 여러 개의 출력 과 비교하는 것이 가능

 

6)시계열 예측
=>시계열 데이터는 하나의 타임 스텝마다 하나 이상의 값을 가지는 시퀀스
=>시계열 마다 하나의 값을 가지는 것을 단변량 시계열이라고 하고 재정 안정성 예시 처럼 회사의 수입이나 부채 등 과 같은 여러 개의 데이터를 가지면 다변량 시계열이라고 합니다.
=>시계열 예측은 타임 스텝의 개수를 정해서 다음 타임 스텝의 값을 예측하는 것

def generate_time_series(batch_size, n_steps):
    freq1, freq2, offset1, offset2 = np.random.rand(4, batch_size, 1)
    time = np.linspace(0, 1, n_steps)
    
    series = 0.5 * np.sin((time - offset1) * (freq1 * 10 + 10))
    series += 0.2 * np.sin((time - offset2) * (freq2 * 20 + 20))
    series += 0.1 * (np.random.rand(batch_size, n_steps) - 0.5)
    
    return series[..., np.newaxis].astype(np.float32)


np.random.seed(42)

#데이터 생성
n_steps = 50
series = generate_time_series(10000, n_steps + 1)

#데이터 분할 - 7:2:1 로 분할
#51개씩 묶었는데 앞의 50개는 예측을 하기 위한 데이터가 되고 뒤의 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]

def plot_series(series, y = None, y_pred=None, x_label="$t$", y_label="$x(t)$"):
    plt.plot(series, ".-")
    
    if y is not None:
        plt.plot(n_steps, y, "bx", markersize=10)
    if y_pred is not None:
        plt.plot(n_steps, y_pred, "ro")
    plt.grid(True)
    
    if x_label:
        plt.xlabel(x_label, fontsize=16)
    if y_label:
        plt.xlabel(y_label, fontsize=16, rotation=0)
        
    plt.hlines(0, 0, 100, linewidth=1)
    plt.axis([0, n_steps+1, -1, 1])

#3개의 데이터 시각화
fig, axes = plt.subplots(nrows=1, ncols=3, sharey=True, figsize=(12, 4))

for col in range(3):
    plt.sca(axes[col])
    plot_series(X_valid[col, :, 0], y_valid[col, 0],
               y_label=("$x(t)$" if col == 0 else None))
plt.show()

 

=>순진한 예측
 - 시계열의 마지막 값을 그대로 예측

y_pred = X_valid[:, -1]
#평균 제곱 오차
np.mean(keras.losses.mean_squared_error(y_valid, y_pred))

#예측한 값 과 실제 값을 시각화
plot_series(X_valid[0, :, 0], y_valid[0, 0], y_pred[0, 0])

 

7)완전 연결 층을 이용한 시계열 예측

=>입력마다 1차원 특성 배열을 기대하기 때문에 Flatten 층을 추가해서 예측

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

#시계열 예측을 위한 모델 - 회귀
model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[50, 1]), #50개의 데이터를 가지고 예측
    keras.layers.Dense(1)
])

#컴파일 과 훈련
model.compile(loss="mse", optimizer="adam")

#조기 종료
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2,
                                                 restore_best_weights=True)

history = model.fit(X_train, y_train, epochs=20, 
                   validation_data = (X_valid, y_valid),
                   callbacks=[early_stopping_cb])

model.evaluate(X_valid, y_valid)

#순진한 예측 보다는 성능이 우수
#완전 연결 층을 이용한 회귀

 

=>loss를 시각화

#훈련 과정의 loss을 시각화: history 객체의 loss 와 val_loss
def plot_learning_curve(loss, val_loss):
    plt.plot(np.arange(len(loss)) + 0.5, loss, "b.-", label="Traning_loss")
    plt.plot(np.arange(len(val_loss)) + 0.5, val_loss, "r.-", label="Validation_loss")
    
    plt.axis=([1, 20, 0, 0.05])
    plt.legend(fontsize=14)
    plt.xlabel("Epochs")
    plt.ylabel("Loss")
    
plot_learning_curve(history.history["loss"], history.history["val_loss"])
plt.show()

 

=>예측

y_pred = model.predict(X_valid)
plot_series(X_valid[0, :, 0], y_valid[0, 0], y_pred[0, 0])
plt.show()

 

8) Basic RNN

=>Keras 에서는 SimpleRNN 이라는 클래스를 이용해서 RNN기능을 제공

RNN은 타임 스텝의 길이에 제약을 받지 않기 떄문에 입력 시퀀스의 길이를 설정할 필요가 없음

=>단순한 RNN은 구현해보면

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

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

 

=>트렌드와 계절성

 - 가중 이동 평균이나 자동 회귀 누적 이동 평균 같이 시계열을 다루는 방법은 한 가지가 아니고 여러 가지
 - 이전에 사용하던 방법들은 트렌드나 게절성을 제거해야 함
   매달 10%씩 성장하는 웹 사이트의 접속 사용자 수를 조사하는 경우 시계열에서 이런 트렌트를 삭제하고 모델을 훈련하고 예측을 만들기 시작할 때 최종 예측에 이 트렌드를 다시 더하는 방식으로 수행

 

매달 선크림 판매량을 예측할 때는 강한 계절성을 갖는 항목이므로 여름에 잘 팔리는 비슷한 패턴이 매년 반복될 텐데 이런 경우는 계절성을 제거하기 위해서 매 타임 스텝의 값과 작년도 값의 차이를 계산해서 사용(이를 차분 - difference)하는데 모델을 만들어서 훈련나고 예측을 만든 후 에 계절 패턴을 적용
 - RNN을 사용할 때는 일반적으로 이런 작업이 모두 필요 없음

 

9)Deep RNN

=>실제 RNN은 한 개층으로 구성되지 않고 여러 개의 층으로 구성하는 것이 일반적

이러한 RNN을 Deep RNN이라고 합니다

=>3개의 RNN 사용

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))

 

위의 모델은 마지막 층은 이상적이지 않음. 단변량 시계열을 예측하기 떄문에 하나의 유닛이 필요하고 이는 타임 스텝마다 하나의 출력을 만들어야 한다는 의미이며 하나의 유닛을 가진다는 것은 은닉 상태가 하나의 숫자라는 의미

이 RNN은 한 타임 스텝에서 다음 타임 스텝으로 필요한 모든 정보를 나르기 위해 다른 순환 층의 은닉 상태를 주로 사용할 것이므로 마지막 층의 은닉 상태는 크게 필요하지 않으며 또한 SimpleRNN 층은 기본적으로 tanh 함수를 사용해서 예측값이 -1에서 1 범위안에서 놓임

 

다른 활성화 함수를 사용하려면 출력층을 Dense 층으로 바꾸는 경우가 ㅁ낳은데 더 빠르면서 정확도는 비슷하고, 원하는 활성화 함수를 선택할 수 있으며, 이렇게 변경하려면 두번째 순환층에거 return_sequences= True를 제거

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))

 

10) 여러 개의 값을 예측

=>방법

 - 이미 훈련된 모델을 이용해서 다음 값을 예측한 다음 이 값을 입력으로 추가하는 방식: many to one
   예측된 값이 실제로 발견된 것 처럼 사용

 - 한 번에 10개를 예측하는 방식: many to many

np.random.seed(43) 

#60개의 타임 생성
series = generate_time_series(1, n_steps + 10)
#50개와 10개로 분할
X_new, Y_new = series[:, :n_steps], series[:, n_steps:]
X = X_new
for step_ahead in range(10):
    #출력되는 값을 새로운 입력으로 사용 - 차원을 늘려줌
    y_pred_one = model.predict(X[:, step_ahead:])[:, np.newaxis, :]
    X = np.concatenate([X, y_pred_one], axis=1)

Y_pred = X[:, n_steps:]
def plot_multiple_forecasts(X, Y, Y_pred):
    n_steps = X.shape[1]
    ahead = Y.shape[1]
    plot_series(X[0, :, 0])
    plt.plot(np.arange(n_steps, n_steps + ahead), Y[0, :, 0], "ro-", label="Actual")
    plt.plot(np.arange(n_steps, n_steps + ahead), Y_pred[0, :, 0], "bx-", label="Forecast", markersize=10)
    plt.axis([0, n_steps + ahead, -1, 1])
    plt.legend(fontsize=14)

plot_multiple_forecasts(X_new, Y_new, Y_pred)
save_fig("forecast_ahead_plot")
plt.show()

 

 

=>출력 층을 변경하면 여러 개의 값을 만들 수 있습니다

-출력층을 만들때 Dense(1) 으로 작성하므로 1개의 출력이 만들어지는 것이므로 

 

=>개선책

- 모든 타임 스탭에서 다음 10개의 값을 예측하도록 하는 것

- 이런 형태를 sequence to sequence 라고 합니다

- 모든 RNN 층에서 출력을 만들어냅니다

return_seuquences를 True로 설정하고 , 마지막 출력층은 다음으로 전달되기 위해서 Dense 를 TimeDistribute 계층으로 감싸면 됩니다

- 모든 층에서 발생한 손실을 포함시킬 수 있어서 더 많은 오차 그라디언트가 모델에 흐르게 되서 모델을 만들 가능성이 높아짐

- 타겟을 변경하게 되는데 타겟을 feature 와 동일한 shape을 갖도록 수정해주어야 합니다

 

#각 층의 출력을 다시 사용
#타겟을 10개의 데이터를 가진 배열로 수정

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]


#타겟도 feature 와 동일한 구조로 만들기 위해서 빈 배열 생성
#이 구조가 many to many 구조로 언어 번역이나 챗봇에 사용
Y = np.empty((10000, n_steps, 10))
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:]

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(lr=0.01), metrics=[last_time_step_mse])
history = model.fit(X_train, Y_train, epochs=20,
                    validation_data=(X_valid, Y_valid))
np.random.seed(42)

series = generate_time_series(1, 50 + 10)
X_new, Y_new = series[:, :50, :], series[:, 50:, :]
Y_pred = model.predict(X_new)[:, -1][..., np.newaxis]

plot_multiple_forecasts(X_new, Y_new, Y_pred)
plt.show()

얼추 비슷하게 예측하는 것을 볼 수 있음

 

2.Embedding

1)개요

=>데이터를 컴퓨터가 이해하는 벡터로 변경하는 작업

=>문장을 컴퓨터가 이해할 수 있는 숫자로 변경하는 것

=>자연어에서는 단어 수준의 임베딩(Word2Vec) 과 문장 수준의 임베딩(Bert,GPT,Elmo)으로 구분

단어 수준의 임베딩은 동음이의어를 구분할 수 없지만 문장 수준의 임베딩은 동음이의어를 좌우에 놓인 단어를 이용해서 구분 가능

 

2)방법

=>원 핫 인코딩

- 단어, 음절, 형태소 등으로 자연어를 나누고 이를 수치화하는 방식

각각의 단어나 음절 또는 형태소를 하나의 feature 로 보고 희소 행렬로 표현하는 방식

-단어가 많아질수록 벡터의 길이가 길어지고 메모리 낭비가 심하고 단어의 유사도를 알기가 어려움

 

=>예시

나는딥러닝이 무엇인지를 알고 있따

 

 

단어의 순서를 기억하고 있다가 거리를 반영해서 숫자로 치환

가까이에서 등장한 단어가 거리가 가까움

임베딩 레이어에는 두 가지 파라미터 값이 필요한데 첫 번째는 입력 차원(단어의 총 수)이고 두 번째는 임베딩 차원이며 그림에서 N개의 입력 차원을 3차원 임베딩 공간으로 매핑하는 것을 확인할 수 있는데 N개의 단어로 구성된 문장을 3개의 원소를 갖는 벡터로 변환하는  개념


Tensorflow 에서는 Embedding이라는 레이어로 이 기능을 제공

생성할 때 단어의 개수와 변환할 피처의 개수를 설정해주면 됩니다

자연어를 RNN에서 처리할때는 무조건 Embedding을 해줘야합니다

# 임베딩 레이어
#100개의 단어를 3개의 숫자로 만들어주는 Embedding
embedding_layer = Embedding(100, 3)
result = embedding_layer(tf.constant([12, 8, 15, 20])) #더미 데이터 입력
print(result)

자연어를 가지고 RNN을 수행하고자하는 경우는 Embedding을 해서 사용하는 경우가 많습니다

 

3.양방향(Bidirectional) RNN

1)개요

=>자연어 데이터의 경우는 순서대로 데이터 처리하고 역순으로 처리할 경우 더 좋은 성능으로 발휘

시계열 데이터는 무조건 순서대로 이뤄지지만, 자연어는 도치법으로 이뤄질 수 있기 떄문에 성능이 좋게 나올떄가 많음

이전 데이터를 가지고 있어야하고, 반대 방향으로도 생각을 해봐야함

 

=> 숫자 패턴은 얘가 되게 효과적으로 해결 가능

 

=>Tensorflow 에서는 클래스로 제공

이름은 Bidirectional 인데 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.Bidirectional(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))

4.Stacking RNN

=>RNN은 그냥 쌓기만 하면 에러

=>여러 개의 레이어를 쌓을 떄는 출력을 다음 RNN에게 전달을 해야합니다

이전 RNN 층에서 출력을 전송하지 않으면 shape가 맞지 않아서 에러가 남

=>RNN 이전 RNN 레이어는 반드시 return_sequences 를 True 로 설정

model = Sequential()
model.add(Embedding(100,32))
model.add(keras.layers.SimpleRNN(32, return_sequences=True))  # 전체 시퀀스 출력 (batch_size, timesteps, units)
model.add(keras.layers.SimpleRNN(32))
model.add(Dense(1))
model.summary()

5.불안정한 그라디언트 문제

=>경사하강법이 최적의 지점을 찾지 못하는 경우가 발생할 떄가 있는데 이유는 학습률이 너무 크거나 데이터의 분포가 정규 분포가 아닌 경우

=>정규 분포를 만들 수 있는 방법 중의 하나가 훈련 중에 평균과 분산을 확인해서 조정하는 방법을 사용할 수 있습니다

=>Batch Normalization 층을 추가해주면  됩니다

=>배치 차원에 대해 정규화 하는 대신 특성 차원에 대해 정규화 하는데 한 가지 장점은 샘플에 독립적으로 타임 스텝마다 동적으로 필요한 통계를 계산할 수 있다는 것이며 이는 훈련과 테스트에서 동일한 방식으로 작동한다는 것을 의미(배치 정규화 와는 다름)

6.LSTM(Long Short Term Memory)

1)개요

=>feature 가 매우 많은 경우 (시계열의 경우 타임스텝이 긴 경우) 에 중요한 정보가 앞쪽에 있게 되면 중요한 정보를 잃어버릴 수 있음

=>중요한 정보를 잊어버리지 않도록 하기 위해서 Cell state 와 Gate라는 것을 추가로 도입해서 잊어야 할 것과 기억해야 할 것을 조절하는 알고리즘

=>첫번째 과정인 은닉 상태는 입력과 이전 타임 스텝의 은닉 상태를 가중치에 곱한 후 활성화 함수를 통과시켜 다음 은닉 상태를 만드는데 기본 순환 층 달리 시그모이드 활성화 함수를 사용하고 tanh 활성화 함수를 통과한 어떤 값과 곱해져서 은닉 상태를 생성

=>시그모이드 함수를 사용해서 출력의 결과값이 0이면 전달하지 않을 수도 있고 1이면 반드시 전달하는 망각게이트가 추가된 형태

=>긴 기간의 데이터를 가지고 다음 데이터를 예측하고자 하는 경우 time step 을 길게 잡을 것인데 이런 경우에는 LSTM을 사용하는 것을 권장

=>만드는 방법은 SimpleRNN과 동일하게 뉴런의 개수와 출력을 전달할지 여부

 

2)주식데이터 학습

=>데이터 가져오기

finance.yahoo.com 에서 카카오 주식 데이터를 수집

 

=>데이터 preprocessing ( 50일씩 끊어서 학습 할 수 있게 해줌)

#결측치 제거
data = data.dropna(axis=0)

#가장 높은 가격 과 낮은 가격의 중간값을 생성
high_price = data['High'].values
low_price = data['Low'].values
mid_price = (high_price + low_price) / 2

#데이터들을 첫날부터 50일씩 끊어서 저장
#[ [0~50일], [1~51일], [2~52일], [3~53일]....... ]
# 50일 치 데이터를 가지고 51일째 되는 날의 주가를 예측하기 위해서

day_divided = 50
day_length = day_divided + 1
day_result = []
for i in range(len(mid_price) - day_length):
    day_result.append(mid_price[i: i + day_length])
    
#정규화 작업    
norm_result = []
for section in day_result:
    norm_section = [((float(p) / float(section[0])) - 1) for p in section]
    norm_result.append(norm_section)
day_result = np.array(norm_result)

print(day_result[0])

정규화 수행은 가격을 가지고 예측하게 되면 각 list 마다 가격이 달라서 잘못된 예측이 될수 있음 따라서 정규화 과정을 거친 후 학습을 진행

 

=> data 나누기

train_data_rate = 0.9
boundary = round(day_result.shape[0] * train_data_rate)
train_data = day_result[:boundary, :]
test_data = day_result[boundary:, :]

x_train = train_data[:, :-1]
x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
y_train = train_data[:, -1]

x_test = test_data[:, :-1]
x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))
y_test = test_data[:, -1]

x_train.shape, x_test.shape

 

=>model 생성 및 학습

RNN  층 이전 층은 출력을 전달해 줘야합니

model = Sequential()
model.add(LSTM(50, return_sequences=True, input_shape=(50, 1)))
model.add(LSTM(64, return_sequences=False))
model.add(Dense(1, activation='relu'))
model.compile(loss='mse', optimizer='sgd')

model.summary()

model.fit(x_train, y_train,
    validation_data=(x_test, y_test),
    batch_size=10,
    epochs=15)

pred = model.predict(x_test)

import matplotlib.pyplot as plt

plot_figure = plt.figure(figsize=(30, 10))
plot_rst = plot_figure.add_subplot(111)
plot_rst.plot(y_test, label='Real')
plot_rst.plot(pred, label='Predict')
plot_rst.legend()
plt.show()

7.GRU

LSTM과 거의 유사한 성능인데 계산을 덜 함. 우리나라 분이 개발했음. LSTM은 계속 기억해나가다보니 메모리에 대한 부담이 존재하지만 , GRU는 부분적으로 나눠서 계산이 가능

=>LSTM 의 경우 3개의 게이트(망각,입력,출력) 가 있지만 GRU 는 2개의 게이트(Update, Reset)만 존재하고 셀 상태가 별도로 존재하지 않고 업데이트 게이트에서 시그모이드 함수를 활용하여 망각게이트와 입력게이트를 모두 제어하는데  망각게이트와 입력게이트는 한쪽이 열리면 한쪽이 닫히는 구조로 새로 입력된 정보를 얼마나 반영할지 결정

 

=> GRU 셀에는 은닉 상태와 입력에 가중치를 곱하고 절편을 더하는 작은 셀 3개가 들어있는데, 2개는 시그모이드 활성화 함수를 사용하고 하나는 tanh 활성화 함수를 사용

=>은닉 상태와 입력에 곱해지는 가중치를 합쳐서 나타냄

=>맨 왼쪽에서 wz 사용하는 셀의 출력이 은닉 상태에 바로 곱해져 삭제 게이트 역할을 수행한 후 이와 똑같은 출력을 1에서 뺀 다음에 가장 오른쪽 wg 사용하는 셀의 출력에 곱하는데 이는 입력되는 정보를 제어하는 역할을 수행

=>GRU 셀은 LSTM 보다 가중치가 적기 때문에 계산량이 적지만, LSTM 못지 않은 좋은 성능을 내는 것으로 알려져 있음

 

=>데이터 가져오기

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences

from sklearn.model_selection import train_test_split

(train_input, train_target), (test_input, test_target) = imdb.load_data(
    num_words=500)

train_input, val_input, train_target, val_target = train_test_split(
    train_input, train_target, test_size=0.2, random_state=42)

train_seq = pad_sequences(train_input, maxlen=100)
val_seq = pad_sequences(val_input, maxlen=100)

 

=>모델 생성

model4 = keras.Sequential()

model4.add(keras.layers.Embedding(500, 16, input_length=100))
model4.add(keras.layers.GRU(8))
model4.add(keras.layers.Dense(1, activation='sigmoid'))

model4.summary()

 

=>모델 훈련

rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model4.compile(optimizer=rmsprop, loss='binary_crossentropy', 
               metrics=['accuracy'])

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-gru-model.h5')
early_stopping_cb = tf.keras.callbacks.EarlyStopping(patience=3,
                                                  restore_best_weights=True)

history = model4.fit(train_seq, train_target, epochs=100, batch_size=64,
                     validation_data=(val_seq, val_target),
                     callbacks=[checkpoint_cb, early_stopping_cb])