RNN에서 대표적인 예제라고 한다면 text 예제가 많이 있습니다.
앞에서 숫자 예제를 만들어 봤는데 이번에는 해당 예제를 변형하여 text예제를 만들어 볼까합니다.
text는 애국가가 나오고 다음 한 글자를 예측하는 형태가 됩니다.
소스 코드가 이전과 거의 비슷하기 때문에 입력 shape과 one hot encoding을 중점적으로 보면 쉽게 이해가 될것입니다.
입력 데이터
앞서 언급했듯이 입력은 애국가가 됩니다. 훈련을 위한 x , y 만들어야 하는데 여기 예제에서는 *(이전 데이터)입력 (time_step_size) 4개를 가지고 다음번 1개를 예측해야하므로 다 대 일(Many-to-One) 구조로 데이터를 만들었습니다.
원본 text
동해 물과 백두산이 마르고 닳도록
하느님이 보우하사 우리나라 만세.
...
x_data.txt
동해 물 해 물과 물과 물과 백 과 백두 백두산 백두산이
...
y_data.txt
과 백 두 산 이 ...
이러한 문자 처리에서는 몇가지 알아둬야 할 사항이 있습니다.
1. python3 utf-8에서는 한글이 한개의 문자가 됩니다. 즉 len의 길이가 byte단위로 측정되는것이 아니기 길이가 1이 됩니다.
len("동해 물") 은 4가 되게 됩니다. (공백도 1이 됩니다.)
2. text학습을 위해서는 결국 숫자(tensor)로 변환해야 하는데 그것을 encoding이라고 부른다. 일종의 약속(변환 테이블)을 해야 합니다, 즉 가=1, 나=2 이런식으로 하게됩니다. 그런데 모든 문자에 대해서 코드화를 해놓으면 불필요한 메모리가 낭비가 되다보니 사용하는 문자들만 하게 됩니다. 즉 동=1, 해=2, 물=3 이런식으로 됩니다.
문자 기반으로 할 경우의 예를 위와 같이 들었고 단어 기반의 경우 단어 단위로 encoding을 하게 됩니다.
3. 엔코딩을 했더라도 한가지 추가 고민이 필요합니다. 동=1, 해=2, 물=3 로 했을때 1,3이 어떤 관계냐는것 입니다. 연산을 하게될텐데 y결과가 3이었다면 동=1 에 뭔가를 더 더해서 물=3이 될 수 있느냐는건데(예를들어서 동+동+동 을 하게되면 물이 되느냐 그런 질문입니다.) 문자에서는 전혀 관계가 없다는것 입니다. 그래서 코드화된 숫자를 다시 하나의 feature로 가지도록 one hot encoding을 사용하는것입니다.
이부분은 다음 함수를 이용해서 전환을 하게 됩니다. from tensorflow.keras.utils import to_categorical
여기에서는 데이터 생성을 하면서 2번 엔코딩 테이블까지 만들어서 pickle로 저장하게 됩니다.
char_vocab = sorted(list(set(timed_y_data))) vocab_size = len(char_vocab) print(char_vocab) print(vocab_size)
set은 텍스트의 중복 데이터를 지워줍니다. sorted는 list를 정렬해주게 됩니다.
[' ', ',', '.', '가', '갑', '강', '거', '고', '공', '과', '괴', '구', '궁', '기', '길', '나', '남', '높', '느', '늘', '님', '다', '단', '달', '닳', '대', '데', '도', '동', '두', '듯', '라', '람', '랑', '려', '로', '록', '르', '른', '름', '리', '마', '만', '맘', '무', '물', '바', '밝', '백', '변', '보', '불', '사', '산', '삼', '상', '서', '성', '세', '소', '슴', '심', '없', '에', '여', '우', '위', '으', '은', '을', '이', '일', '저', '전', '즐', '천', '철', '충', '편', '하', '한', '함', '해', '화', '활'] 85
문자가 들어오면 index로 변환하는 dict 입니다.
char_to_index = dict((char, index) for index, char in enumerate(char_vocab)) print(char_to_index)
결과
{' ': 0, ',': 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, '소': 59, '슴': 60, '심': 61, '없': 62, '에': 63, '여': 64, '우': 65, '위': 66, '으': 67, '은': 68, '을': 69, '이': 70, '일': 71, '저': 72, '전': 73, '즐': 74, '천': 75, '철': 76, '충': 77, '편': 78, '하': 79, '한': 80, '함': 81, '해': 82, '화': 83, '활': 84}
이번에는 반대로 숫자가 들어오면 문자로 변환 해주는 dict 입니다.
index_to_char = {} for key, value in char_to_index.items(): index_to_char[value] = key print(index_to_char)
결과
{0: ' ', 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: '세', 59: '소', 60: '슴', 61: '심', 62: '없', 63: '에', 64: '여', 65: '우', 66: '위', 67: '으', 68: '은', 69: '을', 70: '이', 71: '일', 72: '저', 73: '전', 74: '즐', 75: '천', 76: '철', 77: '충', 78: '편', 79: '하', 80: '한', 81: '함', 82: '해', 83: '화', 84: '활'}
데이터 생성 전체 소스
import pickle timed_y_data = """ 동해 물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세. 무궁화 삼천리 화려 강산 대한 사람, 대한으로 길이 보전하세. 남산 위에 저 소나무, 철갑을 두른 듯 바람 서리 불변함은 우리 기상일세. 무궁화 삼천리 화려 강산 대한 사람, 대한으로 길이 보전하세. 가을 하늘 공활한데 높고 구름 없이 밝은 달은 우리 가슴 일편단심일세. 무궁화 삼천리 화려 강산 대한 사람, 대한으로 길이 보전하세. 이 기상과 이 맘으로 충성을 다하여 괴로우나 즐거우나 나라 사랑하세. 무궁화 삼천리 화려 강산 대한 사람, 대한으로 길이 보전하세. """ timed_y_data = timed_y_data.strip() timed_y_data = timed_y_data.replace("\n","") print(timed_y_data) time_step_size = 4 x_list = [] y_list = [] for xx in range(len(timed_y_data)-time_step_size): x_list.append(timed_y_data[xx:xx+time_step_size]) y_list.append(timed_y_data[xx+time_step_size]) print(x_list) print(y_list) char_vocab = sorted(list(set(timed_y_data))) vocab_size = len(char_vocab) print(char_vocab) print(vocab_size) char_to_index = dict((char, index) for index, char in enumerate(char_vocab)) print(char_to_index) index_to_char = {} for key, value in char_to_index.items(): index_to_char[value] = key print(index_to_char) file = open("x_data.txt", "w", encoding="utf-8") for data in x_list: file.write(data) file.write("\n") file.close() file = open("y_data.txt", "w", encoding="utf-8") for data in y_list: file.write(data) file.write("\n") file.close() with open('char_to_index.pickle', 'wb') as fw: pickle.dump(char_to_index, fw) with open('index_to_char.pickle', 'wb') as fw: pickle.dump(index_to_char, fw)
소스 변경점
기존 RNN 예제 대비 변경점은 3가지 정도입니다.
1. pickle를 이용한 엔코딩 데이터 처리
이 부분은 data 저장하는 곳에서 저장했기 때문에 load가 필요합니다.
import pickle with open('char_to_index.pickle', 'rb') as fr: char_to_index = pickle.load(fr) with open('index_to_char.pickle', 'rb') as fr: index_to_char = pickle.load(fr)
2. CustomDataset 변경
기존에는 csv 파일을 읽어오기만 했었는데 여기에서는 x, y를 읽어서 다시 onehot encoding이 필요합니다.
그래서 값을 읽어오는 부분이 변경되었습니다. 앞에서 문자크기가 85개이기 때문에 onehot encoding시에도 85개의 feature가 필요합니다.
def __getitem__(self, index): """ 주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다. """ if self.base_idx is not None: index = index + self.base_idx x_data = self.x_data_lines[index].replace("\n", "") #print(x_data) x_data = np.array([to_categorical(char_to_index[i], 85) for i in x_data]) #print(x_data.shape) #print(x_data) y_data = self.y_data_lines[index].replace("\n", "") #print(y_data) y_data = to_categorical(char_to_index[y_data], 85) #print(y_data) #x_data = np.reshape(x_data, (-1, 85)) return x_data, y_data
아래 코드에 의해서 (batch, 4, 85) 크기를 가지는 data 입력이 됩니다.
x_data = np.array([to_categorical(char_to_index[i], 85) for i in x_data])
물론 여기에서는 (4, 85) 형태를 리턴해주면 CustomDataloader 에 의해서 batch로 묶여지게 됩니다.
3. 모델
모델은 입력 shape 을 적당히 변경하고 작은 경우 훈련이 거의 되지 않아서 LSTM 크기를 좀 늘렸습니다. Dense는 85개의 입력을 받고 이것을 softmax 활성화 함수를 사용하였습니다.
inputs = keras.Input(shape=(4, 85)) x = layers.LSTM(60)(inputs) x = layers.Dense(85, activation='softmax')(x)
그리고 Loss함수는 CategoricalCrossentropy 를 사용하였고 Adam Optimizer를 사용하였습니다.
INPUT_OPTIMIZER = tf.keras.optimizers.Adam(learning_rate=lr) LOSS = tf.keras.losses.CategoricalCrossentropy()
전체 소스
from tensorflow import keras from tensorflow.keras.utils import Sequence from tensorflow.keras import layers import tensorflow as tf from tensorflow.keras.utils import to_categorical import numpy as np import math import pickle with open('char_to_index.pickle', 'rb') as fr: char_to_index = pickle.load(fr) with open('index_to_char.pickle', 'rb') as fr: index_to_char = pickle.load(fr) batch_size = 20 lr = 1e-3 n_epochs = 50 train_data_ratio = 0.8 class CustomDataset: def __init__(self, x_tensor_filename, y_tensor_filename, base_idx=None, cnt=None): self.x_fn = x_tensor_filename self.y_fn = y_tensor_filename self.base_idx = base_idx self.cnt = cnt if self.base_idx is None: with open(y_tensor_filename, 'r', encoding='utf-8') as fp: for count, line in enumerate(fp): pass self.total_len = count + 1 else: self.total_len = cnt with open(x_tensor_filename, "r", encoding='utf-8') as f: self.x_data_lines = [line for line in f] with open(y_tensor_filename, "r", encoding='utf-8') as f: self.y_data_lines = [line for line in f] def __getitem__(self, index): """ 주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다. """ if self.base_idx is not None: index = index + self.base_idx x_data = self.x_data_lines[index].replace("\n", "") #print(x_data) x_data = np.array([to_categorical(char_to_index[i], 85) for i in x_data]) #print(x_data.shape) #print(x_data) y_data = self.y_data_lines[index].replace("\n", "") #print(y_data) y_data = to_categorical(char_to_index[y_data], 85) #print(y_data) #x_data = np.reshape(x_data, (-1, 85)) return x_data, y_data def __len__(self): """ 데이터셋의 샘플 개수를 반환합니다. """ return self.total_len class CustomDataloader(Sequence): def __init__(self, _dataset, batch_size=1, shuffle=False): self.dataset = _dataset self.batch_size = batch_size self.total_len = math.ceil(len(self.dataset) / self.batch_size) self.shuffle = shuffle self.indexer = np.arange(len(self.dataset)) self.on_epoch_end() def __getitem__(self, index): indexer = self.indexer[index * self.batch_size:(index + 1) * self.batch_size] batch_x = [self.dataset[i][0] for i in indexer] batch_y = [self.dataset[i][1] for i in indexer] #[[5. 5.00999946 5.0199977 5.02999448 5.03998959] # [5.00999946 5.0199977 5.02999448 5.03998959 5.04998279] # ... return np.array(batch_x), np.array(batch_y) def __len__(self): return self.total_len def on_epoch_end(self): if self.shuffle: np.random.shuffle(self.indexer) print("shuffle") temp_dataset = CustomDataset("x_data.txt", "y_data.txt") train_dataset_cnt = int(len(temp_dataset) * train_data_ratio) val_dataset_cnt = len(temp_dataset) - train_dataset_cnt train_dataset = CustomDataset("x_data.txt", "y_data.txt", 0, train_dataset_cnt) val_dataset = CustomDataset("x_data.txt", "y_data.txt", train_dataset_cnt, val_dataset_cnt) print(len(temp_dataset), train_dataset_cnt, val_dataset_cnt) train_loader = CustomDataloader(train_dataset, batch_size=batch_size, shuffle=False) val_loader = CustomDataloader(val_dataset, batch_size=batch_size, shuffle=False) # https://keras.io/api/layers/ inputs = keras.Input(shape=(4, 85)) x = layers.LSTM(60)(inputs) x = layers.Dense(85, activation='softmax')(x) outputs = x model = keras.Model(inputs, outputs) model.summary() # https://keras.io/api/optimizers/ # https://keras.io/api/losses/ INPUT_OPTIMIZER = tf.keras.optimizers.Adam(learning_rate=lr) LOSS = tf.keras.losses.CategoricalCrossentropy() model.compile(optimizer=INPUT_OPTIMIZER, loss=LOSS) model.fit(train_loader, batch_size=batch_size, epochs=n_epochs, validation_data=val_loader) model.save("model_lstm.keras")
결과
279 223 56 Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 4, 85)] 0 lstm (LSTM) (None, 60) 35040 dense (Dense) (None, 85) 5185 ================================================================= Total params: 40,225 Trainable params: 40,225 Non-trainable params: 0 _________________________________________________________________ Epoch 1/50 12/12 [==============================] - 1s 23ms/step - loss: 4.4400 - val_loss: 4.4262 Epoch 2/50 12/12 [==============================] - 0s 4ms/step - loss: 4.4039 - val_loss: 4.3948 Epoch 3/50 12/12 [==============================] - 0s 4ms/step - loss: 4.3606 - val_loss: 4.3512 Epoch 4/50 12/12 [==============================] - 0s 4ms/step - loss: 4.2906 - val_loss: 4.2747 Epoch 5/50 12/12 [==============================] - 0s 4ms/step - loss: 4.1535 - val_loss: 4.1075 Epoch 6/50 12/12 [==============================] - 0s 4ms/step - loss: 3.8944 - val_loss: 3.8651 Epoch 7/50 12/12 [==============================] - 0s 4ms/step - loss: 3.7237 - val_loss: 3.8388 Epoch 8/50 12/12 [==============================] - 0s 4ms/step - loss: 3.6581 - val_loss: 3.7765 Epoch 9/50 12/12 [==============================] - 0s 4ms/step - loss: 3.5942 - val_loss: 3.7746 Epoch 10/50 12/12 [==============================] - 0s 4ms/step - loss: 3.5545 - val_loss: 3.7465 Epoch 11/50 12/12 [==============================] - 0s 4ms/step - loss: 3.5149 - val_loss: 3.7227 Epoch 12/50 12/12 [==============================] - 0s 4ms/step - loss: 3.4697 - val_loss: 3.7108 Epoch 13/50 12/12 [==============================] - 0s 4ms/step - loss: 3.4246 - val_loss: 3.6961 Epoch 14/50 12/12 [==============================] - 0s 4ms/step - loss: 3.3848 - val_loss: 3.6519 Epoch 15/50 12/12 [==============================] - 0s 3ms/step - loss: 3.3276 - val_loss: 3.6426 Epoch 16/50 12/12 [==============================] - 0s 3ms/step - loss: 3.2704 - val_loss: 3.5921 Epoch 17/50 12/12 [==============================] - 0s 4ms/step - loss: 3.2082 - val_loss: 3.5731 Epoch 18/50 12/12 [==============================] - 0s 4ms/step - loss: 3.1412 - val_loss: 3.5327 Epoch 19/50 12/12 [==============================] - 0s 4ms/step - loss: 3.0699 - val_loss: 3.4891 Epoch 20/50 12/12 [==============================] - 0s 4ms/step - loss: 2.9889 - val_loss: 3.4257 Epoch 21/50 12/12 [==============================] - 0s 4ms/step - loss: 2.9030 - val_loss: 3.3826 Epoch 22/50 12/12 [==============================] - 0s 4ms/step - loss: 2.8177 - val_loss: 3.3197 Epoch 23/50 12/12 [==============================] - 0s 4ms/step - loss: 2.7216 - val_loss: 3.2720 Epoch 24/50 12/12 [==============================] - 0s 4ms/step - loss: 2.6283 - val_loss: 3.2156 Epoch 25/50 12/12 [==============================] - 0s 4ms/step - loss: 2.5297 - val_loss: 3.1799 Epoch 26/50 12/12 [==============================] - 0s 4ms/step - loss: 2.4361 - val_loss: 3.1063 Epoch 27/50 12/12 [==============================] - 0s 4ms/step - loss: 2.3331 - val_loss: 3.0865 Epoch 28/50 12/12 [==============================] - 0s 4ms/step - loss: 2.2367 - val_loss: 3.0124 Epoch 29/50 12/12 [==============================] - 0s 4ms/step - loss: 2.1327 - val_loss: 2.9974 Epoch 30/50 12/12 [==============================] - 0s 4ms/step - loss: 2.0387 - val_loss: 2.9617 Epoch 31/50 12/12 [==============================] - 0s 4ms/step - loss: 1.9494 - val_loss: 2.9479 Epoch 32/50 12/12 [==============================] - 0s 3ms/step - loss: 1.8460 - val_loss: 2.9146 Epoch 33/50 12/12 [==============================] - 0s 4ms/step - loss: 1.7531 - val_loss: 2.9316 Epoch 34/50 12/12 [==============================] - 0s 4ms/step - loss: 1.6658 - val_loss: 2.8914 Epoch 35/50 12/12 [==============================] - 0s 4ms/step - loss: 1.5826 - val_loss: 2.9087 Epoch 36/50 12/12 [==============================] - 0s 4ms/step - loss: 1.5006 - val_loss: 2.9242 Epoch 37/50 12/12 [==============================] - 0s 4ms/step - loss: 1.4244 - val_loss: 2.9103 Epoch 38/50 12/12 [==============================] - 0s 4ms/step - loss: 1.3474 - val_loss: 2.9748 Epoch 39/50 12/12 [==============================] - 0s 4ms/step - loss: 1.2815 - val_loss: 2.9538 Epoch 40/50 12/12 [==============================] - 0s 3ms/step - loss: 1.2125 - val_loss: 3.0242 Epoch 41/50 12/12 [==============================] - 0s 3ms/step - loss: 1.1398 - val_loss: 2.9633 Epoch 42/50 12/12 [==============================] - 0s 4ms/step - loss: 1.0929 - val_loss: 3.0585 Epoch 43/50 12/12 [==============================] - 0s 4ms/step - loss: 1.0249 - val_loss: 3.0636 Epoch 44/50 12/12 [==============================] - 0s 4ms/step - loss: 0.9632 - val_loss: 3.0656 Epoch 45/50 12/12 [==============================] - 0s 4ms/step - loss: 0.9112 - val_loss: 3.0943 Epoch 46/50 12/12 [==============================] - 0s 4ms/step - loss: 0.8600 - val_loss: 3.0998 Epoch 47/50 12/12 [==============================] - 0s 3ms/step - loss: 0.8218 - val_loss: 3.1171 Epoch 48/50 12/12 [==============================] - 0s 4ms/step - loss: 0.7693 - val_loss: 3.1759 Epoch 49/50 12/12 [==============================] - 0s 4ms/step - loss: 0.7254 - val_loss: 3.1406 Epoch 50/50 12/12 [==============================] - 0s 4ms/step - loss: 0.6885 - val_loss: 3.1862
validation loss가 마지막에 증가하는것을 봤을때 제대로 훈련되는것 같지는 않습니다.
생각해보면 데이터가 너무 부족합니다. 즉 부족한 text에서 train, validation을 분해했으니 실제 train이 안되는 부분도 있을것 같네요
tester code
이번에는 훈련된 데이터를 이용해서 실제 동작이 어떻게 되는지 확인해보도록 하겠습니다.
처음 4글자가 주어지면 계속해서 예측해서 출력하도록 제작해보았습니다.
여기에서는 np.argmax() 라는 것을 사용했는데 최대값의 index를 넘겨주게 됩니다.
즉 85개의 onehot encoding으로 예측값이 들어가있을텐데 85개 feature중 어디의 값이 가장 큰것인지 index를 넘겨주게 됩니다.
import numpy as np import tensorflow as tf import matplotlib.pyplot as plt import pickle from tensorflow.keras.utils import to_categorical if __name__ == '__main__': with open('char_to_index.pickle', 'rb') as fr: char_to_index = pickle.load(fr) with open('index_to_char.pickle', 'rb') as fr: index_to_char = pickle.load(fr) flag = False fig, ax = plt.subplots() for model_name in ["model_lstm.keras"]: model = tf.keras.models.load_model(model_name) model.summary() x_list = [] x_list.extend(" 삼천리") predict_count = 30 for idx in range(predict_count): print("xlist",x_list) x_data = np.array([[to_categorical(char_to_index[i], 85) for i in x_list]]) print(x_data.shape) y = model.predict(x_data, batch_size=1) #print("y:", y) result = np.argmax(y, axis=1) print("result:", result) newchar = index_to_char[int(result)] print("newchar:", newchar) x_list.append(newchar) x_list.pop(0)
결과
Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 4, 85)] 0 lstm (LSTM) (None, 60) 35040 dense (Dense) (None, 85) 5185 ================================================================= Total params: 40,225 Trainable params: 40,225 Non-trainable params: 0 _________________________________________________________________ xlist [' ', '삼', '천', '리'] (1, 4, 85) 1/1 [==============================] - 0s 275ms/step result: [0] newchar: xlist ['삼', '천', '리', ' '] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [83] newchar: 화 xlist ['천', '리', ' ', '화'] (1, 4, 85) 1/1 [==============================] - 0s 10ms/step result: [34] newchar: 려 xlist ['리', ' ', '화', '려'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [0] newchar: xlist [' ', '화', '려', ' '] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [5] newchar: 강 xlist ['화', '려', ' ', '강'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [53] newchar: 산 xlist ['려', ' ', '강', '산'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [25] newchar: 대 xlist [' ', '강', '산', '대'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [80] newchar: 한 xlist ['강', '산', '대', '한'] (1, 4, 85) 1/1 [==============================] - 0s 11ms/step result: [0] newchar: xlist ['산', '대', '한', ' '] (1, 4, 85) 1/1 [==============================] - 0s 10ms/step result: [52] newchar: 사 xlist ['대', '한', ' ', '사'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [32] newchar: 람 xlist ['한', ' ', '사', '람'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [1] newchar: , xlist [' ', '사', '람', ','] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [0] newchar: xlist ['사', '람', ',', ' '] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [25] newchar: 대 xlist ['람', ',', ' ', '대'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [80] newchar: 한 xlist [',', ' ', '대', '한'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [67] newchar: 으 xlist [' ', '대', '한', '으'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [35] newchar: 로 xlist ['대', '한', '으', '로'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [0] newchar: xlist ['한', '으', '로', ' '] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [14] newchar: 길 xlist ['으', '로', ' ', '길'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [57] newchar: 성 xlist ['로', ' ', '길', '성'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [0] newchar: xlist [' ', '길', '성', ' '] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [50] newchar: 보 xlist ['길', '성', ' ', '보'] (1, 4, 85) 1/1 [==============================] - 0s 13ms/step result: [73] newchar: 전 xlist ['성', ' ', '보', '전'] (1, 4, 85) 1/1 [==============================] - 0s 9ms/step result: [0] newchar: xlist [' ', '보', '전', ' '] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [58] newchar: 세 xlist ['보', '전', ' ', '세'] (1, 4, 85) 1/1 [==============================] - 0s 12ms/step result: [2] newchar: . xlist ['전', ' ', '세', '.'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [3] newchar: 가 xlist [' ', '세', '.', '가'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [44] newchar: 무 xlist ['세', '.', '가', '무'] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [0] newchar: xlist ['.', '가', '무', ' '] (1, 4, 85) 1/1 [==============================] - 0s 8ms/step result: [79] newchar: 하
차례로 결과를 읽어보면 일부 내용이 약간 맞지 않는 부분이 있지만... 그래도 정상적으로 나오는것 같습니다.
sourcecode/keras/04_rnn_char at main · donarts/sourcecode · GitHub