Google Colab GPU Text-classification

Colaboratory(혹은 Colab)를 사용하면 브라우저에서 Python을 작성하고 실행할 수 있습니다. 장점이라면 별도의 구성이 필요 없고 무료로 GPU를 사용할 수 있다는 장점이 있습니다. 또 만든 코드를 간단하게 공유할 수도 있습니다.

감성분석(Text Classification)에 사용한 데이터는 네이버에서 공개한 영화 평점 정보입니다. 해당 데이터는 아래 링크에서 받을 수 있습니다.
https://github.com/e9t/nsmc

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import io

본 예제에서 사용할 konlpy를 Colab에 설치합니다.

!pip install konlpy
from konlpy.tag import Okt
okt = Okt()

Colab에서 사용할 파일을 사용자 계정의 구글 드라이브에 업로드합니다. 그리고 업로드한 파일 정보를 Colab에서 읽을 수 있도록 필요한 python 라이브러리를 등록해야합니다. 아래 코드를 실행하면 구글 계정에 인증할 수 있는 정보가 나오고 키값을 입력하면 드라이브에 접근할 수 있습니다.

from google.colab import drive
drive.mount('/content/gdrive')

구글 드라이브에 접근이 완료되면 파일이 있는 디렉토리 위치로 이동합니다.

from google.colab import drive
drive.mount('/content/gdrive')
%cd gdrive/My\ Drive/Colab\ Notebooks

해당 위치로 이동한 후에 %ls 명령을 실행시켜보면 해당 폴더에 있는 파일 리스트를 표시해줍니다. 파일 중에서 학습에 사용할 파일을 open하면 됩니다.

%ls
def read_data(filename):
    with io.open(filename, 'r',encoding='utf-8') as f:
        data = [line for line in f.read().splitlines()]
        data = data[1:]
    return data 

sentences = []
# 테스트를 위해 길이가 30 이하인 문장을 읽음
for sentence in read_data('./ratings_test.txt'):
  if len(sentence) <= 30:
    sentences.append(sentence)

해당 파일을 읽어보면 아래와 같은 형태로 데이터가 구성되어 있습니다. 이전 예제에서 설명했던 것처럼 1은 긍정적인 답변을 0은 부정적인 답변을 의미합니다.

['6270596|굳 ㅋ|1',
 '7898805|음악이 주가 된, 최고의 음악영화|1',
 '6315043|진정한 쓰레기|0',
 '7462111|괜찮네요오랜만포켓몬스터잼밌어요|1',
 '10268521|소위 ㅈ문가라는 평점은 뭐냐?|1' ...
class Vocab():
    def __init__(self):
        self.vocab2index = {'<pad>':0,'<unk>':1} # padding 0, unkown 1
        self.index2vocab = {0:'<pad>',1:'<unk>'} # 0 padding, 1 unkown
        self.vocab_count = {}
        self.n_vocab = len(self.vocab2index)
        
    def add_vocab(self, sentence):
        for word in sentence:
            if word not in self.vocab2index:
                self.vocab2index[word] = self.n_vocab
                self.index2vocab[self.n_vocab] = word
                self.vocab_count[word] = 1
                self.n_vocab += 1
            else:
                self.vocab_count[word] += 1

vo = Vocab()

def charStrip(s):
    s = s.replace('"','').replace('「','').replace('」','').replace('“','').replace('?','').replace('”','')
    s = s.replace('(',' ').replace(')',' ').replace('‘','').replace('’','').replace('□','').replace('◆','').replace('◇','')
    s = s.replace('[',' ').replace(']',' ').replace('○','').replace('△','').replace('◎','').replace('▣','').replace('◇','')
    s = s.replace('.',' ').replace('*',' ').replace('.',' ').replace('~',' ')
    
    return s

x = [] # text sentence
y = [] # label
for sentence in sentences:
    arr = sentence.split('|')
    if(len(arr) == 3):
      sentence = okt.morphs(charStrip(arr[1]))
      
      vo.add_vocab(sentence)
      x.append(sentence) 
      y.append(float(arr[2])) 
MAX_SEQUENCE_LENGTH = 0

for sentence in x:
    if MAX_SEQUENCE_LENGTH < len(sentence): MAX_SEQUENCE_LENGTH = len(sentence)

MAX_SEQUENCE_LENGTH

데이터 중에서 가장 긴 문장을 확인해봅니다. 이 문장의 크기가 Sequence Length가 됩니다. 이 문장의 길이보다 작은 문장의 경우 빈칸은 <unk> 값으로 채워줍니다.

def tensorize(vocab, sentence):
    idx = [vocab.vocab2index[word] for word in sentence]
    #return torch.Tensor(idx).long().item()
    return idx

tmp_tensor = []
for sentence in x:
    tmp = tensorize(vo, sentence)
    tmp_zero = np.zeros(MAX_SEQUENCE_LENGTH)
    
    for i,val in enumerate(tmp):
        tmp_zero[i] = val
        
    tmp_tensor.append(tmp_zero)
    
x_data = torch.Tensor(tmp_tensor).long()
y_data = torch.Tensor([float(t) for t in y])
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
output : device(type='cuda')

구글 Colab에서는 GPU를 사용할 수 있기 때문에 필요한 설정을 해줍니다. CPU로 연산할 때보다 훨씬 빠른 계산속도를 보여줍니다.

학습용 데이터는 8/2로 학습용 Train Data / Valid Data 데이터로 나눠줍니다.

DATA_LENGTH = x_data.size(0)

train_cnt = int(DATA_LENGTH*.8)
valid_cnt = DATA_LENGTH - train_cnt
print('train_cnt, valid_cnt = ',train_cnt,valid_cnt)

idx = torch.randperm(DATA_LENGTH)
x_train = torch.index_select(x_data, dim=0, index=idx).to(device).split([train_cnt, valid_cnt], dim=0)
y_train = torch.index_select(y_data, dim=0, index=idx).to(device).split([train_cnt, valid_cnt], dim=0)

각 데이터셋은 pytorch의 Dataset, DataLoader를 사용해서 배치사이즈로 나눠줍니다. 학습할 데이터가 많은 경우에 많은 데이터를 한번에 읽으면 메모리 부족현상이 발생하는데 Dataset을 배치사이즈로 분리해서 로딩하면 메모리를 적게 사용하게 되어 큰 데이터도 학습할 수 있습니다.

from torch.utils.data import Dataset, DataLoader

class SimpanDataset(Dataset):
    
    def __init__(self, data, label):
        super().__init__()
        self.data = data
        self.labels = label
        
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        return self.data[idx], self.labels[idx]
    
# train_loader
train_loader = DataLoader(dataset=SimpanDataset(x_train[0], y_train[0]), batch_size=250, shuffle=True)
valid_loader = DataLoader(dataset=SimpanDataset(x_train[1], y_train[1]), batch_size=250, shuffle=False)

학습용 모델을 아래와 같이 설정합니다. 학습 모델은 Embedding -> BiLSTM -> Softmax 레이어를 통과하면서 최종 output을 만들어냅니다.
단, output은 모든 Sequence의 데이터를 사용하지 않고 마지막 시퀀스의 값만 사용하며 LSTM의 모델에서 bidirectional을 True로 설정했기 때문에 output*2의 값이 리턴됩니다. 학습에 최종 결과물은 0과 1이기 때문에 hidden_size는 2입니다.

class SimpanClassificationModel(nn.Module):
    
    def __init__(self, input_size, hidden_size):
        super().__init__()
        
        self.embedding = nn.Embedding(input_size, 300)
        
        self.rnn = nn.LSTM(input_size=300, hidden_size=100, num_layers=4, batch_first=True, bidirectional=True)
        
        self.layers = nn.Sequential(
            nn.ReLU(),
            nn.Linear(100*2,100),
            nn.Linear(100,30),
            
            nn.Linear(30, hidden_size),
        )
        
        self.softmax = nn.Softmax(dim=-1)
        
    def forward(self, x):
        y = self.embedding(x)
        y,_ = self.rnn(y)
        y = self.layers(y)
        
        return self.softmax(y[:,-1,:])
    
input_size = vo.n_vocab
hidden_size = torch.unique(y_train[1]).size(dim=-1)

model = SimpanClassificationModel(input_size, hidden_size)
model = model.cuda()
# loss & optimizer setting
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())

hist_loss = []
hist_accr = []

epochs = 501
# start training
model.train()

for epoch in range(epochs):
    epoch_loss = 0
    for x,y in train_loader:
      x, y = x.to(device), y.to(device)
      output = model(x)
      loss = criterion(output, y.long())
        
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()
        
      epoch_loss += loss.item()
      accuracy = (torch.argmax(output, dim=-1) == y).float().mean().item()
    
      
    hist_loss.append(epoch_loss)
    hist_accr.append(accuracy)
        
    print('Cost: {:.6f}, Accuracy : {:.6f}'.format(loss.item(),accuracy))
    print('--{}--'.format(epoch))

학습의 진행상황을 기록하기 위해서 2개의 배열(hist_loss, hist_accr)을 사용합니다.

hist_loss는 loss값의 변화를 기록하는 배열이며 hist_accr은 해당 모델의 정확도 정보를 얻기 위해서 만든 배열입니다. 학습이 진행되며 해당 배열에 데이터가 기록되고 matplotlib.pyplot을 사용해서 그래프를 그려봅니다.

import matplotlib.pyplot as plt

fig, ax = plt.subplots(2,1)
fig.set_size_inches((12, 8)) 

ax[0].set_title('Loss')
ax[0].plot(hist_loss, color='red')
ax[0].set_ylabel('Loss')
ax[1].set_title('Accuracy')
ax[1].plot(hist_accr, color='blue')
ax[1].set_ylabel('Accuracy')
ax[1].set_xlabel('Epochs')

plt.show()
model.eval()
    
for xv, yv in valid_loader:
    output = model(x)
    
    accuracy = (torch.argmax(output, dim=-1) == y).float().mean().item()

    hist_loss.append(loss.item())
    hist_accr.append(accuracy)

    print('Accuracy : {:.6f}'.format(accuracy))

모델의 학습이 완료된 후 valid data를 통해서 학습 모델의 정확도를 알아봅니다.

Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667
Accuracy : 0.916667

“Google Colab GPU Text-classification”에 대한 한개의 댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다