
(출처 : 내일배움캠프 강의)
순환 구조
RNN의 기본요소는 순환하는, 셀이라한다. 각 시점의 입력과 숨겨진 상태를 결합 및 새로 숨겨진 상태와 출력을 만들어주는 것을 hidden state 순환, 즉 은닉 상태라고 한다.
RNN은 입력 데이터와 이전 시간 단계의 은닉 상태(hidden state)를 입력으로 받아, 현재 시간 단계의 은닉 상태를 출력한다.
은닉 상태는 시퀀스의 정보를 저장하고, 다음 시간 단계로 전달된다.
동작 원리
RNN은 시퀀스의 각 시간 단계에서 동일한 가중치를 업데이트하여, 시퀀스의 패턴을 학습한다.
모든 시점에서 가중치가 동시에 업데이트한다. 마지막만 업데이트가 아닌 처음부터 갱신된다고 생각하면 된다.
순전파(Forward Propagation)와 역전파(Backpropagation Through Time, BPTT)를 통해 가중치를 학습합니다.
cf) 역전파의 경우에는 출력을 실제 값과 비교 오류확인 거슬러 올라가면 가중치 업데이트한다.
RNN은 장기 의존성 문제(long-term dependency problem)를 겪을 수 있기에 이를 해결하기 위해 LSTM과 GRU가 개발되었다.
LSTM은 셀 상태(cell state)와 게이트(gate) 구조를 도입, 장기 의존성을 효과적으로 학습가능하다.
LSTM은 입력 게이트(input gate), 출력 게이트(output gate), 망각 게이트(forget gate)를 사용하여 정보를 조절한다.
LSTM의 원리를 쉽게 풀어 쓴다면 다음과 같다.
장점 : 긴 시퀀스에서도 정보를 잘 저장한다. (소실 적음)
단점 : 기능이 많아서 복잡하고 학습이 오래걸림 그래서 데이터의 의존성이 높다.
GRU (Gated Recurrent Unit)
LSTM과 GRU도 순전파 역전파 동일 적용한다.
즉 여기 게이트들은 함수들로 이루어져있고, 함수들을 계산할 때 가중치 조절해서 함수의 최소가 되는 결과로 오차함수를 줄이게되면 최적화 상태
이를 역전파(Backpropagation)이라고 한다.
RNN은 시계열 데이터나 순차적인 데이터를 처리하는 데 적합하다. 예를 들어, 주식 가격 예측, 날씨 예측, 텍스트 생성 등이 있다.
PyTorch를 사용하여 간단한 RNN과 LSTM 모델을 구축하고, 시계열 데이터를 예측해보려고한다. 예제로는 Sine 파형 데이터를 사용할 것.
<PyTorch 및 필요한 라이브러리 임포트>
# 불러오기
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
<데이터셋 생성 및 전처리>
# Sine 파형 데이터 생성
# ~모양이 나오는 sine 함수에서 값이 중복으로 나오니까 시퀀스가 필요
def create_sine_wave_data(seq_length, num_samples):
X = []
y = []
for _ in range(num_samples):
start = np.random.rand()
x = np.linspace(start, start + 2 * np.pi, seq_length)
X.append(np.sin(x))
y.append(np.sin(x + 0.1))
return np.array(X), np.array(y)
seq_length = 50
num_samples = 1000
X, y = create_sine_wave_data(seq_length, num_samples)
# 데이터셋을 PyTorch 텐서(tensor)로 변환
# PyTouch에서 텐서(tensor)는 기울기를 계산할 수 있는 자료 구조라서 텐서로 변환해주는게 맞음 (가중치를 위해)
X = torch.tensor(X, dtype=torch.float32).unsqueeze(-1)
y = torch.tensor(y, dtype=torch.float32).unsqueeze(-1)
<간단한 RNN 모델 정의>
nn.RNN: 순환 신경망(RNN) 층을 정의한다.nn.Linear: 선형 변환을 적용하는 완전 연결(fully connected) 레이어를 정의한다.class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleRNN, self).__init__()
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
h0 = torch.zeros(1, x.size(0), hidden_size) # 초기 은닉 상태
out, _ = self.rnn(x, h0)
out = self.fc(out[:, -1, :]) # 마지막 시간 단계의 출력
return out
input_size = 1
hidden_size = 32
output_size = 1
model = SimpleRNN(input_size, hidden_size, output_size)
<간단한 LSTM 모델 정의>
nn.LSTM: 장단기 메모리(LSTM) 층을 정의한다. class SimpleLSTM(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleLSTM, self).__init__()
self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
h0 = torch.zeros(1, x.size(0), hidden_size) # 초기 은닉 상태
c0 = torch.zeros(1, x.size(0), hidden_size) # 초기 셀 상태
out, _ = self.lstm(x, (h0, c0))
out = self.fc(out[:, -1, :]) # 마지막 시간 단계의 출력
return out
model = SimpleLSTM(input_size, hidden_size, output_size)
<모델 학습>
# 손실 함수와 최적화 알고리즘 정의
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
# 모델 학습
num_epochs = 100
for epoch in range(num_epochs):
outputs = model(X)
optimizer.zero_grad()
loss = criterion(outputs, y)
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
print('Finished Training')
nn.MSELoss: 평균 제곱 오차(MSE) 손실 함수를 정의한다.optim.Adam: 여태까지 사용했던 것은 SGD 확률적 경사 하강법인데, 여기서는 Adam 적응형 모멘트 추정, 경사 하강법 최적화 알고리즘 중 하나이다. lr은 학습률을 지정한다.optimizer.zero_grad(): 이전 단계에서 계산된 기울기를 초기화한다.loss.backward(): 역전파를 통해 기울기를 계산한다.optimizer.step(): 계산된 기울기를 바탕으로 가중치를 업데이트한다.<모델 평가 및 시각화>
# 모델 평가
model.eval()
with torch.no_grad():
predicted = model(X).detach().numpy()
# 시각화
plt.figure(figsize=(10, 5))
plt.plot(y.numpy().flatten(), label='True')
plt.plot(predicted.flatten(), label='Predicted')
plt.legend()
plt.show()
model.eval(): 모델을 평가 모드로 전환한다.torch.no_grad(): 평가 단계에서는 기울기를 계산할 필요가 없으므로, 이를 비활성화하여 메모리 사용을 줄인다.detach(): 텐서를 계산 그래프에서 분리한다.상황에 따라 사용하는 모델구조가 다르다. 하지만 순환 작용 및 보다 값을 빠르고 효율적으로 적용하는 모델을 센스있게 찾아내는 것이 중요하다는 것을 다시 알게 되는 공부였다.