Tensorflow 2.0 Tutorial ch7.4 - (1) 단어 단위 생성

Page content

공지

  • 본 Tutorial은 교재 시작하세요 텐서플로 2.0 프로그래밍의 강사에게 국비교육 강의를 듣는 사람들에게 자료 제공을 목적으로 제작하였습니다.

  • 강사의 주관적인 판단으로 압축해서 자료를 정리하였기 때문에, 자세하게 공부를 하고 싶으신 분은 반드시 교재를 구매하실 것을 권해드립니다.

  • 본 교재 외에 강사가 추가한 내용에 대한 Reference를 확인하셔서, 추가적으로 학습하시는 것을 권유드립니다.

Tutorial

이전 강의가 궁금하신 분들은 아래에서 선택하여 추가 학습 하시기를 바랍니다.

I. 개요

테슬라 AI Director인 안드레아 카르파티(Andrej Karpathy)는 The Unreasonable Effectiveness of Recurrent Neural Networks라는 글을 개인 블로그에 작성했는데, 짧게 요약하면 문자 단위의 순환 신경망이 셰익스피어의 희곡, 소스코드, Latex등을 재생산하는데 순환 신경망이 효과적이라는 것을 보여줍니다.

이를 바탕으로, 한글 원본 텍스트를 자소 단위와 단어 단위로 나눠서 순환 신경망으로 생성해보도록 합니다.

II. 단어 단위 생성

이번에 작업할 원본 텍스트는 국사편찬위원회에서 제공하는 조선왕조실록 국문 번역본입니다. 먼저 데이터를 다운로드 합니다.1

이제 본격적으로 소스코드를 작성하도록 합니다.

(1) 데이터 로드

데이터 파일은 약 62MB 정도입니다.

# 텐서플로 2 버전 선택
try:
    # %tensorflow_version only exists in Colab.
    %tensorflow_version 2.x
except Exception:
    pass
import tensorflow as tf
import numpy as np
import pandas as pd
path_to_train_file = tf.keras.utils.get_file('input.txt', 'https://raw.githubusercontent.com/greentec/greentec.github.io/master/public/other/data/chosundynasty/corpus.txt')
Downloading data from https://raw.githubusercontent.com/greentec/greentec.github.io/master/public/other/data/chosundynasty/corpus.txt
62013440/62012502 [==============================] - 1s 0us/step

데이터를 메모리에 불러옵니다. 인코딩 형식으로 utf-8을 지정한 뒤 텍스트가 총 몇 자인지 확인해봅니다.

train_text = open(path_to_train_file, 'rb').read().decode(encoding='utf-8')
print('Length of text: {} characters'.format(len(train_text)))
Length of text: 26265493 characters

처음 100자를 확인한 후, 실제 데이터와 일치하는지 확인해봅니다.

print(train_text[:100])
태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 
태조 강헌 지인 계운 성문 신무 대왕(太祖康獻至仁啓運聖文神武大王)의 성은 이씨(李氏)요, 휘

일치하는지 확인이 완료되었다면, 다음 소스코드를 진행하면 됩니다. 다운로드 시, 링크가 달라졌다면, 꼭 댓글을 남겨주시기를 바랍니다.

(2) 데이터 문자열 전처리

한자가 많은 부분을 차지하는데, 여기에서는 단어 기반의 생성을 위해 한자와 한자가 들어간 괄호는 생략합니다. 이전 포스트에 비슷하지만, 조금 다릅니다. 특히 영문 관련 처리는 모두 삭제했습니다. 또한, 한자와 함께 들어가 있는 괄호도 같이 삭제합니다. 또한, 개행 문자(’\n’)의 보존을 위해 텍스트를 먼저 개행 문자로 나눈 뒤에 다시 합칠 때 개행 문자를 추가합니다. 꼭 주의해서 사용자 정의 함수 def를 작성하시기를 바랍니다. 이렇게 정제 과정을 거치면 한자어와 괄호는 보이지 않을 것입니다.

import re
# From https://github.com/yoonkim/CNN_sentence/blob/master/process_data.py
def clean_str(string):    
    string = re.sub(r"[^가-힣A-Za-z0-9(),!?\'\`]", " ", string)
    string = re.sub(r"\'ll", " \'ll", string)
    string = re.sub(r",", " , ", string)
    string = re.sub(r"!", " ! ", string)
    string = re.sub(r"\(", "", string)
    string = re.sub(r"\)", "", string)
    string = re.sub(r"\?", " \? ", string)
    string = re.sub(r"\s{2,}", " ", string)
    string = re.sub(r"\'{2,}", "\'", string)
    string = re.sub(r"\'", "", string)

    return string


train_text = train_text.split('\n')
train_text = [clean_str(sentence) for sentence in train_text]
train_text_X = []
for sentence in train_text:
    train_text_X.extend(sentence.split(' '))
    train_text_X.append('\n')
    
train_text_X = [word for word in train_text_X if word != '']

print(train_text_X[:20])
['태조', '이성계', '선대의', '가계', '목조', '이안사가', '전주에서', '삼척', '의주를', '거쳐', '알동에', '정착하다', '\n', '태조', '강헌', '지인', '계운', '성문', '신무', '대왕']

결과가 잘 나온 것을 확인할 수 있습니다. 이제 단어를 토큰화합니다. 이 때, Tokenizer를 쓰지 않고, 직접 토큰화합니다. 단어의 수가 너무 많고, 모든 단어를 사용할 것이기 때문에, 단어의 빈도 수로 단어를 정렬하는 Tokenizer를 쓰면 불필요하게 많은 계산 시간을 쓰게 된다.

vocab = sorted(set(train_text_X))
vocab.append('UNK')
print ('{} unique words'.format(len(vocab)))

# vocab list를 숫자로 맵핑하고, 반대도 실행합니다.
word2idx = {u:i for i, u in enumerate(vocab)}
idx2word = np.array(vocab)

text_as_int = np.array([word2idx[c] for c in train_text_X])

# word2idx 의 일부를 알아보기 쉽게 print 해봅니다.
print('{')
for word,_ in zip(word2idx, range(10)):
    print('  {:4s}: {:3d},'.format(repr(word), word2idx[word]))
print('  ...\n}')

print('index of UNK: {}'.format(word2idx['UNK']))
332640 unique words
{
  '\n':   0,
  '!' :   1,
  ',' :   2,
  '000명으로':   3,
  '001':   4,
  '002':   5,
  '003':   6,
  '004':   7,
  '005':   8,
  '006':   9,
  ...
}
index of UNK: 332639

2번째 줄에서 텍스트에 들어간 각 단어가 중복되지 않는 리스트를 만든 다음에, 3번째 줄에서는 텍스트에 존재하지 않는 토큰을 UNK를 넣습니다. 총 단어 수는 332,640이고, 이 중 UNK의 인덱스는 332,639입니다. 이 인덱스는 나중에 임의의 문장을 입력할 때 텍스트에 미리 준비돼 있지 않았던 단어를 쓸 수 있기 때문에 그에 대한 토큰으로 쓰일 것입니다.

토큰화가 잘 되었는지 확인하기 위해 처음 20개의 단어에 대한 토큰을 출력합니다.

print(train_text_X[:20])
print(text_as_int[:20])
['태조', '이성계', '선대의', '가계', '목조', '이안사가', '전주에서', '삼척', '의주를', '거쳐', '알동에', '정착하다', '\n', '태조', '강헌', '지인', '계운', '성문', '신무', '대왕']
[299305 229634 161443  17430 111029 230292 251081 155087 225462  29027
 190295 256129      0 299305  25624 273553  36147 163996 180466  84413]

태조는 299,305로 이성계는 229,634로, 개행 문자는 0으로 토큰 변환이 된 것을 확인할 수 있습니다. 이제 학습을 위한 데이터세트를 만듭니다.

(3) 데이터셋 생성

이 때, 기존과는 다르게, train_X, train_Y를 넘파이 array로 만드는 방식이 아닌 tf.data.Dataset를 이용해봅니다. Dataset의 장점은 간단한 코드로 데이터 섞기, 배치(batch)수만큼 자르기, 다른 Dataset에 매핑하기 등을 빠르게 수행할 수 있습니다.

seq_length = 25
examples_per_epoch = len(text_as_int) // seq_length
sentence_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)

sentence_dataset = sentence_dataset.batch(seq_length+1, drop_remainder=True)
for item in sentence_dataset.take(1):
    print(idx2word[item.numpy()])
    print(item.numpy())
['태조' '이성계' '선대의' '가계' '목조' '이안사가' '전주에서' '삼척' '의주를' '거쳐' '알동에' '정착하다'
 '\n' '태조' '강헌' '지인' '계운' '성문' '신무' '대왕' '의' '성은' '이씨' '요' ',' '휘']
[299305 229634 161443  17430 111029 230292 251081 155087 225462  29027
 190295 256129      0 299305  25624 273553  36147 163996 180466  84413
 224182 164549 230248 210912      2 330313]

여기서는 seq_length를 25로 설정해서 25개의 단어가 주어졌을 때, 다음 단어를 예측하도록 데이터를 만듭니다.

sentence_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)

Dataset를 생성하는 코드는 위와 같이 한 줄로 간단합니다.

sentence_dataset = sentence_dataset.batch(seq_length+1, drop_remainder=True)
  • Dataset에 쓰이는 batch()함수는 Dataset에서 한번에 반환하는 데이터의 숫자를 지정합니다.
  • 여기에서는 seq_length_1을 지정했는데, 처음의 25개 단어와 그 뒤에 오는 정답이 될 1단어를 합쳐서 함께 반환하기 위해서입니다.
  • drop_remainder=True옵션으로 남는 부분은 버리도록 합니다. 그러면 출력해서 의도한대로 26단어가 반환되는 것을 확인할 수 있습니다.
def split_input_target(chunk):
    return [chunk[:-1], chunk[-1]]

train_dataset = sentence_dataset.map(split_input_target)
for x,y in train_dataset.take(1):
    print(idx2word[x.numpy()])
    print(x.numpy())
    print(idx2word[y.numpy()])
    print(y.numpy())
['태조' '이성계' '선대의' '가계' '목조' '이안사가' '전주에서' '삼척' '의주를' '거쳐' '알동에' '정착하다'
 '\n' '태조' '강헌' '지인' '계운' '성문' '신무' '대왕' '의' '성은' '이씨' '요' ',']
[299305 229634 161443  17430 111029 230292 251081 155087 225462  29027
 190295 256129      0 299305  25624 273553  36147 163996 180466  84413
 224182 164549 230248 210912      2]
휘
330313

위 코드의 목적은 기존 Dataset으로 새로운 Dataset으로 만드는 과정입니다.

  • 26개의 단어가 각각 입력과 정답으로 묶어서([25단어], 1단어) 형태의 데이터를 반환하게 만드는 Dataset을 만드는 것입니다.
  • 먼저, 26개의 단어를 25단어, 1단어로 잘라주는 함수를 split_input_target(chunk)로 정의한 다음 이 함수를 기존 sentence_datasetmap()함수(참조: 텐서플로 map 함수)를 이용해 새로운 데이터세트인 train_dataset를 만듭니다.

(4) Shuffle, Batch 설정

Dataset의 데이터를 섞고, batch_size를 다시 설정합니다.

BATCH_SIZE = 512
steps_per_epoch = examples_per_epoch // BATCH_SIZE
BUFFER_SIZE = 10000

train_dataset = train_dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)

주의: 이 부분은 교재와 조금 상이합니다! 따라서, 위 코드로 구현 바랍니다.

빠른 학습을 위해 한번에 512개의 데이터를 학습하게 하고, 데이터를 섞을 때의 BUFFER_SIZE는 10,000으로 설정합니다. tf.data는 이론적으로 무한한 데이터에 대해 대응 가능하기 때문에 한번에 모든 데이터를 섞지 않습니다. 따라서, 버퍼에 일정한 양의 데이터를 올려놓고 섞는데, 그 사이즈를 10,000으로 설정한 것입니다.

(5) 생성 모델 정의

total_words = len(vocab)
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(total_words, 100, input_length=seq_length),
    tf.keras.layers.LSTM(units=100, return_sequences=True),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.LSTM(units=100),
    tf.keras.layers.Dense(total_words, activation='softmax')
])

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, 25, 100)           33264000  
_________________________________________________________________
lstm (LSTM)                  (None, 25, 100)           80400     
_________________________________________________________________
dropout (Dropout)            (None, 25, 100)           0         
_________________________________________________________________
lstm_1 (LSTM)                (None, 100)               80400     
_________________________________________________________________
dense (Dense)                (None, 332640)            33596640  
=================================================================
Total params: 67,021,440
Trainable params: 67,021,440
Non-trainable params: 0
_________________________________________________________________

위 소스코드를 통해서 얻고자 하는 결과물은 주어진 입력에 대해 332,640개의 단어 중 어떤 단어를 선택해야 하는지 고르게 하는 기능을 하게 만듭니다.

이제 학습을 시킵니다. 그런데, 이번에 학습시키는 방법은 기존과 조금 다르니 주의깊게 소스코드를 확인하시기를 바랍니다.

또한, 학습시간이 GPU로 할당해도 매우 길기 때문에, 모형 학습 시간 계획을 잘 세우기를 바랍니다. (참고: 안 에포크당 10분 정도 소요됨)

from tensorflow.keras.preprocessing.sequence import pad_sequences

def testmodel(epoch, logs):
    if epoch % 5 != 0 and epoch != 49:
        return
    test_sentence = train_text[0]

    next_words = 100
    for _ in range(next_words):
        test_text_X = test_sentence.split(' ')[-seq_length:]
        test_text_X = np.array([word2idx[c] if c in word2idx else word2idx['UNK'] for c in test_text_X])
        test_text_X = pad_sequences([test_text_X], maxlen=seq_length, padding='pre', value=word2idx['UNK'])

        output_idx = model.predict_classes(test_text_X)
        test_sentence += ' ' + idx2word[output_idx[0]]
    
    print()
    print(test_sentence)
    print()

testmodelcb = tf.keras.callbacks.LambdaCallback(on_epoch_end=testmodel)
history = model.fit(train_dataset.repeat(), epochs=10, steps_per_epoch=steps_per_epoch, callbacks=[testmodelcb], verbose=2)

Epoch 1/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,

533/533 - 255s - loss: 9.3702 - accuracy: 0.0723 Epoch 2/50 533/533 - 249s - loss: 8.3646 - accuracy: 0.0741 Epoch 3/50 533/533 - 249s - loss: 8.0768 - accuracy: 0.0816 Epoch 4/50 533/533 - 249s - loss: 7.8147 - accuracy: 0.0911 Epoch 5/50 533/533 - 249s - loss: 7.5722 - accuracy: 0.1025 Epoch 6/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 , , , , , , 그 , , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그 것은 , 그

533/533 - 250s - loss: 7.3301 - accuracy: 0.1165 Epoch 7/50 533/533 - 249s - loss: 7.0574 - accuracy: 0.1305 Epoch 8/50 533/533 - 249s - loss: 6.7848 - accuracy: 0.1433 Epoch 9/50 533/533 - 249s - loss: 6.5153 - accuracy: 0.1555 Epoch 10/50 533/533 - 249s - loss: 6.2694 - accuracy: 0.1679 Epoch 11/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 등 것입니다 하니 , 임금이 말하기를 , 상참을 받고 , 임금이 아뢰기를 , 이 받고 그 일을 다 다 다 가지고 주고 , 그 거느리고 다 화살을 내어 주고 , 이는 아뢰기를 , 함길도 의 서쪽에 서향하여 없는 것입니다 또 아뢰기를 , 이 받고 반드시 보내어 가서 가지고 가서 가서 주고 , 그 거느리고 의하여 서향하여 있으니 , 그 받고 , 아뢰기를 , 그 일을 가지고 서향하여 서게 하고 , 그 받고 몸을 아뢰기를 , 예조에서 아뢰기를 , 함길도 의 서쪽에 인도하여 가지고 주고 , 아뢰기를 , 평안도 의 서쪽에 서향하여

533/533 - 250s - loss: 6.0086 - accuracy: 0.1822 Epoch 12/50 533/533 - 249s - loss: 5.7584 - accuracy: 0.1960 Epoch 13/50 533/533 - 248s - loss: 5.5062 - accuracy: 0.2102 Epoch 14/50 533/533 - 249s - loss: 5.2491 - accuracy: 0.2261 Epoch 15/50 533/533 - 249s - loss: 4.9958 - accuracy: 0.2423 Epoch 16/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 것이니 , 그 뜻을 다 알지 것이 없습니다 지금 내가 비록 여러 것은 모두 다 일을 다 일을 다 일을 다 말을 말을 다 알고 , 지금 만일 다시 가서 가서 아뢰게 하고 , 드디어 비가 보내어 가서 가서 토물 을 바쳤다 조회를 받았다 을축년 관찰사에게 이르기 살기가 , 의정부에서 아뢰기를 , 맹규의 관찰사에게 유시하기를 , 맹규의 벼슬만 아니오라 , 왕비가 관찰사에게 유시하기를 , 중추원 관찰사에게 유시하기를 , 중추원 부사 권맹경이 하였다 전 도절제사 부사 로 하여금 상언 하기를 , 맹규의 관찰사에게 유시하기를 , 맹규의 죄수 벌할 하게 , 세자가 아뢰기를 ,

533/533 - 249s - loss: 4.7473 - accuracy: 0.2599 Epoch 17/50 533/533 - 249s - loss: 4.5038 - accuracy: 0.2782 Epoch 18/50 533/533 - 249s - loss: 4.2670 - accuracy: 0.2984 Epoch 19/50 533/533 - 249s - loss: 4.0450 - accuracy: 0.3195 Epoch 20/50 533/533 - 249s - loss: 3.8258 - accuracy: 0.3440 Epoch 21/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 것이다 고 하고 , 그 말을 있고 , 모두 같이 같이 같이 같이 한다 또한 있고 한 것은 더욱 말을 있는 것은 모두 이를 살피게 하라 하였다 일본국 정난 공신 에게 유시 하기를 , 일본국 병조 에서 상서 하여 각각 더불어 제수 로 하여금 함께 의논하여 가서 아뢰니 , 일본국 명나라 정문 에 의거하여 아뢰니 , 임금이 말하기를 , 일본국 상송포 새긴들 다만 정난 신숙주 에게 유시 하기를 , 일본국 병조 에서 전지 하기를 , 이 사람을 보내어 와서 와서 가서 와서 이르기를 , 경 은 하직하니 , 그 죄는 와서 와서 의논하게 하였다

533/533 - 250s - loss: 3.6157 - accuracy: 0.3703 Epoch 22/50 533/533 - 249s - loss: 3.4163 - accuracy: 0.3981 Epoch 23/50 533/533 - 249s - loss: 3.2208 - accuracy: 0.4284 Epoch 24/50 533/533 - 249s - loss: 3.0313 - accuracy: 0.4601 Epoch 25/50 533/533 - 249s - loss: 2.8555 - accuracy: 0.4897 Epoch 26/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 것인데 , 만약 혹은 좋은 자는 백성을 주고 , 혹은 폐단이 있어 있어 오래 자가 나누어 주고 , 반드시 말을 일을 가지고 나누어 보내어 가게 하고 , 만일 병이 사실을 염려하여 , 드디어 말하기를 , 함길도 도절제사 의 죄는 돌아와서 보내지 않은 것을 옮겨 주어 아뢰게 하고 , 만일 자기 베어 징계하소서 예조 에서 공조 판서 겸 형조 참판 제학 이조 등이 불러 불러서 조용히 전하다 경상도 상호군 박경무 으로 종이 대부 로 삼았다 함길도 도절제사 에서 와서 와서 내려 죄를 가서 아뢰다 사헌부에서 아뢰기를 , 함길도 나라 일로 감사 의 개를 바치었다 햇무리가 쏘았다

533/533 - 250s - loss: 2.6946 - accuracy: 0.5192 Epoch 27/50 533/533 - 248s - loss: 2.5327 - accuracy: 0.5480 Epoch 28/50 533/533 - 249s - loss: 2.3733 - accuracy: 0.5766 Epoch 29/50 533/533 - 249s - loss: 2.2233 - accuracy: 0.6036 Epoch 30/50 533/533 - 249s - loss: 2.0778 - accuracy: 0.6309 Epoch 31/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 되고 , 청컨대 본조 수는 없는데 , 무릇 여러 일은 이미 심히 알지 자는 서로 알 것이다 또한 이미 여러 사람이 심히 말을 사람을 얻게 합니다 이에 갖추어 아뢰니 , 드디어 세자에게 거둥하여 사실을 묻게 하였으나 , 임금이 말하기를 , 이 사람을 거느리고 가서 와서 이르기를 , 이 사람을 보내 토산물을 바쳤다 나도 아뢴 않겠는가 하였다 임금이 말하기를 , 전하께서 전지 에서 함께 사사로이 듣고 아뢰게 하고 , 만일 잘못 묻지는 를 올려 국문 하여 계문 하여 이러하였다 대간 으로 사제 를 하사하다 대간 에 내리니 , 전하께서 다시 와서 가서 잘 알지 것은

533/533 - 250s - loss: 1.9361 - accuracy: 0.6555 Epoch 32/50 533/533 - 249s - loss: 1.8070 - accuracy: 0.6788 Epoch 33/50 533/533 - 249s - loss: 1.6785 - accuracy: 0.7029 Epoch 34/50 533/533 - 249s - loss: 1.5642 - accuracy: 0.7236 Epoch 35/50 533/533 - 249s - loss: 1.4530 - accuracy: 0.7431 Epoch 36/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 된다 말하다 , 사의가 음악은 옳을 수는 없으니 , 본조 가 같이 같이 같이 있고 북쪽을 안 한 뒤에 모두 다 자리로 나아간다 창한다 마치면 , 통찬이 윤대를 행하고 , 신의 하나는 인도하여 내려와 제자리로 돌아간다 집례가 사배하라 찬하여 , 배 이하가 모두 꿇어앉는다 무릇 다음에 정한 것도 돌아간다 찬자가 사배하라 찬하면 , 감찰 는 단 가 자리에 나아간다 찬인이 감찰과 여러 때의 의식과 같이 한다 집례가 그 곡하라 하면 , 다음에 꿇어앉는다 여러 사람이 각기 각기 각기 자리에 나아간다 알자가 술잔을 씻고 올라가 인도하여 자리에 나간다 집례가 사배하라 찬하면 , 흥 , 흥 , 흥 , 흥 ,

533/533 - 250s - loss: 1.3488 - accuracy: 0.7627 Epoch 37/50 533/533 - 249s - loss: 1.2497 - accuracy: 0.7815 Epoch 38/50 533/533 - 248s - loss: 1.1613 - accuracy: 0.7968 Epoch 39/50 533/533 - 249s - loss: 1.0768 - accuracy: 0.8122 Epoch 40/50 533/533 - 249s - loss: 0.9966 - accuracy: 0.8267 Epoch 41/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 되고 , 청컨대 그전 무지한 곳이 없게 되니 , 물러가 가까이 가고 , 혹은 도로 사람이 있을 것이다 진실로 이미 가 서로 폐단이 심히 남겨서 대신의 무리들이 급히 주게 하고 , 그 온 1백 콩 1백 명을 조사하여 급히 오늘에 가서 크게 시종 중에 살펴보게 한다든가 허락하지 아니하였다 신이 그 죄가 와서 와서 가서 청하여 임금의 두게 하소서 하니 , 의금부에서 아뢰기를 , 평안도 토관 죄수 벌송 애오라지 염법 을 내리어 의논하게 하므로 , 무릇 부득이하여 보면 , 무릇 성상께서 생각해 나아가 바로 전의 를 인도하여 주어 서향하게 하고 , 다음은 나라가 정한 지경에 너무 가지고 반드시 모두

533/533 - 250s - loss: 0.9232 - accuracy: 0.8399 Epoch 42/50 533/533 - 249s - loss: 0.8571 - accuracy: 0.8523 Epoch 43/50 533/533 - 249s - loss: 0.7950 - accuracy: 0.8637 Epoch 44/50 533/533 - 249s - loss: 0.7335 - accuracy: 0.8753 Epoch 45/50 533/533 - 249s - loss: 0.6764 - accuracy: 0.8857 Epoch 46/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 되고 , 청컨대 혹은 감한 많으며 , 겨우 휘 를 올리고 종자 가 서로 알 것이다 또한 감히 뒤에 모두 서로 폐단을 알 것이다 그러나 경 등과 천지 인 약정하기를 사직 과 더불어 글을 모여서 가서 본 말은 서울과 70이 민간에 시행할 있지 않는 것이니 , 이에 어질고 떨어져 구 에 참여하지 않을 것이니 , 마침내 그를 오지 아니하고 , 대개 속전 에 붙여 들지 아니할 것이니 , 그것을 그르다 힘쓰고 수양 3천 떼가 박천 2개는 모순 천하가 의논했다고 되었다 청백 양씨 중후 처소 740 강호덕 615 박사란 김선거 연변에 이등 씩 음식도 어리고 2명씩 뵙고 일본의 주었사오니 하옵고 침체

533/533 - 250s - loss: 0.6241 - accuracy: 0.8957 Epoch 47/50 533/533 - 249s - loss: 0.5790 - accuracy: 0.9038 Epoch 48/50 533/533 - 249s - loss: 0.5355 - accuracy: 0.9114 Epoch 49/50 533/533 - 249s - loss: 0.4964 - accuracy: 0.9183 Epoch 50/50

태조 이성계 선대의 가계 목조 이안사가 전주에서 삼척 의주를 거쳐 알동에 정착하다 들을 것과 , 본조 가 없으면 서향하게 것입니다 만약 먼저 단 가 있고 , 한 법이 같이 같이 같이 되어 한 일이 있을 일이 없습니다 그러나 , 이제부터는 진휼사 에 술을 받들어 그 자리에 매우 옳지 를 나아가서 , 내가 그 아비를 가서 온 사람은 한번 가상히 부임 하여 이러한 때 아울러 국문 하여 외방 전의 일수 를 받들어 잡희 를 나아가서 나아가서 나가고 , 이어서 병법 과 악 을 외유 일어나고 , 의장 을 더하여 적당히 판위 에 올라 그치게 하되 , 앙제 에 나아가서 꿇어앉아 나아가서 선다 판통례가 , 다만 술 서문 궤 앞으로 나아가 부복하고 꿇어앉아 부복

533/533 - 250s - loss: 0.4581 - accuracy: 0.9256

먼저 모델을 학습시키면서 모델의 생성 결과물을 확인하기 위해 testmodel이라는 이름으로 콜백 함수를 정의합니다.

  • 조금 더 콜백 함수에 대해서 정의를 내리면, 먼저 임의의 문장을 입력한 다음, seq_length만큼의 단어 25개를 선택합니다.
test_test_X = test_sentence.split(' ')[-seg_length:]
  • 그 다음에는 문장의 단어를 인덱스 토큰으로 바꿉니다. 이 때 사전에 등록되어 있지 않은 단어의 경우에는 UNK 토큰 값으로 바꿉니다.
test_text_X = pad_sequences([test_text_X], maxlen = seq_length, padding='pre', value=word2idx['UNK'])
  • pad_sequences()로 문장의 앞쪽이 빈 자리가 있을 경우 25단어가 채워지도록 패딩을 넣습니다. 이 때 패딩 갑인 value인수에는 앞에서 지정했던 UNK 토큰의 값인 word2idx['UNK']를 사용합니다.
output_idx = model.predict_classes(test_text_X)
  • model.predict()Dense레이어의 332,640개의 값을 반환하기 때문에 출력을 간결하게 하기 위해 model.predict_classes()함수를 사용합니다. 이 함수는 출력 중에서 가장 값이 큰 인덱스를 반환합니다.
test_sentence += ' ' + idx2word[output_idx[0]]
  • 출력 단어는 test_sentence의 끝 부분에 저장되어 다음 스텝의 입력에 활용됩니다.

  • 이렇게 정의된 콜백 함수는 testmodelcb라는 이름으로 저장되어 tf.kerasmodel.fit()callbacks인수에 포함됩니다.

  • 마지막으로 모형을 학습시킵니다.

history = model.fit(train_dataset.repeat(), epochs=10, steps_per_epoch=steps_per_epoch, callbacks=[testmodelcb], verbose=2)
  • 학습을 시킬 때도 위에서 정의한 Dataset을 활용합니다. 그런데 주목해야 할 점은 Dataset에서 데이터를 끊임없이 반환하도록 repeat()함수를 사용합나다.
  • 넘파이 array 형태의 데이터를 사용할 때는 에포크마다 데이터를 한번씩 순회시켰지만, Dataset을 사용하면 데이터의 시작과 끝을 알 수 없기 때문에 에포크에 데이터를 얼마나 학습시키질를 steps_per_epoch 인수로 지정합니다.

학습 결과를 살펴보면 처음에는 의미없는 단어가 반복되지만, 학습을 진행하면서 문맥이 연결되는 것을 확인할 수 있습니다.

(6) 임의의 문장을 사용한 단어 생성 확인

이제 임의의 문장을 넣어 학습이 잘 되는지 난중일기의 한 구절을 입력해봅니다.

from tensorflow.keras.preprocessing.sequence import pad_sequences
test_sentence = '동헌에 나가 공무를 본 후 활 십오 순을 쏘았다'

next_words = 100
for _ in range(next_words):
    test_text_X = test_sentence.split(' ')[-seq_length:]
    test_text_X = np.array([word2idx[c] if c in word2idx else word2idx['UNK'] for c in test_text_X])
    test_text_X = pad_sequences([test_text_X], maxlen=seq_length, padding='pre', value=word2idx['UNK'])
    
    output_idx = model.predict_classes(test_text_X)
    test_sentence += ' ' + idx2word[output_idx[0]]

print(test_sentence)

동헌에 나가 공무를 본 후 활 십오 순을 쏘았다 그곳의 사로잡힌 대리하는 호군직 2개는 놓기도 토의 으로써 차임 되고 , 저들은 일시에 굳게 것으로서 주고 , 더욱 높은 바가 많아서 정위 라 같다 하는데 하였습니다 빈자 는 부의 에 도와서 알아야 하여 주소서 하니 , 형조 에서 교지 를 하사하고 충청도 도절제사 에서 다시 와서 와서 내려 쓸 하고 , 만일 잘 가는 것이 없었다 상수의 대한 한 한 뒤에 모두 능히 서로 알지 것은 감히 모두 모두 모두 사람을 죄를 죄를 사람을 죄를 온 한 사람을 삼가서 어질고 그믐날에 한다는 것이니 , 청컨대 이러한 정성을 알아서 난 를 정하여 급히 원하는 곳에 두고 특별히 징계하여 인도해 특별히

전체적인 문장의 의미가 통하는 건 아닙니다만, 부분 부분에서는 자연스럽게 연결되는 단어들이 보이는 것을 확인할 수 있습니다.

III. 연습 파일

IV. Reference

김환희. (2020). 시작하세요! 텐서플로 2.0 프로그래밍: 기초 이론부터 실전 예제까지 한번에 끝내는 머신러닝, 딥러닝 핵심 가이드. 서울: 위키북스.

Karpathy, A. (2015). The Unreasonable Effectiveness of Recurrent Neural Networks. Retrieved April 26, 2020, from http://karpathy.github.io/2015/05/21/rnn-effectiveness/


  1. 교재에서는 전체 데이터(400MB) 중, 62MB만 잘라낸 파일을 사용했습니다. 공공데이터포털에서 조선왕조실록 데이터를 다운로드 받을 수 있습니다. ↩︎