[NLP] naver movie comments datasets으로 RNN모델 구현

김보현·2024년 7월 29일
0

torch.nn.Embedding을 더 공부해야할 것 같다.

(bohyun) user@server:~/Desktop/BOHYUN/speedrun$ CUDA_VISIBLE_DEVICES=6 python navermovie.py
PyTorch version:[2.3.1+cu121].
device:[cuda:0].
/home/user/miniconda3/envs/bohyun/lib/python3.8/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.
  warnings.warn(
Epoch [1/5], Loss: 0.6933, Train Acc: 0.4988, Test Acc: 0.5035
Epoch [2/5], Loss: 0.6932, Train Acc: 0.4989, Test Acc: 0.5034
Epoch [3/5], Loss: 0.6932, Train Acc: 0.4988, Test Acc: 0.5035
Epoch [4/5], Loss: 0.6932, Train Acc: 0.4988, Test Acc: 0.5035
Epoch [5/5], Loss: 0.6932, Train Acc: 0.4988, Test Acc: 0.5035

결과가 좋지않다..
왜 그런지에 대해 고찰해보았다.
일단 임포트한 모듈은

import os
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import pandas as pd
from transformers import BertTokenizer
import urllib.request

다음과 같다.

def download_nsmc_data():
    url_train = "https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt"
    url_test = "https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt"
    os.makedirs('./data', exist_ok=True)
    urllib.request.urlretrieve(url_train, './data/ratings_train.txt')
    urllib.request.urlretrieve(url_test, './data/ratings_test.txt')
    
# Download the dataset
download_nsmc_data()

깃허브에서

데이터를 로드해오면 같은 디렉토리에 data폴더가 생기고 하위에 txt파일이 두개가 만들어진다.

SSH서버를 사용해서 CUDA에서 돌렸다.

# Check for device
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print("PyTorch version:[%s]."%(torch.__version__))
print("device:[%s]."%(device))

# Load NSMC dataset
class NSMCDataset(torch.utils.data.Dataset):
    def __init__(self, file_path, tokenizer, max_len=128):
        if not os.path.isfile(file_path):
            raise FileNotFoundError(f"File {file_path} not found.")
        self.data = pd.read_csv(file_path, sep='\t').dropna()
        self.tokenizer = tokenizer
        self.max_len = max_len

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        text = self.data.iloc[idx]['document']
        label = self.data.iloc[idx]['label']
        encoding = self.tokenizer.encode_plus(
            text,
            add_special_tokens=True,
            max_length=self.max_len,
            return_token_type_ids=False,
            padding='max_length',
            truncation=True,
            return_attention_mask=True,
            return_tensors='pt'
        )
        return {
            'input_ids': encoding['input_ids'].squeeze(),
            'attention_mask': encoding['attention_mask'].squeeze(),
            'labels': torch.tensor(label, dtype=torch.long)
        }

NSMCDataset 클래스는 NSMC(Naver Sentiment Movie Corpus) 데이터를 처리하기 위해 만들어진 사용자 정의 데이터셋 클래스다.
1. 초기화 (__init__ 메서드):

  • file_path 매개변수로 데이터셋 파일 경로를 받는다.
  • tokenizer 매개변수로 텍스트를 토큰화할 토크나이저를 받는다.
  • max_len 매개변수로 토큰화된 텍스트의 최대 길이를 지정한다.
  • pandas를 사용해 데이터를 불러오고 결측치를 제거한다. 파일이 없으면 예외를 발생시킨다.
  1. 길이 반환 (__len__ 메서드):

    • 데이터셋의 전체 샘플 수를 반환한다.
  2. 샘플 반환 (__getitem__ 메서드):

    • 주어진 인덱스에 해당하는 샘플을 반환한다.
    • 텍스트 데이터를 토크나이저로 인코딩하고, 결과를 텐서로 반환한다.
    • 인코딩된 input_ids, attention_mask, labels를 반환한다.
# Load tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')

# Create datasets
train_dataset = NSMCDataset('./data/ratings_train.txt', tokenizer)
test_dataset = NSMCDataset('./data/ratings_test.txt', tokenizer)

# Create DataLoader
BATCH_SIZE = 256
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=1)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=1)

아래 코드는 BERT 토크나이저를 사용하여 NSMC(Naver Sentiment Movie Corpus) 데이터셋을 로드하고, 데이터 로더를 생성하는 과정을 설명하고 있어.

# 토크나이저 로드
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')

# 데이터셋 생성
train_dataset = NSMCDataset('./data/ratings_train.txt', tokenizer)
test_dataset = NSMCDataset('./data/ratings_test.txt', tokenizer)

# DataLoader 생성
BATCH_SIZE = 256
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=1)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=1)
  1. 토크나이저 로드:

    • BertTokenizer를 'bert-base-multilingual-cased' 모델로부터 로드하여 텍스트 데이터를 토큰화하는데 사용한다.
  2. 데이터셋 생성:

    • NSMCDataset 클래스를 사용하여 학습(train_dataset)과 테스트(test_dataset) 데이터셋을 생성한다.
    • 각 데이터셋은 주어진 파일 경로(예: './data/ratings_train.txt')와 토크나이저를 사용하여 초기화된다.
  3. DataLoader 생성:

    • torch.utils.data.DataLoader를 사용하여 학습 데이터셋과 테스트 데이터셋을 로드할 수 있도록 데이터 로더를 생성한다.
    • BATCH_SIZE를 256으로 설정하고, 학습 데이터 로더는 데이터를 무작위로 섞어 로드하고(shuffle=True), 테스트 데이터 로더는 순차적으로 로드한다.(shuffle=False).

RNN 모델 정의 및 설명

class RecurrentNeuralNetworkClass(nn.Module):
    def __init__(self, name='rnn', vocab_size=30522, emb_dim=128, hdim=256, ydim=2, n_layer=3):
        super(RecurrentNeuralNetworkClass, self).__init__()
        self.name = name
        self.vocab_size = vocab_size
        self.emb_dim = emb_dim
        self.hdim = hdim
        self.ydim = ydim
        self.n_layer = n_layer
        
        self.embedding = nn.Embedding(self.vocab_size, self.emb_dim)
        self.rnn = nn.LSTM(input_size=self.emb_dim, hidden_size=self.hdim, num_layers=self.n_layer, batch_first=True)
        self.lin = nn.Linear(self.hdim, self.ydim)
    
    def forward(self, x):
        x = self.embedding(x)
        h0 = torch.zeros(self.n_layer, x.size(0), self.hdim).to(device)
        c0 = torch.zeros(self.n_layer, x.size(0), self.hdim).to(device)
        rnn_out, (hn, cn) = self.rnn(x, (h0, c0))
        out = self.lin(rnn_out[:, -1, :])
        return out

R = RecurrentNeuralNetworkClass(name='rnn', vocab_size=tokenizer.vocab_size, emb_dim=128, hdim=256, ydim=2, n_layer=2).to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(R.parameters(), lr=1e-3)

설명

  1. 클래스 정의:

    • RecurrentNeuralNetworkClass라는 이름의 RNN 모델 클래스를 정의하였음.
    • nn.Module을 상속받아 모델을 구현하였음.
  2. 초기화 메서드:

    • __init__ 메서드에서 모델의 주요 파라미터들을 초기화함.
    • name, vocab_size, emb_dim, hdim, ydim, n_layer 등 모델의 구조와 관련된 변수를 설정함.
  3. 임베딩 레이어:

    • self.embedding을 사용하여 단어를 임베딩 벡터로 변환함.
    • nn.Embedding을 사용하여 vocab_sizeemb_dim을 파라미터로 설정함.
  4. RNN 레이어:

    • self.rnnnn.LSTM을 사용하여 순환 신경망을 정의함.
    • input_size, hidden_size, num_layers, batch_first 등의 파라미터를 설정함.
  5. 선형 레이어:

    • self.linnn.Linear를 사용하여 RNN의 출력을 최종 출력으로 변환함.
    • hidden_dimoutput_dim을 설정함.
  6. 순전파 메서드:

    • forward 메서드에서 입력 데이터를 임베딩하고, RNN을 통과시킴.
    • 초기 hidden statecell state를 정의함.
    • RNN의 출력 중 마지막 타임스텝의 출력을 선형 레이어에 입력하여 최종 출력을 얻음.
  7. 모델 초기화 및 설정:

    • R 객체를 생성하여 모델을 초기화함.
    • 손실 함수(loss_fn)와 옵티마이저(optimizer)를 설정함.
    • 모델을 device에 할당함. (GPU 또는 CPU)
class RecurrentNeuralNetworkClass(nn.Module):
    def __init__(self, name='rnn', vocab_size=30522, emb_dim=128, hdim=256, ydim=2, n_layer=3):
        super(RecurrentNeuralNetworkClass, self).__init__()
        self.name = name
        self.vocab_size = vocab_size
        self.emb_dim = emb_dim
        self.hdim = hdim
        self.ydim = ydim
        self.n_layer = n_layer
        
        self.embedding = nn.Embedding(self.vocab_size, self.emb_dim)
        self.rnn = nn.LSTM(input_size=self.emb_dim, hidden_size=self.hdim, num_layers=self.n_layer, batch_first=True)
        self.lin = nn.Linear(self.hdim, self.ydim)
    
    def forward(self, x):
        x = self.embedding(x)
        h0 = torch.zeros(self.n_layer, x.size(0), self.hdim).to(device)
        c0 = torch.zeros(self.n_layer, x.size(0), self.hdim).to(device)
        rnn_out, (hn, cn) = self.rnn(x, (h0, c0))
        out = self.lin(rnn_out[:, -1, :])
        return out

R = RecurrentNeuralNetworkClass(name='rnn', vocab_size=tokenizer.vocab_size, emb_dim=128, hdim=256, ydim=2, n_layer=2).to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(R.parameters(), lr=1e-3)
  1. 클래스 정의:

    • RecurrentNeuralNetworkClass라는 이름의 RNN 모델 클래스를 정의하였음.
    • nn.Module을 상속받아 모델을 구현하였음.
  2. 초기화 메서드:

    • __init__ 메서드에서 모델의 주요 파라미터들을 초기화함.
    • name, vocab_size, emb_dim, hdim, ydim, n_layer 등 모델의 구조와 관련된 변수를 설정함.
  3. 임베딩 레이어:

    • self.embedding을 사용하여 단어를 임베딩 벡터로 변환함.
    • nn.Embedding을 사용하여 vocab_sizeemb_dim을 파라미터로 설정함.
  4. RNN 레이어:

    • self.rnnnn.LSTM을 사용하여 순환 신경망을 정의함.
    • input_size, hidden_size, num_layers, batch_first 등의 파라미터를 설정함.
  5. 선형 레이어:

    • self.linnn.Linear를 사용하여 RNN의 출력을 최종 출력으로 변환함.
    • hidden_dimoutput_dim을 설정함.
  6. 순전파 메서드:

    • forward 메서드에서 입력 데이터를 임베딩하고, RNN을 통과시킴.
    • 초기 hidden statecell state를 정의함.
    • RNN의 출력 중 마지막 타임스텝의 출력을 선형 레이어에 입력하여 최종 출력을 얻음.
  7. 모델 초기화 및 설정:

    • R 객체를 생성하여 모델을 초기화함.
    • 손실 함수(loss_fn)와 옵티마이저(optimizer)를 설정함.
    • 모델을 device에 할당함. (GPU 또는 CPU)

한글 설명

평가 함수 정의

def func_eval(model, data_iter, device):
    with torch.no_grad():
        n_total, n_correct = 0, 0
        model.eval()
        for batch in data_iter:
            input_ids = batch['input_ids'].to(device)
            labels = batch['labels'].to(device)
            outputs = model(input_ids)
            _, preds = torch.max(outputs, 1)
            n_correct += (preds == labels).sum().item()
            n_total += input_ids.size(0)
        val_accr = (n_correct / n_total)
        model.train()
    return val_accr

이 함수는 모델을 평가하는 함수로, data_iter에서 배치를 받아 모델의 예측값과 실제 라벨을 비교하여 정확도를 계산함.

모델 학습

EPOCHS = 5
log_file = open("training_log.txt", "w")
for epoch in range(EPOCHS):
    R.train()
    loss_val_sum = 0
    for batch in train_loader:
        input_ids = batch['input_ids'].to(device)
        labels = batch['labels'].to(device)
        
        optimizer.zero_grad()
        outputs = R(input_ids)
        loss = loss_fn(outputs, labels)
        loss.backward()
        optimizer.step()
        
        loss_val_sum += loss.item()
    
    loss_val_avg = loss_val_sum / len(train_loader)
    train_accr = func_eval(R, train_loader, device)
    test_accr = func_eval(R, test_loader, device)
    log_file.write(f"Epoch [{epoch+1}/{EPOCHS}], Loss: {loss_val_avg:.4f}, Train Acc: {train_accr:.4f}, Test Acc: {test_accr:.4f}\n")
    print(f"Epoch [{epoch+1}/{EPOCHS}], Loss: {loss_val_avg:.4f}, Train Acc: {train_accr:.4f}, Test Acc: {test_accr:.4f}")

log_file.close()

이 부분은 모델을 5 에포크 동안 학습시키는 코드임. 각 에포크마다 손실값을 계산하고, 학습 데이터와 테스트 데이터에 대한 정확도를 측정하여 로그 파일에 기록함.

테스트 결과 로그

n_sample = 25
sample_indices = np.random.choice(len(test_dataset), n_sample, replace=False)
test_samples = [test_dataset[i] for i in sample_indices]
test_x = torch.stack([sample['input_ids'] for sample in test_samples]).to(device)
test_y = torch.tensor([sample['labels'] for sample in test_samples]).to(device)

with torch.no_grad():
    R.eval()
    y_pred = R(test_x)
    y_pred = y_pred.argmax(axis=1)

with open("test_results.txt", "w") as result_file:
    for idx in range(n_sample):
        result_file.write(f"Pred: {y_pred[idx].item()}, Label: {test_y[idx].item()}, Text: {tokenizer.decode(test_x[idx])}\n")

이 부분은 테스트 데이터셋에서 임의의 샘플을 선택하여 모델의 예측 결과를 기록함. 예측값, 실제 라벨, 텍스트를 파일에 저장하여 결과를 확인할 수 있음.

profile
Fall in love with Computer Vision

0개의 댓글