├── README.md ├── configs.py ├── data.py ├── data_in ├── ChatBotData.csv ├── ChatBotData.csv_short └── README.md ├── data_out └── vocabularyData.voc ├── images ├── attention.png └── seq2seq.png ├── main.py ├── model.py └── predict.py /README.md: -------------------------------------------------------------------------------- 1 | # ChatBot(seq2seq) 확장판 2 | NLG를 활용한 ChatBot을 개발함 (V2.0) 3 | 4 | # 수정된 내용 5 | (V1.0) https://github.com/changwookjun/ChatBot_seq2seq 에 대한 수정 내용 적용 6 | ### 1. Attention 적용 7 | ### 2. Teacher Forcing 적용 8 | ### 3. PAD에 대한 mask 기능을 통한 loss 제한 적용 9 | ### 4. Serving 기능 적용 10 | 11 | 12 | # Architecture 13 | ![images](images/seq2seq.png) 14 | 15 | # Attention 16 | ![images](images/attention.png) 17 | 18 | 19 | # Learning Data 20 | Title|Contents|Other 21 | --|--|-- 22 | 데이터 이름|Chatbot data 23 | 데이터 용도|한국어 챗봇 학습을 목적으로 사용한다. 24 | 데이터 권한|MIT 라이센스 25 | 데이터 출처|https://github.com/songys/Chatbot_data (송영숙님) 26 | 27 | # Requirement 28 | Python 3.6.6 29 | tensorflow 1.11 30 | konlpy 31 | pandas 32 | sklearn 33 | 34 | # Project Structure 35 | . 36 | ├── data_in # 데이터가 존재하는 영역 37 | ├── ChatBotData.csv # 전체 데이터 38 | ├── ChatBotData.csv_short # 축소된 데이터 (테스트 용도) 39 | ├── README.md # 데이터 저자 READMD 파일 40 | ├── data_out # 출력 되는 모든 데이터가 모이는 영역 41 | ├── vocabularyData.voc # 사전 파일 42 | ├── check_point # check_point 저장 공간 43 | ├── model # model 저장 공간 44 | ├── configs.py # 모델 설정에 관한 소스 45 | ├── data.py # data 전처리 및 모델에 주입되는 data set 만드는 소스 46 | ├── main.py # 전체적인 프로그램이 시작되는 소스 47 | ├── model.py # 모델이 들어 있는 소스 48 | └── predict.py # 학습된 모델로 실행 해보는 소스 49 | 50 | 51 | # Config 52 | tf.app.flags.DEFINE_integer('batch_size', 64, 'batch size') # 배치 크기 53 | tf.app.flags.DEFINE_integer('train_steps', 10000, 'train steps') # 학습 에포크 54 | tf.app.flags.DEFINE_float('dropout_width', 0.8, 'dropout width') # 드롭아웃 크기 55 | tf.app.flags.DEFINE_integer('layer_size', 1, 'layer size') # 멀티 레이어 크기 (multi rnn) 56 | tf.app.flags.DEFINE_integer('hidden_size', 128, 'weights size') # 가중치 크기 57 | tf.app.flags.DEFINE_float('learning_rate', 1e-3, 'learning rate') # 학습률 58 | tf.app.flags.DEFINE_float('teacher_forcing_rate', 0.7, 'teacher forcing rate') # 학습시 디코더 인풋 정답 지원율 59 | tf.app.flags.DEFINE_string('data_path', './data_in/ChatBotData.csv', 'data path') # 데이터 위치 60 | tf.app.flags.DEFINE_string('vocabulary_path', './data_out/vocabularyData.voc', 'vocabulary path') # 사전 위치 61 | tf.app.flags.DEFINE_string('check_point_path', './data_out/check_point', 'check point path') # 체크 포인트 위치 62 | tf.app.flags.DEFINE_string('save_model_path', './data_out/model', 'save model') # 모델 저장 경로 63 | tf.app.flags.DEFINE_integer('shuffle_seek', 1000, 'shuffle random seek') # 셔플 시드값 64 | tf.app.flags.DEFINE_integer('max_sequence_length', 25, 'max sequence length') # 시퀀스 길이 65 | tf.app.flags.DEFINE_integer('embedding_size', 128, 'embedding size') # 임베딩 크기 66 | tf.app.flags.DEFINE_boolean('embedding', True, 'Use Embedding flag') # 임베딩 유무 설정 67 | tf.app.flags.DEFINE_boolean('multilayer', True, 'Use Multi RNN Cell') # 멀티 RNN 유무 68 | tf.app.flags.DEFINE_boolean('attention', True, 'Use Attention') # 어텐션 사용 유무 69 | tf.app.flags.DEFINE_boolean('teacher_forcing', True, 'Use Teacher Forcing') # 학습시 디코더 인풋 정답 지원 유무 70 | tf.app.flags.DEFINE_boolean('tokenize_as_morph', True, 'set morph tokenize') # 형태소에 따른 토크나이징 사용 유무 71 | tf.app.flags.DEFINE_boolean('serving', False, 'Use Serving') # 서빙 기능 지원 여부 72 | tf.app.flags.DEFINE_boolean('loss_mask', False, 'Use loss mask') # PAD에 대한 마스크를 통한 loss를 제한 73 | 74 | # Usage 75 | python main.py 76 | 77 | # Predict 78 | python predict.py 남자친구가 너무 잘 생겼어 79 | 80 | # Reference 81 | Title|Contents 82 | --|-- 83 | Data|[Chatbot data](https://github.com/songys/Chatbot_data) 84 | Paper|[Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation](https://arxiv.org/pdf/1406.1078.pdf) 85 | Paper|[Neural Machine Translation by Jointly Learning to Align and Translate](https://arxiv.org/abs/1409.0473.pdf) 86 | 87 | # Author 88 | ChangWookJun / @changwookjun (changwookjun@gmail.com) 89 | -------------------------------------------------------------------------------- /configs.py: -------------------------------------------------------------------------------- 1 | #*- coding: utf-8 -*- 2 | import tensorflow as tf 3 | 4 | tf.app.flags.DEFINE_integer('batch_size', 64, 'batch size') # 배치 크기 5 | tf.app.flags.DEFINE_integer('train_steps', 10000, 'train steps') # 학습 에포크 6 | tf.app.flags.DEFINE_float('dropout_width', 0.8, 'dropout width') # 드롭아웃 크기 7 | tf.app.flags.DEFINE_integer('layer_size', 1, 'layer size') # 멀티 레이어 크기 (multi rnn) 8 | tf.app.flags.DEFINE_integer('hidden_size', 128, 'weights size') # 가중치 크기 9 | tf.app.flags.DEFINE_float('learning_rate', 1e-3, 'learning rate') # 학습률 10 | tf.app.flags.DEFINE_float('teacher_forcing_rate', 0.7, 'teacher forcing rate') # 학습시 디코더 인풋 정답 지원율 11 | tf.app.flags.DEFINE_string('data_path', './data_in/ChatBotData.csv', 'data path') # 데이터 위치 12 | tf.app.flags.DEFINE_string('vocabulary_path', './data_out/vocabularyData.voc', 'vocabulary path') # 사전 위치 13 | tf.app.flags.DEFINE_string('check_point_path', './data_out/check_point', 'check point path') # 체크 포인트 위치 14 | tf.app.flags.DEFINE_string('save_model_path', './data_out/model', 'save model') # 모델 저장 경로 15 | tf.app.flags.DEFINE_integer('shuffle_seek', 1000, 'shuffle random seek') # 셔플 시드값 16 | tf.app.flags.DEFINE_integer('max_sequence_length', 25, 'max sequence length') # 시퀀스 길이 17 | tf.app.flags.DEFINE_integer('embedding_size', 128, 'embedding size') # 임베딩 크기 18 | tf.app.flags.DEFINE_boolean('embedding', True, 'Use Embedding flag') # 임베딩 유무 설정 19 | tf.app.flags.DEFINE_boolean('multilayer', True, 'Use Multi RNN Cell') # 멀티 RNN 유무 20 | tf.app.flags.DEFINE_boolean('attention', True, 'Use Attention') # 어텐션 사용 유무 21 | tf.app.flags.DEFINE_boolean('teacher_forcing', True, 'Use Teacher Forcing') # 학습시 디코더 인풋 정답 지원 유무 22 | tf.app.flags.DEFINE_boolean('tokenize_as_morph', False, 'set morph tokenize') # 형태소에 따른 토크나이징 사용 유무 23 | tf.app.flags.DEFINE_boolean('serving', False, 'Use Serving') # 서빙 기능 지원 여부 24 | tf.app.flags.DEFINE_boolean('loss_mask', False, 'Use loss mask') # PAD에 대한 마스크를 통한 loss를 제한 25 | 26 | # Define FLAGS 27 | DEFINES = tf.app.flags.FLAGS 28 | -------------------------------------------------------------------------------- /data.py: -------------------------------------------------------------------------------- 1 | from konlpy.tag import Twitter 2 | import pandas as pd 3 | import tensorflow as tf 4 | import enum 5 | import os 6 | import re 7 | from sklearn.model_selection import train_test_split 8 | import numpy as np 9 | from configs import DEFINES 10 | 11 | from tqdm import tqdm 12 | 13 | PAD_MASK = 0 14 | NON_PAD_MASK = 1 15 | 16 | FILTERS = "([~.,!?\"':;)(])" 17 | PAD = "" 18 | STD = "" 19 | END = "" 20 | UNK = "" 21 | 22 | PAD_INDEX = 0 23 | STD_INDEX = 1 24 | END_INDEX = 2 25 | UNK_INDEX = 3 26 | 27 | MARKER = [PAD, STD, END, UNK] 28 | CHANGE_FILTER = re.compile(FILTERS) 29 | 30 | 31 | def load_data(): 32 | # 판다스를 통해서 데이터를 불러온다. 33 | data_df = pd.read_csv(DEFINES.data_path, header=0) 34 | # 질문과 답변 열을 가져와 question과 answer에 넣는다. 35 | question, answer = list(data_df['Q']), list(data_df['A']) 36 | # skleran에서 지원하는 함수를 통해서 학습 셋과 37 | # 테스트 셋을 나눈다. 38 | train_input, eval_input, train_label, eval_label = train_test_split(question, answer, test_size=0.33, random_state=42) 39 | # 그 값을 리턴한다. 40 | return train_input, train_label, eval_input, eval_label 41 | 42 | 43 | def prepro_like_morphlized(data): 44 | # 형태소 분석 모듈 객체를 45 | # 생성합니다. 46 | 47 | morph_analyzer = Twitter() 48 | # 형태소 토크나이즈 결과 문장을 받을 49 | # 리스트를 생성합니다. 50 | result_data = list() 51 | # 데이터에 있는 매 문장에 대해 토크나이즈를 52 | # 할 수 있도록 반복문을 선언합니다. 53 | for seq in tqdm(data): 54 | # Twitter.morphs 함수를 통해 토크나이즈 된 55 | # 리스트 객체를 받고 다시 공백문자를 기준으로 56 | # 하여 문자열로 재구성 해줍니다. 57 | morphlized_seq = " ".join(morph_analyzer.morphs(seq.replace(' ', ''))) 58 | result_data.append(morphlized_seq) 59 | 60 | return result_data 61 | 62 | 63 | # 인덱스화 할 value와 키가 워드이고 64 | # 값이 인덱스인 딕셔너리를 받는다. 65 | def enc_processing(value, dictionary): 66 | # 인덱스 값들을 가지고 있는 67 | # 배열이다.(누적된다.) 68 | sequences_input_index = [] 69 | # 하나의 인코딩 되는 문장의 70 | # 길이를 가지고 있다.(누적된다.) 71 | sequences_length = [] 72 | # 형태소 토크나이징 사용 유무 73 | if DEFINES.tokenize_as_morph: 74 | value = prepro_like_morphlized(value) 75 | 76 | # 한줄씩 불어온다. 77 | for sequence in value: 78 | # FILTERS = "([~.,!?\"':;)(])" 79 | # 정규화를 사용하여 필터에 들어 있는 80 | # 값들을 "" 으로 치환 한다. 81 | sequence = re.sub(CHANGE_FILTER, "", sequence) 82 | # 하나의 문장을 인코딩 할때 83 | # 가지고 있기 위한 배열이다. 84 | sequence_index = [] 85 | # 문장을 스페이스 단위로 86 | # 자르고 있다. 87 | for word in sequence.split(): 88 | # 잘려진 단어들이 딕셔너리에 존재 하는지 보고 89 | # 그 값을 가져와 sequence_index에 추가한다. 90 | if dictionary.get(word) is not None: 91 | sequence_index.extend([dictionary[word]]) 92 | # 잘려진 단어가 딕셔너리에 존재 하지 않는 93 | # 경우 이므로 UNK(2)를 넣어 준다. 94 | else: 95 | sequence_index.extend([dictionary[UNK]]) 96 | # 문장 제한 길이보다 길어질 경우 뒤에 토큰을 자르고 있다. 97 | if len(sequence_index) > DEFINES.max_sequence_length: 98 | sequence_index = sequence_index[:DEFINES.max_sequence_length] 99 | # 하나의 문장에 길이를 넣어주고 있다. 100 | sequences_length.append(len(sequence_index)) 101 | # max_sequence_length보다 문장 길이가 102 | # 작다면 빈 부분에 PAD(0)를 넣어준다. 103 | sequence_index += (DEFINES.max_sequence_length - len(sequence_index)) * [dictionary[PAD]] 104 | # 뒤로 넣어 준다. 105 | sequence_index.reverse() 106 | # 인덱스화 되어 있는 값을 107 | # sequences_input_index에 넣어 준다. 108 | sequences_input_index.append(sequence_index) 109 | # 인덱스화된 일반 배열을 넘파이 배열로 변경한다. 110 | # 이유는 텐서플로우 dataset에 넣어 주기 위한 111 | # 사전 작업이다. 112 | # 넘파이 배열에 인덱스화된 배열과 113 | # 그 길이를 넘겨준다. 114 | return np.asarray(sequences_input_index), sequences_length 115 | 116 | 117 | # 인덱스화 할 value와 키가 워드 이고 118 | # 값이 인덱스인 딕셔너리를 받는다. 119 | def dec_target_processing(value, dictionary): 120 | # 인덱스 값들을 가지고 있는 121 | # 배열이다.(누적된다) 122 | sequences_target_index = [] 123 | sequences_length = [] 124 | # 형태소 토크나이징 사용 유무 125 | if DEFINES.tokenize_as_morph: 126 | value = prepro_like_morphlized(value) 127 | # 한줄씩 불어온다. 128 | for sequence in value: 129 | # FILTERS = "([~.,!?\"':;)(])" 130 | # 정규화를 사용하여 필터에 들어 있는 131 | # 값들을 "" 으로 치환 한다. 132 | sequence = re.sub(CHANGE_FILTER, "", sequence) 133 | # 문장에서 스페이스 단위별로 단어를 가져와서 134 | # 딕셔너리의 값인 인덱스를 넣어 준다. 135 | # 디코딩 출력의 마지막에 END를 넣어 준다. 136 | sequence_index = [dictionary[word] for word in sequence.split()] 137 | # 문장 제한 길이보다 길어질 경우 뒤에 토큰을 자르고 있다. 138 | # 그리고 END 토큰을 넣어 준다 139 | if len(sequence_index) >= DEFINES.max_sequence_length: 140 | sequence_index = sequence_index[:DEFINES.max_sequence_length-1] + [dictionary[END]] 141 | else: 142 | sequence_index += [dictionary[END]] 143 | 144 | # 학습시 PAD 마스크를 위한 벡터를 구성한다. 145 | sequences_length.append([PAD_MASK if num > len(sequence_index) else NON_PAD_MASK for num in range (DEFINES.max_sequence_length)]) 146 | # max_sequence_length보다 문장 길이가 147 | # 작다면 빈 부분에 PAD(0)를 넣어준다. 148 | sequence_index += (DEFINES.max_sequence_length - len(sequence_index)) * [dictionary[PAD]] 149 | # 인덱스화 되어 있는 값을 150 | # sequences_target_index에 넣어 준다. 151 | sequences_target_index.append(sequence_index) 152 | # 인덱스화된 일반 배열을 넘파이 배열로 변경한다. 153 | # 이유는 텐서플로우 dataset에 넣어 주기 위한 사전 작업이다. 154 | # 넘파이 배열에 인덱스화된 배열과 그 길이를 넘겨준다. 155 | return np.asarray(sequences_target_index), np.asarray(sequences_length) 156 | 157 | 158 | # 인덱스를 스트링으로 변경하는 함수이다. 159 | # 바꾸고자 하는 인덱스 value와 인덱스를 160 | # 키로 가지고 있고 값으로 단어를 가지고 있는 161 | # 딕셔너리를 받는다. 162 | def pred2string(value, dictionary): 163 | # 텍스트 문장을 보관할 배열을 선언한다. 164 | sentence_string = [] 165 | # 인덱스 배열 하나를 꺼내서 v에 넘겨준다. 166 | if DEFINES.serving == True: 167 | for v in value['output']: 168 | sentence_string = [dictionary[index] for index in v] 169 | else: 170 | for v in value: 171 | # 딕셔너리에 있는 단어로 변경해서 배열에 담는다. 172 | sentence_string = [dictionary[index] for index in v['indexs']] 173 | 174 | print(sentence_string) 175 | answer = "" 176 | # 패딩값도 담겨 있으므로 패딩은 모두 스페이스 처리 한다. 177 | for word in sentence_string: 178 | if word not in PAD and word not in END: 179 | answer += word 180 | answer += " " 181 | # 결과를 출력한다. 182 | print(answer) 183 | return answer 184 | 185 | 186 | def rearrange(input, target): 187 | features = {"input": input} 188 | return features, target 189 | 190 | def train_rearrange(input, length, target): 191 | features = {"input": input, "length": length} 192 | return features, target 193 | 194 | # 학습에 들어가 배치 데이터를 만드는 함수이다. 195 | def train_input_fn(train_input_enc, train_target_dec_length, train_target_dec, batch_size): 196 | # Dataset을 생성하는 부분으로써 from_tensor_slices부분은 197 | # 각각 한 문장으로 자른다고 보면 된다. 198 | # train_input_enc, train_target_dec_length, train_target_dec 199 | # 3개를 각각 한문장으로 나눈다. 200 | dataset = tf.data.Dataset.from_tensor_slices((train_input_enc, train_target_dec_length, train_target_dec)) 201 | # 전체 데이터를 썩는다. 202 | dataset = dataset.shuffle(buffer_size=len(train_input_enc)) 203 | # 배치 인자 값이 없다면 에러를 발생 시킨다. 204 | assert batch_size is not None, "train batchSize must not be None" 205 | # from_tensor_slices를 통해 나눈것을 206 | # 배치크기 만큼 묶어 준다. 207 | dataset = dataset.batch(batch_size) 208 | # 데이터 각 요소에 대해서 train_rearrange 함수를 209 | # 통해서 요소를 변환하여 맵으로 구성한다. 210 | dataset = dataset.map(train_rearrange) 211 | # repeat()함수에 원하는 에포크 수를 넣을수 있으면 212 | # 아무 인자도 없다면 무한으로 이터레이터 된다. 213 | dataset = dataset.repeat() 214 | # make_one_shot_iterator를 통해 이터레이터를 215 | # 만들어 준다. 216 | iterator = dataset.make_one_shot_iterator() 217 | # 이터레이터를 통해 다음 항목의 텐서 218 | # 개체를 넘겨준다. 219 | return iterator.get_next() 220 | 221 | 222 | # 평가에 들어가 배치 데이터를 만드는 함수이다. 223 | def eval_input_fn(eval_input_enc, eval_target_dec, batch_size): 224 | # Dataset을 생성하는 부분으로써 from_tensor_slices부분은 225 | # 각각 한 문장으로 자른다고 보면 된다. 226 | # eval_input_enc, eval_target_dec, batch_size 227 | # 3개를 각각 한문장으로 나눈다. 228 | dataset = tf.data.Dataset.from_tensor_slices((eval_input_enc, eval_target_dec)) 229 | # 전체 데이터를 섞는다. 230 | dataset = dataset.shuffle(buffer_size=len(eval_input_enc)) 231 | # 배치 인자 값이 없다면 에러를 발생 시킨다. 232 | assert batch_size is not None, "eval batchSize must not be None" 233 | # from_tensor_slices를 통해 나눈것을 234 | # 배치크기 만큼 묶어 준다. 235 | dataset = dataset.batch(batch_size) 236 | # 데이터 각 요소에 대해서 rearrange 함수를 237 | # 통해서 요소를 변환하여 맵으로 구성한다. 238 | dataset = dataset.map(rearrange) 239 | # repeat()함수에 원하는 에포크 수를 넣을수 있으면 240 | # 아무 인자도 없다면 무한으로 이터레이터 된다. 241 | # 평가이므로 1회만 동작 시킨다. 242 | dataset = dataset.repeat(1) 243 | # make_one_shot_iterator를 통해 244 | # 이터레이터를 만들어 준다. 245 | iterator = dataset.make_one_shot_iterator() 246 | # 이터레이터를 통해 다음 항목의 247 | # 텐서 개체를 넘겨준다. 248 | return iterator.get_next() 249 | 250 | 251 | def data_tokenizer(data): 252 | # 토크나이징 해서 담을 배열 생성 253 | words = [] 254 | for sentence in data: 255 | # FILTERS = "([~.,!?\"':;)(])" 256 | # 위 필터와 같은 값들을 정규화 표현식을 257 | # 통해서 모두 "" 으로 변환 해주는 부분이다. 258 | sentence = re.sub(CHANGE_FILTER, "", sentence) 259 | for word in sentence.split(): 260 | words.append(word) 261 | # 토그나이징과 정규표현식을 통해 만들어진 262 | # 값들을 넘겨 준다. 263 | return [word for word in words if word] 264 | 265 | 266 | def load_vocabulary(): 267 | # 사전을 담을 배열 준비한다. 268 | vocabulary_list = [] 269 | # 사전을 구성한 후 파일로 저장 진행한다. 270 | # 그 파일의 존재 유무를 확인한다. 271 | if (not (os.path.exists(DEFINES.vocabulary_path))): 272 | # 이미 생성된 사전 파일이 존재하지 않으므로 273 | # 데이터를 가지고 만들어야 한다. 274 | # 그래서 데이터가 존재 하면 사전을 만들기 위해서 275 | # 데이터 파일의 존재 유무를 확인한다. 276 | if (os.path.exists(DEFINES.data_path)): 277 | # 데이터가 존재하니 판단스를 통해서 278 | # 데이터를 불러오자 279 | data_df = pd.read_csv(DEFINES.data_path, encoding='utf-8') 280 | # 판다스의 데이터 프레임을 통해서 281 | # 질문과 답에 대한 열을 가져 온다. 282 | question, answer = list(data_df['Q']), list(data_df['A']) 283 | if DEFINES.tokenize_as_morph: # 형태소에 따른 토크나이져 처리 284 | question = prepro_like_morphlized(question) 285 | answer = prepro_like_morphlized(answer) 286 | data = [] 287 | # 질문과 답변을 extend을 288 | # 통해서 구조가 없는 배열로 만든다. 289 | data.extend(question) 290 | data.extend(answer) 291 | # 토큰나이져 처리 하는 부분이다. 292 | words = data_tokenizer(data) 293 | # 공통적인 단어에 대해서는 모두 294 | # 필요 없으므로 한개로 만들어 주기 위해서 295 | # set해주고 이것들을 리스트로 만들어 준다. 296 | words = list(set(words)) 297 | # 데이터 없는 내용중에 MARKER를 사전에 298 | # 추가 하기 위해서 아래와 같이 처리 한다. 299 | # 아래는 MARKER 값이며 리스트의 첫번째 부터 300 | # 순서대로 넣기 위해서 인덱스 0에 추가한다. 301 | # PAD = "" 302 | # STD = "" 303 | # END = "" 304 | # UNK = "" 305 | words[:0] = MARKER 306 | # 사전을 리스트로 만들었으니 이 내용을 307 | # 사전 파일을 만들어 넣는다. 308 | with open(DEFINES.vocabulary_path, 'w') as vocabulary_file: 309 | for word in words: 310 | vocabulary_file.write(word + '\n') 311 | 312 | # 사전 파일이 존재하면 여기에서 313 | # 그 파일을 불러서 배열에 넣어 준다. 314 | with open(DEFINES.vocabulary_path, 'r', encoding='utf-8') as vocabulary_file: 315 | for line in vocabulary_file: 316 | vocabulary_list.append(line.strip()) 317 | 318 | # 배열에 내용을 키와 값이 있는 319 | # 딕셔너리 구조로 만든다. 320 | char2idx, idx2char = make_vocabulary(vocabulary_list) 321 | # 두가지 형태의 키와 값이 있는 형태를 리턴한다. 322 | # (예) 단어: 인덱스 , 인덱스: 단어) 323 | return char2idx, idx2char, len(char2idx) 324 | 325 | 326 | def make_vocabulary(vocabulary_list): 327 | # 리스트를 키가 단어이고 값이 인덱스인 328 | # 딕셔너리를 만든다. 329 | char2idx = {char: idx for idx, char in enumerate(vocabulary_list)} 330 | # 리스트를 키가 인덱스이고 값이 단어인 331 | # 딕셔너리를 만든다. 332 | idx2char = {idx: char for idx, char in enumerate(vocabulary_list)} 333 | # 두개의 딕셔너리를 넘겨 준다. 334 | return char2idx, idx2char 335 | 336 | 337 | def main(self): 338 | char2idx, idx2char, vocabulary_length = load_vocabulary() 339 | 340 | 341 | 342 | if __name__ == '__main__': 343 | tf.logging.set_verbosity(tf.logging.INFO) 344 | tf.app.run(main) 345 | -------------------------------------------------------------------------------- /data_in/ChatBotData.csv_short: -------------------------------------------------------------------------------- 1 | Q,A,label 2 | 가끔 궁금해,그 사람도 그럴 거예요.,0 3 | 가끔 뭐하는지 궁금해,그 사람도 그럴 거예요.,0 4 | 가끔은 혼자인게 좋다,혼자를 즐기세요.,0 5 | 가난한 자의 설움,돈은 다시 들어올 거예요.,0 6 | 가만 있어도 땀난다,땀을 식혀주세요.,0 7 | 가상화폐 쫄딱 망함,어서 잊고 새출발 하세요.,0 8 | 가스불 켜고 나갔어,빨리 집에 돌아가서 끄고 나오세요.,0 9 | 가스불 켜놓고 나온거 같아,빨리 집에 돌아가서 끄고 나오세요.,0 10 | 가스비 너무 많이 나왔다.,다음 달에는 더 절약해봐요.,0 11 | 가스비 비싼데 감기 걸리겠어,따뜻하게 사세요!,0 12 | 남자친구 교회 데려가고 싶어,마음을 열 때까지 설득해보세요.,0 13 | 남자친구 또 운동 갔어,운동을 함께 해보세요.,0 14 | 남자친구 생일인데 뭘 줄까,평소에 필요한 것 생각해보세요.,0 15 | 남자친구 승진 선물로 뭐가 좋을까?,평소에 필요했던 게 좋을 것 같아요.,0 16 | 남자친구 오늘 따라 훈훈해 보인다,전생에 나라를 구하셨나요.,0 17 | 남자친구 오늘 좀 질린다.,결단은 빠를수록 좋아요.,0 18 | 남자친구가 나 안 믿어줘,거짓말 적당히 하세요.,0 19 | 남자친구가 너무 바빠,너무 집착하지 마세요.,0 20 | 남자친구가 너무 운동만 해,운동을 함께 해보세요.,0 21 | 남자친구가 너무 잘생겼어,전생에 나라를 구하셨나요.,0 22 | -------------------------------------------------------------------------------- /data_in/README.md: -------------------------------------------------------------------------------- 1 | # Chatbot_data. 2 | Chatbot_data_for_Korean v1.0 3 | 4 | 5 | ## Data description. 6 | 7 | 1. 챗봇 트레이닝용 문답 페어 11,876개 8 | 2. 일상다반서 0, 이별(부정) 1, 사랑(긍정) 2로 레이블링 9 | 10 | 11 | ## Quick peek. 12 | 13 | ![quick_peek](./data.png) 14 | 15 | 16 | -------------------------------------------------------------------------------- /images/attention.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changwookjun/ChatBot_seq2seq_extend/4f1cd14f7ce45ec404c6c259def7576664b0f3c0/images/attention.png -------------------------------------------------------------------------------- /images/seq2seq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changwookjun/ChatBot_seq2seq_extend/4f1cd14f7ce45ec404c6c259def7576664b0f3c0/images/seq2seq.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import model as ml 3 | import data 4 | import numpy as np 5 | import os 6 | import sys 7 | 8 | from configs import DEFINES 9 | 10 | DATA_OUT_PATH = './data_out/' 11 | 12 | # Serving 기능을 위하여 serving 함수를 구성한다. 13 | def serving_input_receiver_fn(): 14 | receiver_tensor = { 15 | 'input': tf.placeholder(dtype=tf.int32, shape=[None, DEFINES.max_sequence_length]), 16 | 'output': tf.placeholder(dtype=tf.int32, shape=[None, DEFINES.max_sequence_length]) 17 | } 18 | features = { 19 | key: tensor for key, tensor in receiver_tensor.items() 20 | } 21 | return tf.estimator.export.ServingInputReceiver(features, receiver_tensor) 22 | 23 | 24 | def main(self): 25 | data_out_path = os.path.join(os.getcwd(), DATA_OUT_PATH) 26 | os.makedirs(data_out_path, exist_ok=True) 27 | # 데이터를 통한 사전 구성 한다. 28 | char2idx, idx2char, vocabulary_length = data.load_vocabulary() 29 | # 훈련 데이터와 테스트 데이터를 가져온다. 30 | train_input, train_label, eval_input, eval_label = data.load_data() 31 | 32 | # 훈련셋 인코딩 만드는 부분이다. 33 | train_input_enc, train_input_enc_length = data.enc_processing(train_input, char2idx) 34 | # 훈련셋 디코딩 출력 부분 만드는 부분이다. 35 | train_target_dec, train_target_dec_length = data.dec_target_processing(train_label, char2idx) 36 | 37 | # 평가셋 인코딩 만드는 부분이다. 38 | eval_input_enc, eval_input_enc_length = data.enc_processing(eval_input,char2idx) 39 | # 평가셋 디코딩 출력 부분 만드는 부분이다. 40 | eval_target_dec, _ = data.dec_target_processing(eval_label, char2idx) 41 | 42 | # 현재 경로'./'에 현재 경로 하부에 43 | # 체크 포인트를 저장한 디렉토리를 설정한다. 44 | check_point_path = os.path.join(os.getcwd(), DEFINES.check_point_path) 45 | save_model_path = os.path.join(os.getcwd(), DEFINES.save_model_path) 46 | # 디렉토리를 만드는 함수이며 두번째 인자 exist_ok가 47 | # True이면 디렉토리가 이미 존재해도 OSError가 48 | # 발생하지 않는다. 49 | # exist_ok가 False이면 이미 존재하면 50 | # OSError가 발생한다. 51 | os.makedirs(check_point_path, exist_ok=True) 52 | os.makedirs(save_model_path, exist_ok=True) 53 | 54 | # 에스티메이터 구성한다. 55 | classifier = tf.estimator.Estimator( 56 | model_fn=ml.Model, # 모델 등록한다. 57 | model_dir=DEFINES.check_point_path, # 체크포인트 위치 등록한다. 58 | params={ # 모델 쪽으로 파라메터 전달한다. 59 | 'hidden_size': DEFINES.hidden_size, # 가중치 크기 설정한다. 60 | 'layer_size': DEFINES.layer_size, # 멀티 레이어 층 개수를 설정한다. 61 | 'learning_rate': DEFINES.learning_rate, # 학습율 설정한다. 62 | 'teacher_forcing_rate': DEFINES.teacher_forcing_rate, # 학습시 디코더 인풋 정답 지원율 설정 63 | 'vocabulary_length': vocabulary_length, # 딕셔너리 크기를 설정한다. 64 | 'embedding_size': DEFINES.embedding_size, # 임베딩 크기를 설정한다. 65 | 'embedding': DEFINES.embedding, # 임베딩 사용 유무를 설정한다. 66 | 'multilayer': DEFINES.multilayer, # 멀티 레이어 사용 유무를 설정한다. 67 | 'attention': DEFINES.attention, # 어텐션 지원 유무를 설정한다. 68 | 'teacher_forcing': DEFINES.teacher_forcing, # 학습시 디코더 인풋 정답 지원 유무 설정한다. 69 | 'loss_mask': DEFINES.loss_mask, # PAD에 대한 마스크를 통한 loss를 제한 한다. 70 | 'serving': DEFINES.serving # 모델 저장 및 serving 유무를 설정한다. 71 | }) 72 | 73 | # 학습 실행 74 | classifier.train(input_fn=lambda: data.train_input_fn( 75 | train_input_enc, train_target_dec_length, train_target_dec, DEFINES.batch_size), steps=DEFINES.train_steps) 76 | # 서빙 기능 유무에 따라 모델을 Save 한다. 77 | if DEFINES.serving == True: 78 | save_model_path = classifier.export_savedmodel( 79 | export_dir_base=DEFINES.save_model_path, 80 | serving_input_receiver_fn=serving_input_receiver_fn) 81 | 82 | # 평가 실행 83 | eval_result = classifier.evaluate(input_fn=lambda: data.eval_input_fn( 84 | eval_input_enc,eval_target_dec, DEFINES.batch_size)) 85 | print('\nEVAL set accuracy: {accuracy:0.3f}\n'.format(**eval_result)) 86 | 87 | if __name__ == '__main__': 88 | tf.logging.set_verbosity(tf.logging.INFO) 89 | tf.app.run(main) 90 | 91 | tf.logging.set_verbosity 92 | -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import tensorflow as tf 3 | import sys 4 | import numpy as np 5 | from configs import DEFINES 6 | 7 | 8 | def make_lstm_cell(mode, hiddenSize, index): 9 | cell = tf.nn.rnn_cell.BasicLSTMCell(hiddenSize, name="lstm" + str(index), state_is_tuple=False) 10 | if mode == tf.estimator.ModeKeys.TRAIN: 11 | cell = tf.contrib.rnn.DropoutWrapper(cell, output_keep_prob=DEFINES.dropout_width) 12 | return cell 13 | 14 | # 에스티메이터 모델 부분이다. 15 | # freatures : tf.data.Dataset.map을 통해서 만들어진 16 | # features = {"input": input, "length": length} 17 | # labels : tf.data.Dataset.map을 통해서 만들어진 target 18 | # mode는 에스티메이터 함수를 호출하면 에스티메이터 19 | # 프레임워크 모드의 값이 해당 부분이다. 20 | # params : 에스티메이터를 구성할때 params 값들이다. 21 | # (params={ # 모델 쪽으로 파라메터 전달한다.) 22 | def Model(features, labels, mode, params): 23 | TRAIN = mode == tf.estimator.ModeKeys.TRAIN 24 | EVAL = mode == tf.estimator.ModeKeys.EVAL 25 | PREDICT = mode == tf.estimator.ModeKeys.PREDICT 26 | 27 | # 미리 정의된 임베딩 사용 유무를 확인 한다. 28 | # 값이 True이면 임베딩을 해서 학습하고 False이면 29 | # onehotencoding 처리 한다. 30 | if params['embedding'] == True: 31 | # 가중치 행렬에 대한 초기화 함수이다. 32 | # xavier (Xavier Glorot와 Yoshua Bengio (2010) 33 | # URL : http://proceedings.mlr.press/v9/glorot10a/glorot10a.pdf 34 | initializer = tf.contrib.layers.xavier_initializer() 35 | # 인코딩 변수를 선언하고 값을 설정한다. 36 | embedding_encoder = tf.get_variable(name="embedding_encoder", # 이름 37 | shape=[params['vocabulary_length'], params['embedding_size']], # 모양 38 | dtype=tf.float32, # 타입 39 | initializer=initializer, # 초기화 값 40 | trainable=True) # 학습 유무 41 | else: 42 | # tf.eye를 통해서 사전의 크기 만큼의 단위행렬 43 | # 구조를 만든다. 44 | embedding_encoder = tf.eye(num_rows=params['vocabulary_length'], dtype=tf.float32) 45 | # 인코딩 변수를 선언하고 값을 설정한다. 46 | embedding_encoder = tf.get_variable(name="embedding_encoder", # 이름 47 | initializer=embedding_encoder, # 초기화 값 48 | trainable=False) # 학습 유무 49 | 50 | # embedding_lookup을 통해서 features['input']의 인덱스를 51 | # 위에서 만든 embedding_encoder의 인덱스의 값으로 변경하여 52 | # 임베딩된 디코딩 배치를 만든다. 53 | embedding_encoder_batch = tf.nn.embedding_lookup(params=embedding_encoder, ids=features['input']) 54 | 55 | # 미리 정의된 임베딩 사용 유무를 확인 한다. 56 | # 값이 True이면 임베딩을 해서 학습하고 False이면 57 | # onehotencoding 처리 한다. 58 | if params['embedding'] == True: 59 | # 가중치 행렬에 대한 초기화 함수이다. 60 | # xavier (Xavier Glorot와 Yoshua Bengio (2010) 61 | # URL : http://proceedings.mlr.press/v9/glorot10a/glorot10a.pdf 62 | initializer = tf.contrib.layers.xavier_initializer() 63 | # 디코딩 변수를 선언하고 값을 설정한다. 64 | embedding_decoder = tf.get_variable(name="embedding_decoder", # 이름 65 | shape=[params['vocabulary_length'], params['embedding_size']], # 모양 66 | dtype=tf.float32, # 타입 67 | initializer=initializer, # 초기화 값 68 | trainable=True) # 학습 유무 69 | else: 70 | # tf.eye를 통해서 사전의 크기 만큼의 단위행렬 71 | # 구조를 만든다. 72 | embedding_decoder = tf.eye(num_rows=params['vocabulary_length'], dtype=tf.float32) 73 | # 인코딩 변수를 선언하고 값을 설정한다. 74 | embedding_decoder = tf.get_variable(name='embedding_decoder', # 이름 75 | initializer=embedding_decoder, # 초기화 값 76 | trainable=False) # 학습 유무 77 | 78 | # 변수 재사용을 위해서 reuse=.AUTO_REUSE를 사용하며 범위를 79 | # 정해주고 사용하기 위해 scope설정을 한다. 80 | # make_lstm_cell이 "cell"반복적으로 호출 되면서 재사용된다. 81 | with tf.variable_scope('encoder_scope', reuse=tf.AUTO_REUSE): 82 | # 값이 True이면 멀티레이어로 모델을 구성하고 False이면 83 | # 단일레이어로 모델을 구성 한다. 84 | if params['multilayer'] == True: 85 | # layerSize 만큼 LSTMCell을 encoder_cell_list에 담는다. 86 | encoder_cell_list = [make_lstm_cell(mode, params['hidden_size'], i) for i in range(params['layer_size'])] 87 | # MUltiLayer RNN CEll에 encoder_cell_list를 넣어 멀티 레이어를 만든다. 88 | rnn_cell = tf.contrib.rnn.MultiRNNCell(encoder_cell_list, state_is_tuple=False) 89 | else: 90 | # 단층 LSTMLCell을 만든다. 91 | rnn_cell = make_lstm_cell(mode, params['hidden_size'], "") 92 | # rnn_cell에 의해 지정된 반복적인 신경망을 만든다. 93 | # encoder_outputs(RNN 출력 Tensor)[batch_size, 94 | # max_time, cell.output_size] 95 | # encoder_states 최종 상태 [batch_size, cell.state_size] 96 | encoder_outputs, encoder_states = tf.nn.dynamic_rnn(cell=rnn_cell, # RNN 셀 97 | inputs=embedding_encoder_batch, # 입력 값 98 | dtype=tf.float32) # 타입 99 | # 변수 재사용을 위해서 reuse=.AUTO_REUSE를 사용하며 범위를 정해주고 100 | # 사용하기 위해 scope설정을 한다. 101 | # make_lstm_cell이 "cell"반복적으로 호출 되면서 재사용된다. 102 | with tf.variable_scope('decoder_scope', reuse=tf.AUTO_REUSE): 103 | # 값이 True이면 멀티레이어로 모델을 구성하고 False이면 단일레이어로 104 | # 모델을 구성 한다. 105 | if params['multilayer'] == True: 106 | # layer_size 만큼 LSTMCell을 decoder_cell_list에 담는다. 107 | decoder_cell_list = [make_lstm_cell(mode, params['hidden_size'], i) for i in range(params['layer_size'])] 108 | # MUltiLayer RNN CEll에 decoder_cell_list를 넣어 멀티 레이어를 만든다. 109 | rnn_cell = tf.contrib.rnn.MultiRNNCell(decoder_cell_list, state_is_tuple=False) 110 | else: 111 | # 단층 LSTMLCell을 만든다. 112 | rnn_cell = make_lstm_cell(mode, params['hidden_size'], "") 113 | 114 | decoder_state = encoder_states 115 | # 매 타임 스텝에 나오는 아웃풋을 저장하는 리스트 두개를 만든다. 116 | # 하나는 토큰 인덱스는 predict_tokens 저장 117 | # 다른 하나는 temp_logits에 logits 저장한다. 118 | predict_tokens = list() 119 | temp_logits = list() 120 | 121 | # 평가인 경우에는 teacher forcing이 되지 않도록 해야한다. 122 | # 따라서 학습이 아닌경우에 is_train을 False로 하여 teacher forcing이 되지 않도록 한다. 123 | output_token = tf.ones(shape=(tf.shape(encoder_outputs)[0],), dtype=tf.int32) * 1 124 | # 전체 문장 길이 만큼 타임 스텝을 돌도록 한다. 125 | for i in range(DEFINES.max_sequence_length): 126 | # 두 번쨰 스텝 이후에는 teacher forcing을 적용하는지 확률에 따라 결정하도록 한다. 127 | # teacher forcing rate은 teacher forcing을 어느정도 줄 것인지를 조절한다. 128 | if TRAIN: 129 | if i > 0: 130 | # tf.cond를 통해 rnn에 입력할 입력 임베딩 벡터를 결정한다 여기서 true인 경우엔 입력된 output값 아닌경우에는 이전 스텝에 131 | # 나온 output을 사용한다. 132 | input_token_emb = tf.cond( 133 | tf.logical_and( # 논리 and 연산자 134 | True, 135 | tf.random_uniform(shape=(), maxval=1) <= params['teacher_forcing_rate'] # 률에 따른 labels값 지원 유무 136 | ), 137 | lambda: tf.nn.embedding_lookup(embedding_encoder, labels[:, i-1]), # labels 정답을 넣어주고 있다. 138 | lambda: tf.nn.embedding_lookup(embedding_encoder, output_token) # 모델이 정답이라고 생각 하는 값 139 | ) 140 | else: 141 | input_token_emb = tf.nn.embedding_lookup(embedding_encoder, output_token) # 모델이 정답이라고 생각 하는 값 142 | else: # 평가 및 예측은 여기를 진행해야 한다. 143 | input_token_emb = tf.nn.embedding_lookup(embedding_encoder, output_token) 144 | 145 | # 어텐션 적용 부분 146 | if params['attention'] == True: 147 | W1 = tf.keras.layers.Dense(params['hidden_size']) 148 | W2 = tf.keras.layers.Dense(params['hidden_size']) 149 | V = tf.keras.layers.Dense(1) 150 | # (?, 256) -> (?, 128) 151 | hidden_with_time_axis = W2(decoder_state) 152 | # (?, 128) -> (?, 1, 128) 153 | hidden_with_time_axis = tf.expand_dims(hidden_with_time_axis, axis=1) 154 | # (?, 1, 128) -> (?, 25, 128) 155 | hidden_with_time_axis = tf.manip.tile(hidden_with_time_axis, [1, DEFINES.max_sequence_length, 1]) 156 | # (?, 25, 1) 157 | score = V(tf.nn.tanh(W1(encoder_outputs) + hidden_with_time_axis)) 158 | # score = V(tf.nn.tanh(W1(encoderOutputs) + tf.manip.tile(tf.expand_dims(W2(decoder_state), axis=1), [1, DEFINES.maxSequenceLength, 1]))) 159 | # (?, 25, 1) 160 | attention_weights = tf.nn.softmax(score, axis=-1) 161 | # (?, 25, 128) 162 | context_vector = attention_weights * encoder_outputs 163 | # (?, 25, 128) -> (?, 128) 164 | context_vector = tf.reduce_sum(context_vector, axis=1) 165 | # (?, 256) 166 | input_token_emb = tf.concat([context_vector, input_token_emb], axis=-1) 167 | 168 | # RNNCell을 호출하여 RNN 스텝 연산을 진행하도록 한다. 169 | input_token_emb = tf.keras.layers.Dropout(0.5)(input_token_emb) 170 | decoder_outputs, decoder_state = rnn_cell(input_token_emb, decoder_state) 171 | decoder_outputs = tf.keras.layers.Dropout(0.5)(decoder_outputs) 172 | # feedforward를 거쳐 output에 대한 logit값을 구한다. 173 | output_logits = tf.layers.dense(decoder_outputs, params['vocabulary_length'], activation=None) 174 | 175 | # softmax를 통해 단어에 대한 예측 probability를 구한다. 176 | output_probs = tf.nn.softmax(output_logits) 177 | output_token = tf.argmax(output_probs, axis=-1) 178 | 179 | # 한 스텝에 나온 토큰과 logit 결과를 저장해둔다. 180 | predict_tokens.append(output_token) 181 | temp_logits.append(output_logits) 182 | 183 | # 저장했던 토큰과 logit 리스트를 stack을 통해 메트릭스로 만들어 준다. 184 | # 만들게 뙤면 차원이 [시퀀스 X 배치 X 단어 feature 수] 이렇게 되는데 185 | # 이를 transpose하여 [배치 X 시퀀스 X 단어 feature 수] 로 맞춰준다. 186 | predict = tf.transpose(tf.stack(predict_tokens, axis=0), [1, 0]) 187 | logits = tf.transpose(tf.stack(temp_logits, axis=0), [1, 0, 2]) 188 | 189 | print(predict.shape) 190 | print(logits.shape) 191 | 192 | if PREDICT: 193 | if params['serving'] == True: 194 | export_outputs = { 195 | 'indexs': tf.estimator.export.PredictOutput(predict) # 서빙 결과값을 준다. 196 | } 197 | 198 | predictions = { # 예측 값들이 여기에 딕셔너리 형태로 담긴다. 199 | 'indexs': predict, # 시퀀스 마다 예측한 값 200 | 'logits': logits, # 마지막 결과 값 201 | } 202 | # 에스티메이터에서 리턴하는 값은 tf.estimator.EstimatorSpec 203 | # 객체를 리턴 한다. 204 | # mode : 에스티메이터가 수행하는 mode (tf.estimator.ModeKeys.PREDICT) 205 | # predictions : 예측 값 206 | if params['serving'] == True: 207 | return tf.estimator.EstimatorSpec(mode, predictions=predictions, export_outputs=export_outputs) 208 | 209 | return tf.estimator.EstimatorSpec(mode, predictions=predictions) 210 | 211 | # 마지막 결과 값과 정답 값을 비교하는 212 | # tf.nn.sparse_softmax_cross_entropy_with_logits(로스함수)를 213 | # 통과 시켜 틀린 만큼의 214 | # 에러 값을 가져 오고 이것들은 차원 축소를 통해 단일 텐서 값을 반환 한다. 215 | # pad의 loss값을 무력화 시킨다. pad가 아닌값은 1 pad인 값은 0을 주어 동작 216 | # 하도록 한다. 217 | if TRAIN and params['loss_mask'] == True: 218 | loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels) 219 | masks = features['length'] 220 | 221 | loss = loss * tf.cast(masks, tf.float32) 222 | loss = tf.reduce_mean(loss) 223 | else: 224 | loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels)) 225 | # 라벨과 결과가 일치하는지 빈도 계산을 통해 226 | # 정확도를 측정하는 방법이다. 227 | accuracy = tf.metrics.accuracy(labels=labels, predictions=predict, name='accOp') 228 | 229 | # 정확도를 전체값으로 나눈 값이다. 230 | metrics = {'accuracy': accuracy} 231 | tf.summary.scalar('accuracy', accuracy[1]) 232 | 233 | # 평가 mode 확인 부분이며 평가는 여기 까지 234 | # 수행하고 리턴한다. 235 | if EVAL: 236 | # 에스티메이터에서 리턴하는 값은 237 | # tf.estimator.EstimatorSpec 객체를 리턴 한다. 238 | # mode : 에스티메이터가 수행하는 mode (tf.estimator.ModeKeys.EVAL) 239 | # loss : 에러 값 240 | # eval_metric_ops : 정확도 값 241 | return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics) 242 | 243 | # 파이썬 assert구문으로 거짓일 경우 프로그램이 종료 된다. 244 | # 수행 mode(tf.estimator.ModeKeys.TRAIN)가 245 | # 아닌 경우는 여기 까지 오면 안되도록 방어적 코드를 넣은것이다. 246 | assert TRAIN 247 | 248 | # 아담 옵티마이저를 사용한다. 249 | optimizer = tf.train.AdamOptimizer(learning_rate=DEFINES.learning_rate) 250 | # 에러값을 옵티마이저를 사용해서 최소화 시킨다. 251 | train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step()) 252 | # 에스티메이터에서 리턴하는 값은 tf.estimator.EstimatorSpec 객체를 리턴 한다. 253 | # mode : 에스티메이터가 수행하는 mode (tf.estimator.ModeKeys.EVAL) 254 | # loss : 에러 값 255 | # train_op : 그라디언트 반환 256 | return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op) 257 | -------------------------------------------------------------------------------- /predict.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import data 3 | import sys 4 | import model as ml 5 | 6 | from configs import DEFINES 7 | 8 | if __name__ == '__main__': 9 | tf.logging.set_verbosity(tf.logging.INFO) 10 | arg_length = len(sys.argv) 11 | 12 | if(arg_length < 2): 13 | raise Exception("Don't call us. We'll call you") 14 | 15 | 16 | # 데이터를 통한 사전 구성 한다. 17 | char2idx, idx2char, vocabulary_length = data.load_vocabulary() 18 | 19 | # 테스트용 데이터 만드는 부분이다. 20 | # 인코딩 부분 만든다. 21 | input = "" 22 | for i in sys.argv[1:]: 23 | input += i 24 | input += " " 25 | 26 | print(input) 27 | predic_input_enc, predic_input_enc_length = data.enc_processing([input], char2idx) 28 | # 학습 과정이 아니므로 디코딩 입력은 29 | # 존재하지 않는다.(구조를 맞추기 위해 넣는다.) 30 | # 학습 과정이 아니므로 디코딩 출력 부분도 31 | # 존재하지 않는다.(구조를 맞추기 위해 넣는다.) 32 | predic_target_dec, _ = data.dec_target_processing([""], char2idx) 33 | 34 | if DEFINES.serving == True: 35 | # 모델이 저장된 위치를 넣어 준다. export_dir 36 | predictor_fn = tf.contrib.predictor.from_saved_model( 37 | export_dir="/home/evo_mind/DeepLearning/NLP/Work/ChatBot2_Final/data_out/model/1541575161" 38 | ) 39 | else: 40 | # 에스티메이터 구성한다. 41 | classifier = tf.estimator.Estimator( 42 | model_fn=ml.Model, # 모델 등록한다. 43 | model_dir=DEFINES.check_point_path, # 체크포인트 위치 등록한다. 44 | params={ # 모델 쪽으로 파라메터 전달한다. 45 | 'hidden_size': DEFINES.hidden_size, # 가중치 크기 설정한다. 46 | 'layer_size': DEFINES.layer_size, # 멀티 레이어 층 개수를 설정한다. 47 | 'learning_rate': DEFINES.learning_rate, # 학습율 설정한다. 48 | 'teacher_forcing_rate': DEFINES.teacher_forcing_rate, # 학습시 디코더 인풋 정답 지원율 설정 49 | 'vocabulary_length': vocabulary_length, # 딕셔너리 크기를 설정한다. 50 | 'embedding_size': DEFINES.embedding_size, # 임베딩 크기를 설정한다. 51 | 'embedding': DEFINES.embedding, # 임베딩 사용 유무를 설정한다. 52 | 'multilayer': DEFINES.multilayer, # 멀티 레이어 사용 유무를 설정한다. 53 | 'attention': DEFINES.attention, # 어텐션 지원 유무를 설정한다. 54 | 'teacher_forcing': DEFINES.teacher_forcing, # 학습시 디코더 인풋 정답 지원 유무 설정한다. 55 | 'loss_mask': DEFINES.loss_mask, # PAD에 대한 마스크를 통한 loss를 제한 한다. 56 | 'serving': DEFINES.serving # 모델 저장 및 serving 유무를 설정한다. 57 | }) 58 | 59 | if DEFINES.serving == True: 60 | predictions = predictor_fn({'input':predic_input_enc, 'output':predic_target_dec}) 61 | data.pred2string(predictions, idx2char) 62 | else: 63 | # 예측을 하는 부분이다. 64 | predictions = classifier.predict( 65 | input_fn=lambda:data.eval_input_fn(predic_input_enc, predic_target_dec, DEFINES.batch_size)) 66 | # 예측한 값을 인지 할 수 있도록 67 | # 텍스트로 변경하는 부분이다. 68 | data.pred2string(predictions, idx2char) 69 | --------------------------------------------------------------------------------