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