파이토치, 워드투벡 텍스트 딥러닝 해보기

2021. 1. 6. 06:39
x_train = np_data[:, 0:19, :]
y_train = np_data[:, 19, :]​
np_data = np.empty(shape=(0,20,vec_size))

for x in tqdm(tot_list):
  if len(x)>=20:
    temp = make_df_vec(x)
    np_data=np.vstack((np_data, temp))
  else:
    pass

딥러닝 프레임워크는 여러가지가 있다. 그 중에서 가장 많이 사용하는 것은 Tensorflow, Keras, Pytorch인 것 같다. Tensorflow와 Keras를 일반적으로 많이 사용하는 듯 하나, 새로운 기술들은 Pytorch로 먼저 공개가 많이 되는 듯 하여 Pytorch를 중점적으로 배워보고자 한다. 오늘은 파이토치를 이용해서, 블로그 텍스트 생성을 해보고자 한다.

 

파이토치 텍스트 생성

 

 

코랩을 계속해서 이용하다 보니 불편한 점들이 하나씩 생긴다. 그 중에 하나는 생성해놓은 워드투벡 파일을 사용하기 어렵다는 점이다. 코랩에 업로드하여 사용하고 있는데, 어쩔 때는 파일이 깨져서인지 인코딩 에러가 발생한다. 그래서 zip파일로 압축하여 업로드한 후에 압축을 해제하여 사용해봤는데 다행히 이상 없이 잘 동작한다.

import sys
sys.path.append('/content')

import pandas as pd
import pickle
from textprepr import TextPreprocessing
import sqlite3
import numpy as np
from tqdm import tqdm
con = sqlite3.connect("/content/post_data.db")
df = pd.read_sql("SELECT * FROM total_df", con)
import gensim

class MyWord2Vec:
  """
    토큰화된 텍스트를 워드투벡으로 임베딩해서 반환
  """
  def __init__(self, file="/content/ko_new.bin"):
    self.model = gensim.models.Word2Vec.load(file)

  def to_w2v(self, pos):
    """
      pos:단어,,  워드투벡으로 임베딩
    """
    try:
      pos_vec = self.model.wv.get_vector(pos)
    except:
      pos_vec = np.zeros(200, )

    return pos_vec
  
  def get_predict_words(self, predict):
    return self.model.wv.most_similar(positive=predict, topn=1)[0][0]
w2v = MyWord2Vec()

x_len = 20
vec_size = 200

def make_df_vec(content_pos, x_len=x_len):
  """
    여러 단어를 워드투벡으로 변경하기
  """
  data = list()
  for i in range(len(content_pos) - x_len):
    data.append(content_pos[i:i+x_len])

  tot_list = list()
  for d in data:
    temp_list = np.array([w2v.to_w2v(pos) for pos in d])
    tot_list.append(temp_list)
  
  np_data = np.array(tot_list)

  return np_data
  
tp = TextPreprocessing()
tot_list = [tp.tagging(x) for x in df["content"]]

 

처음에 할 때 메모리 초과로 코랩이 계속 재부팅되어, 가비지 컬렉트 하는 코드를 추가하였다.

del(df)
import gc
gc.collect()
np_data = np.empty(shape=(0,20,vec_size))

for x in tqdm(tot_list):
  if len(x)>=20:
    temp = make_df_vec(x)
    np_data=np.vstack((np_data, temp))
  else:
    pass

 

numpy의 random.shuffle함수로 만들어 놓은 데이터셋을 섞었다.

x_train = np_data[:, 0:19, :]
y_train = np_data[:, 19, :]

 

파이토치는 GPU를 사용하기 위해, 모델과 입력데이터를 GPU에 넣어주어야 한다. GPU를 사용가능한 환경인지 확인하고, 이를 DEVICE라는 변수에 저장하였다.

import torch

USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")

from torch import nn

class TextGenerator(nn.Module):
	def __init__(self, input_size, hidden_size):
		super(TextGenerator, self).__init__()
		self.lstm = nn.LSTM(200,200,2, batch_first = True, bidirectional = True)
		self.dropout1 = nn.Dropout(p=0.5)

		self.lstm2 = nn.LSTM(400, 300, 2)
		self.dropout2 = nn.Dropout(p=0.3)

		self.linear1 = nn.Linear(300, 300)
		self.linear2 = nn.Linear(300, 300)
		self.dropout3 = nn.Dropout(p=0.1)
		self.linear3 = nn.Linear(300, 200)

		self.relu = nn.ReLU()

	def forward(self, X):
		h_t, _ = self.lstm(X)
		relu1 = self.relu(h_t)
		relu1 = self.dropout1(relu1)

		lstm2, _ = self.lstm2(relu1)
		relu2 = self.relu(lstm2)
		relu2 = self.dropout2(relu2)

		linear1 = self.linear1(relu2)
		linear1 = self.relu(linear1)
		temp = torch.sum(linear1, dim=1)

		linear2 = self.linear2(temp)
		relu3 = self.relu(linear2)
		relu3 = self.dropout3(relu3)

		logit = self.linear3(relu3)
		output = self.relu(logit)

		return output

 

파이토치도 Sequential로 쌓을 수 있어서 그렇게 하려고 했는데, 에러가 발생한다. 아래와 같이 하면, LSTM에서 튜플을 반환하기 때문에 ' AttributeError: 'tuple' object has no attribute 'dim'' 에러가 발생한다. nn.SelectTable(-1)모듈을 넣어서 문제를 해결하는 코드가 있어서, 추가하려고 했는데 버전이 달라서 그런지 모듈이 없다.

model=nn.Sequential(
	nn.LSTM(200,100,2),
	nn.Linear(100,200)
	)

 

model 객체를 만들고, 출력해 보았다.

model = TextGenerator(200,100).to(DEVICE)
print(model)

TextGenerator(
  (lstm): LSTM(200, 200, num_layers=2, batch_first=True, bidirectional=True)
  (dropout1): Dropout(p=0.5, inplace=False)
  (lstm2): LSTM(400, 300, num_layers=2)
  (dropout2): Dropout(p=0.3, inplace=False)
  (linear1): Linear(in_features=300, out_features=300, bias=True)
  (linear2): Linear(in_features=300, out_features=300, bias=True)
  (dropout3): Dropout(p=0.1, inplace=False)
  (linear3): Linear(in_features=300, out_features=200, bias=True)
  (relu): ReLU()
)

 

학습률과 optimizer, 손실함수를 지정하였다.

learning_rate = 0.005
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
criterion = torch.nn.MSELoss()

 

모델을 train모드로 바꾸고 학습을 시작하였다. 학습데이터를 GPU에 넣어야 하는데, GPU메모리 때문에 에러가 발생한다. 그래서 배치크기를 정하고, 배치크기에 해당하는 데이터만 잘라서 GPU에 넣을 수 있도록 수정하였다.

model.train()

batch_size = 2048

for epoch in range(0,15):
	loss_list = list()

	run_th = int(x_train.shape[0]/batch_size)+1

	for i in range(0, run_th):
		optimizer.zero_grad()
		x_temp=x_train[i*batch_size:(i+1)*batch_size]
		y_temp=y_train[i*batch_size:(i+1)*batch_size]
		input_x = torch.FloatTensor(x_temp).to(DEVICE)
		input_y = torch.FloatTensor(y_temp).to(DEVICE)

		train_output = model(input_x)
		loss=criterion(train_output,input_y)

		loss.backward()
		optimizer.step()

		loss_list.append(loss)

	gc.collect()
	print("loss: {}".format(sum(loss_list)))

 

모델을 평가 모드로 바꾸고, 예측을 해 보았다.

model.eval()

tot =0
is_right = 0

for idx in range(0, x_train.shape[0]):
	word_vec_list = x_train[idx]
	y=y_train[idx]

	for word_vec in word_vec_list:
		word=w2v.get_predict_words([word_vec])
		print(word, end=",")

	# 예측
	temp = torch.FloatTensor(word_vec_list).to(DEVICE)
	temp=temp.unsqueeze(0)
	pred=model(temp).to("cpu")
	pred=pred.detach().numpy()[0]
	pre_pos=w2v.get_predict_words([pred])

	real_word = w2v.get_predict_words([y])
	print("|predict={} | real={}".format(pre_pos, real_word))

	tot +=1

	if pre_pos==real_word:
		is_right+=1

	if idx==50:
		print("전체 갯수:{}, 최종 맞춘 갯수: {}".format(tot, is_right))
		break

 

예측한 결과는 그다지 좋지 않지만, 이 역시 학습의 목적이 강하므로 우선 패쓰다.

파이토치 예측 결과

 

위에 작성한 파이토치 코드는 동작하지만, formal하지 않은 면이 있는 것 같다. 배치크기도 직접 잘랐는데 이렇게 사용하지 않고, dataloader를 이용하는 것이 훨씬 편리할 듯 하다. 학습률도 변경해서 하면, 더욱 효율적일 듯 하다. 다음 포스팅에서는 이 코드를 더욱 디벨롭하는 방향으로 작업을 해보도록 하겠다.

댓글()