forked from NLP-kr/tensorflow-ml-nlp-tf2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
preprocess.py
259 lines (230 loc) · 10.9 KB
/
preprocess.py
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
import os
import re
import json
import numpy as np
import pandas as pd
from tqdm import tqdm
from konlpy.tag import Okt
FILTERS = "([~.,!?\"':;)(])"
PAD = "<PAD>"
STD = "<SOS>"
END = "<END>"
UNK = "<UNK>"
PAD_INDEX = 0
STD_INDEX = 1
END_INDEX = 2
UNK_INDEX = 3
MARKER = [PAD, STD, END, UNK]
CHANGE_FILTER = re.compile(FILTERS)
MAX_SEQUENCE = 25
def load_data(path):
# 판다스를 통해서 데이터를 불러온다.
data_df = pd.read_csv(path, header=0)
# 질문과 답변 열을 가져와 question과 answer에 넣는다.
question, answer = list(data_df['Q']), list(data_df['A'])
return question, answer
def data_tokenizer(data):
# 토크나이징 해서 담을 배열 생성
words = []
for sentence in data:
# FILTERS = "([~.,!?\"':;)(])"
# 위 필터와 같은 값들을 정규화 표현식을
# 통해서 모두 "" 으로 변환 해주는 부분이다.
sentence = re.sub(CHANGE_FILTER, "", sentence)
for word in sentence.split():
words.append(word)
# 토그나이징과 정규표현식을 통해 만들어진
# 값들을 넘겨 준다.
return [word for word in words if word]
def prepro_like_morphlized(data):
morph_analyzer = Okt()
result_data = list()
for seq in tqdm(data):
morphlized_seq = " ".join(morph_analyzer.morphs(seq.replace(' ', '')))
result_data.append(morphlized_seq)
return result_data
def load_vocabulary(path, vocab_path, tokenize_as_morph=False):
# 사전을 담을 배열 준비한다.
vocabulary_list = []
# 사전을 구성한 후 파일로 저장 진행한다.
# 그 파일의 존재 유무를 확인한다.
if not os.path.exists(vocab_path):
# 이미 생성된 사전 파일이 존재하지 않으므로
# 데이터를 가지고 만들어야 한다.
# 그래서 데이터가 존재 하면 사전을 만들기 위해서
# 데이터 파일의 존재 유무를 확인한다.
if (os.path.exists(path)):
# 데이터가 존재하니 판단스를 통해서
# 데이터를 불러오자
data_df = pd.read_csv(path, encoding='utf-8')
# 판다스의 데이터 프레임을 통해서
# 질문과 답에 대한 열을 가져 온다.
question, answer = list(data_df['Q']), list(data_df['A'])
if tokenize_as_morph: # 형태소에 따른 토크나이져 처리
question = prepro_like_morphlized(question)
answer = prepro_like_morphlized(answer)
data = []
# 질문과 답변을 extend을
# 통해서 구조가 없는 배열로 만든다.
data.extend(question)
data.extend(answer)
# 토큰나이져 처리 하는 부분이다.
words = data_tokenizer(data)
# 공통적인 단어에 대해서는 모두
# 필요 없으므로 한개로 만들어 주기 위해서
# set해주고 이것들을 리스트로 만들어 준다.
words = list(set(words))
# 데이터 없는 내용중에 MARKER를 사전에
# 추가 하기 위해서 아래와 같이 처리 한다.
# 아래는 MARKER 값이며 리스트의 첫번째 부터
# 순서대로 넣기 위해서 인덱스 0에 추가한다.
# PAD = "<PADDING>"
# STD = "<START>"
# END = "<END>"
# UNK = "<UNKNWON>"
words[:0] = MARKER
# 사전을 리스트로 만들었으니 이 내용을
# 사전 파일을 만들어 넣는다.
with open(vocab_path, 'w', encoding='utf-8') as vocabulary_file:
for word in words:
vocabulary_file.write(word + '\n')
# 사전 파일이 존재하면 여기에서
# 그 파일을 불러서 배열에 넣어 준다.
with open(vocab_path, 'r', encoding='utf-8') as vocabulary_file:
for line in vocabulary_file:
vocabulary_list.append(line.strip())
# 배열에 내용을 키와 값이 있는
# 딕셔너리 구조로 만든다.
char2idx, idx2char = make_vocabulary(vocabulary_list)
# 두가지 형태의 키와 값이 있는 형태를 리턴한다.
# (예) 단어: 인덱스 , 인덱스: 단어)
return char2idx, idx2char, len(char2idx)
def make_vocabulary(vocabulary_list):
# 리스트를 키가 단어이고 값이 인덱스인
# 딕셔너리를 만든다.
char2idx = {char: idx for idx, char in enumerate(vocabulary_list)}
# 리스트를 키가 인덱스이고 값이 단어인
# 딕셔너리를 만든다.
idx2char = {idx: char for idx, char in enumerate(vocabulary_list)}
# 두개의 딕셔너리를 넘겨 준다.
return char2idx, idx2char
def enc_processing(value, dictionary, tokenize_as_morph=False):
# 인덱스 값들을 가지고 있는
# 배열이다.(누적된다.)
sequences_input_index = []
# 하나의 인코딩 되는 문장의
# 길이를 가지고 있다.(누적된다.)
sequences_length = []
# 형태소 토크나이징 사용 유무
if tokenize_as_morph:
value = prepro_like_morphlized(value)
# 한줄씩 불어온다.
for sequence in value:
# FILTERS = "([~.,!?\"':;)(])"
# 정규화를 사용하여 필터에 들어 있는
# 값들을 "" 으로 치환 한다.
sequence = re.sub(CHANGE_FILTER, "", sequence)
# 하나의 문장을 인코딩 할때
# 가지고 있기 위한 배열이다.
sequence_index = []
# 문장을 스페이스 단위로
# 자르고 있다.
for word in sequence.split():
# 잘려진 단어들이 딕셔너리에 존재 하는지 보고
# 그 값을 가져와 sequence_index에 추가한다.
if dictionary.get(word) is not None:
sequence_index.extend([dictionary[word]])
# 잘려진 단어가 딕셔너리에 존재 하지 않는
# 경우 이므로 UNK(2)를 넣어 준다.
else:
sequence_index.extend([dictionary[UNK]])
# 문장 제한 길이보다 길어질 경우 뒤에 토큰을 자르고 있다.
if len(sequence_index) > MAX_SEQUENCE:
sequence_index = sequence_index[:MAX_SEQUENCE]
# 하나의 문장에 길이를 넣어주고 있다.
sequences_length.append(len(sequence_index))
# max_sequence_length보다 문장 길이가
# 작다면 빈 부분에 PAD(0)를 넣어준다.
sequence_index += (MAX_SEQUENCE - len(sequence_index)) * [dictionary[PAD]]
# 인덱스화 되어 있는 값을
# sequences_input_index에 넣어 준다.
sequences_input_index.append(sequence_index)
# 인덱스화된 일반 배열을 넘파이 배열로 변경한다.
# 이유는 텐서플로우 dataset에 넣어 주기 위한
# 사전 작업이다.
# 넘파이 배열에 인덱스화된 배열과
# 그 길이를 넘겨준다.
return np.asarray(sequences_input_index), sequences_length
def dec_output_processing(value, dictionary, tokenize_as_morph=False):
# 인덱스 값들을 가지고 있는
# 배열이다.(누적된다)
sequences_output_index = []
# 하나의 디코딩 입력 되는 문장의
# 길이를 가지고 있다.(누적된다)
sequences_length = []
# 형태소 토크나이징 사용 유무
if tokenize_as_morph:
value = prepro_like_morphlized(value)
# 한줄씩 불어온다.
for sequence in value:
# FILTERS = "([~.,!?\"':;)(])"
# 정규화를 사용하여 필터에 들어 있는
# 값들을 "" 으로 치환 한다.
sequence = re.sub(CHANGE_FILTER, "", sequence)
# 하나의 문장을 디코딩 할때 가지고
# 있기 위한 배열이다.
sequence_index = []
# 디코딩 입력의 처음에는 START가 와야 하므로
# 그 값을 넣어 주고 시작한다.
# 문장에서 스페이스 단위별로 단어를 가져와서 딕셔너리의
# 값인 인덱스를 넣어 준다.
sequence_index = [dictionary[STD]] + [dictionary[word] if word in dictionary else dictionary[UNK] for word in sequence.split()]
# 문장 제한 길이보다 길어질 경우 뒤에 토큰을 자르고 있다.
if len(sequence_index) > MAX_SEQUENCE:
sequence_index = sequence_index[:MAX_SEQUENCE]
# 하나의 문장에 길이를 넣어주고 있다.
sequences_length.append(len(sequence_index))
# max_sequence_length보다 문장 길이가
# 작다면 빈 부분에 PAD(0)를 넣어준다.
sequence_index += (MAX_SEQUENCE - len(sequence_index)) * [dictionary[PAD]]
# 인덱스화 되어 있는 값을
# sequences_output_index 넣어 준다.
sequences_output_index.append(sequence_index)
# 인덱스화된 일반 배열을 넘파이 배열로 변경한다.
# 이유는 텐서플로우 dataset에 넣어 주기 위한
# 사전 작업이다.
# 넘파이 배열에 인덱스화된 배열과 그 길이를 넘겨준다.
return np.asarray(sequences_output_index), sequences_length
def dec_target_processing(value, dictionary, tokenize_as_morph=False):
# 인덱스 값들을 가지고 있는
# 배열이다.(누적된다)
sequences_target_index = []
# 형태소 토크나이징 사용 유무
if tokenize_as_morph:
value = prepro_like_morphlized(value)
# 한줄씩 불어온다.
for sequence in value:
# FILTERS = "([~.,!?\"':;)(])"
# 정규화를 사용하여 필터에 들어 있는
# 값들을 "" 으로 치환 한다.
sequence = re.sub(CHANGE_FILTER, "", sequence)
# 문장에서 스페이스 단위별로 단어를 가져와서
# 딕셔너리의 값인 인덱스를 넣어 준다.
# 디코딩 출력의 마지막에 END를 넣어 준다.
sequence_index = [dictionary[word] if word in dictionary else dictionary[UNK] for word in sequence.split()]
# 문장 제한 길이보다 길어질 경우 뒤에 토큰을 자르고 있다.
# 그리고 END 토큰을 넣어 준다
if len(sequence_index) >= MAX_SEQUENCE:
sequence_index = sequence_index[:MAX_SEQUENCE - 1] + [dictionary[END]]
else:
sequence_index += [dictionary[END]]
# max_sequence_length보다 문장 길이가
# 작다면 빈 부분에 PAD(0)를 넣어준다.
sequence_index += (MAX_SEQUENCE - len(sequence_index)) * [dictionary[PAD]]
# 인덱스화 되어 있는 값을
# sequences_target_index에 넣어 준다.
sequences_target_index.append(sequence_index)
# 인덱스화된 일반 배열을 넘파이 배열로 변경한다.
# 이유는 텐서플로우 dataset에 넣어 주기 위한 사전 작업이다.
# 넘파이 배열에 인덱스화된 배열과 그 길이를 넘겨준다.
return np.asarray(sequences_target_index)