순환 신경망(Recurrent Neural Network : RNN) : 기초 이해

1. RNN이란?

 

순차 데이터(Sequence data) 또는 시계열 데이터(Time series data)의 경우, 이전의 정보가 다음의 결과에 영향을 미치기 때문에, 기존의 피드 포워드 신경망만으로는 충분하지 않습니다. RNN(Recurrent Neural Network)은 이러한 시간에 따라 누적된 정보를 처리할 수 있는 신경망입니다. 

 


1.1 RNN과 피드 포워드 신경망 비교

 

피드 포워드 신경망은 입력이 출력층으로 향하는 구조를 가진다. 입력이 각 층에서 가중치와 편향을 곱하고 활성화 함수를 통해 변환된다. 출력은 각 층의 결과의 계산에 기반한다. 피드 포워드 신경망은 분류, 회귀 분석 등의 문제에 적합하며, 간단한 구조와 계산이 용이한 장점을 가지고 있다.

 

RNN은 시간에 따라 누적된 정보를 처리할 수 있는 신경망이다. RNN은 메모리 셀을 가지고 있어, 시간에 따라 정보를 저장하고, 이전의 정보를 이용하여 현재의 결과를 예측하거나 분류할 수 있다. RNN은 시계열 데이터 등에 적합하며, 언어 모델링, 시퀀스 생성, 시계열 예측 등의 문제에서 효과적으로 사용된다.

 

RNN과 피드 포워드 신경망은 두 가지 다른 유형의 신경망이며, 각각의 장단점을 가지고 있다. 특정 문제에 적합한 신경망을 선택하기 위해서는 데이터와 문제의 성격을 고려하면 된다.

 

피드 포워드 신경망 이미지순환 신경망 이미지

 


2. RNN의 형태

RNN은 다양한 형태로 구성될 수 있습니다. 각 형태는 입력, 출력의 개수에 따라 결정됩니다.

 

2.1 RNN의 형태 : 일대일

 

1:1 형태의 RNN은 한 개의 입력에 대해 한 개의 출력을 생성하는 모델입니다.  1:1 형태의 RNN은 간단한 기계 학습 문제에 사용됩니다. Vanila Neural network라고도 합니다.(아이스크림 중에서 바닐라 아이스크림이 가장 기본이기 때문에 이와 같은 이름이 붙여졌다고 합니다.)

 

RNN의 형태 : 일대일 이미지

 


2.2 RNN의 형태 : 일대다

 

1 : n 형태의 RNN은 한 개의 입력에 대해 여러 개의 출력을 생성하는 모델입니다. 이 형태의 RNN은 입력에 대한 출력을 여러 단계로 나누어서 처리할 수 있습니다. 1 : n 형태의 RNN은 시계열 데이터의 예측, 감정 분석, 번역 등에 사용됩니다. 예를 들어 주식 가격을 예측하거나, 문장의 감정을 분석하는 등의 문제에 적용할 수 있습니다.

 

RNN의 형태 : 일대다 이미지

 


2.3 RNN의 형태 : 다대일

 

n : 1 형태의 RNN은 여러 개의 입력에 대해 한 개의 출력을 생성하는 모델입니다.  n : 1 형태의 RNN은 시계열 데이터의 예측, 상태 감지, 경고 발생 등에 사용됩니다. 예를 들어 날씨 예측, 센서 데이터를 기반으로 경보 발생, 장애 감지 등의 문제에 적용할 수 있습니다.

 

RNN의 형태 : 다대일 이미지

 


2.4 RNN의 형태 : 다대다

 

n:m 형태의 RNN은 여러 개의 입력에 대해 여러 개의 출력을 생성하는 모델입니다. 이 형태의 RNN은 복잡한 문제를 풀 수 있는 능력이 있습니다. n : m형태의 RNN은 복잡한 문제를 풀기 위해 사용됩니다. 예를 들어, 비디오 분류, 이미지 캡셔닝, 게임 AI등의 문제에 적용할 수 있습니다.

 

RNN의 형태 : 다대다 이미지

 


3. RNN의 아키텍처

 

RNN은 입력 레이어, 히든 레이어, 출력 레이어로 구성된 아키텍처를 가지고 있습니다. 입력 레이어는 데이터를 입력으로 받습니다. 히든 레이어는 입력 데이터를 처리하고 이전의 정보를 유지하는 역할을 합니다. 출력 레이어는 히든 레이어의 상태를 기반으로 최종 출력을 생성합니다. 히든 레이어의 유닛은 메모리 셀(memory cell)이라고 불린다. 과거의 값을 기억하기 때문에 이러한 이름이 붙었습니다. RNN은 시간적 정보를 처리할 수 있는 특징을 갖습니다. 그렇기 때문에 특정 시간 t에 입력 xt를 처리한 뒤에 출력 ht를 내보내며, 그 출력은 다음 입력에도 영향을 미칠 수 있습니다. 이는 입력과 함께 과거 상태를 저장하는 구조로, 시간에 따라 변화하는 입력에 대한 처리를 가능하게 합니다.

 

순환 신경망 이미지

 

RNN은 연속적인 입력 x1, x2..xt 벡터를 받아 h1, h2, ht의 출력을 차례로 만듭니다. 입력 x1이 처음으로 제공될 때, 뉴런은 입력 가중치와 활성화 함수를 적용하여 h1을 만듭니다. 다음에 제공되는 입력 x2는 이전 출력 h1과 함께 제공되어 계산됩니다. 전달되는 신호는 학습을 통해 변경된 가중치에 의해 곱해지게 됩니다.

 

순환 신경망 상세 이미지

 


4. RNN  셀의 구조

 

메모리 셀은 자신의 이전 상태와 새로운 입력을 결합하여 활성화 함수에 적용됩니다. 일반적으로 이 활성화 함수는 하이퍼볼릭 탄젠트 함수(tanh)를 사용합니다. 결과는 노드의 출력 h로 계산되며, 이는 다시 순환되어 피드백됩니다. 따라서 순환 신경망 노드의 상태는 특정 시간 t에 대하여 다음과 같이 표현될 수 있습니다.

 

RNN 수식

 

"ht"는 출력 값이며, "ht-1"은 이전 단계의 출력 값입니다. 입력 값은 시간에 따라 연속적으로 제공되는 "xt"입니다."wxh", "why", "whh"는 각각의 가중치 행렬입니다.  "f()"는 결과 값인 "yt"를 계산하기 위한 활성화 함수로, 이진 분류 문제일 경우 시그모이드 함수를 사용하고, 다양한 분류 중에서 하나를 선택하는 문제일 경우 소프트맥스 함수를 사용합니다. 

 

RNN 메모리 셀 상세 이미지

 

 


5. RNN에서 탄젠트 함수를 사용하는 이유

 

순환 신경망(RNN)은 일반적인 신경망과 같이 경사 하강법과 오차 역전파를 이용하여 학습됩니다. 그러나 RNN에서는 활성화 함수로 시그모이드나 ReLU와 같은 함수보다는 하이퍼볼릭 탄젠트(tanh) 함수를 주로 사용합니다. 그 이유는 다음과 같습니다. ReLU를 사용하면 이전 값이 커짐에 따라 오버플로우가 발생할 수 있는 문제가 있기 때문입니다.  시그모이드와 tanh함수는 특정한 범위를 넘지 못하게 되어 있어 오버플로우 문제를 피할 수 있지만 모두 기울기 소실 문제를 가지고 있습니다. 그나마 tanh함수는 상당 부분 기울기 소실 문제를 해소할 수 있는 특징을 가지기 있기 때문에 RNN에서 많이 사용됩니다.

 

시그모이드와 탄젠트 함수 이미지
https://medium.com/analytics-vidhya/how-batch-normalization-and-relu-solve-vanishing-gradients-3f1a8ace1c88

 


6. RNN의 한계

 

첫째로, RNN은 시퀀스의 길이가 길어질수록 기울기 소실 문제(vanishing gradient problem)가 발생할 가능성이 높습니다. 이는 오랜 시간 동안 반영된 정보가 손실되어 성능 저하를 일으킬 수 있습니다.

둘째로, RNN은 시퀀스의 길이에 따라 파라미터 수가 증가하는 경향이 있습니다. 이로 인해 과적합(overfitting) 문제가 발생할 수 있으며, 이를 해결하기 위해 많은 데이터와 복잡한 모델 구조가 필요할 수 있습니다.

셋째로, RNN은 순차적으로 처리되기 때문에 병렬 처리(parallel processing)가 어렵습니다. 이는 학습 속도와 성능을 저하시킬 수 있습니다.

넷째로, RNN은 장기 의존성(long-term dependency)을 학습하는 데 어려움이 있습니다. 이는 시퀀스가 길어지면 이전의 정보가 희석되어 장기적인 의존성을 파악하는 데 어려움을 겪게 됩니다.

RNN의 이러한 한계를 극복하기 위해 LSTM(Long Short-Term Memory)과 GRU(Gated Recurrent Unit) 같은 RNN의 변형 모델이 개발되었습니다.

 


7. RNN 텐서플로우 코드

 

import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing import sequence

# Load the IMDB dataset
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=10000)

# Pad the sequences to the same length
max_len = 500
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)

# Build the model
model = tf.keras.Sequential()
model.add(tf.keras.layers.Embedding(10000, 128))
model.add(tf.keras.layers.SimpleRNN(128, dropout=0.2, recurrent_dropout=0.2))
model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

# Compile the model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

# Train the model
history = model.fit(x_train, y_train, batch_size=32, epochs=10, validation_data=(x_test, y_test))

# Plot the loss and accuracy results
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model Loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper right')
plt.show()

plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model Accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='lower right')
plt.show()