딥러닝 파이토치 강좌, 순환 신경망(RNN)

순환 신경망(RNN)은 시퀀스 데이터를 처리하는 데 강력한 능력을 가진 딥러닝 모델입니다. 이번 강좌에서는 RNN의 기초 개념부터 시작하여, 파이토치로 RNN을 구현하는 방법을 자세히 설명합니다.

1. 순환 신경망(RNN)의 개요

순환 신경망(RNN)은 이전의 정보가 현재의 정보에 영향을 미칠 수 있도록 설계된 신경망 구조입니다. 이는 주로 시퀀스 데이터(예: 자연어 텍스트, 시계열 데이터) 처리에 사용됩니다. 전통적인 신경망은 입력 데이터의 독립성을 가정하지만, RNN은 시간에 따른 종속성을 학습할 수 있습니다.

RNN의 기본 구조에서 각 시점을 나타내는 입력은 이전 시점의 은닉 상태와 함께 모델에 입력됩니다. 이러한 연결로 인해 RNN은 정보의 흐름을 시퀀스에 따라 처리할 수 있습니다.

2. RNN의 구조

RNN의 기본 구조는 다음과 같습니다:

  • 입력층: 시퀀스 데이터를 입력받습니다.
  • 은닉층: 시간적으로 연결된 여러 층을 가집니다.
  • 출력층: 최종 예측 결과를 제공합니다.

RNN 구조

수학적 표현: RNN의 업데이트는 다음과 같이 표현됩니다:

ht = f(Whhht-1 + Wxhxt + bh)

yt = Whyht + by

3. RNN의 한계

전통적인 RNN은 긴 시퀀스에 대한 의존성을 학습하는 데 한계가 있습니다. 이는 기울기 소실(vanishing gradient) 문제로 이어지며, 이에 대한 해결책으로 LSTM(장기 단기 메모리)와 GRU(게이티드 순환 유닛) 같은 다양한 RNN 변형이 제안되었습니다.

4. PyTorch로 RNN 구현하기

이번 섹션에서는 파이토치를 사용하여 간단한 RNN 모델을 구현해 보겠습니다. 우리는 유명한 IMDB 영화 리뷰 데이터셋을 사용하여 영화 리뷰의 긍정/부정 감정을 분류하는 작업을 수행할 것입니다.

4.1 데이터 로드 및 전처리

파이토치의 torchtext 라이브러리를 사용하여 IMDB 데이터를 로드하고 전처리합니다.


import torch
from torchtext.datasets import IMDB
from torchtext.data import Field, BucketIterator

TEXT = Field(tokenize='spacy', include_lengths=True)
LABEL = Field(dtype=torch.float)

train_data, test_data = IMDB.splits(TEXT, LABEL)
TEXT.build_vocab(train_data, max_size=25000)
LABEL.build_vocab(train_data)

train_iterator, test_iterator = BucketIterator.splits(
    (train_data, test_data), 
    batch_size=64, 
    sort_within_batch=True)
        

위 코드는 IMDB 데이터셋을 로드하고, 이를 위한 텍스트와 레이블 필드를 정의하여 데이터셋을 전처리하는 과정을 보여줍니다.

4.2 RNN 모델 정의하기

RNN 모델을 정의합니다. 파이토치의 nn.Module을 상속받아 기본 모델을 구현하겠습니다.


import torch.nn as nn

class RNN(nn.Module):
    def __init__(self, input_dim, emb_dim, hidden_dim, output_dim):
        super().__init__()
        
        self.embedding = nn.Embedding(input_dim, emb_dim)
        self.rnn = nn.RNN(emb_dim, hidden_dim)
        self.fc = nn.Linear(hidden_dim, output_dim)
        self.dropout = nn.Dropout(0.5)
    
    def forward(self, text, text_length):
        embedded = self.dropout(self.embedding(text))
        packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded, text_length)
        packed_output, hidden = self.rnn(packed_embedded)
        output, output_length = nn.utils.rnn.pad_packed_sequence(packed_output)
        return self.fc(hidden.squeeze(0))
        

이 코드에서는 입력 차원, 임베딩 차원, 은닉 차원, 출력 차원을 인자로 받아 RNN 모델을 구성합니다. 이 모델은 임베딩 레이어, RNN 레이어, 그리고 출력 레이어로 이루어져 있습니다.

4.3 모델 훈련하기

이제 모델을 훈련시키기 위한 과정을 살펴보겠습니다. 손실 함수로는 이진 교차 엔트로피를 사용하고, 최적화 기법으로 Adam을 사용합니다.


import torch.optim as optim

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

model = RNN(len(TEXT.vocab), 100, 256, 1)
model = model.to(device)

optimizer = optim.Adam(model.parameters())
criterion = nn.BCEWithLogitsLoss()
criterion = criterion.to(device)

def train(model, iterator, optimizer, criterion):
    model.train()
    epoch_loss = 0
    
    for batch in iterator:
        text, text_length = batch.text
        labels = batch.label
        
        optimizer.zero_grad()
        predictions = model(text, text_length).squeeze(1)
        
        loss = criterion(predictions, labels)
        loss.backward()
        optimizer.step()
        
        epoch_loss += loss.item()
        
    return epoch_loss / len(iterator)
        

train 함수는 주어진 데이터 배치에 대해 모델을 훈련시키고 손실을 반환합니다.

4.4 모델 평가하기

모델을 평가하는 함수도 정의할 필요가 있습니다. 다음 코드를 통해 평가할 수 있습니다.


def evaluate(model, iterator, criterion):
    model.eval()
    epoch_loss = 0
    
    with torch.no_grad():
        for batch in iterator:
            text, text_length = batch.text
            labels = batch.label
            
            predictions = model(text, text_length).squeeze(1)
            loss = criterion(predictions, labels)
            epoch_loss += loss.item()
        
    return epoch_loss / len(iterator)
        

evaluate 함수는 평가 데이터에 대해 모델을 평가하고 손실 값을 반환합니다.

4.5 훈련 및 평가 루프

마지막으로, 훈련 및 평가 루프를 작성하여 모델의 학습을 수행합니다.


N_EPOCHS = 5

for epoch in range(N_EPOCHS):
    train_loss = train(model, train_iterator, optimizer, criterion)
    valid_loss = evaluate(model, test_iterator, criterion)

    print(f'Epoch: {epoch+1:02}, Train Loss: {train_loss:.3f}, Valid Loss: {valid_loss:.3f}')
        

이 루프는 주어진 에포크 수에 따라 모델을 훈련시키고 매 에포크마다 훈련 손실과 검증 손실을 출력합니다.

5. 결론

이번 강좌에서는 순환 신경망(RNN)의 기본 개념과 파이토치를 사용하여 이 모델을 구현하는 방법을 배웠습니다. RNN은 시퀀스 데이터를 처리하는 데 효과적이지만, 긴 시퀀스에 대한 한계가 존재합니다. 이에 따라 LSTM과 GRU와 같은 변형 모델을 고려할 필요가 있습니다. 이 내용을 기반으로 더 나아가 다양한 시퀀스 데이터에 대한 실험을 해보는 것도 좋은 학습이 될 것입니다.

이 블로그 글은 딥러닝과 머신러닝의 기초를 다지는 사람들에게 유용한 자료가 될 것입니다. 계속해서 다양한 모델을 실험해보세요!