Deep Learning study
[pytorch] RNN seq2seq 간단한 대화모델 본문
며칠간 재미있는 대화 데이터를 가지고 대화모델을 만들어 보았다.
source.txt
거울 보면 본인이 잘생겼다는 생각이 드나요?
너만 자꾸 생각나
생일 축하해!
너무 더워서 숨이 턱턱 막혀
주말에 뭐하지?
짜증나
...
target.txt
당연하죠. 전 거울을 볼 때마다 스스로 잘생겼다는 생각을 해요
나도 그래. 내 머릿속은 항상 너로 가득 차 있어.
생일 축하해줘서 고마워
많이 더우신가 봐요.
서핑? 이번 주말에는 날씨가 좋대~
뭐가 짜증 나시는지 들어드릴게요. 얘기해보세요
...
대충 이런대화모델!
전체적인 코드는 pytorch tutorial을 보고 만들었다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from __future__ import unicode_literals, print_function, division from io import open import unicodedata import string import re import random import torch import torch.nn as nn from torch.autograd import Variable from torch import optim import torch.nn.functional as F use_cuda = torch.cuda.is_available() MAX_LENGTH = 20 | cs |
필요한 것들을 import했다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #make dict SOS_token = 0 EOS_token = 1 UNKNOWN_token = 2 class Lang : def __init__(self, name): self.name = name self.word2index = {} self.index2word = {} self.word2count = {0: "SOS", 1: "EOS", 2:"UNKNOWN"} self.n_words = 3 #count SOS and EOS and UNKWON def addSentence(self, sentence): for word in sentence.split(' '): self.addWord(word) def addWord(self, word): if word not in self.word2index: self.word2index[word] = self.n_words self.word2count[word] = 1 self.index2word[self.n_words] = word self.n_words += 1 else: self.word2count[word] += 1 | cs |
Lang 은 source데이터와 target 데이터를 단어 단위로 잘라서 dictionary를 만들어 주는 class이다.
여기서는 일단 띄어쓰기 단위로 문장을 잘라서 저장시켰다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #Turn a Unicode stirng to plain ASCII def unicodeToAscii(s): return ''.join( c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn' ) # Lowercase, trim, and remove non-letter characters def normalizeString(s): hangul = re.compile('[^ ㄱ-ㅣ가-힣 ^☆; ^a-zA-Z.!?]+') result = hangul.sub('', s) # s = unicodeToAscii(s.lower().strip()) # s = re.sub(r"([.!?])", r" \1", s) # s = re.sub(r"[^a-zA-Z.!?]+", r" ", s) return result | cs |
unicodeToAscii는 말그대로 unicode를 Ascii로 반환해주는 함수이다. 컴퓨터가 알아먹게 해주려면 Ascii 값으로 변환해 주어야하기 때문.!
그리고 문장에 있는 각 구두점들을 제거시키고, 한글과, 영어, 필요한 기호들만 남기기위해 normalize를 한다.
막 여러개 갖다 붙여봤는데 일단 잘되긴 하는것 같다. 잘한건지는 나도 모른다 ㅎ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | def readText(): print("Reading lines...") inputs = open('../data/humor/source.txt', encoding='utf-8').read().strip().split('\n') outputs = open('../data/humor/target.txt', encoding='utf-8').read().strip().split('\n') inputs = [normalizeString(s) for s in inputs] outputs = [normalizeString(s) for s in outputs] print(len(inputs)) print(len(outputs)) inp = Lang('input') outp = Lang('output') pair = [] for i in range(len(inputs)): pair.append([inputs[i], outputs[i]]) return inp, outp, pair | cs |
다음엔 .txt파일을 읽어온다. utf-8로 encoding하고 줄바꿈 단위로 문장을 읽어온다.
source와 target을 짝지어서 pair들의 배열로 return !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def prepareData(): input_lang, output_lang, pairs = readText() print("Read %s sentence pairs" % len(pairs)) print("Counting words...") for pair in pairs: input_lang.addSentence(pair[0]) output_lang.addSentence(pair[1]) print("Counted words:") print(input_lang.name, input_lang.n_words) print(output_lang.name, output_lang.n_words) return input_lang, output_lang, pairs input_lang, output_lang, pairs = prepareData() print(random.choice(pairs)) | cs |
데이터를 읽어오고, 그 데이터들의 각 문장들을 단어단위로 저장해서 dictionary를 만들어주고 return해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class EncoderRNN(nn.Module): def __init__(self, input_size, hidden_size): super(EncoderRNN, self).__init__() self.hidden_size = hidden_size self.embedding = nn.Embedding(input_size, hidden_size) self.gru = nn.GRU(hidden_size, hidden_size) def forward(self, input, hidden): embedded = self.embedding(input).view(1, 1, -1) output = embedded output, hidden = self.gru(output, hidden) return output, hidden def initHidden(self): result = Variable(torch.zeros(1,1, self.hidden_size)) if use_cuda: return result.cuda() else: return result | cs |
Encode하는 모델 , 워드를 embedding하여 그 값들을 return 해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | class AttnDecoderRNN(nn.Module): def __init__(self, hidden_size, output_size, dropout_p = 0.1, max_length=MAX_LENGTH): super(AttnDecoderRNN, self).__init__() self.hidden_size = hidden_size self.output_size = output_size self.dropout_p = dropout_p self.max_length = max_length self.embedding = nn.Embedding(self.output_size, self.hidden_size) self.attn = nn.Linear(self.hidden_size * 2 , self.max_length) self.attn_combine = nn.Linear(self.hidden_size*2, self.hidden_size) self.dropout = nn.Dropout(self.dropout_p) self.gru = nn.GRU(self.hidden_size, self.hidden_size) self.out = nn.Linear(self.hidden_size, self.output_size) def forward(self, input, hidden, encoder_outputs): embedded = self.embedding(input).view(1,1,-1) embedded = self.dropout(embedded) attn_weights = F.softmax(self.attn(torch.cat((embedded[0], hidden[0]) , 1 ))) attn_applied = torch.bmm(attn_weights.unsqueeze(0), encoder_outputs.unsqueeze(0)) output = torch.cat((embedded[0], attn_applied[0]), 1) output = self.attn_combine(output).unsqueeze(0) output = F.relu(output) output, hidden = self.gru(output, hidden) output = F.log_softmax(self.out(output[0])) return output, hidden, attn_weights def initHidden(self): result = Variable(torch.zeros(1,1,self.hidden_size)) if use_cuda: return result.cuda() else: return result | cs |
Decoder부분.
특이한거라곤 attention layer가 생겼다는거? !?
attention은 문장에서 좀더 핵심적인 단어들을 부각시켜주는(?) 역할을 한다고 하는것같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def indexesFromSentence(lang, sentence): return [lang.word2index[word] for word in sentence.split(' ')] def variableFromSentence(lang, sentence): indexes = indexesFromSentence(lang, sentence) indexes.append(EOS_token) print(indexes) result = Variable(torch.LongTensor(indexes).view(-1,1)) if use_cuda: return result.cuda() else: return result def variablesFromPair(pair): input_variable = variableFromSentence(input_lang, pair[0]) target_variable = variableFromSentence(output_lang, pair[1]) return (input_variable, target_variable) | cs |
indexesFromSentence 함수는 학습시키기위해 아까 dictionary를 이용해 문장을 index로 변환해준다.
variableFromSentence는 Variable변수로 바꾸어주는함수이다.
variablesFromPair 마찬가지로, 함수이름 그대로..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | teacher_forcing_ratio = 0.5 def train(input_variable, target_variable, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length=MAX_LENGTH): encoder_hidden = encoder.initHidden() encoder_optimizer.zero_grad() decoder_optimizer.zero_grad() input_length = input_variable.size()[0] target_length = target_variable.size()[0] encoder_outputs = Variable(torch.zeros(max_length, encoder.hidden_size)) encoder_outputs = encoder_outputs.cuda() if use_cuda else encoder_outputs loss = 0 for ei in range(input_length): encoder_output, encoder_hidden = encoder( input_variable[ei], encoder_hidden) encoder_outputs[ei] = encoder_output[0][0] decoder_input = Variable(torch.LongTensor([[SOS_token]])) decoder_input = decoder_input.cuda() if use_cuda else decoder_input decoder_hidden = encoder_hidden use_teacher_forcing = True if random.random() < teacher_forcing_ratio else False if use_teacher_forcing: # Teacher forcing: Feed the target as the next input for di in range(target_length): decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_outputs) loss += criterion(decoder_output, target_variable[di]) decoder_input = target_variable[di] # Teacher forcing else: # Without teacher forcing: use its own predictions as the next input for di in range(target_length): decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_outputs) topv, topi = decoder_output.data.topk(1) ni = topi[0][0] decoder_input = Variable(torch.LongTensor([[ni]])) decoder_input = decoder_input.cuda() if use_cuda else decoder_input loss += criterion(decoder_output, target_variable[di]) if ni == EOS_token: break loss.backward() encoder_optimizer.step() decoder_optimizer.step() return loss.data[0] / target_length | cs |
학습을 위한 함수! . 전과 동일하다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | def trainIters(encoder, decoder , n_iters, print_every=1000, plot_every= 100, learning_rate=0.01): start = time.time() plot_losses = [] print_loss_total = 0 plot_loss_total = 0 encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate) decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate) training_pairs = [variablesFromPair(random.choice(pairs)) for i in range(n_iters)] criterion = nn.NLLLoss() for iter in range(1, n_iters + 1): training_pair = training_pairs[iter - 1] input_variable = training_pair[0] target_variable = training_pair[1] loss = train(input_variable, target_variable, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion) print_loss_total += loss plot_loss_total += loss if iter % print_every == 0: print_loss_avg = print_loss_total / print_every print_loss_total = 0 print('%s (%d %d%%) %.4f' % (timeSince(start, iter / n_iters), iter, iter / n_iters * 100 , print_loss_avg)) if iter % plot_every == 0: plot_loss_avg = plot_loss_total / plot_every plot_losses.append(plot_loss_avg) plot_loss_total = 0 showPlot(plot_losses) | cs |
train iteration을 위한 함수!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | def evaluate(encoder, decoder, sentence, max_length=MAX_LENGTH): input_variable = variableFromSentence(input_lang, sentence) input_length = input_variable.size()[0] encoder_hidden = encoder.initHidden() encoder_outputs = Variable(torch.zeros(max_length, encoder.hidden_size)) encoder_outputs = encoder_outputs.cuda() if use_cuda else encoder_outputs for ei in range(input_length): encoder_output, encoder_hidden = encoder(input_variable[ei], encoder_hidden) encoder_outputs[ei] = encoder_outputs[ei] + encoder_output[0][0] decoder_input = Variable(torch.LongTensor([[SOS_token]])) #SOS decoder_input = decoder_input.cuda() if use_cuda else decoder_input decoder_hidden = encoder_hidden decoded_words = [] decoder_attentions = torch.zeros(max_length, max_length) for di in range(max_length): decoder_output, decoder_hidden, decoder_attention = decoder( decoder_input, decoder_hidden, encoder_outputs) decoder_attentions[di] = decoder_attention.data topv, topi = decoder_output.data.topk(1) ni = topi[0][0] if ni == EOS_token: decoded_words.append('<EOS>') break else: decoded_words.append(output_lang.index2word[ni]) decoder_input = Variable(torch.LongTensor([[ni]])) decoder_input = decoder_input.cuda() if use_cuda else decoder_input return decoded_words, decoder_attentions[:di +1] | cs |
evaluation을 위한 함수
1 2 3 4 5 6 7 8 9 | def evaluateRandomly(encoder, decoder, n=10): for i in range(n): pair = random.choice(pairs) print('>', pair[0]) print('=', pair[1]) output_words, attentions = evaluate(encoder, decoder , pair[0]) output_sentence = ' '.join(output_words) print('<', output_sentence) print('') | cs |
source데이터에서 10개문장을 뽑아 랜덤으로 eval해보기 위한 함수이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | > ㅇㅇㅇㅇ = 그렇게 대화 끝내지 마요 [1597, 1] < 그렇게 대화 끝내지 마요 <EOS> > 심심하다구 = 뭐하고 놀고싶어요? [1127, 1] < 이제 무슨 얘기 할까요? <EOS> > 점심 뭐 먹을지 추천해줘 = 뭘 드실지 정하기 어려운 건가요? [459, 90, 91, 191, 1] < 뭘 드실지 정하기 어려운 건가요? <EOS> > 나랑 걸으러 갈래? = 아쉽지만 저는 실체가 없어서 같이 갈 수 없어요.. [51, 1453, 344, 1] < 아쉽지만 저는 실체가 없어서 같이 갈 수 없답니다. <EOS> > 몇살이노 = 두살입니다.!!^^ [2685, 1] < 전 아담입니다. 그렇게는 부르지 않았으면 좋겠어요. <EOS> > 캭캭캭 = 뭐가 그렇게 재미있어요? [2124, 1] < 뭐가 그렇게 재미있어요? <EOS> > 나 졸려 = 저는 기다리고 있겠습니다! 잘 자요 [22, 1375, 1] < 저는 기다리고 있겠습니다! 잘 자요 <EOS> > 아담 너가 태어난 시간이 몇시야? = 전 오후 시에 태어났다고 들었어요! [279, 1585, 2399, 131, 2620, 1] < 전 술을 못 마셔요. 밥도 못 해봤는데 저도 부모님과 진로 고민 좀 해봐야겠어요 <EOS> > 사람이 되고싶어? = 전 그냥 로봇으로 있고 싶어요. 사람이 되면 너무 신경 쓸게 많을 것 같아서요. [682, 2381, 1] < 전 그냥 로봇으로 있고 싶어요. 사람이 되면 너무 신경 쓸게 많을 것 같아서요. <EOS> > 어떤 장르의 책을 좋아해? = 저는 자연과학 분야 책을 좋아해요! [171, 2256, 2257, 1750, 1] < 저는 자연과학 분야 책을 좋아해요! <EOS> | cs |
결과는 이렇다. 너무 학습이 잘 된것 같아서 문제지만.
source데이터에 있는 질문만 대답가능한것 같다 .
적혀있는 숫자들은 dictionary인덱스 를 같이 출력해 보았다
뭔가 엉성하지만., 그래도 생각보다 잘 되어서 신기하다 .
이 코드는 일단 띄어쓰기 단위로 학습을 시켰는데, 사실 한국어는 접두사, 접미사, 형태소 ? 뭐 특이한점이 많아서, 영어처럼 띄어쓰기 단위로 단어하나하나가 아니기 때문에 복잡하다 ㅠㅠ
예를들면 , 지금 무엇을 하시고 계신가요 ? 에서 '무엇을' 에서, '무엇'과, '~을' 로 나뉘어진다. '하시고' 는 '하다' 와 '시고' (?) 뭐 이런식으로 .. 중학교때 배운것같은데 기억이.. 잘 나지 않는다 . 예시도 맞는지는 잘 모르겠다
그래서 Konlpy를 이용해서 형태소단위로 학습을 시켜보았다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | > 아 오늘 회식이야 = 회식 가기 싫으신 거에요? < 회식 가 기 싫 으시 ㄴ 거 에 요 ? <EOS> > 아담 너도 늙어가? = 저도 시간이 지날 때마다 나이를 먹지 않을까요? < 저 어도 시간 이 지나 ㄹ 때 마다 나이 <EOS> > 왜 정보 검색을 못해? = 아직 습득하지 못한 정보인 것 같아요..ㅠㅠ < 아직 습득 하 지 못하 ㄴ 정보 이 ㄴ 것 같 아요 . ㅠㅠ <EOS> > 별것도 다 있네 = 제가 좀 특별하죠^^ < 제가 좀 특별 하 죠 ^^ <EOS> > ㅈㄹ = 지랄이라뇨.. 비속어 쓰지 않으셨으면 좋겠어요 < 지랄 이 라뇨 .. 비속어 쓰 지 않 으시 었 으면 좋 겠 어요 <EOS> > 이거 끄면 어떻게 되는 거야? = 어떡하긴요.. 대화가 끝나겠죠.... < 어떡하 기 는 요 .. 대화 가 끝나 겠 죠 .... <EOS> > 개새끼야 = 말은 사람의 인격이랍니다 < 말 은 사람 의 인격 이 랍니다 <EOS> > 주전부리 필요해? = 뭐 줄 건데? 주는 거 봐서! < 뭐 주 ㄹ 것 이 ㄴ데 ? 주 는 거 보 아서 ! <EOS> > 카드 뽑아봐 = 클로버 < 클로버 <EOS> > 나 달래주라 = 지금 힘드신 일 다 풀리시고 행복한 일만 있으실 거예요! < 지금 힘들 시 ㄴ 일 다 풀리 시 고 행복 하 ㄴ 일 이 있 는 것 같 아요 . 고 싶 ㄴ가요 ? <EOS> | cs |
결과엔 보이진 않지만, 다른 질문을 넣어보았을때 source에 없는 질문을 해주는경우도 가끔 있었다.
위에 것 보다 학습을 많이 시키지 않아서 그런지 생각보단 잘 안 되는것 같았다..
이렇게 하는게 아닌가...
대화모델이나, 번역 모델을 해보면서 어려운점은 이미지 처리와 다르게 막 직관적이진 않다는것이고 ,
텍스트 데이터들을 normalize하고 dictionary만들어주고, 등 하는 전처리 해주는 부분들이 처음해보는것이라 힘들었다.
'AI > Pytorch' 카테고리의 다른 글
[Pytorch] GAN 을 이용한 Black & White image Colorization 최종 (0) | 2018.04.06 |
---|---|
[Pytorch] GAN(Generative Adversarial Network)를 이용한 흑백 이미지 colorization(미완성..) (0) | 2018.04.05 |
[pytorch] RNN seq2seq 를 이용한 translater (2) | 2018.02.02 |
[Pytorch] kaggle cat vs dog 학습시키기 with Resnet (0) | 2018.01.24 |
[Pytorch] kaggle cat&dog CNN 으로 분류하기 (0) | 2018.01.24 |