파이토치 첫걸음 2020.ver을 리뷰하고 있습니다.
이번에는 셰익스피어의 다양한 비극작품 대본을 모아둔 tinyshakespeare 데이터로
중세식 영어 말투를 생성하는 자연어 처리 모델을 만들어보고자 합니다.
※ 참고사항
셰익스피어가 활동한 16~17세기는 영어가 현대적으로 완성되기 한참 전,
중세 영어를 사용하던 시기였습니다. 완전히 고대 영어처럼 아예 알아볼 수 없는 것은 아니지만 라틴어나
게르만어에서 영향을 받은 문체들이 섞여 있습니다.
잠시 중세 말투가 무엇인지, 참고 자료를 보겠습니다.
어떤 상대인지 그대는 모른다
영화 '어벤져스'에서 주인공 일행인 아이언맨(토니 스타크)와
토르(토르 오딘슨)과 처음 대우한 장면
설정상 토르와 로키는 1000년 전부터 지구를 들락날락 하던 타 우주의 왕족으로,
어벤져스 일행과 만난지 얼마 안되었을때 중세식 영어(귀족 영어라는 해석도 존재)를 사용한다는 설정입니다.
이는 마블 영화에서도 자주 놀림거리로 쓰이기도 합니다.
그만큼 중세식 영어는 같은 영어이지만 어체도 상이하고 언뜻 들어서는
다른 언어처럼 들리는 명사들도 상당히 자주 등장한다는 뜻이지요.
실제로 영미권 사람들이 이해하기에는 책에서나 나올법한,
고전소설 속 말투를 따라하는 듯한 느낌이 든다고 합니다.
현재로부터 400년 이상의 격차가 있으니 어색함을 느끼는 것이 당연할지도 모르겠네요.
이제 모델로 돌아와서, 필자는 랜덤하게 영문장을 생성하며,
이를 중세식 영어(셰익스피어 말투)로 변환해보는 과정을 처리해보겠습니다.
사용하는 모델은 RNN, 자연어 처리를 위해서 LSTM을 사용합니다.
최적화 function은 Adam, AdamW 등 다양하게 사용했습니다.
Reference
- https://github.com/karpathy/char-rnn/tree/master/data/tinyshakespeare
- 원데이터 작성자는 TESLA, OpenAi Director 출신 사이언티스트로 현재는 유튜브에서도 연구를 만나볼수 있습니다.(https://www.youtube.com/@AndrejKarpathy)
먼저 ascii문자로 된 vocabulary를 만들었습니다.
import string
all_chars = string.printable
vocab_size = len(all_chars)
vocab_dict = dict((c,i) for (i,c) in enumerate(all_chars))
그리고 문자열(string)을 int로 바꾸는 함수를 정의합니다.
def str2ints(s, vocab_dict):
return [vocab_dict[c] for c in s]
이 이외로 int를 다시 string으로 바꾸는 함수도 정의합니다.
위는 텍스트를 배열로, 아래는 배열을 다시 텍스트로 치환한다고 보면 좋겠네요.
아래는 필자가 사용한 colab으로 원본 텍스트 파일을 불러오는 작업입니다.
source는 위 참조한 karpathy 님의 깃허브에서도 확인하실 수 있습니다.
from google.colab import files
uploaded = files.upload()
불러왔습니다.
Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.
Saving input.txt to input (1).txt
필요한 라이브러리를 불러와줍니다.
import torch
from torch import nn, optim
from torch.utils.data import Dataset, DataLoader, TensorDataset
import tqdm
import tqdm은 일정의 진행 게이지바를 보여주는 패키지 입니다.
이번 모델이나 딥러닝의 학습 모델에서 자주 사용하고 있습니다.
🔽 공식 설명
데이터셋을 불러오는 클래스를 정의합니다. 핵심은 str2ints를 이용해 변환했고,
이를 tensor로 200개 단어 단위로 쪼갰다는 점입니다.
그리고 실행합니다.
ds = ShakespeareDataset('/content/input.txt', chunk_size = 200)
loader = DataLoader(ds, batch_size = 32, shuffle = True, num_workers = 4)
ds = ShakespeareDataset(' ') 안에 들어가는 경로는 따로 설정한 경로입니다.
필자는 모델 학습 전에 근본적으로 gpu를 쓸 수 있도록 변경해주는 편입니다.
(속도 향상을 위한 CUDA 사용)
USE_CUDA = torch.cuda.is_available()
device = torch.device("cuda" if USE_CUDA else "cpu")
print(f'running in {device}')
그리고 임베딩층, LSTM 신경망을 선형 결합으로 해주는 신경망을 만듭니다.
(이번 시간 이후 dropout과 최적화 함수는 계속 변경할 예정입니다. 현재는 dropout 0.2)
# model 신경망
class SequenceGenerationNet(nn.Module):
def __init__(self, num_embeddings, embedding_dim = 50, hidden_size = 50, num_layers = 1, dropout = 0.2):
super().__init__()
self.emb = nn.Embedding(num_embeddings, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_size, num_layers, batch_first = True,
dropout=dropout)
self.linear = nn.Linear(hidden_size, num_embeddings)
def forward(self, x, h0 = None):
x = self.emb(x)
x, h = self.lstm(x, h0)
x = self.linear(x)
return x, h
이제 중세식 소설 말투를 만들어줄 함수를 선언할 차례입니다.
시작은 멋들어지게 '왕께서 말씀하시길' 로 시작합니다.
# 문장 생성하는 함수
def generate_seq(net, start_phrase = 'The King said ', length = 200, temperature = 0.8, device = device):
# 평가모드
net.eval()
result = []
start_tensor = torch.tensor(str2ints(start_phrase, vocab_dict),
dtype = torch.int64).to(device)
# 선두에 batch 차원
x0 = start_tensor.unsqueeze(0)
# RNN을 통해서 출력과 새로운 내부 상태 추출
o, h = net(x0)
# 출력을 확률로 변환 (exp() =지수함수)
out_dist = o[:, -1].view(-1).exp()
# 확률로부터 실제 문자의 인덱스 샘플링
top_i = torch.multinomial(out_dist, 1)[0]
# save
result.append(top_i)
# 결과는 RNN에 투입
for i in range(length):
inp = torch.tensor([[top_i]], dtype = torch.int64)
inp = inp.to(device)
o, h = net(inp, h)
out_dist = o.view(-1).exp()
top_i = torch.multinomial(out_dist,1)[0]
result.append(top_i)
# 시작 문자열과 생성된 문자열을 모아서 반환
return start_phrase + ints2str(result, all_chars)
추가적으로, loss의 평균, 최적화 함수를 위한 패키지 호출
from statistics import mean
from torch.optim.adam import Adam
모델 훈련용 함수를 학습에 사용할 cuda에 넣어주면서 이제 최적화 함수와 loss function도
선언해줍니다. Adam과 crossentropy를 사용합니다.
추가적으로, optimizer 함수를 설명해주는 좋은 위키를 발견했습니다.
Adam는 SGD 계열의 확장 버전으로, 일반적인 처리에서 최적의 성능을 보입니다.
🌍 물론 더 Adamas, AdamW로 다르게 조율해서 최적화를 더 진행할수도 있습니다.
Adam - Cornell University Computational Optimization Open Textbook - Optimization Wiki
훈련을 진행합니다.
epoch는 일단 50회를 추천해서 진행했습니다.
epoch별 loss 평균과 생성된 영어 문장을 조회해줍니다.
# 훈련 시작
for epoch in range(50):
net.train()
losses = []
for data in tqdm(loader):
x = data[:,:-1]
# y는 두번째부터 마지막 문자까지
y = data[:, 1:]
x = x.to(device)
y = y.to(device)
y_pred, _ = net(x)
# batch와 step축을 통합해서 crossentropyloss에 전달
loss = loss_function(y_pred.view(-1, vocab_size), y.view(-1))
net.zero_grad()
loss.backward()
optim.step()
losses.append(loss.item())
print(f'에포크 : [{epoch}]. loss 평균 : [{mean(losses)}]')
with torch.no_grad():
print(generate_seq(net, device = device))
에포크 1회차(for문에서 1부터 시작을 안했네요)일 때를 공개합니다.
100%
175/175 [00:01<00:00, 178.85it/s]
에포크 : [0]. loss 평균 : [4.622120701926095]
The King said 2|RCZDqTVHP(u94DU?%kpRu/zZ7Epx8MAFIJ&+^J?ct&N,bj?*Oh?=RK*np,%Jz4{ %G;!kOxdGV9v}
xe6K/Ia7w&LwB9O5,S<PF3RG5WAOB'cO5Fp'[^TPS^1pT)0X?M;CzEA8nU1P5l+ZbHm4PozcBQ<4d@>XNIL~s,17g_%dak9k4\w[yg31be&LIZ$4|uq00
에포크 50회차일때를 공개합니다.
100%
175/175 [00:01<00:00, 179.82it/s]
에포크 : [49]. loss 평균 : [4.622109421321324]
<6sWB+\s#*q9lFKiU4'Nyqw527];]b_h4W\Z,[Rg\V^}.%cd.sI(.D460X)m7*B#vk<Zom75x
nOi'%h`wU?`,S=zysS]wDY4 !4&O 4rC2\\Uw[aZA\^yml&w
예상처럼 학습이 잘 되지 않은 모습입니다.
epoch가 모두 돌아가는 시간을 고려하여, 다음 회차에는 Adamax, AdamW를 learning rate까지
조절하여 생긴 모델을 공개하도록 하겠습니다.
(2회로 이어집니다)
'딥러닝 > 개인구현 정리' 카테고리의 다른 글
[DeepLearning] GAN 모델 활용_CelebA얼굴 이미지 구분_1 (0) | 2023.02.01 |
---|---|
[DeepLearning] 이미지 구분 모델_Pokemon 809 세트_ep.2 (0) | 2023.01.31 |
[DeepLearning] 이미지 구분 모델_Pokemon 809 세트_ep.1 (0) | 2023.01.26 |
[이미지 처리] 타코와 브리또의 이미지 구분 모델 (0) | 2023.01.24 |
[자연어 처리 학습] 셰익스피어 비극 대본집_중세말투 학습_2 (0) | 2023.01.23 |