pytorch

[pytorch] RNN 계층 구현하기

독립성이 강한 ISFP 2023. 7. 11. 15:55
728x90
반응형

RNN(Recurrent Neural Network, 순환 신경망)은 딥러닝 모델 중 하나로,

주로 시계열 데이터와 같이 순서가 있는 데이터를 처리하는 데 사용됩니다. 

 

RNN의 핵심 구조는 간단한 개념으로 시작합니다.

기본적인 신경망, 즉 인공 신경망(Artificial Neural Network, ANN)은 데이터를 입력받아 가중치를 조정하고,

활성화 함수와 같은 방법으로 출력 값을 생성하는데 사용됩니다.

그러나 ANN은 입력 간의 연관성이 없다고 가정하며, 이로 인해 순서가 있는 데이터 처리에 어려움이 있습니다. 

 

이 문제를 해결하기 위해 RNN이 등장했습니다.

RNN은 인공 신경망 구조를 사용하지만, 순환적으로 연결되어 있어 이전 입력의 정보를 저장하고 처리할 수 있습니다.

이를 통해 연속된 데이터 인식에 사용할수 있는데, 가장 대표적으로 텍스트 데이터를 처리하는 데 사용됩니다. 

RNN을 제대로 이해하려면, 각각의 셀이 어떻게 작동하고, 전체 네트워크가 계산되는 과정을 살펴볼 필요가 있습니다.

RNN의 셀은 활성화 함수로써 tanh와 같은 비선형 활성화 함수와 덧셈 등의 연산을 사용하여 입력과 이전 셀로부터의 정보를 추가적으로 조정하여 출력합니다.

 

요약하자면, RNN은 순서가 있는 데이터를 처리하는데 효과적인 딥러닝 모델로,

일반적인 인공 신경망과의 차이점은 순환적으로 연결되어 이전 입력 정보를 저장하고 처리할 수 있다는 것입니다.

 

ANN 과 RNN의 차이

아래는 RNN의 구조입니다.

RNN의 구조

RNN은 입력층, 은닉층, 출력층 외에도 세 개의 가중치를 가집니다.

첫 번째 가중치인 wxh입력층에서 은닉층으로 정보를 전달합니다.

두 번째 가중치인 whh이전 시점의 은닉층에서 현재 시점의 은닉층으로 정보를 전달합니다.

마지막으로 세 번째 가중치인 why은닉층에서 출력층으로 정보를 전달합니다.

 

각 가중치는 해당 경로에서의 정보 전달과 관련된 역할을 수행합니다.

 


1. 라이브러리 호출

import torch
import torchtext
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import time

2. 데이터셋 다운로드 & 전처리

  • TEXT
    • sequential=True: 시퀀스 데이터임을 나타냄. 단어들의 시퀀스로 이루어진 텍스트임을 의미함.
    • batch_first=True: 배치 차원이 텐서의 첫 번째 축이 됨 (batch_size, seq_len, embedding_dim) 형태를 가짐.
    • lower=True: 모든 텍스트를 소문자로 변환.
  • LABEL
    • sequential = False: 시퀀스가 아닌 단일 데이터임을 나타냄
    • batch_first=True: 배치 차원이 텐서의 첫 번째 축이 됨 (batch_size,) 형태를 가짐
  • build_vocab: 텍스트 데이터의 단어들을 기반으로 어휘 사전 생성
    • max_size: 어휘 사전의 최대 크기를 단어로 설정. 가장 빈번하게 등장하는 상위 10000개의 단어만 선택.
    • min_freq: 어휘 사전에 포함되는 최소 단어 빈도. train 에서 최소한 10번 이상 등장한 단어만 선택
    • vectors=None: 사전 훈련된 단어 임베딩 벡터를 사용하지 않음
start=time.time()

TEXT = torchtext.legacy.data.Field(sequential = True, batch_first = True, lower = True)
LABEL = torchtext.legacy.data.Field(sequential = False, batch_first = True) 

from torchtext.legacy import datasets
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
train_data, valid_data = train_data.split(split_ratio = 0.8)

TEXT.build_vocab(train_data, max_size=10000, min_freq=10, vectors=None)
LABEL.build_vocab(train_data)

3. 데이터셋 분리

BATCH_SIZE = 100
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

train_iterator, valid_iterator, test_iterator = torchtext.legacy.data.BucketIterator.splits(
    (train_data, valid_data, test_data), 
    batch_size = BATCH_SIZE,
    device = device)

4. 변수 값 지정

vocab_size = len(TEXT.vocab) # 고유한 단어의 수
n_classes = 2   # 분류 문제에 대한 클래스 수

5. RNN 계층 네트워크

class BasicRNN(nn.Module):                  # n_vocab: 단어 집합의 크기
    def __init__(self, n_layers, hidden_dim, n_vocab, embed_dim, n_classes, dropout_p = 0.2):
        super(BasicRNN, self).__init__()
        self.n_layers = n_layers                        # RNN의 계층 수
        self.embed = nn.Embedding(n_vocab, embed_dim)   # 임베딩(embedding) 차원의 크기
        self.hidden_dim = hidden_dim                    # 은닉 상태(hidden state)의 크기
        self.dropout = nn.Dropout(dropout_p)            # 드롭아웃(dropout) 확률
        self.rnn = nn.RNN(embed_dim, self.hidden_dim, num_layers = self.n_layers, batch_first = True) 
        self.out = nn.Linear(self.hidden_dim, n_classes) # n_classes: 클래스(레이블)의 개수


    def forward(self, x):       # x: 입력 데이터로, 시퀀스의 인덱스들로 이루어진 텐서입니다.
        x = self.embed(x)       # 임베딩 적용(문자를 숫자/벡터로 변환)
        h_0 = self._init_state(batch_size = x.size(0)) # RNN의 초기 은닉 상태를 0으로 초기화
        x, _ = self.rnn(x, h_0) # 입력값과 이전 시점의 은닉 상태를 받음
        h_t = x[:, -1, :]       # RNN의 마지막 시점에서의 출력값을 선택
        self.dropout(h_t)       # 드롭아웃을 적용
        logit = torch.sigmoid(self.out(h_t)) # 0과 1 사이의 확률값으로 변환
        return logit 

    # RNN 계층의 초기 은닉 상태 텐서를 생성하는 함수. 이 함수는 입력 텐서의 배치 크기에 맞춰 크기가 조절됩니다.
    def _init_state(self, batch_size = 1): 
        weight = next(self.parameters()).data 
        return weight.new(self.n_layers, batch_size, self.hidden_dim).zero_() # RNN의 초기 은닉 상태를 생성하여 반환

6. 손실함수 & 옵티마이저 설정

model = BasicRNN(n_layers = 1, hidden_dim = 256, n_vocab = vocab_size, embed_dim = 128, n_classes = n_classes, dropout_p = 0.5)
model.to(device)

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)

7. 모델 학습

def train(model, optimizer, train_iter):
    model.train() # 학습 모드 

    for b, batch in enumerate(train_iter): # b: 현재 배치의 인덱스 / batch: 배치 데이터
        x, y = batch.text.to(device), batch.label.to(device) # x: text / y: label
        y.data.sub_(1) # label 값을 2/1 -> 1/0 으로 변경
        optimizer.zero_grad() # 옵티마이저 초기화

        logit = model(x) # 
        loss = F.cross_entropy(logit, y)
        loss.backward()
        optimizer.step()

        if b % 50 == 0:
            print("Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}".format(e,
                                                                           b * len(x),
                                                                           len(train_iter.dataset),
                                                                           100. * b / len(train_iter),
                                                                           loss.item()))

8. 모델 평가

def evaluate(model, val_iter):
    model.eval() # 평가 모드
    
    corrects, total, total_loss = 0, 0, 0

    for batch in val_iter:
        x, y = batch.text.to(device), batch.label.to(device)
        y.data.sub_(1) 
        logit = model(x)
        loss = F.cross_entropy(logit, y, reduction = "sum")
        total += y.size(0)
        total_loss += loss.item()
        corrects += (logit.max(1)[1].view(y.size()).data == y.data).sum()
        
    avg_loss = total_loss / len(val_iter.dataset)
    avg_accuracy = corrects / total
    return avg_loss, avg_accuracy

9. 모델 학습 & 평가

BATCH_SIZE = 100
LR = 0.001
EPOCHS = 5
for e in range(1, EPOCHS + 1):
    train(model, optimizer, train_iterator)
    val_loss, val_accuracy = evaluate(model, valid_iterator)
    print("[EPOCH: %d], Validation Loss: %5.2f | Validation Accuracy: %5.2f" % (e, val_loss, val_accuracy))

10. test 데이터셋을 이용한 모델 예측

test_loss, test_acc = evaluate(model,test_iterator)
print("Test Loss: %5.2f | Test Accuracy: %5.2f" % (test_loss, test_acc))

> Test Loss: 0.68 | Test Accuracy: 0.61

728x90
반응형