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

Page content

공지

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

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

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

Tutorial

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

I. 개요

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

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

II. 자소 단위 생성

자소 단위 생성을 하기 위해서는 한글을 자소 단위로 분리하고 다시 합칠 수 있는 라이브러리가 필요합니다. 이러한 작업을 할 수 있도록 신해빈 님이 만든 jamotools가 있습니다.

구글 코랩에서 배시 셀 명령어를 이용해 라이브러리를 설치합니다.

!pip install jamotools
Collecting jamotools
  Downloading https://files.pythonhosted.org/packages/3d/d6/ec13c68f7ea6a8085966390d256d183bf8488f8b9770028359acb86df643/jamotools-0.1.10-py2.py3-none-any.whl
Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from jamotools) (1.18.3)
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from jamotools) (1.12.0)
Requirement already satisfied: future in /usr/local/lib/python3.6/dist-packages (from jamotools) (0.16.0)
Installing collected packages: jamotools
Successfully installed jamotools-0.1.10

jamotools의 기능을 테스트하기 위해 조선왕조실록 텍스트를 다시 사용합니다.

(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
import jamotools
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')
s = train_text[:100]
print(s)

# 한글 텍스트를 자모 단위로 분리합니다. 한자 등에는 영향이 없습니다. 
s_split = jamotools.split_syllables(s)
print(s_split)
태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 
태조 강헌 지인 계운 성문 신무 대왕(太祖康獻至仁啓運聖文神武大王)의 성은 이씨(李氏)요, 휘
ㅌㅐㅈㅗ ㅇㅣㅅㅓㅇㄱㅖ ㅅㅓㄴㄷㅐㅇㅢ ㄱㅏㄱㅖ. ㅁㅗㄱㅈㅗ ㅇㅣㅇㅏㄴㅅㅏㄱㅏ ㅈㅓㄴㅈㅜㅇㅔㅅㅓ ㅅㅏㅁㅊㅓㄱ·ㅇㅢㅈㅜㄹㅡㄹ ㄱㅓㅊㅕ ㅇㅏㄹㄷㅗㅇㅇㅔ ㅈㅓㅇㅊㅏㄱㅎㅏㄷㅏ 
ㅌㅐㅈㅗ ㄱㅏㅇㅎㅓㄴ ㅈㅣㅇㅣㄴ ㄱㅖㅇㅜㄴ ㅅㅓㅇㅁㅜㄴ ㅅㅣㄴㅁㅜ ㄷㅐㅇㅘㅇ(太祖康獻至仁啓運聖文神武大王)ㅇㅢ ㅅㅓㅇㅇㅡㄴ ㅇㅣㅆㅣ(李氏)ㅇㅛ, ㅎㅟ

split_syllables()함수를 이용해서 100글자의 한글이 자모 단위로 정상적으로 분리되는 것을 확인할 수 있습니다. jamotools는 영문이나 한자 등에는 영향을 주지 않습니다. 분리한 자모를 다시 합칠 수도 있습니다.

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

자모를 분리했다가 다시 합친 s2s와 같다는 것을 두 번째 출력이 True인 것에서 확인할 수 있습니다.

(2) 자모 토큰화

그럼 이제 자모를 토큰화합니다. 여기서는 따로 텍스트 전처리를 하지 않습니다. 괄호, 한자 등이 토큰에 모두 포함될 것입니다.

  • 텍스트를 자모 단위로 나눕니다. 데이터가 크기 때문에 약간 시간이 걸립니다.
train_text_X = jamotools.split_syllables(train_text)
vocab = sorted(set(train_text_X))
vocab.append('UNK')

print('{} unique characters'.format(len(vocab)))
6198 unique characters
  • 이제, vocal list를 숫자로 매핑하고, 반대도 실행합니다.
char2idx = {u:i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)

test_as_int = np.array([char2idx[c] for c in train_text_X])
  • 이제 word2idx의 일부를 알아보기 쉽게 출력합니다.
print('{')
for char, _ in zip(char2idx, range(10)):
  print(' {:4s}: {:3d}, '.format(repr(char), char2idx[char]))
print('   ...\n}')

print('index of UNK: {}'.format(char2idx['UNK']))
{
 '\n':   0, 
 ' ' :   1, 
 '!' :   2, 
 '"' :   3, 
 "'" :   4, 
 '(' :   5, 
 ')' :   6, 
 '+' :   7, 
 ',' :   8, 
 '-' :   9, 
   ...
}
index of UNK: 6197

자모를 토큰화하고 혹시 사전에 정의되지 않은 기호를 만날수도 있으므로 UNK도 사전에 추가합니다. 이렇게 중복되 않은 자모는 총 6197개가 나옵니다. 단어에 비해 매우 적은 숫자임을 알 수 있습니다.

이제 토큰 데이터를 출력합니다.

print(train_text_X[:20])
print(test_as_int[:20])
ㅌㅐㅈㅗ ㅇㅣㅅㅓㅇㄱㅖ ㅅㅓㄴㄷㅐㅇ
[6158   83   87   79   94    1   78  106   76   90   78   56   93    1
   76   90   59   62   87   78]

은 6158, 는 83 등으로 토큰화가 된 것을 확인할 수 있습니다.

(3) 데이터 생성

단어 생성 단위에 있던 학습 데이터세트를 생성합니다. 이 부분의 코드는 큰 변경사항이 없습니다.

  • sentence_dataet에서 char_dataset으로 변동하시면 됩니다.
  • idx2word에서 idx2char으로 변동하시면 됩니다.
seq_length = 80
examples_per_epoch = len(test_as_int) // seq_length
char_dataset = tf.data.Dataset.from_tensor_slices(test_as_int)

char_dataset = char_dataset.batch(seq_length+1, drop_remainder=True)
for item in char_dataset.take(1):
    print(idx2char[item.numpy()])
    print(item.numpy())

def split_input_target(chunk):
    return [chunk[:-1], chunk[-1]]

train_dataset = char_dataset.map(split_input_target)
for x,y in train_dataset.take(1):
    print(idx2char[x.numpy()])
    print(x.numpy())
    print(idx2char[y.numpy()])
    print(y.numpy())

BATCH_SIZE = 256
steps_per_epoch = examples_per_epoch // BATCH_SIZE
BUFFER_SIZE = 10000

train_dataset = train_dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
['\ufeff' 'ㅌ' 'ㅐ' 'ㅈ' 'ㅗ' ' ' 'ㅇ' 'ㅣ' 'ㅅ' 'ㅓ' 'ㅇ' 'ㄱ' 'ㅖ' ' ' 'ㅅ' 'ㅓ' 'ㄴ'
 'ㄷ' 'ㅐ' 'ㅇ' 'ㅢ' ' ' 'ㄱ' 'ㅏ' 'ㄱ' 'ㅖ' '.' ' ' 'ㅁ' 'ㅗ' 'ㄱ' 'ㅈ' 'ㅗ' ' ' 'ㅇ'
 'ㅣ' 'ㅇ' 'ㅏ' 'ㄴ' 'ㅅ' 'ㅏ' 'ㄱ' 'ㅏ' ' ' 'ㅈ' 'ㅓ' 'ㄴ' 'ㅈ' 'ㅜ' 'ㅇ' 'ㅔ' 'ㅅ' 'ㅓ'
 ' ' 'ㅅ' 'ㅏ' 'ㅁ' 'ㅊ' 'ㅓ' 'ㄱ' '·' 'ㅇ' 'ㅢ' 'ㅈ' 'ㅜ' 'ㄹ' 'ㅡ' 'ㄹ' ' ' 'ㄱ' 'ㅓ'
 'ㅊ' 'ㅕ' ' ' 'ㅇ' 'ㅏ' 'ㄹ' 'ㄷ' 'ㅗ' 'ㅇ' 'ㅇ']
[6158   83   87   79   94    1   78  106   76   90   78   56   93    1
   76   90   59   62   87   78  105    1   56   86   56   93   10    1
   72   94   56   79   94    1   78  106   78   86   59   76   86   56
   86    1   79   90   59   79   99   78   91   76   90    1   76   86
   72   81   90   56   36   78  105   79   99   64  104   64    1   56
   90   81   92    1   78   86   64   62   94   78   78]
['\ufeff' 'ㅌ' 'ㅐ' 'ㅈ' 'ㅗ' ' ' 'ㅇ' 'ㅣ' 'ㅅ' 'ㅓ' 'ㅇ' 'ㄱ' 'ㅖ' ' ' 'ㅅ' 'ㅓ' 'ㄴ'
 'ㄷ' 'ㅐ' 'ㅇ' 'ㅢ' ' ' 'ㄱ' 'ㅏ' 'ㄱ' 'ㅖ' '.' ' ' 'ㅁ' 'ㅗ' 'ㄱ' 'ㅈ' 'ㅗ' ' ' 'ㅇ'
 'ㅣ' 'ㅇ' 'ㅏ' 'ㄴ' 'ㅅ' 'ㅏ' 'ㄱ' 'ㅏ' ' ' 'ㅈ' 'ㅓ' 'ㄴ' 'ㅈ' 'ㅜ' 'ㅇ' 'ㅔ' 'ㅅ' 'ㅓ'
 ' ' 'ㅅ' 'ㅏ' 'ㅁ' 'ㅊ' 'ㅓ' 'ㄱ' '·' 'ㅇ' 'ㅢ' 'ㅈ' 'ㅜ' 'ㄹ' 'ㅡ' 'ㄹ' ' ' 'ㄱ' 'ㅓ'
 'ㅊ' 'ㅕ' ' ' 'ㅇ' 'ㅏ' 'ㄹ' 'ㄷ' 'ㅗ' 'ㅇ']
[6158   83   87   79   94    1   78  106   76   90   78   56   93    1
   76   90   59   62   87   78  105    1   56   86   56   93   10    1
   72   94   56   79   94    1   78  106   78   86   59   76   86   56
   86    1   79   90   59   79   99   78   91   76   90    1   76   86
   72   81   90   56   36   78  105   79   99   64  104   64    1   56
   90   81   92    1   78   86   64   62   94   78]
ㅇ
78
  • seq_length의 의미는 자소 단위에서는 80개의 자소를 입력받았을 때 1개의 자소를 출력하도록 합니다.

(4) 자소 단위 생성 모델 정의 및 학습

자소 단위 생성 모델에서는 겹쳐진 순환 신경망을 사용하지 않고 LSTM레이어를 하나만 사용했습니다. 대신 하나의 LSTM레이어에서는 사용하는 뉴런의 수를 4배로 늘렸습니다. 또 단어의 수보다 자소의 수가 훨씬 적기 때문에 마지막 Dense레이어의 뉴런 수가 적어집니다.

total_chars = len(vocab)
model = tf.keras.Sequential([
  tf.keras.layers.Embedding(total_chars, 100, input_length=seq_length), 
  tf.keras.layers.LSTM(units=400), 
  tf.keras.layers.Dense(total_chars, activation='softmax')
])

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, 80, 100)           619800    
_________________________________________________________________
lstm (LSTM)                  (None, 400)               801600    
_________________________________________________________________
dense (Dense)                (None, 6198)              2485398   
=================================================================
Total params: 3,906,798
Trainable params: 3,906,798
Non-trainable params: 0
_________________________________________________________________

모형을 학습시킵니다. 이 때 주의해야 하는 것은 testmodel 함수에서, jamotools 입력 부분을 추가해야 합니다. 이 소스코드의 위치를 살펴보시기를 바랍니다.

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]
    test_sentence = jamotools.split_syllables(test_sentence)

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

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

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

Epoch 1/100

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

2364/2364 - 270s - loss: 2.5904 - accuracy: 0.3065 Epoch 2/100 2364/2364 - 266s - loss: 1.9905 - accuracy: 0.4264 Epoch 3/100 2364/2364 - 266s - loss: 1.8423 - accuracy: 0.4608 Epoch 4/100 2364/2364 - 266s - loss: 1.7423 - accuracy: 0.4823 Epoch 5/100 2364/2364 - 264s - loss: 1.6784 - accuracy: 0.4948 Epoch 6/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 일이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다. 임금이 있었다.

2364/2364 - 265s - loss: 1.6241 - accuracy: 0.5070 Epoch 7/100 2364/2364 - 264s - loss: 1.5739 - accuracy: 0.5184 Epoch 8/100 2364/2364 - 264s - loss: 1.5297 - accuracy: 0.5286 Epoch 9/100 2364/2364 - 266s - loss: 1.4916 - accuracy: 0.5372 Epoch 10/100 2364/2364 - 268s - loss: 1.4595 - accuracy: 0.5451 Epoch 11/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하였다. 임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임금이 말하기를, “임그

2364/2364 - 270s - loss: 1.4323 - accuracy: 0.5516 Epoch 12/100 2364/2364 - 269s - loss: 1.4059 - accuracy: 0.5585 Epoch 13/100 2364/2364 - 269s - loss: 1.3848 - accuracy: 0.5631 Epoch 14/100 2364/2364 - 269s - loss: 1.3646 - accuracy: 0.5685 Epoch 15/100 2364/2364 - 270s - loss: 1.3473 - accuracy: 0.5730 Epoch 16/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하였다. 임금이 말하기를, “이제 이를 받들고 전하께서 아뢰기를, “이제 이를 받들고 전하께서 아뢰기를, “이제 이를 받들고 전하께서 아뢰기를, “이제 이를 받들고 전하께서 아뢰기를, “이제 이를 받들고 전하께서 아뢰기를, “이제 이를 받들고 전하께서 아뢰기를, “이제 이를 받들

2364/2364 - 272s - loss: 1.3297 - accuracy: 0.5777 Epoch 17/100 2364/2364 - 271s - loss: 1.3138 - accuracy: 0.5825 Epoch 18/100 2364/2364 - 270s - loss: 1.3013 - accuracy: 0.5857 Epoch 19/100 2364/2364 - 270s - loss: 1.2859 - accuracy: 0.5904 Epoch 20/100 2364/2364 - 272s - loss: 1.2727 - accuracy: 0.5941 Epoch 21/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하는 것은 그 고을에 있으면 그 사람을 감하게 하였다. 임금이 말하기를, “각각 15석을 가지고 감사(監司)에서 아뢰기를, “각각 15석을 가지고 감사(監司)에서 아뢰기를, “각각 15석을 가지고 감사(監司)에서 아뢰기를, “각각 15석을 가지고 감사(監司)에서 아뢰기를, “각각 15석을 가지

2364/2364 - 275s - loss: 1.2607 - accuracy: 0.5970 Epoch 22/100 2364/2364 - 273s - loss: 1.2478 - accuracy: 0.6010 Epoch 23/100 2364/2364 - 273s - loss: 1.2370 - accuracy: 0.6044 Epoch 24/100 2364/2364 - 273s - loss: 1.2262 - accuracy: 0.6075 Epoch 25/100 2364/2364 - 273s - loss: 1.2161 - accuracy: 0.6101 Epoch 26/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하였다. 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를, “이제 임금이 말하기를,

2364/2364 - 275s - loss: 1.2050 - accuracy: 0.6134 Epoch 27/100 2364/2364 - 273s - loss: 1.1956 - accuracy: 0.6167 Epoch 28/100 2364/2364 - 273s - loss: 1.1863 - accuracy: 0.6193 Epoch 29/100 2364/2364 - 274s - loss: 1.1771 - accuracy: 0.6223 Epoch 30/100 2364/2364 - 274s - loss: 1.1682 - accuracy: 0.6246 Epoch 31/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하니, 임금이 말하기를, “이 때에 이르기를, “상석(成石)을 진향하게 하였다. 임금이 말하기를, “이 때에 이르기를, “그러나 이를 주게 하다 예조에서 아뢰기를, “이 때에 이르기를, “그러나 이를 주게 하다 예조에서 아뢰기를, “이 때에 이르기를, “그러나 이를 주게 하다 예조에서 아뢰기를, “이 ㄸ

2364/2364 - 276s - loss: 1.1608 - accuracy: 0.6267 Epoch 32/100 2364/2364 - 273s - loss: 1.1522 - accuracy: 0.6293 Epoch 33/100 2364/2364 - 273s - loss: 1.1448 - accuracy: 0.6321 Epoch 34/100 2364/2364 - 273s - loss: 1.1370 - accuracy: 0.6344 Epoch 35/100 2364/2364 - 273s - loss: 1.1303 - accuracy: 0.6361 Epoch 36/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 할 것입니다. 이것은 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이 이를 받았다. 임금이

2364/2364 - 275s - loss: 1.1220 - accuracy: 0.6390 Epoch 37/100 2364/2364 - 272s - loss: 1.1161 - accuracy: 0.6407 Epoch 38/100 2364/2364 - 273s - loss: 1.1087 - accuracy: 0.6429 Epoch 39/100 2364/2364 - 273s - loss: 1.1020 - accuracy: 0.6448 Epoch 40/100 2364/2364 - 272s - loss: 1.0957 - accuracy: 0.6467 Epoch 41/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 한다. 전하께서는 알지 못한 자가 있으면 나와서 아뢰다 예조에서 아뢰기를, “이 앞서 있는 것이 어떻게 아뢰다 예조에서 아뢰기를, “이 앞서 정지는 알지 못한 자가 있으면 날이 없으니, 이제 있는 것이 어떻겠습니까. 이제 상소하기를, “이 앞서 중에 있는 것은 알지 못한 작

2364/2364 - 268s - loss: 1.0895 - accuracy: 0.6494 Epoch 42/100 2364/2364 - 265s - loss: 1.0821 - accuracy: 0.6507 Epoch 43/100 2364/2364 - 265s - loss: 1.0770 - accuracy: 0.6527 Epoch 44/100 2364/2364 - 265s - loss: 1.0712 - accuracy: 0.6547 Epoch 45/100 2364/2364 - 266s - loss: 1.0654 - accuracy: 0.6565 Epoch 46/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하여 그 직임을 만들어 보낸 자는 이러하였다. 임금이 말하기를, “근일의 일을 이루지 않는다면 어찌 그 직임을 만들어 주는 것이 아니었다. ’고 하였습니다. 이제 상상도 염려되는 것이 아니었다. ’고 하였습니다. 신 등이 상소하기를, “근일의 일을 이루지 않는다면 어ㅉ

2364/2364 - 268s - loss: 1.0604 - accuracy: 0.6578 Epoch 47/100 2364/2364 - 266s - loss: 1.0563 - accuracy: 0.6588 Epoch 48/100 2364/2364 - 265s - loss: 1.0498 - accuracy: 0.6608 Epoch 49/100 2364/2364 - 266s - loss: 1.0455 - accuracy: 0.6626 Epoch 50/100 2364/2364 - 266s - loss: 1.0404 - accuracy: 0.6640 Epoch 51/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하였으니 그 장(狀)을 강하게 하고, 상소를 이루게 하고, 이를 죽입니다. 이제 이미 정한 사람은 그 장(狀)을 기다리기를 청하였으니, 이것은 그를 구경하고 서울에 의거하여 아뢰기를, “경연(經筵司) 이상의 아들이 이를 주고, 우의정 신주(春秋)를 사용하고, 한 사람은 그 장차 북쪽에 ㄷ

2364/2364 - 270s - loss: 1.0354 - accuracy: 0.6654 Epoch 52/100 2364/2364 - 267s - loss: 1.0294 - accuracy: 0.6679 Epoch 53/100 2364/2364 - 265s - loss: 1.0260 - accuracy: 0.6683 Epoch 54/100 2364/2364 - 267s - loss: 1.0213 - accuracy: 0.6698 Epoch 55/100 2364/2364 - 270s - loss: 1.0165 - accuracy: 0.6715 Epoch 56/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하였다. 이를 주었다. 임금이 말하기를, “이 앞에 있어서 아뢰기를, “이 사람이 일찍이 행하고, 인도하여 아뢰기를, “이 사람이 있다. 그 아들이 아뢰기를, “근일에 이르러 있다. 그 아들이 이를 주었다. 임금이 말하기를, “이 앞에 있어서 아뢰기를, “이 앞에 드리게 하다 임금이 말

2364/2364 - 272s - loss: 1.0129 - accuracy: 0.6727 Epoch 57/100 2364/2364 - 270s - loss: 1.0085 - accuracy: 0.6744 Epoch 58/100 2364/2364 - 271s - loss: 1.0048 - accuracy: 0.6753 Epoch 59/100 2364/2364 - 270s - loss: 1.0004 - accuracy: 0.6765 Epoch 60/100 2364/2364 - 270s - loss: 0.9973 - accuracy: 0.6772 Epoch 61/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하여 바라옵건대, 상소(上疏)하여 상고하여 아뢰기를, “이 사람은 반드시 사이에 있었으니, 이것을 아뢰다 의정부에서 아뢰기를, “이 사람은 임금이 말하기를, ‘고을의 각 고을의 성을 일으키고 돌아왔으나, 이는 이름을 인도하여 아뢰기를, “성상의 의논을 인도하여 아뢰기를, “이 ㅇ

2364/2364 - 272s - loss: 0.9945 - accuracy: 0.6779 Epoch 62/100 2364/2364 - 270s - loss: 0.9906 - accuracy: 0.6801 Epoch 63/100 2364/2364 - 270s - loss: 0.9874 - accuracy: 0.6808 Epoch 64/100 2364/2364 - 270s - loss: 0.9834 - accuracy: 0.6817 Epoch 65/100 2364/2364 - 270s - loss: 0.9804 - accuracy: 0.6828 Epoch 66/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하고, 이에 이미 다행하게 되면 일이 있었다. 임금이 말하기를, “이 사람이 있었다. 임금이 말하기를, “이 사람의 상소를 인도하여 아뢰기를, “신 등이 이르기를, “만약 대마도는 이러한 일을 맡아서 연훙한 일이 없었으나, 윤허하지 않고 있으니, 청컨대 이제 앞에 의하여 알

2364/2364 - 272s - loss: 0.9771 - accuracy: 0.6839 Epoch 67/100 2364/2364 - 270s - loss: 0.9758 - accuracy: 0.6841 Epoch 68/100 2364/2364 - 270s - loss: 0.9714 - accuracy: 0.6859 Epoch 69/100 2364/2364 - 270s - loss: 0.9699 - accuracy: 0.6862 Epoch 70/100 2364/2364 - 270s - loss: 0.9658 - accuracy: 0.6871 Epoch 71/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하여 그대로 따랐다.경상도 등을 아뢰다 상왕이 사신에게 말하기를, “이보다 큰 곳을 정지하기를, “이보다 큰 곳이 없었으므로 나와 같이 없어질 수 없는데, 이를 주다 의정부에서 상소하기를, “이보다 큰 곳을 정지하기를 의심하고 있었는데, 지금 남을 것이다. 이제 상(喪)에 ㅇ

2364/2364 - 272s - loss: 0.9634 - accuracy: 0.6882 Epoch 72/100 2364/2364 - 270s - loss: 0.9626 - accuracy: 0.6882 Epoch 73/100 2364/2364 - 270s - loss: 0.9582 - accuracy: 0.6893 Epoch 74/100 2364/2364 - 270s - loss: 0.9563 - accuracy: 0.6894 Epoch 75/100 2364/2364 - 270s - loss: 0.9539 - accuracy: 0.6913 Epoch 76/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하겠습니다. 이를 금하게 하다가 집에 있으면 지금 아니라 이를 만들어 보내어 이를 만들어 보내어 이를 만약 관찰사(咸吉道都節制使) 윤장(印章)·정하여 인명하면서 군사를 가지고 있으니, 청컨대 이제부터는 이직(遞職)하여 아뢰기를, “이제 이미 전하께서 이를 주다 정사(慶事)가 만약 그 인ㅇ

2364/2364 - 272s - loss: 0.9535 - accuracy: 0.6909 Epoch 77/100 2364/2364 - 270s - loss: 0.9502 - accuracy: 0.6919 Epoch 78/100 2364/2364 - 270s - loss: 0.9518 - accuracy: 0.6904 Epoch 79/100 2364/2364 - 270s - loss: 0.9459 - accuracy: 0.6932 Epoch 80/100 2364/2364 - 269s - loss: 0.9433 - accuracy: 0.6941 Epoch 81/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하다가 중국 종사를 보고 대장궁에 나아가 그 중에 있었으나, 이미 전에 의거하여 전하께서 아뢰기를, “전일의 의논을 거느리고 돌아간 공신이 불을 드리는 것은 이미 공사를 보고 같은 것을 몹아서 임금을 입었으나, 이미 종사를 보고 대장군의 집에 있었으나 윤허하지 않ㅇ

2364/2364 - 272s - loss: 0.9425 - accuracy: 0.6940 Epoch 82/100 2364/2364 - 270s - loss: 0.9436 - accuracy: 0.6937 Epoch 83/100 2364/2364 - 272s - loss: 0.9405 - accuracy: 0.6945 Epoch 84/100 2364/2364 - 270s - loss: 0.9372 - accuracy: 0.6959 Epoch 85/100 2364/2364 - 268s - loss: 0.9354 - accuracy: 0.6963 Epoch 86/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하여, 일이 있으니, 청컨대 그 죄를 주고, 인사(人事)를 올리는 것이 없습니다. 그러나, 오직 관가(三文理)로 하여금 임금이 상소하여 사신에게 전지하기를, “신이 임금이 있었다. 임금이 상성(宮城)을 정하여 일어나 전하께서 상소하여 사신에게 장숙주(申叔舟)에서 일찍이 각기 그 공이

2364/2364 - 272s - loss: 0.9354 - accuracy: 0.6957 Epoch 87/100 2364/2364 - 271s - loss: 0.9322 - accuracy: 0.6968 Epoch 88/100 2364/2364 - 271s - loss: 0.9318 - accuracy: 0.6971 Epoch 89/100 2364/2364 - 271s - loss: 0.9336 - accuracy: 0.6964 Epoch 90/100 2364/2364 - 271s - loss: 0.9294 - accuracy: 0.6979 Epoch 91/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하였으니, 일찍이 일을 무엇이 있었다. 임금이 말하기를, “정성을 인송하게 하다 임금이 말하기를, “이제부터 그 죄를 내렸다. 임금이 말하기를, “이제부터 어려운 것이 없습니다. 신이 임금이 이를 비록 일으켜든 중일에 가서 주었다.상왕이 불을 다시 성하게 여기어 성서

2364/2364 - 273s - loss: 0.9289 - accuracy: 0.6981 Epoch 92/100 2364/2364 - 272s - loss: 0.9283 - accuracy: 0.6979 Epoch 93/100 2364/2364 - 272s - loss: 0.9259 - accuracy: 0.6989 Epoch 94/100 2364/2364 - 272s - loss: 0.9252 - accuracy: 0.6992 Epoch 95/100 2364/2364 - 272s - loss: 0.9245 - accuracy: 0.6994 Epoch 96/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하였다. 임금이 말하기를, “정상이 들어온 자에게 정지하고, 이어서 상언(上言)하여 정하고, 또 정진수가 처음에 이르렀으니, 다음에는 아래에 있는 자가 있으면 마땅히 윤망한 것이 아니라, 이에 있어서 상언(上言)하여 상언(上言)하여 상언(上言)하여 상언(上言)하여 상언(上言)하여 정부에

2364/2364 - 273s - loss: 0.9235 - accuracy: 0.6991 Epoch 97/100 2364/2364 - 271s - loss: 0.9234 - accuracy: 0.6989 Epoch 98/100 2364/2364 - 271s - loss: 0.9217 - accuracy: 0.6998 Epoch 99/100 2364/2364 - 272s - loss: 0.9215 - accuracy: 0.7000 Epoch 100/100

태조 이성계 선대의 가계. 목조 이안사가 전주에서 삼척·의주를 거쳐 알동에 정착하다 하여, 일이 있으면, 중국의 음사운의 청한 것은 아뢰기를, “각도의 아들을 보내어 여러 신하들이 이르기를, ‘군사의 임금이 말하기를, “이박한 그렇게 한 것은 아뢰기를, “각도에 이르렀던 것이 없습니다. 그러나 이에 가서 수령을 감동하다 사헌부에서 계하기를, “원산군(楊根

2364/2364 - 274s - loss: 0.9206 - accuracy: 0.6999

  • 모형 학습 시, 굉장히 많은 시간이 소요됩니다. 따라서 모형을 확인하려면 시간을 충분히 가지시고, 모형 학습을 진행합니다.
  • 단어 생성 모델처럼 같은 문장을 자소 단위로 넣어서 에포크가 끝날 때마다 생성 결과를 확인합니다.
  • 단어 생성 모델과 마찬가지로 처음에는 반복되는 패턴이 자주 나타나지만 점점 그럴듯한 결과를 만들어내기 시작합니다.
  • 특히 한자와 괄호를 그대로 학습에 사용하고 있기 때문에 한글과 한자의 병기도 잘 하는 것을 볼 수 있습니다.

(5) 학습모형 테스트

이제 임의의 문장을 통해서 학습을 진행합니다. 마찬가지로 기존 코드와 크게 달라진 것은 없으나 몇몇 변수만 수정하면 됩니다.

  • next_words에서 next_chars로 변경
  • word2idx에서 char2idx로 변경
  • idx2word에서 idx2char로 변경
from tensorflow.keras.preprocessing.sequence import pad_sequences
test_sentence = '동헌에 나가 공무를 본 후 활 십오 순을 쏘았다'
test_sentence = jamotools.split_syllables(test_sentence)

next_chars = 300
for _ in range(next_chars):
    test_text_X = test_sentence[-seq_length:]
    test_text_X = np.array([char2idx[c] if c in char2idx else char2idx['UNK'] for c in test_text_X])
    test_text_X = pad_sequences([test_text_X], maxlen=seq_length, padding='pre', value=char2idx['UNK'])
    
    output_idx = model.predict_classes(test_text_X)
    test_sentence += idx2char[output_idx[0]]
    

print(jamotools.join_jamos(test_sentence))

동헌에 나가 공무를 본 후 활 십오 순을 쏘았다. 임금이 말하기를, “이보다 큰 공상은 그 집에 돌아오다 정사를 보았다. 임금이 말하기를, “이방성을 아뢰다 함길도 감사가 이미 나라를 행하였다.상왕이 그 사람을 금하다 임금이 말하기를, “이보다 큰 공상은 그 집에 돌아온다. 【모든 것을 보내어 여러 관원은 농산ㄱ

여기서도 뒤로 가면 비슷한 문장이 다시 나오고 있지만, 한글을 정확하게 조합할 수 있도록 네트워크가 자소를 생성하고 있음을 보여주고 있습니다.

III. 정리

여기서 정리해야 하는 것은 순환신경망이 언제 쓰이는 것인지, LSTM, SimpleRNN, GRU레이어에 대해 학습하였습니다.

기본적인 이론을 중심으로 재 학습을 하는 걸 권해드립니다. 특히, 자연어처리는 아직도 연구중인 분야고, 사실 굉장히 까다롭기 때문에, 학습자가 특별한 Mission이 있지 않으면 감정 분석 정도에서 마무리하는 것이 좋습니다. 자연어처리를 통한 비즈니스 활용 연구는 실제로 대기업에서 본격적인 연구가 가능합니다.

자연어처리는 학습을 시켜서 아시겠지만, 학습시간이 매우 오래 걸리는 대신, 결과물이 사실 애매모호한 경우가 많습니다. 기본적인 이론을 바탕으로 실무에서는 클라우드를 활용한 서비스 개발(예: 챗봇)에 집중하시는 것을 권유드립니다.

IV. 연습 파일

V. 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/