DL/RNN

[DL][RNN] RNN(Recurrent Neural Network, 순환 신경망) 구조

어떻게든 되겠지~ 2024. 8. 26. 22:52

 

 

 

 

앞선 글에서 RNN에 대해 간략하게 알아보았습니다

 

 

[DL][RNN] RNN(Recurrent Neural Network, 순환 신경망) Introduce

RNN(Recurrent Neural Network, 순환 신경망) 이란?RNN이란 시간적으로 연속성이 있는 데이터를 처리하기 위해 고안된 인공신경망이다'Recurrent'는 이전 은닉층이 현재 은닉층의 입력이 되면서 '반복되는

self-objectification.tistory.com

 

이번 글에서는 RNN Cell과 RNN Layer의 구조 및 수식에 대해 깊게 공부해보고 PyTorch를 통해 구현해보도록 하겠습니다


RNN(Recurrent Neural Network, 순환 신경망) 구조

RNN은 은닉층 노드들이 연결되어 이전 단계 정보를 은닉층 노드에 저장할 수 있도록 구성한 신경망이다

위의 그림에서 $x_{t-1}$에서 $h_{t-1}$을 얻고 다음 단계에서 $h_{t-1}$과 $x_t$를 사용하여 과거 정보와 현재 정보를 모두 반영한다.      
또한, $h_t$와 $x_{t+1}$의 정보를 이용하여 과거와 현재의 정보를 반복해서 반영한다.   

 

RNN Weights

  • ${\large W_{xh}}$ : 입력층에서 은닉층으로 전달되는 가중치
  • ${\large W_{hh}}$ :  t 시점의 은닉층에서 t + 1 시점의 은닉층으로 전달되는 가중치
  • ${\large W_{hy}}$ : 은닉층에서 출력층으로 전달되는 가중치

이 떄, ${\large W_{xh}, W_{hh}, W_{hy} }$는 모든 시점에서 동일하다'


t 시점에서의 RNN Cell 계산

1. 은닉층

t 시점의 Input $x_t$와 이전 시점의 은닉층 값 $h_{t-1}$를 통해 계산한다

일반적으로 Activation Function은 하이퍼볼릭 탄젠트 함수를 사용한다

$${\Large h_t = \tanh{ (W_{hh} \cdot h_{t-1} + W_{xh} \cdot x_t)} }$$

2. 출력층

DNN(심층 신경망)과 계산 방법이 동일하다

$${\Large \hat{y_{t}} = softmax(W_{hy} \cdot h_t)}$$

3. 오차

RNN의 오차(E)는 심층 신경망에서 전방향 학습과 달리 각 단계(t)마다 오차를 측정한다   

즉, 각 단계마다 실제 값($y_t$)과 예측 값($\hat{y_t}$)으로 오차를 이용하여 측정  

 

RNN 순방향 학습

4. 역전파

BPTT(BackPropagation Through Time)를 이용하여 모든 단계마다 처음부터 끝까지 역전파를 수행한다

오차는 각 단계(t)마다 측정하고 이전 단계로 전달되는데, 이것을 BPTT라 한다  

즉, 구한 오차를 이용하여 $W_{xh}$, $W_{hh}$, $W_{hy}$ 및 bias를 업데이트한다

이 때, BPTT는 오차가 멀리 전파될 때(왼쪽으로 전파) 계산량이 많아지고 전파되는 양이 점점 적어지는 문제점(기울기 소멸 문제)이 발생한다.

기울기 소멸 문제를 보완하기 위해 오차를 몇 단계까지만 전파시키는 생략된-BPTT를 사용할 수도 있고, 근본적으로 LSTM, GRU를 많이 사용한다   


BPTT(BackPropagation Through Time)를 이용하여 모든 단계마다 처음부터 끝까지 역전파를 수행한다

오차는 각 단계(t)마다 측정하고 이전 단계로 전달되는데, 이것을 BPTT라 한다  

즉, 구한 오차를 이용하여 $W_{xh}$, $W_{hh}$, $W_{hy}$ 및 bias를 업데이트

이 때, BPTT는 오차가 멀리 전파될 때(왼쪽으로 전파) 계산량이 많아지고 전파되는 양이 점점 적어지는 문제점(기울기 소멸 문제)이 발생한다.

기울기 소멸 문제를 보완하기 위해 오차를 몇 단계까지만 전파시키는 생략된-BPTT를 사용할 수도 있고, 근본적으로 LSTM, GRU를 많이 사용한다   

※ RNN 역전파 수식

 

학습 대상 : $W_{xh}, W_{hh}, W_{hy}$

 

수식에서 볼 수 있듯이 $t_3$에서의 영향, $t_3$로 부터 전해진 영향, $t_2$에서부터 전해진 영향을 모두 고려해야하기 때문에 계산량이 많다        
따라서 생략된 - BPTT를 사용한다


RNN의 한계

  • 장기 의존성 문제(Long Term Dependency Probelm)
    • 은닉층의 과거 정보가 마지막까지 전달되지 못하는 현상
    • \begin{aligned} h_t &= \tanh{(W_{xh}\cdot x_t + W_{hh} \cdot h_{t-1})}\\ &= \tanh{(W_{xh} \cdot x_t + W_{hh} \cdot \tanh{(W_{xh} \cdot x_{t-1} + W_{hh} \cdot h_{t-2})})} \end{aligned}
    • Forward 과정에서 뒷단으로 갈수록 앞의 정보를 충분히 전달하지 못한다(왜냐하면 $\tanh$ 값이 (-1, 1)이기 때문이다)
  • 기울기 소멸 문제(Vanishing Gradient)
    • ${\large \frac{\partial h_t}{\partial h_{t-1}} = W_{hh} \cdot \frac{d}{dx}\tanh{(W_{xh} \cdot x_t + W_{hh} \cdot h_{t-1})}}$
    • 위의 식과 같기 때문에 t가 커질수록 (0, 1) 사이의 값이 계속 곱해지기 때문에 기울기 소멸 문제가 발생할 수 있다

이제 RNN Cell을 PyTorch를 통해 구현해보도록 하겠습니다

 

RNN Cell 구현

1) 필요한 라이브러리 Import

import torch
# torchtext
# NLP 분야에서 사용되는 DataLoader
# 파일 가져오기, 토큰화, 단어 집합 생성, 인코딩, 단어 벡터 생성 등 작업을 지원
# torchtext version : 0.6.0
# torchvision, torchaudio 버전을 사용하기 위해서는 torch 2.2.1이 필요함
import torchtext
import numpy as np 
import torch.nn as nn
import torch.nn.functional as F
import time

2) 데이터 셋 준비 및 전처리

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

 

※ torchtext.data.Field

데이터 전처리를 위해 사용   

  • lower : 대문자를 모두 소문자로 변경
  • fix_length : 고정된 길이의 데이터를 얻는다(지정한 길이보다 짧다면 padding을 통해 맞춰준다, 길면 자른다)
  • batch_first : 신경망에 입력되는 텐서의 첫 번째 차원값이 batch_size가 되도록 한다
    • 모델 네트워크로 입력되는 데이터는 [시퀀스 길이, 배치 크기, 은닉층의 뉴런 개수(Embedding Dimension)]의 형태이다
    • 이 때 batch_first=True로 설정하면 [배치 크기, 시퀀스 길이, 은닉층 뉴런 개수] 형태로 변경된다
    • 데이터 형태를 맞추는 것은 정말 중요하다
  • sequential : 데이터 순서가 있는지를 나타낸다
from torchtext import datasets
# 영화 리뷰 5만건이 담긴 데이터로 긍정은 2, 부정은 1로 레이블링 되어있다
train_data, test_data = datasets.IMDB.splits(text_field=TEXT, label_field=LABEL, root='data')

 

 

IMDB 데이터 셋은 영화 리뷰 5만건이 담긴 데이터 셋으로 긍정 리뷰는 2로, 부정 리뷰는 1로 레이블링 되어있다

 

import string

for example in train_data.examples:
    text = [x.lower() for x in vars(example)['text']] # 소문자로 변경
    text = [x.replace('<br', '') for x in text] # <br을 공백으로 변경
    # string.punctuation : 특수문자들(!, #, . 등등)
    # text에 있는 모든 단어 -> s 즉, 각 단어 -> c 즉, 각 문자 하나
    text = [s for s in text if s]   
    text = [''.join(c for c in s if c not in string.punctuation)  for s in text] # 구두점(.) 제거 (특수문자를 공백으로)

    vars(example)['text'] = text

3) 데이터 셋 내용 확인

# 실제 train_data의 길이는 다양한데
# Train 과정에서는 length가 200으로 맞춰진다
# 200이 넘어가는 데이터들은 앞/뒤 단어가 짤려 200개가 되고
# 200이 되지않는 데이터들은 '<pad>'가 채워져 200으로 맞추어 진다
print(len(vars(train_data.examples[1])['text']))
print(len(vars(train_data.examples[6])['text']))

# 'text' : ~~~ , 'label' : 'pos', 'neg'

print(vars(train_data.examples[0]))
print(len(vars(train_data.examples[0])['text']))
print(vars(train_data.examples[0])['label'])

 

출력 결과 : 

424
105
{'text': ['bromwell', 'high', 'is', 'a', 'cartoon', 'comedy', 'it', 'ran', 'at', 'the', 'same', 'time', 'as', 'some', 'other', 'programs', 'about', 'school', 'life', 'such', 'as', 'teachers', 'my', '35', 'years', 'in', 'the', 'teaching', 'profession', 'lead', 'me', 'to', 'believe', 'that', 'bromwell', 'highs', 'satire', 'is', 'much', 'closer', 'to', 'reality', 'than', 'is', 'teachers', 'the', 'scramble', 'to', 'survive', 'financially', 'the', 'insightful', 'students', 'who', 'can', 'see', 'right', 'through', 'their', 'pathetic', 'teachers', 'pomp', 'the', 'pettiness', 'of', 'the', 'whole', 'situation', 'all', 'remind', 'me', 'of', 'the', 'schools', 'i', 'knew', 'and', 'their', 'students', 'when', 'i', 'saw', 'the', 'episode', 'in', 'which', 'a', 'student', 'repeatedly', 'tried', 'to', 'burn', 'down', 'the', 'school', 'i', 'immediately', 'recalled', 'at', 'high', 'a', 'classic', 'line', 'inspector', 'im', 'here', 'to', 'sack', 'one', 'of', 'your', 'teachers', 'student', 'welcome', 'to', 'bromwell', 'high', 'i', 'expect', 'that', 'many', 'adults', 'of', 'my', 'age', 'think', 'that', 'bromwell', 'high', 'is', 'far', 'fetched', 'what', 'a', 'pity', 'that', 'it', 'isnt'], 'label': 'pos'}
138
pos

4) Train, Valid 데이터 셋 분리

import random
train_data, valid_data = train_data.split(random_state=random.seed(42), split_ratio=0.8)

print(f"Number of training examples : {len(train_data)}")
print(f"Number of validation examples : {len(valid_data)}")
print(f"Number of testing examples : {len(test_data)}")

출력 결과 : 

Number of training examples : 20000
Number of validation examples : 5000
Number of testing examples : 25000

 

5) 단어 집합 만들기

# max_size : 단어 집합의 크기(포함되는 어휘의 수)
# min_freq : 특정 단어의 최소 등장 횟수
# vectors : 임베딩 벡터 지정(워드 임베딩의 결과, word2vec, Glove ...)

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

# 10002개 인 이유 : <unk> (unknown)와 <pad> (padding) 같은 특수 토큰이 기본적으로 포함
print(f"Unique tokens in TEXT vocabulary : {len(TEXT.vocab)}")
# 3개인 이유 :  pos, neg에 <unk>까지 포함되기 때문이다
print(f"Unique tokens in LABEL vocabulary : {len(LABEL.vocab)}")

 

Train Dataset에서 사용될 단어 집합을 만드는 과정입니다

단어 집합이란 데이터 셋에 포함된 단어들을 이용하여 하나의 딕셔너리와 같은 집합을 만드는 것입니다

TEXT.vocab.stoi

위 코드로 단어집합을 확인할 수 있습니다

{'<unk>': 0,
'<pad>': 1,
'the': 2,
'and': 3,
'a': 4,
'of': 5,
'to': 6,
'is': 7,
'in': 8,
'it': 9,
'i': 10,
...

 

LABEL.vocab.stoi

 

{'<unk>': 0, 'neg': 1, 'pos': 2})

 

6) 데이터 셋 메모리로 가져오기

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

embedding_dim = 100 # 단어집합을 10002 -> 100차원으로 조정
hidden_size = 300   # 은닉층의 유닛(각각의 뉴런) 개수 지정

# BucketIterator를 이용하여 데이터셋을 메모리로 가져온다
train_iterator, valid_iterator, test_iterator = torchtext.data.BucketIterator.splits(
    (train_data, valid_data, test_data),
    batch_size=BATCH_SIZE,
    device=device
)

 

BucketIterator     
데이터로더와 쓰임새가 같다. 즉, 배치 크기 단위로 값을 차례대로 꺼내어 메모리로 가져온다     
BucketIterator는 비슷한 길이의 데이터를 한 배치에 할당하여 padding을 최소화시켜준다 

7) 워드 임베딩 및 RNN Cell 정의

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

embedding_dim = 100 # 단어집합을 10002 -> 100차원으로 조정
hidden_size = 300   # 은닉층의 유닛(각각의 뉴런) 개수 지정

# BucketIterator를 이용하여 데이터셋을 메모리로 가져온다
train_iterator, valid_iterator, test_iterator = torchtext.data.BucketIterator.splits(
    (train_data, valid_data, test_data),
    batch_size=BATCH_SIZE,
    device=device
)

 

10002개의 단어로 이루어진 단어집합을 워드 임베딩을 통해 100차원으로 조정한다

그리하여 앞서 전처리 과정에서 설정한 fix_length=200, BATCH_SIZE=60, embedding_dim=100으로 설정하여 모델 네트워크에 입력되는 데이터의 형태는 [200, 64, 100] ([시퀀스 길이, 배치 크기, 은닉층의 뉴런 개수(Embedding Dimension)])f로 맞춰지게 되었다

 

Train 과정에 들어가기 전의 과정을 정리하면 아래 순서와 같다

 

1. 10002개의 단어 집합을 만들고 이를 수에 대응하게 함(Field)
2. 10002개의 단어 집합을 100차원의 임베딩 벡터로 변환('i':10 -> [-0.4989, -0.2806, ... , -0.9877] (Dimension : 100차원) )
3. 20000개의 Train data(batch_size=64) 준비
4. train_iterator를 통해 [200, 64]의 1 batch를 모델에 적용 (하나의 컬럼이 1 train data)
   1. 왜냐하면 RNN은 시계열 데이터를 사용하기 때문에 64개의 데이터(batch)의 첫번째 element를 모델에 적용시켜야하고
   2. 이렇게 초기 hidden state와 $x_0$를 통해 $h_0$을 구하고
   3. 구한 $h_0$과 다음 $x_1$을 통해 $h_1$을 구하는 방법으로 진행되기 때문이다

class RNNCell_Encoder(nn.Module):
    def __init__(self, input_dim, hidden_size):
        super(RNNCell_Encoder, self).__init__()
        self.rnn = nn.RNNCell(input_size=input_dim, hidden_size=hidden_size, bias=True, nonlinearity= "tanh")

    # inputs : (input_dim, batch_size, embedding_dim)
    def forward(self, inputs):
        bz = inputs.shape[1] # Batch size 가져오기
        ht = torch.zeros((bz, hidden_size)).to(device) # [Batch size, Hidden Size] 크기의 Matrix를 0으로 초기화
        # Recursive하게 발생하는 상태 값 처리
        for word in inputs:
            # 현재 상태 : 현재 입력과 이전 상태를 통해 계산
            # word : x_t
            # ht : h_{t-1}
            ht = self.rnn(word, ht)
        return ht
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 워드 임베딩을 해주지 않았기 떄문에 Embedding을 해준다
        self.em = nn.Embedding(len(TEXT.vocab), embedding_dim=embedding_dim) # 10002 -> 100
        self.rnn = RNNCell_Encoder(embedding_dim, hidden_size)
        self.fc1 = nn.Linear(hidden_size, 256)
        self.fc2 = nn.Linear(256, 3)

    def forward(self, x):
        x = self.em(x)
        x = self.rnn(x)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

 

 

8) 모델 학습을 위한 Optimizer, Loss Function 및 함수 정의

model = Net()
model.to(device)

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

Loss Function은 Cross Entropy Loss, Optimizer는 Adam을 사용하였다

def training(epoch, model, trainloader, validloadder):
    correct = 0
    total = 0
    running_loss = 0.0  

    model.train()
    for b in trainloader:
        x, y = b.text, b.label
        x, y = x.to(device), y.to(device)

        y_pred = model(x)
        loss = criterion(y_pred, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        with torch.no_grad():
            y_pred = torch.argmax(y_pred, dim=1)
            correct += (y_pred == y).sum().item()
            total += y.size(0)
            running_loss += loss.item()
    epoch_loss = running_loss / len(trainloader.dataset)
    epoch_acc = correct / total

    valid_correct = 0
    valid_total = 0
    valid_running_loss = 0.0

    model.eval()
    with torch.no_grad():
        for b in validloadder:
            x, y = b.text, b.label
            x, y = x.to(device), y.to(device)
            y_pred = model(x)
            loss = criterion(y_pred, y)
            y_pred = torch.argmax(y_pred, 1)
            valid_correct += (y_pred == y).sum().item()
            valid_total += y.size(0)
            valid_running_loss += loss.item()

    epoch_valid_loss = valid_running_loss / len(validloadder.dataset)
    epoch_valid_acc = valid_correct / valid_total

    print('epoch : ', epoch, 
          'loss : ', round(epoch_loss, 3),
          'accuracy : ', round(epoch_acc, 3),
          'valid_loss : ', round(epoch_valid_loss, 3), 
          'valid_accuracy : ', round(epoch_valid_acc, 3))
    return epoch_loss, epoch_acc, epoch_valid_loss, epoch_valid_acc

 

9) 모델 학습

epochs = 5
train_loss = []
train_acc = []
valid_loss = []
valid_acc = []


for epoch in range(epochs):
    epoch_loss, epoch_acc, epoch_valid_loss, epoch_valid_acc = training(epoch, model, train_iterator, valid_iterator)
    train_loss.append(epoch_loss)
    train_acc.append(epoch_acc)
    valid_loss.append(epoch_valid_loss)
    valid_acc.append(epoch_valid_acc)

 

학습 결과 :

epoch : 0 loss : 0.011 accuracy : 0.498 valid_loss : 0.011 valid_accuracy : 0.5
epoch : 1 loss : 0.011 accuracy : 0.507 valid_loss : 0.011 valid_accuracy : 0.506
epoch : 2 loss : 0.011 accuracy : 0.514 valid_loss : 0.011 valid_accuracy : 0.5
epoch : 3 loss : 0.011 accuracy : 0.52 valid_loss : 0.011 valid_accuracy : 0.505
epoch : 4 loss : 0.011 accuracy : 0.522 valid_loss : 0.011 valid_accuracy : 0.502

 

10) Test

def evaluate(epoch, model, testloader):
    test_correct = 0
    test_total = 0
    test_running_loss = 0.0

    model.eval()
    with torch.no_grad():
        for b in testloader:
            x, y = b.text, b.label
            x, y = x.to(device), y.to(device)

            y_pred = model(x)
            loss = criterion(y_pred, y)
            y_pred = torch.argmax(y_pred, 1)
            test_correct += (y_pred == y).sum().item()
            test_total += y.size(0)
            test_running_loss += loss.item()

    epoch_test_loss = test_running_loss / len(testloader.dataset)
    epoch_test_acc = test_correct / test_total

    
    print('epoch : ', epoch,
          'test_loss : ', round(epoch_test_loss, 3),
          'test_accuracy : ', round(epoch_test_acc, 3))
    return epoch_test_loss, epoch_test_acc

 

epochs = 5
test_loss = []
test_acc = []

for epoch in range(epochs):
    epoch_loss, epoch_acc = evaluate(epoch, model, test_iterator)
    test_loss.append(epoch_loss)
    test_acc.append(epoch_acc)

 

Test 결과 :

epoch : 0 test_loss : 0.011 test_accuracy : 0.509
epoch : 1 test_loss : 0.011 test_accuracy : 0.509
epoch : 2 test_loss : 0.011 test_accuracy : 0.509
epoch : 3 test_loss : 0.011 test_accuracy : 0.509
epoch : 4 test_loss : 0.011 test_accuracy : 0.509

 


RNN Layer 구현

RNN Cell 구현에서 데이터 전처리 부분은 동일하므로 RNN을 구현한 부분의 코드부터 보도록 하겠습니다

RNN Layer 정의

class BasicRNN(nn.Module):
    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 Hidden Layer 개수
        self.embed = nn.Embedding(num_embeddings=n_vocab, embedding_dim=embed_dim) # Word Embedding(10002 -> 128)
        self.hidden_dim = hidden_dim
        self.dropout = nn.Dropout(dropout_p)

        # RNN Layer
        # embed_dim : 훈련 데이터셋의 feature 개수(Embedding 후)
        # hidden_dim : 은닉 계층의 유닛 개수
        # num_layers : RNN 은닉층의 개수    
        self.rnn = nn.RNN(input_size=embed_dim,
                          hidden_size=self.hidden_dim,
                          num_layers=self.n_layers,
                          batch_first=True)
        
        self.out = nn.Linear(self.hidden_dim, n_classes)
        
    def forward(self, x):
        x = self.embed(x)
        h_0 = self._init_state(batch_size=x.size(0)) # 최초 Hidden state를 0으로 초기화
        x, _ = self.rnn(x, h_0) # RNN Layer를 의미하며, Parameter로 input과 이전 Hidden state 값을 받는다
        h_t = x[:, -1, :] # 모든 Network를 거쳐 가장 마지막에 나온 단어의 임베딩 값(마지막 은닉 상태의 값)

        self.dropout(h_t)
        logit = torch.sigmoid(self.out(h_t))
        return logit
    
    def _init_state(self, batch_size=1):
        weight = next(self.parameters()).data # 모델 파라미터 값을 가져와서 저장
        return weight.new(self.n_layers, batch_size, self.hidden_dim).zero_() # [계층의 개수, 배치 크기, 은닉층의 유닛 개수]인 Hidden State를 생성하여 0으로 초기화

 

 

위 그림은 Layer를 2로 쌓아올린 RNN Model의 그림입니다

여기서 1 layer의 결과로 Hidden Layer의 출력 $h_1$이 출력되는데 이는 2 Layer의 입력이자 2단계 time step에서의 이전 상태 즉 $h_{t-1}$의 역할을 하게됩니다

 

Loss Function 및 Optimizer 설정

vocab_size = len(TEXT.vocab)
n_classes = 2

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

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

모델 학습 및 평가 함수

def train(model, optimizer, train_iter):
    model.train()
    for b, batch in enumerate(train_iter):
        x, y = batch.text.to(device), batch.label.to(device)
        y.data.sub_(1) # 긍정 : 2, 부정 : 1 이기 때문에 1을 빼서 사용
        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()))

 

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() 
        # max(1)[1] : max(dim=)[0 or 1] : [0]은 최대값, [1]은 최대값을 갖는 index
        # view(y.size()) : logit.max(1)[1]의 결과를 y.size()로 크기를 변경
        # .data == y.data : 오델의 예측결과가 레이블과 같은지를 확인
        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

 

모델 학습 및 평가

BATCH_SIZE = 100
LR = 0.001
EPOCHS = 5

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

 

반응형