├── LICENSE ├── README.md ├── Requirements.txt ├── image ├── alexnet2.png ├── char2.PNG ├── rnn.jpg ├── selfAA.png ├── skipgram.png ├── tfidf.png └── ykim14.png └── kor_char.npy /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 warnikchow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DLK2NLP: Day-by-day Line-by-line Keras-based Korean NLP 2 | ## Sentence classification: From data construction to self-attentive BiLSTM 3 | ### The tutorial is provided in both English (for *Korean* NLP learners) and Korean (for Korean *NLP* learners) - but the meanings may differ. 4 | * 문장 분류 task를 중심으로 본 한국어 NLP 튜토리얼입니다. 본 튜토리얼에 사용된 컨텐츠 일부는 [페이스북 페이지](https://www.facebook.com/nobodybelongs/notes/)에 기고했던 글에서 발췌하였음을 명시합니다. 한국어 NLP를 하고 싶은 외국인들과 NLP를 배워보고 싶은 한국인들을 모두 대상으로 하여 영/한 설명을 모두 제공하나, 번역이 아닌 의역이며 내용도 다를 수 있습니다. 5 | 6 | ## Requirements 7 | #### The recommended versions are in *Requirements.txt*, but can be replaced depending on the environment 8 | fasttext (Gensim if inavailable), Keras, konlpy (refer to the [documentation](http://konlpy.org/en/v0.4.4/)), hgtk, nltk, numpy, scikit-learn, tensorflow-gpu 9 | #### Download [100-dimension fastText vector dictionary which was trained with 2M drama scripts](https://drive.google.com/open?id=1jHbjOcnaLourFzNuP47yGQVhBTq6Wgor) and place in the folder named *vectors*. For the utilization of the dictionary, cite the following: 10 | ``` 11 | @article{cho2018real, 12 | title={Real-time Automatic Word Segmentation for User-generated Text}, 13 | author={Cho, Won Ik and Cheon, Sung Jun and Kang, Woo Hyun and Kim, Ji Won and Kim, Nam Soo}, 14 | journal={arXiv preprint arXiv:1810.13113}, 15 | year={2018} 16 | } 17 | ``` 18 | #### Download the [dataset](https://github.com/warnikchow/3i4k/blob/master/data/fci.txt) and place in the folder named *data*. For the utilization of the dataset, cite the following: 19 | ``` 20 | @article{cho2018speech, 21 | title={Speech Intention Understanding in a Head-final Language: A Disambiguation Utilizing Intonation-dependency}, 22 | author={Cho, Won Ik and Lee, Hyeon Seung and Yoon, Ji Won and Kim, Seok Min and Kim, Nam Soo}, 23 | journal={arXiv preprint arXiv:1811.04231}, 24 | year={2018} 25 | } 26 | ``` 27 | 28 | ## Contents (to be updated) 29 | [0. Corpus labelling](https://github.com/warnikchow/dlk2nlp/blob/master/README.md#0-corpus-labeling) 30 | [1. Data preprocessing](https://github.com/warnikchow/dlk2nlp#1-data-preprocessing) 31 | [2. One-hot encoding and sentence vector](https://github.com/warnikchow/dlk2nlp#2-one-hot-encoding-and-sentence-vector) 32 | [3. TF-IDF and basic classifiers](https://github.com/warnikchow/dlk2nlp#3-tf-idf-and-basic-classifiers) 33 | [4. Dense word embeddings](https://github.com/warnikchow/dlk2nlp#4-dense-word-embeddings) 34 | [5. Document vectors and NN classifier](https://github.com/warnikchow/dlk2nlp#5-document-vectors-and-nn-classifier) 35 | [6. CNN-based sentence classification](https://github.com/warnikchow/dlk2nlp#6-cnn-based-sentence-classification) 36 | [7. RNN (BiLSTM)-based sentence classification](https://github.com/warnikchow/dlk2nlp#7-rnn-bilstm-based-sentence-classification) 37 | [8. Character embedding](https://github.com/warnikchow/dlk2nlp#8-character-embedding) 38 | [9. Concatenation of CNN and RNN layers](https://github.com/warnikchow/dlk2nlp#9-concatenation-of-cnn-and-rnn-layers) 39 | [10. Self-attentive BiLSTM](https://github.com/warnikchow/dlk2nlp#10-self-attentive-bilstm) 40 | [11. Transformer, BERT, and after](https://github.com/warnikchow/dlk2nlp#11-transformer-bert-and-after) 41 | 42 | ## 0. Corpus labeling 43 | **The most annoying and confusing process. Annotation guideline should be provided to annotators, and more than two natives are to be engaged in to make the labeling reliable and also for the computation of inter-annotator agreement (IAA). In this project, a multi-class (7) annotation of short Korean utterances is utilized.** 44 | 45 | * 데이터를 만드는, 가장 귀찮은 과정입니다. 언어학적 직관은 1인분이기 때문에, 레이블링이 설득력을 얻기 위해서는 적어도 3명 이상의 1화자를 통한 레이블링으로 그 타당성을 검증해야 합니다 (아카데믹하게는...) 46 | * 본 프로젝트에서는 7-class의 한국어 문장 분류 방법이 사용됩니다. 47 | 48 | --- 49 | 50 | **The task is on classification; especially about extracting intention from a single utterance with the punctuation removed, which is suggested in [3i4k](https://github.com/warnikchow/3i4k). As the description displays, the corpus was partially hand-annotated and incorporates the utterances which are generated or semi-automatically collected. The total number of the utterances reaches 61K, with each label denoting** 51 | **0: Fragments** 52 | **1: Statement** 53 | **2: Question** 54 | **3: Command** 55 | **4: Rhetorical question** 56 | **5: Rhetorical command** 57 | **6: Intonation-dependent utterance** 58 | **where the [inter-annotator (IAA)](https://en.wikipedia.org/wiki/Cohen%27s_kappa) was computed 0.85 (quite high!) for the manually annotated 2K utterance set (corpus 1 in the table below). The [annotation guideline](https://drive.google.com/open?id=1AvxzEHr7wccMw7LYh0J3Xbx5GLFfcvMW) is in Korean.** 59 |
60 |
154 |
296 |
580 |
607 |
713 |
1401 |
155 | (Image from https://skymind.ai/wiki/bagofwords-tf-idf)
156 |
157 | * 앞서 bag-of-words 모델을 통해 문장을 1-hot vector들의 합(여기서 합은 Boolean의 or에 해당)으로 나타내어 컴퓨터가 알아먹을 만한 어떤 수치로 나타내는 과정을 설명했습니다. 이는 전통적이고 직관적이며 상당히 강력한 방법이기도 합니다.
158 |
159 | * 하지만 이 방법의 문제는 문장 내에 등장하는 모든 단어들을, 등장 횟수에 상관없이 공평하게 대한다는 것입니다. 예컨대 "나는 아브라함의 하나님이요 이삭의 하나님이요 야곱의 하나님이로라 하신 것을 읽어 보지 못하였느냐 (전 종교가 없습니다! 그냥 예문을 찾고싶었을뿐)" 라는 문장이 하고 싶은 말은 누가 봐도 내가 하나님이란 것 같은데 아브라함이니 이삭이니 하는 것들과 동급으로 1로 카운트되면 얼마나 억울하겠습니까.
160 |
161 | * 이러한 문제를 해결하기 위해 우린 일종의 normalization으로 볼 수 있는 term frequency, 즉 문장의 전체 word중 해당 word의 빈도를 고려해 문장을 벡터화해 볼 수 있습니다. 위의 문장에선 '하나님'이 3/X (형태소분석한 결과를 모두 카운트하기 귀찮으니 X로 퉁칩시다)의 값을 부여받고 나머지 녀석들은 1/X의 값을 부여받게 되겠죠.
162 |
163 | ---
164 |
165 | **However, the problem is that the high frequency does not indicate the importance of the word. Thus here, the concept of *inverse-document frequency* (IDF) is introduced, as a way of multiplying the inverse fraction of the frequency of the word among the whole documents. This prevents the overestimation of the functional words in many cases; to be honest, 'I' and 'you' are not as important as 'love', 'want' and 'need' in most cases (unless if the task deals with directivity). For instance, in the morpheme-level analysis, many particles in Korean are used repetitively in the sentences; those will be assigned a low IDF so that the lexical words are emphasized alternatively. The following code utilizing Scikit-learn library demonstrates how the *term frequency-inverse document frequency* (TF-IDF) is computed for our corpus.**
166 |
167 | ```python
168 | # Referred to the followings for the code:
169 | # https://gist.github.com/jason-riddle/1a854af26562c0cdb1e6ff550d1bf32d#file-complex-tf-idf-example-py-L40
170 | # http://blog.christianperone.com/2011/10/machine-learning-text-feature-extraction-tf-idf-part-ii/
171 |
172 | from sklearn.feature_extraction.text import CountVectorizer
173 | from sklearn.feature_extraction.text import TfidfTransformer
174 | from sklearn.feature_extraction.text import TfidfVectorizer
175 |
176 | def tfidf_featurizer(corpus_train,corpus_test):
177 | count_vectorizer = CountVectorizer()
178 | count_vectorizer.fit_transform(corpus_train)
179 | freq_term_matrix = count_vectorizer.transform(corpus_train)
180 | tfidf = TfidfTransformer(norm="l2")
181 | tfidf.fit(freq_term_matrix)
182 | # unigram features
183 | tfidf_token = TfidfVectorizer(ngram_range=(1,1),max_features=3000)
184 | token_tfidf_total = tfidf_token.fit_transform(corpus_train+corpus_test)
185 | token_tfidf_total_mat = token_tfidf_total.toarray()
186 | # bigram features
187 | tfidf_bi_token = TfidfVectorizer(ngram_range=(1,2),max_features=3000)
188 | token_tfidf_bi_total = tfidf_bi_token.fit_transform(corpus_train+corpus_test)
189 | token_tfidf_bi_total_mat = token_tfidf_bi_total.toarray()
190 | return token_tfidf_total_mat,token_tfidf_bi_total_mat
191 |
192 | fci_tfidf,fci_tfidf_bi = tfidf_featurizer(fci_token_train,fci_token_test)
193 | fci_tfidf_train = fci_tfidf[:len_train]
194 | fci_tfidf_test = fci_tfidf[len_train:]
195 | fci_tfidf_bi_train = fci_tfidf_bi[:len_train]
196 | fci_tfidf_bi_test = fci_tfidf_bi[len_train:]
197 | ```
198 |
199 | * 그런데 또 문제가 있습니다. 바로 하나님이 소유를 나타내는 '의'와 동급이 돼버리는 겁니다. 아아, 이렇게 원통할 데가... 딱 봐도 '의'라는 녀석은 문장의 의미를 판단하는 데에 큰 도움을 주지 않을 것으로 보입니다. 다른 문장들에도 많이 나올 게 분명하거든요.
200 |
201 | * 그래서 우리가 생각해볼 수 있는 건 '다른 문장들에도 많이 나오는 녀석엔 가중치를 조금 주면 어떨까?'하는 것입니다. 예컨대 전체 문장 중 해당 문장이 나오는 비율의 역수 같은 걸 곱해준다면? 이런 생각으로 나온 녀석이 바로 inverse document frequency입니다. 약간의 smoothing factor을 추가하자면, test corpus에 해당 term이 없는 경우를 대비해 분모에 1을 더해주고, corpus size가 방대해질 때를 고려해 log를 입히는 정도?
202 |
203 | * 상기한 두 개의 요소를 곱해 BoW를 개량한 모델이 바로 TF-IDF (term frequency-inverse document frequency) 입니다. 문서 분류에 아직도 활발히 사용되는 모델이지요. 위의 코드에서 *tfidf_vectorizer* 함수는 train corpus에서 tf-idf statistics를 뽑아내고 이를 이용해 train corpus+test corpus를 수치화하는 과정을 담고 있습니다. statistics를 뽑아낼 때 train corpus만 사용하는 것은, test corpus까지 되면 prediction의 본래 의미에 맞지 않게 되기 때문이죠. 수치화하는 과정은 이 statistics를 이용하여 *fit_transform*의 함수를 통해 일괄적으로 진행됩니다.
204 |
205 | ---
206 | **The aforementioned sentence representation can be directly utilized with basic classifiers such as naive Bayes (NB), decision tree (DT), support vector machine (SVM) and logistic regression (LR). The evaluation is done with accuracy and F1-score; accuracy refers to the fraction of correct instances out of the whole data. Understanding the meaning of the value is intuitive, but the flaw is that it does not convey how incorrect the model predicts for the classes of data with a small portion. For example, if the data consists of the binary label and the portion is 9:1 yielding an imbalance, then the accuracy may reach 90% just by a simple guess of a class with the larger volume. A better solution can be obtained by using F1-score, which considers the true negatives and the false positives. The following code displays how the sparse vectors we previously obtained are used in training and prediction, with both an accuracy and an F1-score.**
207 |
208 | ```python
209 | from sklearn.linear_model import LogisticRegression
210 | from sklearn import metrics
211 | from sklearn.metrics import accuracy_score, precision_recall_fscore_support
212 |
213 | classifier_uni = LogisticRegression(random_state=1234)
214 | classifier_uni.fit(fci_tfidf_train,fci_label_train)
215 | uni_pred = classifier_uni.predict(fci_tfidf_test)
216 | accuracy_score(uni_pred,fci_label_test)
217 | metrics.f1_score(uni_pred,fci_label_test,average="macro")
218 | metrics.f1_score(uni_pred,fci_label_test,average="weighted")
219 | precision_recall_fscore_support(uni_pred,fci_label_test)
220 |
221 | classifier_bi = LogisticRegression(random_state=1234)
222 | classifier_bi.fit(fci_tfidf_bi_train,fci_label_train)
223 | bi_pred = classifier_bi.predict(fci_tfidf_test)
224 | accuracy_score(bi_pred,fci_label_test)
225 | metrics.f1_score(bi_pred,fci_label_test,average="macro")
226 | metrics.f1_score(bi_pred,fci_label_test,average="weighted")
227 | precision_recall_fscore_support(bi_pred,fci_label_test)
228 | ```
229 |
230 | * 지금까지 tf-idf를 이용한 sentence 의 sparse representation에 대해 얘기했지요. 이제 구체적으로 이 녀석들을 문장 분류에 이용해먹을 만한 방법들에 대해 생각해봐야 하는데요, 그 중 하나가 이 글의 task로 제시된 분류 (classification)입니다.
231 |
232 | * 사실 데이터 집합(문장들)과 레이블 집합(긍정문/부정문, 의문문별 분류, 토픽별 분류 등)으로 된 트레이닝 데이터를 input으로 넣어, 별도의 테스트 데이터로 얻어진 prediction와 정답의 비교를 통해 그 네트워크의 accuracy, F-measure 등을 evaluate하여 성능을 평가한다는 점은 많은 분야에서 사용되는 분류기 evaluation의 구조입니다.
233 |
234 | * 이 때 accuracy는 전체 테스트 인풋 중 분류에 성공한 input의 비율이 될 겁니다. 그런데 함정이 있다면, 데이터 편중을 배제하기 어렵단 것이죠. 예컨대 데이터셋의 90퍼센트가 긍정문이고 10퍼센트만 부정문이라면, 공정한 트레이닝 및 테스트가 되었다고 하기 어렵겠죠. 그래서 binary classification에서 많이 사용되는 F-measure의 경우는 precision과 recall이라는, 각각 '1이라고 했을 때 정말 맞을 확률'과 '전체 맞은 것들 중 1이라고 했을 확률'을 생각하여 이들의 조화평균을 구해주는 방향으로 accuracy의 함정을 보정합니다. 물론 단순 조화평균이 아닌 별도 parameter을 주기도 하고, multiclass에 대해선 따로 class별 weight를 정의하기도 하지만요.
235 |
236 | ---
237 |
238 | **The result is as below; it is weird that the bigram features yielded quite a lower accuracy. It is assumed that the property of the corpus, where the sentence label does not depend solely on the polarity items or emotion words, may have affected the performance. Also, the dimension of the feature (3,000) which was fixed to guarantee the fair comparison between the two models, could have been a harsh obstacle for the bigram model.**
239 |
240 | ```properties
241 | # CONSOLE RESULT
242 | >>> accuracy_score(uni_pred,fci_label_test)
243 | 0.7742409402546523
244 | >>> metrics.f1_score(uni_pred,fci_label_test,average="macro")
245 | 0.6000194409152938
246 | >>> metrics.f1_score(uni_pred,fci_label_test,average="weighted")
247 | 0.7880295802371478
248 | >>> precision_recall_fscore_support(uni_pred,fci_label_test)
249 | (array([0.74875208, 0.87206124, 0.88099174, 0.73828125, 0.14534884,
250 | 0.23148148, 0.32398754]), array([0.70754717, 0.71046771, 0.84513742, 0.82749562, 0.5952381 ,
251 | 0.80645161, 0.75362319]), array([0.72756669, 0.78301424, 0.8626922 , 0.78034682, 0.23364486,
252 | 0.35971223, 0.45315904]), array([ 636, 2245, 1892, 1142, 42, 31, 138]))
253 |
254 | >>> accuracy_score(bi_pred,fci_label_test)
255 | 0.321743388834476
256 | >>> metrics.f1_score(bi_pred,fci_label_test,average="macro")
257 | 0.18947002986641723
258 | >>> metrics.f1_score(bi_pred,fci_label_test,average="weighted")
259 | 0.35601345547363006
260 | >>> precision_recall_fscore_support(bi_pred,fci_label_test)
261 | (array([0.82529118, 0.46473483, 0.18787879, 0.22109375, 0. ,
262 | 0. , 0.00311526]), array([0.28101983, 0.36340316, 0.34236948, 0.28936605, 0. ,
263 | 0. , 0.05 ]), array([0.41927303, 0.40786948, 0.24261829, 0.2506643 , 0. ,
264 | 0. , 0.0058651 ]), array([1765, 2339, 996, 978, 12, 16, 20]))
265 | ```
266 |
267 | * 결과는 뜻밖이었는데요, bigram으로 구한 결과가 그다지 신통치 않은 것 같습니다. (코드를 잘못 짰나...?) 어쨌든 궁색하게 분석을 해보자면, 본 task가 sentiment analysis 처럼 단어 한두개로 문장의 의미가 크게 달라지는 task들과 다르게 전체적인 맥락 및 어순을 고려해야 하기도 하고, fair comparison을 위해 3,000으로 제한한 feature dimension이 오히려 bigram에는 해가 되어 정작 중요한 형태소들을 놓친 게 아닌가...하는 생각이 듭니다.
268 |
269 | * 앞서 구한 one-hot encoding으로도 이러한 evaluation을 손쉽게 할 수 있습니다. 방금 TF-IDF에서 그랬듯, evaluation을 위한 prediction은 10%의 test data로 하게 되지요. Bigram TF-IDF는 좀 불만족스럽지만, 어쨌든 sparse representation으로도 어느 정도는 높은 정확도를 가진 모델을 얻을 수 있음을 알 수 있습니다. 물론 일부 class들에 대해선 random guess만도 못한 결과를 내고 있지만요. 다만 아직도 찝찝한 점은, 컴퓨터가 단어를 세고만 있지 그 단어들이 문장 내에서, 작게는 컨텍스트에서 어떤 역할을 하는지 아무것도 모르는 것 같다는 점입니다.
270 |
271 | ## 4. Dense word embeddings
272 |
273 | **Three-line summary:**
274 | **1. Computational linguistics aims making machines understand human language.**
275 | **2. As a fundamental approach for the representation of words and sentences, one-hot encoding and TF-IDF are introduced.**
276 | **3. For the Korean language, due to the property of the agglutinative language, morpheme-level analysis can be more effective than the word (*eojeol*)-level one.**
277 |
278 | **However, considering the computation issue which has been crucial up to this date, the sparse representations may not be the optimal solution for the contemporary neural network-based systems. This is the point where the dense representation such as [*Word2Vec*](https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf) is successfully adopted.**
279 |
280 | * 여태까지 한 내용을 세줄요약해 보면 다음과 같습니다.
281 | 1. 전산언어학은 기계로 하여금 사람 말을 잘 알아먹도록 하는 학문
282 | 2. 단어 및 문장을 모델링하는 기본적인 방법으로 one-hot encoding (BoW) 및 TF-IDF가 있음
283 | 3. 한국어는 교착어적 특징 때문에 어절 단위 분석보다 형태소 단위 분석이 효과적일 수 있음
284 |
285 | * 이외에도 수치화된 문장을 분류하는 알고리즘에는 SVM 이나 LR 등이 있다, 그 성능을 평가하는 measure에는 accuracy나 F1 score 등이 있다 ... 라는 것도 간략히 말하고 지나왔죠.
286 |
287 | * 그런데 이러한 방법론들은 분석해야 할 데이터가 많아지고 사전의 크기가 커질수록 computation의 문제에 당면하게 됩니다. 특히나 데이터를 몰빵해넣고 병렬연산으로 승부하는 딥러닝의 경우 더욱 그렇지요. 예컨대 30 개의 형태소로 된 문장을 벡터 sequence로 나타내어 recurrent neural network 같은 시스템으로 요약하고자 할 때, 딕셔너리 사이즈가 10만이라면, 10만 x 30이라는 무시무시한 사이즈의 행렬이 문장 하나를 표현하여 인풋으로 들어가게 되는거죠. 물론 아까 보았듯 사용하는 벡터의 크기는 조절할 수 있고, 각 벡터가 각 단어를 표현하는 것이 아주 명확하긴 하지만요.
288 |
289 | * 이럴 때 유용하게 사용되는 개념이 2013년 태동한 word2vec, 혹은 dense low-dimension embedding 입니다. 등장 목적이 위와 같다고는 할 수 없지만, [2006년부터 촉발된 딥러닝 아키텍쳐](http://www.cs.toronto.edu/~fritz/absps/ncfast.pdf)와 맞물려 사용되며 문장 분석의 패러다임 자체를 바꾸고 있죠. Word2vec, GloVe, fastText에 대한 설명을 간략하게만이라도 해보려 합니다.
290 |
291 | ---
292 |
293 | The term word2Vec is very intuitive, and it converts the words, which are discrete (and were sparsely represented so far), into the numerics that are close to continuousness. Since there are a [bunch of](https://skymind.ai/wiki/word2vec) [terrific](https://towardsdatascience.com/introduction-to-word-embedding-and-word2vec-652d0c2060fa) [articles](https://www.tensorflow.org/tutorials/representation/word2vec) on the topic outside, in English, a review on word2vec and its related models are discussed mainly in Korean.
294 |
295 |
297 | (Image from [the tutorial site](https://sheffieldnlp.github.io/com4513-6513/lectures_reveal_js/lecture12_word_embeddings.html). The matrix W x D is obtained by minimizing the cost function regarding the performance of context prediction given an input vector.)
298 |
299 | * word2vec이라는 말은 상당히 직관적입니다. 말 그대로 이산적 개념인 단어를 수치적 개념인 벡터로 바꿔 준다는 의미이죠. 사실 one-hot encoding 역시 고차원의 벡터를 만들어준다는 점을 생각하면 어폐가 있긴 합니다. 그래서 두 개념의 차이를 벡터가 sparse한지 (드문드문하게 nonzero인 성분이 있는지), 아니면 dense한지 (유클리드 공간에서처럼 빽빽이 들어차 있는지)를 차이점으로 봅니다. 물론 word2vec은 후자를 의미하죠.
300 |
301 | * 당연한 얘기겠지만, 어떤 방식의 워드 임베딩이든, 임베딩 벡터 셋을 트레이닝하는 과정에서 어떤 원칙 (혹은 제약조건)을 주냐에 따라 결과물로 나오는 벡터들 간의 관계가 달라지게 됩니다. 예컨대 아무 제약 조건도 주지 않는 one-hot encoding의 경우, 모든 단어가 평등합니다. 아무리 비슷해 보이는 단어들이라도 1이 위치하는 엔트리가 다르다면 아무 상관없는 단어인 것이나 다름없게 되는 것이죠. distributional semantics에 대한 고려는 one-hot vector에 들어 있지 않은 겁니다.
302 |
303 | * 모든 sparse vector가 one-hot vector같이 엔트리 간 equivalence를 지니는 건 아닙니다. 오히려 희소한 케이스에 가깝죠. 가중치를 곱해준 TF-IDF만 해도, 개별 단어를 모델링하지는 않지만 어떤 분포적인 특성이 반영되게 됩니다. binary이지만 multi-hot encoding을 차용하는 [boolean distributional semantics](https://transacl.org/ojs/index.php/tacl/article/view/616)의 경우, 의미론에서 얘기하는 feature의 개념이 등장하여, taxonomy 상 상위 위계에 해당하는 단어의 embedding이 하위 위계의 단어의 embedding에 포함되게 하는 방식으로 트레이닝이 됩니다. 아마도 벡터 사이즈는 one-hot보다는 줄어들겠죠? 벡터들 간의 distance measure가 euclidean이 아닌 어떤 이산적인 개념이 된다는 건 challenging하긴 합니다만, 언어가 기본적으로 이산적인 속성을 버릴 수 없다는 생각을 갖고 있는 저로썬 매우 매력적이라는 생각을 했습니다.
304 |
305 | * 그렇지만 이상은 이상이고, 우리는 많은 순간 현실과 타협해야 합니다. 작은 벡터에 정보들을 우겨 넣어야 하죠. 또한 back propagation과 같은 연산을 통해 이루어지는 최신 최적화 기법들의 수혜를 받으려면, 벡터 간의 거리가 어떤 미분가능한, 이산적이지 않은 개념으로 정의가 되어야 함도 사실입니다 (물론 이산적인 목적 함수들을 위해 별도의 신경망을 연결해 주는 알고리즘도 있는 것으로 압니다만 일단 그건 나중에 생각하도록 하지요). 그러기 위해서, 고차원의 벡터를 저차원에 임베딩해 넣으면서도, 단어들 간의 관계가 좀 더 유기적으로 연결될 수 있는 방법엔 뭐가 있을까요?
306 |
307 | * 이러한 관점에서 볼 때, word2vec은 상당히 매력적인 시도였다 볼 수 있겠습니다. 혹자는 '비슷한 context에 등장하는 녀석들은 실제로도 비슷한/관련 있는 녀석들일 가능성이 높다는 distributional semantics의 원칙을 수치적 제약으로 잘 지키면서 one-hot vector을 저차원에 pca한 결과물'이라고 하더군요. 예컨대 '너는 나쁜 아이야'와 '너는 착한 아이야'라는 문장들을 볼 때, '나쁜'과 '착한'이 실제로 저런 류의 context를 많이 공유한다 생각해 보면, 둘이 아주 관계가 없는 단어들은 아니다, 어느정도 유기적이다, 그런 판단을 할 수 있겠죠? word2vec의 큰 철학은 이렇습니다. 컨텍스트를 주고 center word를 추론하는 CBOW와 그 반대인 skip-gram (SG) 모두 word2vec의 최적화와 관련된 알고리듬인데요, SG가 여러 태스크에서 더 성능이 좋음이 보여진 바 있고 실제로도 더 자주 활용됩니다.
308 |
309 | ---
310 |
311 | **Up to date, many advanced models of word vectors which base on word2vec have been proposed. For instance, [*GloVe*](https://nlp.stanford.edu/pubs/glove.pdf) represents the word vectors considering the co-occurrence in the window, and [*fastText*](https://arxiv.org/abs/1607.04606) utilizes subword n-gram so that the embedding can be efficient for morphologically rich languages. In the following approaches that use dense word vectors, we adopt [100-dimension fastText vector dictionary which was trained with 2M drama scripts](https://drive.google.com/open?id=1jHbjOcnaLourFzNuP47yGQVhBTq6Wgor), for the project [RAWS](github.com/warnikchow/raws). Use Gensim if fastText is not installed.**
312 |
313 | ```python
314 | import fasttext
315 | model_ft = fasttext.load_model('vectors/model_drama.bin')
316 |
317 | # In case fasttext is not installed
318 | # use FastText wrapper in Gensim as:
319 | # from gensim.models.wrappers import FastText
320 | # model_ft = FastText.load_fasttext_format('vectors/model_drama.bin')
321 | ```
322 |
323 | * Word2vec은 이런 저차원 임베딩의 포문을 열었을 뿐이고 (for문 아닙니다) 이후로 수많은 베리에이션이 등장했습니다. 이 때 사용하는 알고리듬이 skip-gram이란 점이 크게 변하지는 않았지만, 다양한 objective를 설정하며 특정 task에 효율적으로 적용할 수 있는 vector들이 등장했죠.
324 |
325 | * 가장 많이 알려진 두 베리에이션은 GloVe와 fastText입니다. 전자는 word2vec 이전에 사용되던 sparse representation 중 하나인 co-occurrence matrix의 개념을 training objective에 활용하여 보다 global하고 syntactic한 특징도 word vector에 반영할 수 있게 한 것이고, 후자는 word2vec과 사실상 같은 알고리즘이지만 그 트레이닝 효율을 거의 몇백배 수준으로 끌어올려 빠르게 트레이닝하면서도 task 성능은 엇비슷하게 유지할 수 있게 하는 알고리듬입니다. 또한 fastText는 subword n-gram model을 차용하여, word 단위로 나뉘어지지 않은, 단어 내의 character n-gram들도 일종의 word로 보고 그 분포를 전체 트레이닝에 고려한다는 특징을 가지고 있지요. 이를 통해 morphologically rich한 언어들에서도 효율적으로 활용 가능하다는 점을 논문에서 어필하고 있구요.
326 |
327 | * GloVe의 경우 stanford에서 제공하는 [wiki/twitter기반 pre-trained vector](https://nlp.stanford.edu/projects/glove/)가 있으며, fastText의 경우는 꽤 많은 언어로 pre-trained vector을 제공하지만 학습 속도가 굉장히 빨라 저 같은 경우는 갖고 있는 코퍼스로 새로 training하기도 합니다. Subword n-gram이 교착어인 한국어에도 굉장히 유용하여, 저 같은 경우는 fastText로 트레이닝된 word vector set을 character embedding에 사용하고 있구요. 약 200만 문장의 드라마 스크립트를 통해 학습한 word vector dictionary는 [다음의 주소](https://drive.google.com/open?id=1jHbjOcnaLourFzNuP47yGQVhBTq6Wgor)에서 제공됩니다. [fasttext 라이브러리](https://pypi.org/project/fasttext/0.8.3/)를 이용해 bin 파일을 바로 load할 수 있으며, 설치가 잘 되지 않을 경우 Gensim의 FastText wrapper을 이용해 비슷한 방식으로 load가 가능합니다.
328 |
329 | * 앞서도 말했지만, 26개의 letter로 된 알파벳만 있으면 되는 영어와 달리, 한국어는 2500개 상당의 자모 조합이 있어 one-hot encoding을 직접적으로 character-level embedding에 사용하기 쉽지 않죠. 이럴 때 유용하게 사용할 수 있는 것이 저차원으로 임베딩된 character vector입니다. 어느 정도 분포적인 특징을 반영할 수 있으면서도 computational하게 부담을 덜 줄 수 있는 그런 feature로 사용할 수 있는 것입니다. 물론 형태소, 어절 모두 임베딩의 대상이 될 수 있습니다. 어떤 것을 선택할지는 형태소 분석기의 유무, 구동 및 개발 환경 등에 따라 자유롭게 선택하면 될 것입니다.
330 |
331 | ## 5. Document vectors and NN classifier
332 |
333 | **In the first few chapters, we've demonstrated how the corpus we adopted is preprocessed, featurized, trained and used in prediction with the sparse sentence encodings. However, since we've obtained the dense word embeddings for the morphemes (as we've obtained for one-hot encoded words), it's plausible to extend it to the sentence vector, for instance by summation. **
334 |
335 | ```python
336 | from numpy import linalg as la
337 |
338 | def featurize_nn(corpus,wdim):
339 | nn_total = np.zeros((len(corpus),wdim))
340 | for i in range(len(corpus)):
341 | if i%1000 ==0:
342 | print(i)
343 | s = corpus[i]
344 | count_word=0
345 | for j in range(len(s)):
346 | if s[j] in model_ft:
347 | nn_total[i,:] = nn_total[i,:] + model_ft[s[j]]
348 | count_word = count_word + 1
349 | if count_word > 0:
350 | nn_total[i] = nn_total[i]/la.norm(nn_total[i])
351 | return nn_total
352 |
353 | fci_nn = featurize_nn(fci_sp_token_train+fci_sp_token_test,100)
354 | ```
355 |
356 | * 앞서 one-hot encoding과 tf-idf로 문장을 수치화하는 방법, 그리고 그것이 sparse vector이라는 것까지 말씀드린 바 있습니다. 정보가 discrete하다는 것은 기계에게나 사람에게나 매우 직관적으로 다가오기에, 문장의 분류에 있어 굉장히 유용하게 사용된다는 것두요. 하지만 지난 몇 시간 동안 dense word vector들을 얘기하는 동안, 그 친구들을 문장 수치화에 이용할 수 있지 않을까 생각하는 분도 분명 계실 겁니다. word2vec을 제안한 사람들의 머릿속을 들여다볼 수는 없지만, 결과적으로 그 등장이 문장 임베딩에 있어 하나의 패러다임 전환이 되었죠.
357 |
358 | * “I really love you” 이라는 영어 문장을 한번 생각해 봅시다. 일단 가장 먼저 생각해볼 수 있는 것은, \[0 0 0 ... 1 ... 1 ... 1 ... 1 ... 0 0 0\] 이런 식으로 나타내어진 multi-hot vector (one-hot vectors의 or summation)이겠죠. 그 다음은 \[0 0 0 ... 0.3 ... 0.7 ... 0.9 ... 0.4 ... 0 0 0\] 이런 식으로 표현된 tf-idf일 겁니다. 이제 word2vec이 무엇인지 배웠으므로, 이런 방법도 생각해 볼 수 있을 겁니다. ‘문장 내 모든 word와 word embedding function f에 대해, f(w)를 모두 더하기’ 즉, 워드 임베딩이 100차원의 real vector이라면, 전체 sentence vector s=sum(f(w))도 100차원의 real vector가 되는 방법이죠. 여기에 normalization을 위해 l2_norm(s)나 word 개수 (여기서는 4)로 s를 나눠 주면 좀 더 reliable한 표현이 될 것입니다. 앞서 말한 과정이 *featurize_nn*에 구현되어 있으며, 해당 함수는 corpus 전체를 input으로 받아 각 문장마다 100차원 (word vector dim)의 real vector을 출력합니다.
359 |
360 | ---
361 |
362 | **And here comes Keras, which is a widely used high-level wrapper for TensorFlow (and other libraries i.e. Theano and CNTK; though not used in general). Since we only use *Sequential()* for the codes not incorporating the concatenated layers or attention models, only *layer* will be imported. For the computation of average/weighted F1 score per epoch, an additional module is defined here. Also, considering the imbalance of the class volumes, we obtain the class weight set that is utilized in the training session.**
363 |
364 | ```python
365 | import tensorflow as tf
366 | from keras.backend.tensorflow_backend import set_session
367 | config = tf.ConfigProto()
368 | config.gpu_options.per_process_gpu_memory_fraction = 0.1
369 | set_session(tf.Session(config=config))
370 | from keras.models import Sequential
371 | import keras.layers as layers
372 | from keras import optimizers
373 | adam_half = optimizers.Adam(lr=0.0005)
374 | from keras.callbacks import ModelCheckpoint
375 |
376 | from keras.callbacks import Callback
377 | from sklearn import metrics
378 | class Metricsf1macro(Callback):
379 | def on_train_begin(self, logs={}):
380 | self.val_f1s = []
381 | self.val_recalls = []
382 | self.val_precisions = []
383 | self.val_f1s_w = []
384 | self.val_recalls_w = []
385 | self.val_precisions_w = []
386 | def on_epoch_end(self, epoch, logs={}):
387 | val_predict = np.asarray(self.model.predict(self.validation_data[0]))
388 | val_predict = np.argmax(val_predict,axis=1)
389 | val_targ = self.validation_data[1]
390 | _val_f1 = metrics.f1_score(val_targ, val_predict, average="macro")
391 | _val_f1_w = metrics.f1_score(val_targ, val_predict, average="weighted")
392 | _val_recall = metrics.recall_score(val_targ, val_predict, average="macro")
393 | _val_recall_w = metrics.recall_score(val_targ, val_predict, average="weighted")
394 | _val_precision = metrics.precision_score(val_targ, val_predict, average="macro")
395 | _val_precision_w = metrics.precision_score(val_targ, val_predict, average="weighted")
396 | self.val_f1s.append(_val_f1)
397 | self.val_recalls.append(_val_recall)
398 | self.val_precisions.append(_val_precision)
399 | self.val_f1s_w.append(_val_f1_w)
400 | self.val_recalls_w.append(_val_recall_w)
401 | self.val_precisions_w.append(_val_precision_w)
402 | print("— val_f1: %f — val_precision: %f — val_recall: %f"%(_val_f1, _val_precision, _val_recall))
403 | print("— val_f1_w: %f — val_precision_w: %f — val_recall_w: %f"%(_val_f1_w, _val_precision_w, _val_recall_w))
404 |
405 | metricsf1macro = Metricsf1macro()
406 |
407 | from sklearn.utils import class_weight
408 | class_weights_fci = class_weight.compute_class_weight('balanced', np.unique(fci_label), fci_label)
409 | ```
410 |
411 | * 본격적으로 Keras를 써볼 때가 왔습니다. 일단 제가 TensorFlow를 제대로 써본 적은 없지만, conctenated layer나 attention model같은 복잡한 시스템을 포함하지 않는 대부분의 모델들에 대해서는 *Sequential()* 모듈을 import하는 것으로 대부분 구현 가능합니다 ㅎㅎ TF-IDF의 evaluation에서 활용한 average/weighted F1 score을 epoch마다 계산하기 위해 별도의 함수를 정의했으며, class imbalance를 고려하여 weight set을 구했습니다. 다음의 코드에서 활용되는 것을 볼 수 있습니다.
412 |
413 | ---
414 |
415 | **And the Below is the model construction and evaluation phase. Note that the folder *tutorial* was created in the same directory to save checkpoint models, recording F1 scores and the accuracy. It is quite surprising that a simple summation boosts the accuracy and F1 score by a large factor, even considering that the concept of making up the sentence vector is fundamentally identical to that of one-hot encoding and TF-IDF!**
416 |
417 | ```python
418 | def validate_nn(result,y,hidden_dim,cw,filename):
419 | model = Sequential()
420 | model.add(layers.Dense(hidden_dim, activation = 'relu', input_dim=len(result[0])))
421 | model.add(layers.Dense(int(max(y))+1, activation='softmax'))
422 | model.summary()
423 | model.compile(optimizer=adam_half, loss="sparse_categorical_crossentropy", metrics=['accuracy'])
424 | filepath=filename+"-{epoch:02d}-{val_acc:.4f}.hdf5"
425 | checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, mode='max')
426 | callbacks_list = [metricsf1macro,checkpoint]
427 | model.fit(result,y,validation_split=0.1,epochs=30,batch_size=16,callbacks=callbacks_list,class_weight=cw)
428 |
429 | validate_nn(fci_nn,fci_label,128,class_weights_fci,'model/tutorial/nn')
430 | ```
431 |
432 | ```properties
433 | # CONSOLE RESULT
434 | >>> validate_nn(fci_nn,fci_label,128,class_weights_fci,'model/tutorial/nn')
435 | _________________________________________________________________
436 | Layer (type) Output Shape Param #
437 | =================================================================
438 | dense_3 (Dense) (None, 128) 12928
439 | _________________________________________________________________
440 | dense_4 (Dense) (None, 7) 903
441 | =================================================================
442 | Total params: 13,831
443 | Trainable params: 13,831
444 | Non-trainable params: 0
445 | _________________________________________________________________
446 | Train on 55129 samples, validate on 6126 samples
447 | Epoch 1/30
448 | 54960/55129 [============================>.] - ETA: 0s - loss: 0.8674 - acc: 0.7068— val_f1: 0.591953 — val_precision: 0.721290 — val_recall: 0.549977
449 | — val_f1_w: 0.731234 — val_precision_w: 0.747052 — val_recall_w: 0.742899
450 | 55129/55129 [==============================] - 6s 116us/step - loss: 0.8672 - acc: 0.7069 - val_loss: 0.7665 - val_acc: 0.7429
451 | Epoch 2/30
452 | 55056/55129 [============================>.] - ETA: 0s - loss: 0.7255 - acc: 0.7537— val_f1: 0.642565 — val_precision: 0.721759 — val_recall: 0.602373
453 | — val_f1_w: 0.754185 — val_precision_w: 0.761563 — val_recall_w: 0.760366
454 | 55129/55129 [==============================] - 7s 119us/step - loss: 0.7253 - acc: 0.7537 - val_loss: 0.7111 - val_acc: 0.7604
455 | Epoch 3/30
456 | 54992/55129 [============================>.] - ETA: 0s - loss: 0.6856 - acc: 0.7660— val_f1: 0.650076 — val_precision: 0.731066 — val_recall: 0.608824
457 | — val_f1_w: 0.760444 — val_precision_w: 0.769572 — val_recall_w: 0.766569
458 | 55129/55129 [==============================] - 7s 119us/step - loss: 0.6856 - acc: 0.7660 - val_loss: 0.6827 - val_acc: 0.7666
459 | Epoch 4/30
460 | 54704/55129 [============================>.] - ETA: 0s - loss: 0.6587 - acc: 0.7760— val_f1: 0.665685 — val_precision: 0.750019 — val_recall: 0.625693
461 | — val_f1_w: 0.768111 — val_precision_w: 0.777307 — val_recall_w: 0.773914
462 | 55129/55129 [==============================] - 7s 119us/step - loss: 0.6585 - acc: 0.7761 - val_loss: 0.6611 - val_acc: 0.7739
463 | Epoch 5/30
464 | 54912/55129 [============================>.] - ETA: 0s - loss: 0.6361 - acc: 0.7839— val_f1: 0.675093 — val_precision: 0.756560 — val_recall: 0.633105
465 | — val_f1_w: 0.772584 — val_precision_w: 0.779329 — val_recall_w: 0.779465
466 | 55129/55129 [==============================] - 6s 117us/step - loss: 0.6363 - acc: 0.7838 - val_loss: 0.6531 - val_acc: 0.7795
467 | Epoch 6/30
468 | 54752/55129 [============================>.] - ETA: 0s - loss: 0.6175 - acc: 0.7909— val_f1: 0.677678 — val_precision: 0.760474 — val_recall: 0.638087
469 | — val_f1_w: 0.782750 — val_precision_w: 0.789371 — val_recall_w: 0.788116
470 | 55129/55129 [==============================] - 6s 116us/step - loss: 0.6178 - acc: 0.7908 - val_loss: 0.6286 - val_acc: 0.7881
471 | Epoch 7/30
472 | 55056/55129 [============================>.] - ETA: 0s - loss: 0.6018 - acc: 0.7958— val_f1: 0.698941 — val_precision: 0.753141 — val_recall: 0.667789
473 | — val_f1_w: 0.790828 — val_precision_w: 0.793462 — val_recall_w: 0.795462
474 | 55129/55129 [==============================] - 6s 117us/step - loss: 0.6021 - acc: 0.7957 - val_loss: 0.6192 - val_acc: 0.7955
475 | Epoch 8/30
476 | 54736/55129 [============================>.] - ETA: 0s - loss: 0.5884 - acc: 0.8009— val_f1: 0.699604 — val_precision: 0.766330 — val_recall: 0.662288
477 | — val_f1_w: 0.794905 — val_precision_w: 0.797012 — val_recall_w: 0.800033
478 | 55129/55129 [==============================] - 7s 120us/step - loss: 0.5887 - acc: 0.8008 - val_loss: 0.6067 - val_acc: 0.8000
479 | Epoch 9/30
480 | 54880/55129 [============================>.] - ETA: 0s - loss: 0.5753 - acc: 0.8059— val_f1: 0.703015 — val_precision: 0.768262 — val_recall: 0.665053
481 | — val_f1_w: 0.800541 — val_precision_w: 0.807472 — val_recall_w: 0.804114
482 | 55129/55129 [==============================] - 7s 118us/step - loss: 0.5758 - acc: 0.8057 - val_loss: 0.5961 - val_acc: 0.8041
483 | Epoch 10/30
484 | 55040/55129 [============================>.] - ETA: 0s - loss: 0.5649 - acc: 0.8090— val_f1: 0.707821 — val_precision: 0.784078 — val_recall: 0.666537
485 | — val_f1_w: 0.804109 — val_precision_w: 0.811529 — val_recall_w: 0.809011
486 | 55129/55129 [==============================] - 6s 116us/step - loss: 0.5646 - acc: 0.8091 - val_loss: 0.5840 - val_acc: 0.8090
487 | Epoch 11/30
488 | 55056/55129 [============================>.] - ETA: 0s - loss: 0.5552 - acc: 0.8127— val_f1: 0.711974 — val_precision: 0.784070 — val_recall: 0.671254
489 | — val_f1_w: 0.809180 — val_precision_w: 0.819130 — val_recall_w: 0.813092
490 | 55129/55129 [==============================] - 7s 118us/step - loss: 0.5550 - acc: 0.8128 - val_loss: 0.5850 - val_acc: 0.8131
491 | Epoch 12/30
492 | 55008/55129 [============================>.] - ETA: 0s - loss: 0.5454 - acc: 0.8167— val_f1: 0.714970 — val_precision: 0.773000 — val_recall: 0.679629
493 | — val_f1_w: 0.807968 — val_precision_w: 0.811567 — val_recall_w: 0.810970
494 | 55129/55129 [==============================] - 7s 119us/step - loss: 0.5459 - acc: 0.8165 - val_loss: 0.5757 - val_acc: 0.8110
495 | Epoch 13/30
496 | 54880/55129 [============================>.] - ETA: 0s - loss: 0.5371 - acc: 0.8196— val_f1: 0.708290 — val_precision: 0.802882 — val_recall: 0.662145
497 | — val_f1_w: 0.808888 — val_precision_w: 0.817245 — val_recall_w: 0.814887
498 | 55129/55129 [==============================] - 7s 118us/step - loss: 0.5369 - acc: 0.8196 - val_loss: 0.5711 - val_acc: 0.8149
499 | Epoch 14/30
500 | 55008/55129 [============================>.] - ETA: 0s - loss: 0.5295 - acc: 0.8216— val_f1: 0.720639 — val_precision: 0.784796 — val_recall: 0.684186
501 | — val_f1_w: 0.813487 — val_precision_w: 0.820760 — val_recall_w: 0.816520
502 | 55129/55129 [==============================] - 7s 119us/step - loss: 0.5294 - acc: 0.8216 - val_loss: 0.5735 - val_acc: 0.8165
503 | Epoch 15/30
504 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.5218 - acc: 0.8245— val_f1: 0.723226 — val_precision: 0.793214 — val_recall: 0.682388
505 | — val_f1_w: 0.813296 — val_precision_w: 0.821299 — val_recall_w: 0.817009
506 | 55129/55129 [==============================] - 7s 118us/step - loss: 0.5217 - acc: 0.8246 - val_loss: 0.5625 - val_acc: 0.8170
507 | Epoch 16/30
508 | 54784/55129 [============================>.] - ETA: 0s - loss: 0.5147 - acc: 0.8259— val_f1: 0.718378 — val_precision: 0.805836 — val_recall: 0.671866
509 | — val_f1_w: 0.812241 — val_precision_w: 0.821222 — val_recall_w: 0.817009
510 | 55129/55129 [==============================] - 7s 118us/step - loss: 0.5150 - acc: 0.8259 - val_loss: 0.5591 - val_acc: 0.8170
511 | Epoch 17/30
512 | 55056/55129 [============================>.] - ETA: 0s - loss: 0.5085 - acc: 0.8286— val_f1: 0.710259 — val_precision: 0.800253 — val_recall: 0.667554
513 | — val_f1_w: 0.811823 — val_precision_w: 0.817403 — val_recall_w: 0.818642
514 | 55129/55129 [==============================] - 7s 118us/step - loss: 0.5087 - acc: 0.8285 - val_loss: 0.5605 - val_acc: 0.8186
515 | Epoch 18/30
516 | 54704/55129 [============================>.] - ETA: 0s - loss: 0.5026 - acc: 0.8304— val_f1: 0.719479 — val_precision: 0.797672 — val_recall: 0.676889
517 | — val_f1_w: 0.816203 — val_precision_w: 0.823536 — val_recall_w: 0.820764
518 | 55129/55129 [==============================] - 7s 119us/step - loss: 0.5029 - acc: 0.8303 - val_loss: 0.5533 - val_acc: 0.8208
519 | Epoch 19/30
520 | 54784/55129 [============================>.] - ETA: 0s - loss: 0.4975 - acc: 0.8326— val_f1: 0.718767 — val_precision: 0.790945 — val_recall: 0.677815
521 | — val_f1_w: 0.815264 — val_precision_w: 0.819433 — val_recall_w: 0.820601
522 | 55129/55129 [==============================] - 6s 118us/step - loss: 0.4972 - acc: 0.8328 - val_loss: 0.5533 - val_acc: 0.8206
523 | Epoch 20/30
524 | 54704/55129 [============================>.] - ETA: 0s - loss: 0.4914 - acc: 0.8341— val_f1: 0.732335 — val_precision: 0.777586 — val_recall: 0.702288
525 | — val_f1_w: 0.817729 — val_precision_w: 0.819907 — val_recall_w: 0.820927
526 | 55129/55129 [==============================] - 6s 118us/step - loss: 0.4915 - acc: 0.8340 - val_loss: 0.5515 - val_acc: 0.8209
527 | Epoch 21/30
528 | 54832/55129 [============================>.] - ETA: 0s - loss: 0.4868 - acc: 0.8365— val_f1: 0.730587 — val_precision: 0.787088 — val_recall: 0.694430
529 | — val_f1_w: 0.816936 — val_precision_w: 0.820749 — val_recall_w: 0.819948
530 | 55129/55129 [==============================] - 7s 118us/step - loss: 0.4865 - acc: 0.8365 - val_loss: 0.5466 - val_acc: 0.8199
531 | Epoch 22/30
532 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.4820 - acc: 0.8377— val_f1: 0.729099 — val_precision: 0.795526 — val_recall: 0.689722
533 | — val_f1_w: 0.820917 — val_precision_w: 0.825831 — val_recall_w: 0.825171
534 | 55129/55129 [==============================] - 6s 117us/step - loss: 0.4821 - acc: 0.8377 - val_loss: 0.5439 - val_acc: 0.8252
535 | Epoch 23/30
536 | 54960/55129 [============================>.] - ETA: 0s - loss: 0.4769 - acc: 0.8401— val_f1: 0.728253 — val_precision: 0.777373 — val_recall: 0.697222
537 | — val_f1_w: 0.816949 — val_precision_w: 0.818159 — val_recall_w: 0.820764
538 | 55129/55129 [==============================] - 7s 120us/step - loss: 0.4770 - acc: 0.8399 - val_loss: 0.5483 - val_acc: 0.8208
539 | Epoch 24/30
540 | 54880/55129 [============================>.] - ETA: 0s - loss: 0.4726 - acc: 0.8416— val_f1: 0.737025 — val_precision: 0.785367 — val_recall: 0.705383
541 | — val_f1_w: 0.822261 — val_precision_w: 0.824854 — val_recall_w: 0.825661
542 | 55129/55129 [==============================] - 7s 122us/step - loss: 0.4725 - acc: 0.8416 - val_loss: 0.5429 - val_acc: 0.8257
543 | Epoch 25/30
544 | 54736/55129 [============================>.] - ETA: 0s - loss: 0.4682 - acc: 0.8432— val_f1: 0.729383 — val_precision: 0.774703 — val_recall: 0.698946
545 | — val_f1_w: 0.819586 — val_precision_w: 0.823633 — val_recall_w: 0.821580
546 | 55129/55129 [==============================] - 6s 117us/step - loss: 0.4684 - acc: 0.8430 - val_loss: 0.5506 - val_acc: 0.8216
547 | Epoch 26/30
548 | 54928/55129 [============================>.] - ETA: 0s - loss: 0.4644 - acc: 0.8430— val_f1: 0.719513 — val_precision: 0.784795 — val_recall: 0.681772
549 | — val_f1_w: 0.816008 — val_precision_w: 0.819116 — val_recall_w: 0.820601
550 | 55129/55129 [==============================] - 6s 117us/step - loss: 0.4645 - acc: 0.8429 - val_loss: 0.5515 - val_acc: 0.8206
551 | Epoch 27/30
552 | 54864/55129 [============================>.] - ETA: 0s - loss: 0.4604 - acc: 0.8444— val_f1: 0.739462 — val_precision: 0.782457 — val_recall: 0.710235
553 | — val_f1_w: 0.823328 — val_precision_w: 0.825483 — val_recall_w: 0.826641
554 | 55129/55129 [==============================] - 7s 119us/step - loss: 0.4607 - acc: 0.8443 - val_loss: 0.5390 - val_acc: 0.8266
555 | Epoch 28/30
556 | 54912/55129 [============================>.] - ETA: 0s - loss: 0.4567 - acc: 0.8456— val_f1: 0.726324 — val_precision: 0.789678 — val_recall: 0.689405
557 | — val_f1_w: 0.821804 — val_precision_w: 0.826340 — val_recall_w: 0.825171
558 | 55129/55129 [==============================] - 6s 116us/step - loss: 0.4569 - acc: 0.8455 - val_loss: 0.5420 - val_acc: 0.8252
559 | Epoch 29/30
560 | 54752/55129 [============================>.] - ETA: 0s - loss: 0.4533 - acc: 0.8479— val_f1: 0.735079 — val_precision: 0.787528 — val_recall: 0.700854
561 | — val_f1_w: 0.824626 — val_precision_w: 0.828260 — val_recall_w: 0.828273
562 | 55129/55129 [==============================] - 6s 115us/step - loss: 0.4529 - acc: 0.8479 - val_loss: 0.5414 - val_acc: 0.8283
563 | ```
564 |
565 | * 모델 construction과 (최고 performance를 보이는 지점까지의) training-evaluation 입니다. 매 checkpoint에서 모델들이 tutorial이라는 폴더에 저장되어야 하니, 미리 만들어 두어야겠죠 ㅎㅎ TF-IDF의 결과들이 그렇게 만족스럽지는 못했다는 걸 생각하면, 괄목할 만한 성장입니다. accuracy도 올랐고, F1의 평균값 (val_f1)도 상당한 수준으로 상승했네요. one-hot vector을 만들 때 그랬던 것처럼 그냥 구성요소들을 더했을 뿐인데 ...?
566 |
567 | * 겨우 100차원인 벡터들을 더해서 뭘 표현할 수 있을까? 싶은 분들도 분명 계실 겁니다. 하지만, 두 가지를 상기할 필요가 있습니다. (1) word vector들은 one-hot vector들처럼 equivalent하지 않고, 특정 기준에 의해 training되었다 - 즉 그 자체로 어떤 의미를 지니고 있다. (2) 벡터들의 합으로 얻는 벡터 역시 100dim 공간에 표현될 수 있으며, 100dim은 그 방향만 해도 2^100 개 이상을 나타낼 수 있을 정도로 꽤나 많은 것을 표현할 수 있다!
568 |
569 | ---
570 |
571 | **This kind of sentence encoding gives us quite a rich representation of the sentences in the sense that the 100-dimensional vector itself yields a variety of values. This might be advantageous for tasks such as sentiment analysis, in which the inference largely relies on some polarity items or emotion words. However, we should notice that a simple summation does not say anything about the distributional or sequential information the sentence possesses; for instance, “You haven’t done it at all” and “Haven’t you done it at all” share the same word composition but their intention clearly differs. The same kind of problem is more critical in Korean, due to the language being scrambling.**
572 |
573 | * 이런 방식의 sentence vector 만들기는 sentiment classification 같은 task에서는 꽤나 좋은 성능을 냅니다. sentiment는 주로 단어 내의 어떤 polarity 및 subjectivity가 있는 단어에 의해 형성될 가능성이 높은데, 더하는 것만으로도 어떤 word가 있다는 것을 classifier가 알게 하기엔 충분하기 때문이죠. 하지만, summation의 단점은 분포나 순서를 고려할 수 없다는 겁니다. 예컨대, “You haven’t done it at all”과 “Haven’t you done it at all”은 그 단어 구성은 같아도 (capital 여부는 무시합시다) 전달하는 의미는 전혀 다르죠. word vector의 summation이 아니라 concatenation으로 한다면 이런 일을 예방할 수 있겠습니다만, 뭔가 임시방편적인 처방이고 결국 다시 저차원 임베딩이 아니게 되어버리겠죠. 이런 문제를 어떻게 하면 해결할 수 있을까요?
574 |
575 | ## 6. CNN-based sentence classification
576 |
577 | [**Convolutional neural network**](https://ieeexplore.ieee.org/abstract/document/726791) (CNN), originally developed by LeCun, is a widely used neural network system which was primarily suggested for image processing (handwriting recognition). It roughly resembles the perception process of a visual system, summarizing a given image into an abstract values via repititive application of convolution and pooling.
578 |
579 |
581 | (image from https://jeremykarnowski.wordpress.com/2015/07/15/alexnet-visualization/)
582 |
583 | * AI를 공부하시는 분이라면 convolutional neural network, 즉 CNN을 한번쯤 들어보셨을 겁니다. 초기에 르쿤 등등에 의해 연구되고, 6-7년 전쯤부터 폭발적으로 성장하여 지금은 이미지 관련 태스크라면 베이스라인 혹은 그 베리에이션으로 인용된다는 것두요. 그러한 이력 덕분인지, NLP에 CNN을 사용한다고 하면 의아해하는 경우가 있더군요. 저도 사실 익숙해져서 그렇지, 다시 첨부터 써보라고 하면 이게 무슨 소리야 싶을지도 모르겠네요ㅎㅎ
584 |
585 | * 제가 이미지에 CNN을 사용해본 적은 거의 없지만, 쉽게 말해 raw data를 부분부분 보고 그 정보를 추상화하는 과정을 여러번 거친다, 라고 생각하고 있습니다. 아주 러프하게요. 그것이 이미지에 처음 적용된 것이죠. 하지만 사실 정보의 추상화란 이미지에만 적용될 이유는 없습니다. 그래서 저는 cnn을 distributional information의 summarizer로 표현합니다. 어디에 무엇이 있는지 아주 간단하게 요약해 주는.
586 |
587 | ---
588 |
589 | **However, since the very classic breakthrough of [Kim 2014](https://arxiv.org/abs/1408.5882), CNN has been widely used in the text processing, understanding the word vector sequence as a single channel image. Unlike the previous approach where all the information of the words in the sentence are aggregated into a single vector, the featurization for CNN has its limitation in the volume. Thus, here we restrict the maximum length of the morpheme sequence to 30, with zero-padding for the short utterances. Taking into account the head-finality of Korean, we decided to place the word vectors on the right side of the matrix. That is, for the long utterances, only the last 30 morphemes are utilized.**
590 |
591 | ```python
592 | def featurize_cnn(corpus,wdim,maxlen):
593 | conv_total = np.zeros((len(corpus),maxlen,wdim,1))
594 | for i in range(len(corpus)):
595 | if i%1000 ==0:
596 | print(i)
597 | s = corpus[i]
598 | for j in range(len(s)):
599 | if s[-j-1] in model_ft and j < maxlen:
600 | conv_total[i][-j-1,:,0] = model_ft[s[-j-1]]
601 | return conv_total
602 |
603 | fci_conv = featurize_cnn(fci_sp_token_train+fci_sp_token_test,100,30)
604 | ```
605 |
606 |
608 | (image from Kim 2014)
609 |
610 | * 이것이 문장 분류에 어떻게 사용되느냐? 가장 먼저 거치는 과정은 쉽게 말해 문장을 그림처럼 바꾸는 겁니다. 즉, 단일 채널 matrix를 만드는 거죠 (그림은 보통 rgb의 3 channel). 우린 sentence matrix란 걸 논한 적 없으니 word vector들로 어떻게 해 봐야 될 텐데, word vector나 TF-IDF를 가지고는 듬성듬성하게 nonzero가 박혀 있는 것들밖에 만들지 못할 테죠. 애초에 값에 대한 위치 bias가 없는 녀석들이니 순서(order)적인 것 외에 아무 정보도 CNN에 주지는 못할 겁니다.
611 |
612 | * 이 때 다시 등장하는 것이 앞서 언급한 word2vec입니다. 문장을 수치화해 넣을 수 있는 일종의 고정된 사이즈의 도화지가 있다고 생각해 봅시다. 예컨대 100 x 30정도의? 거기에 100-dim word vector 30개를 padding해 넣는 겁니다. 물론 문장 길이가 30이 되지 않을 수도 있지요. 그러면 빈 부분은 0으로 채웁니다. 진짜 없으니까요. 문장이 더 길다면? 자릅니다. 물론 이 부분은 '문장 최대길이'를 조사해서 적절히 설정하면 될 일입니다 (물론 이렇게 하지 않고 모두 보존하는 방법도 있겠습니다만, 일단 여기선 다루지 않겠습니다). 이 과정에서 한국어의 head-finality를 고려하여 문장은 오른쪽에 치우치게 배열하도록 결정하였습니다. 한국어는 역시 끝까지 들어봐야 하니까, 자르더라도 끝은 남겨야죠!
613 |
614 | ---
615 |
616 | **There are so many types of convolutional networks out there (LeNet, AlexNet, VGG, YOLO ...), however such deep and wide networks might not be required for the sentence classification. The model architecture used for the implementation is quite simple; two convolutional layers with the window width 3 and a max pooling layer between with the size (2,1). For the first conv-layer, the window size is (3,100) and for the second (3,1), since the information was abstracted and max-pooled to make up a single vector.**
617 |
618 | ```python
619 | def validate_cnn(result,y,filters,hidden_dim,cw,filename):
620 | model = Sequential()
621 | model.add(layers.Conv2D(filters,(3,len(result[0][0])),activation= 'relu',input_shape = (len(result[0]),len(result[0][0]),1)))
622 | model.add(layers.MaxPooling2D((2,1)))
623 | model.add(layers.Conv2D(filters,(3,1),activation='relu'))
624 | model.add(layers.Flatten())
625 | model.add(layers.Dense(hidden_dim,activation='relu'))
626 | model.add(layers.Dense(int(max(y)+1), activation='softmax'))
627 | model.summary()
628 | model.compile(optimizer=adam_half, loss="sparse_categorical_crossentropy", metrics=["accuracy"])
629 | filepath=filename+"-{epoch:02d}-{val_acc:.4f}.hdf5"
630 | checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, mode='max')
631 | callbacks_list = [metricsf1macro,checkpoint]
632 | model.fit(result,y,validation_split=0.1,epochs=30,batch_size=16,callbacks=callbacks_list,class_weight=cw)
633 |
634 | validate_cnn(fci_conv,fci_label,32,128,class_weights_fci,'model/tutorial/conv')
635 | ```
636 |
637 | * 일단 image를 cnn에 적용하는 과정을 패러미터화하면, 채널, 필터, 컨벌루션레이어, 윈도우, 풀링 정도로 요약할 수 있습니다. 채널은 앞서 말했듯 rgb 같이 몇 개의 요소로 나타내냐이며 필터는 얼마나 병렬로 처리할거냐, 컨벌루션레이어 수는 추상화 과정을 몇번 거칠거냐, 윈도우는 어떤 식으로 각 컨벌루션 레이어를 훑을거냐, 풀링은 컨벌루션 레이어를 훑은 값들에서 중요한 요소들을 어떻게 취사선택할거냐? 이정도로 나타낼 수 있겠네요.
638 |
639 | * 이미지의 cnn은 그래서 일반적으로 3채널, 많은 필터 (>64?), 다층 컨벌루션 레이어, 상하좌우로 stride되는 3 by 3 혹은 5 by 5 window 등으로 요약될 수 있습니다. 물론 alexnet, vgg, yolo 등 다양한 아키텍쳐들이 있고, 모두 특색이 있겠지만, 기본적으론 저렇습니다. 하지만 word vector sequence에서 상하좌우로 움직이는 window가 어떤 의미가 있을까요? 우리는 100dim의 벡터 각 엔트리에 어떤 성질의 성분들이 자리잡고 있는지 알지 못하며, 굳이 그런 성질을 지정해줄 필요도 느끼지 못하였습니다. 이미지는 두차원 모두가 semantic을 포함하지만, sentence에서 semantic이 의미가 있는 방향은 word vector가 pad되는 방향이니까요.
640 |
641 | * 그래서 저는 sentence의 cnn에선 3 by 100 혹은 5 by 100 window를 사용합니다. 결론적으론 2D convolution이 1D처럼 돼버리긴 합니다만, 역설적으로 문장을 그림이 아니라 문장처럼 볼 수 있는 방법이 되는 것 같아요. Word2vec의 결과물로 나온 그 단어의 vector의 특색을 결정짓는 entry를 가장 왜곡 없이 전달해줄 수 있는 방법이라고 저는 보고 있습니다. 그 이후의 max-pooling과 추가적인 convolution 아키텍쳐는 개인의 선택에 달렸지만요. Boolean distributional semantics처럼 word vector의 각 entry가 어떤 의미를 갖는다면 모르겠지만, 그렇지 않다면 문장은 문장처럼 읽는 것이 cnn에 있어서도 효과적이지 않나? 라는 것이 저의 생각입니다. (물론 반례 및 피드백은 언제나 환영입니다!)
642 |
643 | ---
644 |
645 | **The result is encouraging! Although there was a little improvement in F1 score, we had about 20% RRER for the accuracy (regarding the model with the best performance). The CNN-based featurization and classification of the sentence shows quite satisfactory result with very fast training. However, the architecture does not seem to still convey the correlation between the non-consecutive components. The recurrent neural network covers such characteristics, with a sequence-based approach.**
646 |
647 | ```properties
648 | # CONSOLE RESULT
649 | >>> validate_cnn(fci_conv,fci_label,32,128,class_weights_fci,'model/tutorial/conv')
650 | _________________________________________________________________
651 | Layer (type) Output Shape Param #
652 | =================================================================
653 | conv2d_1 (Conv2D) (None, 28, 1, 32) 9632
654 | _________________________________________________________________
655 | max_pooling2d_1 (MaxPooling2 (None, 14, 1, 32) 0
656 | _________________________________________________________________
657 | conv2d_2 (Conv2D) (None, 12, 1, 32) 3104
658 | _________________________________________________________________
659 | flatten_1 (Flatten) (None, 384) 0
660 | _________________________________________________________________
661 | dense_5 (Dense) (None, 128) 49280
662 | _________________________________________________________________
663 | dense_6 (Dense) (None, 7) 903
664 | =================================================================
665 | Total params: 62,919
666 | Trainable params: 62,919
667 | Non-trainable params: 0
668 | _________________________________________________________________
669 | Train on 55129 samples, validate on 6126 samples
670 | Epoch 1/30
671 | 54992/55129 [============================>.] - ETA: 0s - loss: 0.5817 - acc: 0.8026— val_f1: 0.715119 — val_precision: 0.766993 — val_recall: 0.687496
672 | — val_f1_w: 0.836404 — val_precision_w: 0.840650 — val_recall_w: 0.842148
673 | 55129/55129 [==============================] - 21s 375us/step - loss: 0.5816 - acc: 0.8027 - val_loss: 0.4907 - val_acc: 0.8421
674 | Epoch 2/30
675 | 54992/55129 [============================>.] - ETA: 0s - loss: 0.4391 - acc: 0.8505— val_f1: 0.748518 — val_precision: 0.785899 — val_recall: 0.724397
676 | — val_f1_w: 0.850521 — val_precision_w: 0.852202 — val_recall_w: 0.853901
677 | 55129/55129 [==============================] - 21s 376us/step - loss: 0.4389 - acc: 0.8506 - val_loss: 0.4531 - val_acc: 0.8539
678 | Epoch 3/30
679 | 54992/55129 [============================>.] - ETA: 0s - loss: 0.3934 - acc: 0.8667— val_f1: 0.755061 — val_precision: 0.791092 — val_recall: 0.731512
680 | — val_f1_w: 0.855977 — val_precision_w: 0.857298 — val_recall_w: 0.859288
681 | 55129/55129 [==============================] - 20s 371us/step - loss: 0.3936 - acc: 0.8667 - val_loss: 0.4413 - val_acc: 0.8593
682 | Epoch 4/30
683 | 54992/55129 [============================>.] - ETA: 0s - loss: 0.3640 - acc: 0.8753— val_f1: 0.762634 — val_precision: 0.783417 — val_recall: 0.755429
684 | — val_f1_w: 0.853230 — val_precision_w: 0.858127 — val_recall_w: 0.856024
685 | 55129/55129 [==============================] - 21s 374us/step - loss: 0.3640 - acc: 0.8753 - val_loss: 0.4558 - val_acc: 0.8560
686 | Epoch 5/30
687 | 54992/55129 [============================>.] - ETA: 0s - loss: 0.3402 - acc: 0.8824— val_f1: 0.756524 — val_precision: 0.798401 — val_recall: 0.728596
688 | — val_f1_w: 0.857324 — val_precision_w: 0.857582 — val_recall_w: 0.860431
689 | 55129/55129 [==============================] - 20s 370us/step - loss: 0.3403 - acc: 0.8824 - val_loss: 0.4348 - val_acc: 0.8604
690 | Epoch 6/30
691 | 55024/55129 [============================>.] - ETA: 0s - loss: 0.3210 - acc: 0.8910— val_f1: 0.761926 — val_precision: 0.782188 — val_recall: 0.748026
692 | — val_f1_w: 0.856406 — val_precision_w: 0.857747 — val_recall_w: 0.858146
693 | 55129/55129 [==============================] - 21s 374us/step - loss: 0.3209 - acc: 0.8910 - val_loss: 0.4406 - val_acc: 0.8581
694 | Epoch 7/30
695 | 55040/55129 [============================>.] - ETA: 0s - loss: 0.3051 - acc: 0.8947— val_f1: 0.758700 — val_precision: 0.784020 — val_recall: 0.740566
696 | — val_f1_w: 0.850813 — val_precision_w: 0.850469 — val_recall_w: 0.853575
697 | 55129/55129 [==============================] - 21s 373us/step - loss: 0.3051 - acc: 0.8947 - val_loss: 0.4540 - val_acc: 0.8536
698 | Epoch 8/30
699 | 55088/55129 [============================>.] - ETA: 0s - loss: 0.2900 - acc: 0.9003— val_f1: 0.764175 — val_precision: 0.814364 — val_recall: 0.732869
700 | — val_f1_w: 0.861861 — val_precision_w: 0.865253 — val_recall_w: 0.865491
701 | 55129/55129 [==============================] - 21s 373us/step - loss: 0.2899 - acc: 0.9004 - val_loss: 0.4441 - val_acc: 0.8655
702 | ```
703 |
704 | * 결과입니다. F1 score에서는 그렇게 큰 향상을 얻지는 못했으나, accuracy는 0.8283에서 0.8655로, error rate가 약 17%에서 14%로 감소하며 약 20%의 에러율 감소 (RRER)을 보였습니다. 아무래도 distributional한 information을 반영하는 것이, 단순히 existence를 체크하는 것보다는 더 정확하겠죠.
705 |
706 | * 이상으로 distributional information의 가장 효과적인 summarizer 중 하나에 대하여 살펴봤습니다. 하지만 그 단점은, non-consecutive한 성분들 간의 상관관계를 설명하기 쉽지 않다는 점이죠. 다음 편부터 설명되는 rnn은 그러한 점들을 보완합니다.
707 |
708 | ## 7. RNN (BiLSTM)-based sentence classification
709 |
710 | ***Recurrent neural network (RNN)*, which was suggested originally in the late 20th century, is a representative network that reflects the sequential information in the numerical summarization. Due to high-computation issue, its materialization has recently been possible with the help of modern computing systems (e.g. GPU boost-up). The problem of vanishing gradient has been partially solved by [*long short-term memory (LSTM)*](https://www.mitpressjournals.org/doi/abs/10.1162/neco.1997.9.8.1735), whose direction-bias was improved along with the bidirectional sequencing (BiLSTM).**
711 |
712 |
714 | (image from https://aikorea.org/blog/rnn-tutorial-1/)
715 |
716 | * RNN, 즉 recurrent neural network란 time-series의 input을 받아, 그에 대한 latent information을 담고 있는 hidden layer sequence를 생성하되, 특정 시점에서의 hidden layer가 그 시점에서의 input data와 이전 시점의 hidden layer로부터 연산되는 알고리듬입니다. hidden Markov model (HMM)과 그 컨셉은 유사하지만 바로 이전 시점에만 영향받지 않고, 앞서 있는 데이터 모두의 정보를 뒷부분의 hidden layer에 반영한다는 장점이 있죠. 쉽게 말해 sequential data의 summarization이라고 보면 될 것 같네요.
717 |
718 | * rnn을 트레이닝하는 과정 역시 일반적인 mlp나 cnn에서와 마찬가지로 back-propagation을 이용하게 되는데요, 이 과정에서 vanishing gradient의 문제가 발생하게 됩니다. 너무 많은 정보들이 encoding되다 보니 패러미터가 폭발해 버리는 겁니다. 사람은 이와 다르게, 문장이나 글이 길어지게 되면 너무 멀리 떨어져 있는 정보는 잊어버리죠 :D 뭐 그게 꼭 좋은 건 아니겠지만서두, 정보 과잉을 방지해주거나 뭐 그렇지 않겠습니까? 그런 컨셉으로 나온 것이 적당히 forget gate를 추가한, 1997년의 long short-term memory (LSTM) 입니다. lstm이 앞부분의 정보를 반영하지 못한다는 단점을 보완하기 위해 제시된 것이, lstm을 양방향으로 (처음에서 시작해서 끝으로, 끝에서 시작해서 처음으로) 하여 얻은 hidden layer sequences를 augment한 Bidirectional lstm도 같은 해에 제시되었구요. 생각보다 옛날인데 왜 이제 와서 유행하게 됐냐구요? 계산량이 엄청나기 때문이죠... 갓비디아 짱짱컴퍼니
719 |
720 | ---
721 |
722 | **In this tutorial, we utilize the BiLSTM structure. The featurization is almost identical to the case of CNN, except that the channel information is omitted. To say short, for the same data (ignoring the channel number), CNN extracts locally notable features and RNN extracts the relations reflected in the sequential arrangement.**
723 |
724 | ```python
725 | def featurize_rnn(corpus,wdim,maxlen):
726 | rnn_total = np.zeros((len(corpus),maxlen,wdim))
727 | for i in range(len(corpus)):
728 | if i%1000 ==0:
729 | print(i)
730 | s = corpus[i]
731 | for j in range(len(s)):
732 | if s[-j-1] in model_ft and j < maxlen:
733 | rnn_total[i][-j-1,:] = model_ft[s[-j-1]]
734 | return rnn_total
735 |
736 | fci_rec = featurize_rnn(fci_sp_token_train+fci_sp_token_test,100,30)
737 |
738 | from keras.layers import LSTM
739 | from keras.layers import Bidirectional
740 |
741 | def validate_bilstm(result,y,hidden_lstm,hidden_dim,cw,filename):
742 | model = Sequential()
743 | model.add(Bidirectional(LSTM(hidden_lstm), input_shape=(len(result[0]), len(result[0][0]))))
744 | model.add(layers.Dense(hidden_dim, activation='relu'))
745 | model.add(layers.Dense(int(max(y)+1), activation='softmax'))
746 | model.summary()
747 | model.compile(optimizer=adam_half, loss="sparse_categorical_crossentropy", metrics=["accuracy"])
748 | filepath=filename+"-{epoch:02d}-{val_acc:.4f}.hdf5"
749 | checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, mode='max')
750 | callbacks_list = [metricsf1macro,checkpoint]
751 | model.fit(result,y,validation_split=0.1,epochs=30,batch_size=16,callbacks=callbacks_list,class_weight=cw)
752 |
753 | validate_bilstm(fci_rec,fci_label,32,128,class_weights_fci,'model/tutorial/rec')
754 | ```
755 |
756 | * 본 튜토리얼에서는 가장 무난히 적용할 수 있는 BiLSTM을 사용합니다. LSTM의 문제는 문장이 길어질수록 앞부분의 내용을 반영하지 못할 수 있다는 것인데, BiLSTM은 LSTM을 양방향으로 돌린 두 sequence를 hidden layer sequence 하나로 concatenate하면서 어느 정도 그런 점을 보완할 수 있게 합니다. CNN과 간단히 차이를 말해 보자면, CNN은 global하게 본 후 local하게 중요한 피쳐들을 뽑아내어 추상화시키는 것이고, RNN (BiLSTM)은 sequential한 배열에 내재한 관계를 추상화한다고 보면 될 것 같습니다.
757 |
758 | ---
759 |
760 | **We obtained another boost in performance, especially very high in F1 score. This implies that our task which deals with the intention of Korean sentences is largely influenced by the word order. This may have been an important factor in identifying the rhetoricalness, for instance, '뭐 해 지금 (what / do / now, *what (on earth) are you doing now*)' sounds much more rhetorical than '지금 뭐 해 (now / what / do, *what are you doing now*)', in view of native. However, so far we've conducted the experiments given the morpheme-level decomposition. Will the real semantics NOT be embedded in the characters? Well, before that, how should *character* be defined in Korean, which has distinguished morpho-syllabic blocks (which are different from alphabets) as a unit of character? **
761 |
762 | ```properties
763 | # CONSOLE RESULT
764 | >>> validate_bilstm(fci_rec,fci_label,32,128,class_weights_fci,0.1,16,'model/tutorial/rec')
765 | _________________________________________________________________
766 | Layer (type) Output Shape Param #
767 | =================================================================
768 | bidirectional_1 (Bidirection (None, 64) 34048
769 | _________________________________________________________________
770 | dense_7 (Dense) (None, 128) 8320
771 | _________________________________________________________________
772 | dense_8 (Dense) (None, 7) 903
773 | =================================================================
774 | Total params: 43,271
775 | Trainable params: 43,271
776 | Non-trainable params: 0
777 | _________________________________________________________________
778 | Train on 55129 samples, validate on 6126 samples
779 | Epoch 1/50
780 | 55088/55129 [============================>.] - ETA: 0s - loss: 0.5545 - acc: 0.8145— val_f1: 0.732477 — val_precision: 0.764964 — val_recall: 0.716559
781 | — val_f1_w: 0.838581 — val_precision_w: 0.844676 — val_recall_w: 0.841169
782 | 55129/55129 [==============================] - 86s 2ms/step - loss: 0.5544 - acc: 0.8145 - val_loss: 0.4835 - val_acc: 0.8412
783 | Epoch 2/50
784 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.4302 - acc: 0.8532— val_f1: 0.755420 — val_precision: 0.818620 — val_recall: 0.719585
785 | — val_f1_w: 0.856714 — val_precision_w: 0.860478 — val_recall_w: 0.860757
786 | 55129/55129 [==============================] - 83s 2ms/step - loss: 0.4302 - acc: 0.8531 - val_loss: 0.4248 - val_acc: 0.8608
787 | Epoch 3/50
788 | 55088/55129 [============================>.] - ETA: 0s - loss: 0.3909 - acc: 0.8658— val_f1: 0.769535 — val_precision: 0.820192 — val_recall: 0.738047
789 | — val_f1_w: 0.860804 — val_precision_w: 0.862253 — val_recall_w: 0.864675
790 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.3908 - acc: 0.8658 - val_loss: 0.4071 - val_acc: 0.8647
791 | Epoch 4/50
792 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3626 - acc: 0.8757— val_f1: 0.780424 — val_precision: 0.814412 — val_recall: 0.758550
793 | — val_f1_w: 0.868592 — val_precision_w: 0.869740 — val_recall_w: 0.870389
794 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.3626 - acc: 0.8758 - val_loss: 0.3861 - val_acc: 0.8704
795 | Epoch 5/50
796 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3419 - acc: 0.8823— val_f1: 0.783106 — val_precision: 0.806826 — val_recall: 0.767035
797 | — val_f1_w: 0.871497 — val_precision_w: 0.871639 — val_recall_w: 0.872837
798 | 55129/55129 [==============================] - 83s 2ms/step - loss: 0.3418 - acc: 0.8823 - val_loss: 0.3835 - val_acc: 0.8728
799 | Epoch 6/50
800 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3238 - acc: 0.8889— val_f1: 0.787453 — val_precision: 0.815045 — val_recall: 0.766895
801 | — val_f1_w: 0.872195 — val_precision_w: 0.874534 — val_recall_w: 0.874633
802 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.3239 - acc: 0.8889 - val_loss: 0.3798 - val_acc: 0.8746
803 | Epoch 7/50
804 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3093 - acc: 0.8935— val_f1: 0.782065 — val_precision: 0.797697 — val_recall: 0.772691
805 | — val_f1_w: 0.873711 — val_precision_w: 0.875246 — val_recall_w: 0.873327
806 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.3093 - acc: 0.8935 - val_loss: 0.3732 - val_acc: 0.8733
807 | Epoch 8/50
808 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2953 - acc: 0.8978— val_f1: 0.777524 — val_precision: 0.800048 — val_recall: 0.762805
809 | — val_f1_w: 0.867708 — val_precision_w: 0.868467 — val_recall_w: 0.869572
810 | 55129/55129 [==============================] - 83s 1ms/step - loss: 0.2952 - acc: 0.8978 - val_loss: 0.3942 - val_acc: 0.8696
811 | Epoch 9/50
812 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2820 - acc: 0.9028— val_f1: 0.783454 — val_precision: 0.822934 — val_recall: 0.758621
813 | — val_f1_w: 0.875902 — val_precision_w: 0.877293 — val_recall_w: 0.879367
814 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2820 - acc: 0.9028 - val_loss: 0.3732 - val_acc: 0.8794
815 | Epoch 10/50
816 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2708 - acc: 0.9065— val_f1: 0.790558 — val_precision: 0.811155 — val_recall: 0.773668
817 | — val_f1_w: 0.877717 — val_precision_w: 0.876924 — val_recall_w: 0.879530
818 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2708 - acc: 0.9065 - val_loss: 0.3804 - val_acc: 0.8795
819 | Epoch 11/50
820 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2572 - acc: 0.9108— val_f1: 0.778422 — val_precision: 0.789590 — val_recall: 0.770837
821 | — val_f1_w: 0.870727 — val_precision_w: 0.872009 — val_recall_w: 0.872674
822 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2571 - acc: 0.9108 - val_loss: 0.3960 - val_acc: 0.8727
823 | Epoch 12/50
824 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2460 - acc: 0.9154— val_f1: 0.788357 — val_precision: 0.805196 — val_recall: 0.775248
825 | — val_f1_w: 0.876743 — val_precision_w: 0.876289 — val_recall_w: 0.878387
826 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2460 - acc: 0.9154 - val_loss: 0.3865 - val_acc: 0.8784
827 | Epoch 13/50
828 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2356 - acc: 0.9185— val_f1: 0.768287 — val_precision: 0.766577 — val_recall: 0.772194
829 | — val_f1_w: 0.864052 — val_precision_w: 0.865701 — val_recall_w: 0.864022
830 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2355 - acc: 0.9186 - val_loss: 0.4198 - val_acc: 0.8640
831 | Epoch 14/50
832 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2253 - acc: 0.9225— val_f1: 0.768811 — val_precision: 0.775967 — val_recall: 0.768394
833 | — val_f1_w: 0.864381 — val_precision_w: 0.867127 — val_recall_w: 0.864185
834 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2254 - acc: 0.9225 - val_loss: 0.4194 - val_acc: 0.8642
835 | Epoch 15/50
836 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2147 - acc: 0.9258— val_f1: 0.782913 — val_precision: 0.790473 — val_recall: 0.778720
837 | — val_f1_w: 0.875200 — val_precision_w: 0.876058 — val_recall_w: 0.875775
838 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2147 - acc: 0.9258 - val_loss: 0.4084 - val_acc: 0.8758
839 | Epoch 16/50
840 | 55088/55129 [============================>.] - ETA: 0s - loss: 0.2045 - acc: 0.9285— val_f1: 0.790696 — val_precision: 0.819661 — val_recall: 0.768516
841 | — val_f1_w: 0.878328 — val_precision_w: 0.878134 — val_recall_w: 0.880673
842 | 55129/55129 [==============================] - 82s 1ms/step - loss: 0.2044 - acc: 0.9285 - val_loss: 0.4099 - val_acc: 0.8807
843 | ```
844 |
845 | * 놀랍게도 정확도가 다시 한번 올랐고, F1 score가 상당한 수준으로 올랐습니다. 평균치가 저 정도 올랐다는 것은, 이제 아무리 F1 score가 안 좋아도 0.5 정도는 된다는 걸 의미한다고 봐도 무방할 것 같습니다. 처음에 TF-IDF로 코드를 돌릴 때 intonation-dependent utterances (마지막 case) 에 대해 상당히 낮은 F1 score가 나왔던 것 같은데, 해당 태스크에서 상대적으로 Recall (재현률)을 높게 하여 false alarm을 울리는 것이 false positive보다는 낫다는 점을 고려하면 나쁘지 않은 결과입니다. 그런데 한 가지 간과한 것이 있습니다. 지금까지 우리는 열심히 morpheme들을 가지고 놀았는데, 과연 이것을 character-level로 끊어 보면 어떻게 될까요? 아니, 그 전에 우선, 한국어에서 character을 어떻게 정의하는 것이 바람직할까요?
846 |
847 | ## 8. Character embedding
848 |
849 | **Due to the distinguished writing style, the embedding of Korean letters *Hangul* is difficult even from its definition. For many Romanian or German langauges, the letters of latin alphabet are utilized as character and thereby character-level embeddding has been widely used in the region of text analysis since [Zhang, 2015](http://papers.nips.cc/paper/5782-character-level-convolutional-networks-for-text-classifica). However, for Korean, the morpho-syllabic blocks that represent the syllables, can be decomposed into *jamo*, the alphabet of the Korean writing system.**
850 |
851 | **To be specific, The letters of alphabet make up the morpho-syllabic blocks (characters) that are equal to the phonetic unit of the syllable, in the conjunct form of Syllable: CV(C). This notation implies that there should be at least one consonant (namely *cho-seng*, the first sound) and one vowel (namely *cwung-seng*, the second sound). An additional consonant (namely *cong-seng*, the third sound) is auxiliary. However, usually in the character decomposition, three slots are fully held for each component; an empty cell comes for the third entry if there is no auxiliary consonant. The number of the possible letters, or (composite) consonants/vowels, that can come for each slot is 19, 21, and 27. For instance, in a character '각 (*kak*)', three clock-wisely arranged alphabets ㄱ, ㅏ, and ㄱ, each sounds *k*, *a*, and *k* respectively.**
852 |
853 | * 앞서 말했듯, 한국어의 writing system은 자연발생된 것이 아닌 단체 (거진 개인)에 의해 창조되었다는 점에서 매우 특별하며, 어떤 음절에 해당하는 morpho-syllabic block을 decompose하여 그 음절을 이루는 소리 성분들에 해당하는 sub-character, 즉 자모를 알아낼 수 있다는 점에서도 독특합니다.
854 |
855 | * 좀 더 자세히 얘기하자면 한글은 CV(C)의 conjunct form으로 되어 있으며, 이는 한 음절을 구성하기 위해 최소한 자모 한 개씩은 있어야 함을 의미합니다. 이는 초성과 중성이라고 불리며, CV를 지칭하죠. 종성, 즉 third sound (C)의 경우는, 있어도 되고 없어도 상관없습니다. 초성으로 가능한 자음은 19개, 중성으로 가능한 모음은 21개이며, 종성으로 가능한 자음은 composite consonants를 포함하여 27개입니다 (empty일 경우 포함하면 28개).
856 |
857 | ---
858 |
859 | **For a clearness, let's denote the morpho-syllabic blocks as *characters* and the consonants/vowels as *Jamo*. The description above yields total 11,172 possible characters. However, one-hot encoding those combinations into 11,172-dim vector seems very redundant at a glance. Therefore, there have been various character encoding schemes suggested. The schemes include two approaches; (1) decomposing the blocks into sub-characters and (2) preserving them.**
860 |
861 | * 보다 논의를 명확히 하기 위해, 음절들을 character라고 하고 자모를 Jamo라고 둡시다. 위의 설명에 따르면 우리는 총 11,172개의 가능한 자모 조합을 얻는데, 이를 one-hot encoding하는 것은 매우 costly해 보이기도 하거니와 그에 따른 merit을 딱히 찾기도 어렵습니다. 따라서 이 문제에 대하여 한국어 character을 encoding하는 다양한 방법들이 소개되었는데, 이 방법들은 크게 1) 자소 분리 2) 음절 유지의 두 가지로 나뉠 수 있습니다.
862 |
863 | ---
864 |
865 | **For the first approach, the most simple way is to spread a character into three alphabet sequence. There are some toolkits (e.g., [hgtk](https://github.com/bluedisk/hangul-toolkit)) that decompose Hangul characters into alphabets; by utilizing them, we can obtain the tuple ('ㄱ', 'ㅏ', 'ㄱ') from a single character '각'. This blurs the property of full characters but allows a sparse representation with low dimensional vectors (of size 67). However note that the total length of numericalization increases since the sole alphabets ('ㄱ' and 'ㅏ') and the characters ('각') are assigned equally two bytes. Due to this, degrade in computation efficiency is inavoidable. To deal with this, [romanization has been suggested and showed a good performance](https://arxiv.org/abs/1708.02657), but here we don't utilize it since the processing may induce an ambiguity. **
866 |
867 | **In this tutorial, we present two conventional methodologies in one-hot encoding, namely [Shin](https://www.dbpia.co.kr/Journal/ArticleDetail/NODE07207314#) and [Cho](http://www.dbpia.co.kr/Journal/ArticleDetail/NODE07503227). The former is a simple 67-dim version, and the latter considers the cases where the alphabets are used solely (e.g., 'ㅠㅠ', 'ㅋㅋ'), not in a form of block. Each corresponds with *shin_onehot* and *cho_onehot* in the code below. A description on *char2onehot* will be provided afterwards.**
868 |
869 | ```python
870 | import hgtk
871 | from hgtk.letter import decompose as decom
872 |
873 | choseng = ['ㄱ','ㄴ','ㄷ','ㄹ','ㅁ','ㅂ','ㅅ','ㅇ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ','ㄲ','ㄸ','ㅃ','ㅆ','ㅉ']
874 | cwungseng = ['ㅏ','ㅑ','ㅓ','ㅕ','ㅗ','ㅛ','ㅜ','ㅠ','ㅡ','ㅣ','ㅐ','ㅒ','ㅔ','ㅖ','ㅘ','ㅙ','ㅚ','ㅝ','ㅞ','ㅟ','ㅢ']
875 | congseng = ['ㄱ','ㄴ','ㄷ','ㄹ','ㅁ','ㅂ','ㅅ','ㅇ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ','ㄲ','ㅆ','ㄳ','ㄵ','ㄶ','ㄺ','ㄻ','ㄼ','ㄽ','ㄾ','ㄿ','ㅀ','ㅄ
876 | ']
877 | alp = choseng+cwungseng+congseng
878 | uniquealp = list(set(choseng+cwungseng+congseng))
879 |
880 | def cho2onehot(s):
881 | res = np.zeros(len(choseng))
882 | if s in choseng:
883 | res[choseng.index(s)]=1
884 | return res
885 |
886 | def cwu2onehot(s):
887 | res = np.zeros(len(cwungseng))
888 | if s in cwungseng:
889 | res[cwungseng.index(s)]=1
890 | return res
891 |
892 | def con2onehot(s):
893 | res = np.zeros(len(congseng))
894 | if s in congseng:
895 | res[congseng.index(s)]=1
896 | return res
897 |
898 | def uni2onehot(s):
899 | res = np.zeros(len(uniquealp))
900 | if s in uniquealp:
901 | res[uniquealp.index(s)]=1
902 | return res
903 |
904 | def shin_onehot(s):
905 | z = decom(s)
906 | res = np.zeros((len(alp),3))
907 | res[:len(choseng),0] = cho2onehot(z[0])
908 | res[len(choseng):len(choseng)+len(cwungseng),1] = cwu2onehot(z[1])
909 | res[len(choseng)+len(cwungseng):len(alp),2] = con2onehot(z[2])
910 | return res
911 |
912 | def cho_onehot(s):
913 | z = decom(s)
914 | res = np.zeros((len(alp)+len(uniquealp),3))
915 | if len(z[0]+z[1]+z[2]) > 1:
916 | res[:len(alp),:] = shin_onehot(s)
917 | elif len(z[0])>0:
918 | res[len(alp):,0] = uni2onehot(s)
919 | elif len(z[1])>0:
920 | res[len(alp):,1] = uni2onehot(s)
921 | else:
922 | res[len(alp):,2] = uni2onehot(s)
923 | return res
924 |
925 | def char2onehot(s):
926 | z = decom(s)
927 | res = np.concatenate([cho2onehot(z[0]),cwu2onehot(z[1]),con2onehot(z[2])])
928 | return res
929 | ```
930 |
931 | * 자소 분리의 방법에 있어 가장 먼저 생각해볼 수 있는 것은 한 글자 (character)을 세 개의 자모 sequence (e.g., '각' > 'ㄱ', 'ㅏ', 'ㄱ')로 나타내는 것입니다. 이를 손쉽게 수행할 수 있는 [hgtk](https://github.com/bluedisk/hangul-toolkit)와 같은 툴킷들도 제공되고 있구요. 이러한 변환이 full character (syllabic block)의 property를 뚜렷하게 하지 못할 수 있으나, low dimension(67?)의 sparse representation을 가능하게 한다는 점에서 유용할 수 있습니다. 다만 이 과정에서 정보량이 세 배로 증가해서 computation efficiency를 떨어뜨릴 수 있다는 점은 단점이 될 수 있겠네요. 이를 해결할 수 있는 방법으로 romanized Hangul을 생각해볼 수 있겠지만, 직관적으로 어색하고 중의성을 유발할 수 있기에 여기서는 따로 다루지 않겠습니다.
932 |
933 | * 본 튜토리얼에서 다루는 one-hot encoding방법은 [Shin](https://www.dbpia.co.kr/Journal/ArticleDetail/NODE07207314#) 과 [Cho](http://www.dbpia.co.kr/Journal/ArticleDetail/NODE07503227)의 두 가지입니다. 전자는 앞서 말한 67-dim의 단순 임베딩이며, 후자는 자모가 단독으로 사용되는 경우들을 고려하여 unique하게 사용되는 것을 지칭하는 별도의 벡터를 augment해 줍니다. 물론 해당 논문들에서는 특수 기호들에 대한 placeholder들도 고려하지만, 3i4k데이터셋에는 punctuation이 지워져 있기 때문에 각각 67/118 dim을 쓰는 것으로 충분하다고 보도록 하겠습니다. *char2onehot*에 대한 설명은 다음 단락에서 이어가도록 하겠습니다.
934 |
935 | ---
936 |
937 | For the second type of approachs, namely block-preserving ones, a significant advantage is that we can treat the characters as subword, which can be detered in the previous approaches. Theoretically we can utilize 11,172 characters as a syllable, but in real life it is sufficient with only around 2,500 ones. This allows us to utilize the one-hot encoding of the syllables. A simple integer-indexed dictionary that contains 2,534 syllables from [100-dimension fastText vector dictionary which was trained with 2M drama scripts](https://drive.google.com/open?id=1jHbjOcnaLourFzNuP47yGQVhBTq6Wgor) is uploaded here. However, another practical approach that can be adopted is using the dense character vectors from the fastText dictionary we've embedded. It compresses a sparse 2,534-dim vector into a dense 100-dim vector, possibly reflecting the distributive semantics of syllables as subwords of morphemes.
938 |
939 | Besides, another sparse embedding scheme that aggregates all sounds in a block into a form of multi-hot encoding, was suggested recently, and we call it [Song](https://www.researchgate.net/publication/331987503_Sequence-to-Sequence_Autoencoder_based_Korean_Text_Error_Correction_using_Syllable-level_Multi-hot_Vector_Representation/stats). The code below imports the dictionary for sparse syllable encoding, and the multi-hot encoded vectors can be obtained by *char2onehot* defined above.
940 |
941 | ```python
942 | kor_char = np.load('kor_char.npy').item()
943 | ```
944 |
945 | * 그 다음으로 자소 분리를 하지 않고 음절을 임베딩하는 것을 생각해볼 수 있는데요, 가장 큰 장점은 일종의 subword로써의 음절의 성질을 유지할 수 있다는 점입니다. 물론 형태소가 '-ㄴ', '-ㄹ'처럼 decompose되어 나타나는 경우도 있지만, 앞서 말했듯 우리는 non-invasive한 형태소 분석기를 쓸 거니까요. 위에 언급했듯 가능한 음절 조합은 11,172개이지만 실생활에서 사용되는 가짓수는 대략 2,500개 정도이며 이는 NN classification들에서 사용했던 fastText dictionary에 존재하는 음절의 갯수가 2,534개임을 고려해 볼 때 어느 정도 타당한 주장임을 알 수 있습니다. 그래서 가장 먼저 생각해볼 수 있는 one-hot encoding 방법은 해당 dictionary에 있는 모든 음절에 번호를 매겨 사용하는 것이죠. 물론 2,534도 작은 수는 아닙니다만, 모두 사용되는지조차 알 수 없는 11,172개의 음절을 모두 가정하는 것보단 훨씬 효율적이리라 예측할 수 있지요. 해당 음절 모음은 위의 code를 통해 import할 수 있습니다.
946 |
947 | * 또다른 음절 임베딩 방법으로는, 언급했던 fastText dictionary에 있는 length 1의 단어들 (음절들)의 dense embedding만 활용하는 것을 생각해볼 수 있겠습니다. 이는 2,534의 크기를 100으로 줄이면서, 음절들 간의 distributional semantics도 고려해줄 수 있기에 상당한 성능 개선이 있을 것으로 예상해볼 수 있는 방법입니다. 실제로 제가 [딥러닝 기반 띄어쓰기](https://github.com/warnikchow/ttuyssubot)에서 활용하여 어느 정도의 효과를 보았지요.
948 |
949 | * 그리고 또 하나의 임베딩 방법이 최근 제시되었는데, 바로 하나의 character의 초/중/종성을 multi-hot vector로 만들어 주는 방법입니다. 저는 그런 생각을 한 게 제가 처음인 줄 알고 일단 특허 쓰고 논문을 작성 중이었는데, 구글 스칼라에서는 검색되지 않지만 이미 2018년 HCLT에서 [같은 방법](https://www.researchgate.net/publication/331987503_Sequence-to-Sequence_Autoencoder_based_Korean_Text_Error_Correction_using_Syllable-level_Multi-hot_Vector_Representation/stats)이 제시가 되었더라구요 ㅎㅎ역시 이 세계는 생각나는 대로 바로바로 하지 않으면 누가 하는 곳... 어쨌든 그래서 그 방식도 함께 여기서 비교해 보도록 하겠습니다. 해당 위에 있는 *char2onehot*을 통해 정의됩니다.
950 |
951 | ---
952 |
953 | **The aforementioned methodologies are implemented in the following code as a featurization for RNN. Since the number of morphemes are generally smaller than the number of 솓 characters, we fixed the max character length to 80. Consequently, the sequence length for the *Jamo*-level embeddings (Shin & Cho) reaches 240. Again, to reflect the head-finality of Korean, the Jamos/characters are padded from the sentence-final.**
954 |
955 | ```python
956 | def featurize_rnnchar(corpus,wdim,chardict,maxlen):
957 | rnn_shin = np.zeros((len(corpus),maxlen*3,len(alp)))
958 | rnn_cho = np.zeros((len(corpus),maxlen*3,len(alp)+len(uniquealp)))
959 | rnn_char = np.zeros((len(corpus),maxlen,len(alp)))
960 | rnn_onehot= np.zeros((len(corpus),maxlen,len(chardict)))
961 | rnn_total = np.zeros((len(corpus),maxlen,wdim))
962 | for i in range(len(corpus)):
963 | if i%1000 ==0:
964 | print(i)
965 | s = corpus[i]
966 | for j in range(len(s)):
967 | if j < maxlen and hgtk.checker.is_hangul(s[-j-1])==True:
968 | if j>0:
969 | rnn_shin[i][-3*j-3:-3*j,:] = np.transpose(shin_onehot(s[-j-1]))
970 | rnn_cho[i][-3*j-3:-3*j,:] = np.transpose(cho_onehot(s[-j-1]))
971 | else:
972 | rnn_shin[i][-3*j-3:,:] = np.transpose(shin_onehot(s[-j-1]))
973 | rnn_cho[i][-3*j-3:,:] = np.transpose(cho_onehot(s[-j-1]))
974 | rnn_char[i][-j-1,:] = char2onehot(s[-j-1])
975 | if s[-j-1] in model_ft:
976 | rnn_total[i][-j-1,:] = model_ft[s[-j-1]]
977 | if s[-j-1] in chardict:
978 | rnn_onehot[i][-j-1,chardict[s[-j-1]]]=1
979 | return rnn_shin, rnn_cho, rnn_char, rnn_onehot, rnn_total
980 |
981 | fci_rec_shin, fci_rec_cho, fci_rec_char, fci_rec_onehot, fci_rec = featurize_rnnchar(fci_data,100,kor_char,80)
982 | ```
983 |
984 | * 앞서 얘기한 character embedding 방법론들이 RNN featurization의 형태로 정리된 코드입니다. 각각 Shin, Cho, Song, one-hot encoded char embedding, 그리고 dense char embedding 을 output으로 합니다. 보통 형태소의 개수보다 음절 개수가 훨씬 많기 때문에 길이는 80으로 하였고, 이 과정에서 space도 하나의 character로 카운트됩니다. 자소분리 임베딩의 경우 3배의 길이를 가지는 것으로 간주하여 size 240을 최대로 하였습니다. 그리고 앞에서 그랬듯, 한국어의 head-finality를 고려하여 문장 뒷쪽에서부터 padding하였습니다.
985 |
986 | ---
987 |
988 | The below is the evaluation phase utilizing the case of dense character embedding, which is the best case among the five feature engineerings above. Since the feature size is much bigger than the morpheme-based models, training epoch was extended to 50 for a fair comparison. The convergence was significantly slow compared with the morpheme-based cases, but we've obtained compatible accuracy (0.8802 > 0.8823) and F1 score (0.7906 > 0.7934) with respect to the performance with the morpheme-bilstm model. We assume that this result originates in the property of the syllables in Korean as subwords and the nature of the intention understanding task as a syntax-semantic task.
989 |
990 | ```python
991 | validate_bilstm(fci_rec,fci_label,32,128,class_weights_fci,0.1,16,'model/tutorial/rec_char_dense')
992 | ```
993 |
994 | ```properties
995 | # CONSOLE RESULT
996 | >>> validate_bilstm(fci_rec,fci_label,32,128,class_weights_fci,0.1,16,'model/tutorial/rec_char_dense')
997 | _________________________________________________________________
998 | Layer (type) Output Shape Param #
999 | =================================================================
1000 | bidirectional_12 (Bidirectio (None, 64) 34048
1001 | _________________________________________________________________
1002 | dense_43 (Dense) (None, 128) 8320
1003 | _________________________________________________________________
1004 | dense_44 (Dense) (None, 7) 903
1005 | =================================================================
1006 | Total params: 43,271
1007 | Trainable params: 43,271
1008 | Non-trainable params: 0
1009 | _________________________________________________________________
1010 | Train on 55129 samples, validate on 6126 samples
1011 | Epoch 1/50
1012 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.6033 - acc: 0.7970— val_f1: 0.611853 — val_precision: 0.759945 — val_reca
1013 | — val_f1_w: 0.805877 — val_precision_w: 0.822164 — val_recall_w: 0.821907
1014 | 55129/55129 [==============================] - 169s 3ms/step - loss: 0.6034 - acc: 0.7970 - val_loss: 0.5275 - val_acc: 0.8219
1015 | Epoch 2/50
1016 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.4744 - acc: 0.8399— val_f1: 0.694396 — val_precision: 0.770346 — val_reca
1017 | — val_f1_w: 0.834075 — val_precision_w: 0.840706 — val_recall_w: 0.840679
1018 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.4743 - acc: 0.8399 - val_loss: 0.4752 - val_acc: 0.8407
1019 | Epoch 3/50
1020 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.4298 - acc: 0.8538— val_f1: 0.735305 — val_precision: 0.779485 — val_reca
1021 | — val_f1_w: 0.851305 — val_precision_w: 0.851509 — val_recall_w: 0.855534
1022 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.4298 - acc: 0.8538 - val_loss: 0.4277 - val_acc: 0.8555
1023 | Epoch 4/50
1024 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.4013 - acc: 0.8632— val_f1: 0.743459 — val_precision: 0.793688 — val_reca
1025 | — val_f1_w: 0.850187 — val_precision_w: 0.859941 — val_recall_w: 0.854228
1026 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.4012 - acc: 0.8632 - val_loss: 0.4334 - val_acc: 0.8542
1027 | Epoch 5/50
1028 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3808 - acc: 0.8699— val_f1: 0.759329 — val_precision: 0.791125 — val_reca
1029 | — val_f1_w: 0.862013 — val_precision_w: 0.863887 — val_recall_w: 0.864838
1030 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.3809 - acc: 0.8699 - val_loss: 0.4008 - val_acc: 0.8648
1031 | Epoch 6/50
1032 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3639 - acc: 0.8744— val_f1: 0.772891 — val_precision: 0.818381 — val_reca
1033 | — val_f1_w: 0.867532 — val_precision_w: 0.868421 — val_recall_w: 0.870715
1034 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.3638 - acc: 0.8744 - val_loss: 0.3888 - val_acc: 0.8707
1035 | Epoch 7/50
1036 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3497 - acc: 0.8791— val_f1: 0.769384 — val_precision: 0.800740 — val_reca
1037 | — val_f1_w: 0.865818 — val_precision_w: 0.866474 — val_recall_w: 0.869083
1038 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.3496 - acc: 0.8791 - val_loss: 0.3859 - val_acc: 0.8691
1039 | Epoch 8/50
1040 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3363 - acc: 0.8844— val_f1: 0.776859 — val_precision: 0.809094 — val_reca
1041 | — val_f1_w: 0.871162 — val_precision_w: 0.870220 — val_recall_w: 0.874306
1042 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.3363 - acc: 0.8844 - val_loss: 0.3775 - val_acc: 0.8743
1043 | Epoch 9/50
1044 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3238 - acc: 0.8888— val_f1: 0.769087 — val_precision: 0.823962 — val_reca
1045 | — val_f1_w: 0.871311 — val_precision_w: 0.872008 — val_recall_w: 0.874633
1046 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.3238 - acc: 0.8888 - val_loss: 0.3690 - val_acc: 0.8746
1047 | Epoch 10/50
1048 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3147 - acc: 0.8912— val_f1: 0.778358 — val_precision: 0.815374 — val_reca
1049 | — val_f1_w: 0.871938 — val_precision_w: 0.872361 — val_recall_w: 0.873980
1050 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.3148 - acc: 0.8912 - val_loss: 0.3691 - val_acc: 0.8740
1051 | Epoch 11/50
1052 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3051 - acc: 0.8944— val_f1: 0.775244 — val_precision: 0.801135 — val_reca
1053 | — val_f1_w: 0.872543 — val_precision_w: 0.872058 — val_recall_w: 0.875449
1054 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.3051 - acc: 0.8944 - val_loss: 0.3665 - val_acc: 0.8754
1055 | Epoch 12/50
1056 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2970 - acc: 0.8981— val_f1: 0.781538 — val_precision: 0.809303 — val_reca
1057 | — val_f1_w: 0.875536 — val_precision_w: 0.875564 — val_recall_w: 0.878387
1058 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2970 - acc: 0.8981 - val_loss: 0.3629 - val_acc: 0.8784
1059 | Epoch 13/50
1060 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2891 - acc: 0.9001— val_f1: 0.781662 — val_precision: 0.795075 — val_reca
1061 | — val_f1_w: 0.872933 — val_precision_w: 0.873100 — val_recall_w: 0.873817
1062 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.2891 - acc: 0.9001 - val_loss: 0.3684 - val_acc: 0.8738
1063 | Epoch 14/50
1064 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2811 - acc: 0.9032— val_f1: 0.778117 — val_precision: 0.799786 — val_reca
1065 | — val_f1_w: 0.873586 — val_precision_w: 0.874717 — val_recall_w: 0.875775
1066 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.2810 - acc: 0.9032 - val_loss: 0.3671 - val_acc: 0.8758
1067 | Epoch 15/50
1068 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2744 - acc: 0.9035— val_f1: 0.784468 — val_precision: 0.810065 — val_reca
1069 | — val_f1_w: 0.874109 — val_precision_w: 0.873767 — val_recall_w: 0.876265
1070 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.2744 - acc: 0.9036 - val_loss: 0.3618 - val_acc: 0.8763
1071 | Epoch 16/50
1072 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2686 - acc: 0.9063— val_f1: 0.780115 — val_precision: 0.807169 — val_reca
1073 | — val_f1_w: 0.874687 — val_precision_w: 0.874115 — val_recall_w: 0.876755
1074 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.2687 - acc: 0.9063 - val_loss: 0.3641 - val_acc: 0.8768
1075 | Epoch 17/50
1076 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2615 - acc: 0.9094— val_f1: 0.782015 — val_precision: 0.796383 — val_reca
1077 | — val_f1_w: 0.874941 — val_precision_w: 0.876734 — val_recall_w: 0.875939
1078 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2616 - acc: 0.9094 - val_loss: 0.3640 - val_acc: 0.8759
1079 | Epoch 18/50
1080 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2548 - acc: 0.9099— val_f1: 0.790453 — val_precision: 0.812157 — val_reca
1081 | — val_f1_w: 0.880186 — val_precision_w: 0.880995 — val_recall_w: 0.881815
1082 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2548 - acc: 0.9098 - val_loss: 0.3696 - val_acc: 0.8818
1083 | Epoch 19/50
1084 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2484 - acc: 0.9136— val_f1: 0.787465 — val_precision: 0.791837 — val_reca
1085 | — val_f1_w: 0.877686 — val_precision_w: 0.880073 — val_recall_w: 0.877245
1086 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2484 - acc: 0.9136 - val_loss: 0.3612 - val_acc: 0.8772
1087 | Epoch 20/50
1088 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2432 - acc: 0.9150— val_f1: 0.764492 — val_precision: 0.800527 — val_reca
1089 | — val_f1_w: 0.863816 — val_precision_w: 0.865599 — val_recall_w: 0.867124
1090 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2431 - acc: 0.9150 - val_loss: 0.4086 - val_acc: 0.8671
1091 | Epoch 21/50
1092 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2364 - acc: 0.9179— val_f1: 0.786431 — val_precision: 0.825723 — val_reca
1093 | — val_f1_w: 0.877461 — val_precision_w: 0.877627 — val_recall_w: 0.880346
1094 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2364 - acc: 0.9179 - val_loss: 0.3791 - val_acc: 0.8803
1095 | Epoch 22/50
1096 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2314 - acc: 0.9198— val_f1: 0.784653 — val_precision: 0.813028 — val_reca
1097 | — val_f1_w: 0.879205 — val_precision_w: 0.880109 — val_recall_w: 0.880999
1098 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2314 - acc: 0.9198 - val_loss: 0.3741 - val_acc: 0.8810
1099 | Epoch 23/50
1100 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2251 - acc: 0.9218— val_f1: 0.780445 — val_precision: 0.804015 — val_reca
1101 | — val_f1_w: 0.875199 — val_precision_w: 0.874711 — val_recall_w: 0.877897
1102 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2251 - acc: 0.9218 - val_loss: 0.3859 - val_acc: 0.8779
1103 | Epoch 24/50
1104 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2195 - acc: 0.9240— val_f1: 0.784534 — val_precision: 0.789635 — val_reca
1105 | — val_f1_w: 0.876094 — val_precision_w: 0.875345 — val_recall_w: 0.877081
1106 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2195 - acc: 0.9240 - val_loss: 0.3862 - val_acc: 0.8771
1107 | Epoch 25/50
1108 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2145 - acc: 0.9252— val_f1: 0.789590 — val_precision: 0.800697 — val_reca
1109 | — val_f1_w: 0.879318 — val_precision_w: 0.878898 — val_recall_w: 0.880346
1110 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2145 - acc: 0.9252 - val_loss: 0.3892 - val_acc: 0.8803
1111 | Epoch 26/50
1112 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2087 - acc: 0.9281— val_f1: 0.786858 — val_precision: 0.804829 — val_reca
1113 | — val_f1_w: 0.878004 — val_precision_w: 0.877461 — val_recall_w: 0.879693
1114 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2087 - acc: 0.9281 - val_loss: 0.3853 - val_acc: 0.8797
1115 | Epoch 27/50
1116 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2042 - acc: 0.9290— val_f1: 0.779832 — val_precision: 0.808263 — val_reca
1117 | — val_f1_w: 0.873661 — val_precision_w: 0.872953 — val_recall_w: 0.876755
1118 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.2042 - acc: 0.9290 - val_loss: 0.4095 - val_acc: 0.8768
1119 | Epoch 28/50
1120 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.1993 - acc: 0.9306— val_f1: 0.792366 — val_precision: 0.799918 — val_reca
1121 | — val_f1_w: 0.881174 — val_precision_w: 0.881553 — val_recall_w: 0.881489
1122 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.1994 - acc: 0.9306 - val_loss: 0.3898 - val_acc: 0.8815
1123 | Epoch 29/50
1124 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.1939 - acc: 0.9330— val_f1: 0.787657 — val_precision: 0.799441 — val_reca
1125 | — val_f1_w: 0.878101 — val_precision_w: 0.877934 — val_recall_w: 0.879367
1126 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.1939 - acc: 0.9330 - val_loss: 0.4101 - val_acc: 0.8794
1127 | Epoch 30/50
1128 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.1895 - acc: 0.9333— val_f1: 0.767905 — val_precision: 0.779847 — val_reca
1129 | — val_f1_w: 0.863264 — val_precision_w: 0.863594 — val_recall_w: 0.865491
1130 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.1896 - acc: 0.9332 - val_loss: 0.4535 - val_acc: 0.8655
1131 | Epoch 31/50
1132 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.1856 - acc: 0.9350— val_f1: 0.787478 — val_precision: 0.806525 — val_reca
1133 | — val_f1_w: 0.875649 — val_precision_w: 0.874640 — val_recall_w: 0.877571
1134 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.1856 - acc: 0.9350 - val_loss: 0.4093 - val_acc: 0.8776
1135 | Epoch 32/50
1136 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.1798 - acc: 0.9380— val_f1: 0.796609 — val_precision: 0.802828 — val_reca
1137 | — val_f1_w: 0.877572 — val_precision_w: 0.877537 — val_recall_w: 0.878224
1138 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.1799 - acc: 0.9380 - val_loss: 0.4327 - val_acc: 0.8782
1139 | Epoch 33/50
1140 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.1760 - acc: 0.9389— val_f1: 0.794457 — val_precision: 0.807102 — val_reca
1141 | — val_f1_w: 0.878977 — val_precision_w: 0.879220 — val_recall_w: 0.879693
1142 | 55129/55129 [==============================] - 164s 3ms/step - loss: 0.1760 - acc: 0.9389 - val_loss: 0.4271 - val_acc: 0.8797
1143 | Epoch 34/50
1144 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.1714 - acc: 0.9403— val_f1: 0.794570 — val_precision: 0.800183 — val_reca
1145 | — val_f1_w: 0.874815 — val_precision_w: 0.875788 — val_recall_w: 0.874959
1146 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.1713 - acc: 0.9404 - val_loss: 0.4511 - val_acc: 0.8750
1147 | Epoch 35/50
1148 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.1675 - acc: 0.9414— val_f1: 0.793498 — val_precision: 0.804919 — val_reca
1149 | — val_f1_w: 0.881061 — val_precision_w: 0.881226 — val_recall_w: 0.882305
1150 | 55129/55129 [==============================] - 165s 3ms/step - loss: 0.1675 - acc: 0.9414 - val_loss: 0.4274 - val_acc: 0.8823
1151 | ```
1152 |
1153 | * 이제 evaluation part입니다. 그런데 다섯 개나 되는 모델의 train 결과를 모두 올리는 건 좀 무리수인 것 같네요. 그래서 결론만 말씀드리면, one-hot encoded char < Cho < Shin < Song < dense char 의 순서로 성능이 좋습니다 ㅎㅎ 아무래도 궁금하실 dense char embedding의 트레이닝 phase만을 여기엔 업로드하도록 하겠습니다. 다른 알고리즘들은 validate_bilstm 모듈에 한번씩 돌려보면서 성능을 살펴보시면 좋을 것 같아요!
1154 |
1155 | * 비록 feature size의 차이로 training epoch가 좀 늘어나긴 했지만 이는 convergence를 유도하기 위함이고, converge했다는 가정 하에서 morpheme based model보다 근소하게 더 성능이 좋음을 확인할 수 있었습니다. 이는 좀 의외의 결과였는데요, morpheme-based model과 다르게 여기서는 의미에 관련된 어떤 preprocessing도 거치지 않고 raw data만을 dictionary embedding한 것이기 때문입니다. 여기서, 한국어에서는 각 음절이 어느 정도 subword 역할을 해 주며 그 sequential한 배열로부터 문장의 의도를 파악하면 형태소 단위의 분석보다 때로는 더 정확할 수 있다는 것을 짐작할 수 있습니다. 좋은 성능의 배경에는 intention understanding 이라는 task의 특징이, 그리고 한국어의 agglutinative language로써의 특징이 있겠지만요.
1156 |
1157 | ---
1158 |
1159 | **Advanced from the vanilla models we've utilized so far, we may customize some Keras layers in the following chapter, to boost the performance.**
1160 |
1161 | * 지금까지 우리가 사용한 알고리즘들은 모두 vanilla cnn/bilstm이었고, 별도로 레이어를 쪼개고 합하고 곱하고 하지는 않았습니다. 하지만 back propagation을 이용한 트레이닝들의 장점은 연산의 커스터마이징이 가능하다는 점이며, 케라스에서도 그러한 연산들은 대부분 가능합니다. 다음 꼭지부터는 cnn과 bilstm을 엮어서 모델을 짜는 방법을 한번 알아보도록 하겠습니다.
1162 |
1163 | ## 9. Concatenation of CNN and RNN layers
1164 |
1165 | **Now, we get back to the morpheme-based approaches. Here, for the first time we customize the layers for a new implementation! The procedure is the most simple one, a concatenation of two separate networks. We've done with CNN and RNN(BiLSTM)-based approaches so far, thus, a concatenation of the two systems will be executed. Beyond simply putting *model.xxx*, we need some unseen module at this point, *Model*. This helps make in-out of the customized layers.**
1166 |
1167 | ```python
1168 | from keras.models import Model
1169 | import keras.layers as layers
1170 | from keras.layers import Input
1171 | from keras.layers.core import Dense, Dropout
1172 | ```
1173 |
1174 | * 이제 다시 형태소 기반의 접근으로 돌아가 보도록 하겠습니다. 이제 처음으로, 단순히 model.머시기로 쌓는 게 아닌, 커스터마이즈드 레이어를 만들어보려고 해요! 물론 가장 기본적인, simple concatenation부터 시작해보도록 하겠습니다. 대상은 앞서 다루었던 CNN과 RNN(BiLSTM)입니다. 이를 위해서, 앞과는 다른 모듈들을 import할 필요가 있겠죠. 바로 **Model** 입니다. 커스터마이즈드 레이어의 입출력을 다룰 수 있게 해주죠!
1175 |
1176 | ---
1177 |
1178 | **The following is a simple code for a concatenation of CNN and RNN networks. The input dimensions were filled with the property regarding RNN input. There are some points you should look at: the dense layers after CNN and RNN summarization, the line with *layer.concatenate*, and the model declaration stage *model = Model(inputs = [cnn,rnn], outputs = [main_output])*. Dropouts were added since the model became so bigger than the previous ones. Wait ... there is another unseen function ... in callbacks?**
1179 |
1180 | ```python
1181 | def validate_cnnrnn(conv,rnn,train_y,filters,hidden_lstm,hidden_dim,cw,filename):
1182 | cnn_input = Input(shape=(len(rnn[0]),len(rnn[0][0]),1), dtype='float32')
1183 | cnn_layer = layers.Conv2D(filters,(3,len(rnn[0][0])),activation='relu')(cnn_input)
1184 | cnn_layer = layers.MaxPooling2D((2,1))(cnn_layer)
1185 | cnn_layer = layers.Conv2D(filters,(3,1),activation='relu')(cnn_layer)
1186 | cnn_layer = layers.Flatten()(cnn_layer)
1187 | cnn_output= Dense(hidden_dim, activation='relu')(cnn_layer)
1188 | rnn_input = Input(shape=(len(rnn[0]),len(rnn[0][0])), dtype='float32')
1189 | rnn_layer = Bidirectional(LSTM(hidden_lstm))(rnn_input)
1190 | rnn_output= Dense(hidden_dim, activation='relu')(rnn_layer)
1191 | output = layers.concatenate([cnn_output,rnn_output])
1192 | output = Dense(hidden_dim, activation='relu')(output)
1193 | output = Dropout(0.3)(output)
1194 | output = Dense(hidden_dim, activation='relu')(output)
1195 | output = Dropout(0.3)(output)
1196 | main_output = Dense(7,activation='softmax')(output)
1197 | model = Sequential()
1198 | model = Model(inputs=[cnn_input,rnn_input], outputs=[main_output])
1199 | model.compile(optimizer=adam_half, loss="sparse_categorical_crossentropy", metrics=["accuracy"])
1200 | filepath=filename+"-{epoch:02d}-{val_acc:.4f}.hdf5"
1201 | checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, mode='max')
1202 | callbacks_list = [metricsf1macro_2input,checkpoint]
1203 | model.summary()
1204 | model.fit([conv,rnn],train_y,validation_split=0.1,epochs=30,batch_size=16,callbacks=callbacks_list,class_weight=cw)
1205 | ```
1206 |
1207 | 상기 코드는 간단하게 CNN과 RNN 구조를 concatenate해 줍니다! 앞에서부터 쭉 보면 CNN_input과 CNN_output을 정의하고, RNN_input과 RNN_output을 정의하고, 두 레이어를 layer.concatenate를 이용해 합친 뒤, 앞서 했던 output에 대한 방법론과 비슷하게 진행하는 것을 파악할 수 있지요. 여기서 중요하게 볼 point들은 1. CNN과 RNN을 Dense layer로 summarize하여 concatenate하기 쉽게 만드는 부분, 2. *layer.concatenate*로 concatenate하는 부분, 3. *Model* 펑션으로 inputs와 outputs를 이은 하나의 큰 덩어리를 만드는 부분입니다. Dropout은 그냥 모델이 너무 커져서 넣어 보았습니다 ㅎㅎ 어... 그런데 callback부분에 못 보던 내용이 있군요?
1208 |
1209 | ---
1210 |
1211 | **As in Keras, we need to define another callback function for F1 score if we adopt more than one input. Actually, a different format of function is required for every number of more-than-one-input! Very annoying, but anyway, we've adopted two inputs, thus let's make up another function for the evaluation.**
1212 |
1213 | ```python
1214 | class Metricsf1macro_2input(Callback):
1215 | def on_train_begin(self, logs={}):
1216 | self.val_f1s = []
1217 | self.val_recalls = []
1218 | self.val_precisions = []
1219 | self.val_f1s_w = []
1220 | self.val_recalls_w = []
1221 | self.val_precisions_w = []
1222 | def on_epoch_end(self, epoch, logs={}):
1223 | if len(self.validation_data)>2:
1224 | val_predict = np.asarray(self.model.predict([self.validation_data[0],self.validation_data[1]]))
1225 | val_predict = np.argmax(val_predict,axis=1)
1226 | val_targ = self.validation_data[2]
1227 | else:
1228 | val_predict = np.asarray(self.model.predict(self.validation_data[0]))
1229 | val_predict = np.argmax(val_predict,axis=1)
1230 | val_targ = self.validation_data[1]
1231 | _val_f1 = metrics.f1_score(val_targ, val_predict, average="macro")
1232 | _val_f1_w = metrics.f1_score(val_targ, val_predict, average="weighted")
1233 | _val_recall = metrics.recall_score(val_targ, val_predict, average="macro")
1234 | _val_recall_w = metrics.recall_score(val_targ, val_predict, average="weighted")
1235 | _val_precision = metrics.precision_score(val_targ, val_predict, average="macro")
1236 | _val_precision_w = metrics.precision_score(val_targ, val_predict, average="weighted")
1237 | self.val_f1s.append(_val_f1)
1238 | self.val_recalls.append(_val_recall)
1239 | self.val_precisions.append(_val_precision)
1240 | self.val_f1s_w.append(_val_f1_w)
1241 | self.val_recalls_w.append(_val_recall_w)
1242 | self.val_precisions_w.append(_val_precision_w)
1243 | print("— val_f1: %f — val_precision: %f — val_recall: %f"%(_val_f1, _val_precision, _val_recall))
1244 | print("— val_f1_w: %f — val_precision_w: %f — val_recall_w: %f"%(_val_f1_w, _val_precision_w, _val_recall_w))
1245 |
1246 | metricsf1macro_2input = Metricsf1macro_2input()
1247 | ```
1248 |
1249 | 매-우 귀찮지만, 케라스에서 하나 이상의 input을 가진 모델에 대해 F1 score을 얻고 싶다면, 앞서 정의한 것과 다른, 별도의 function을 만들어야 합니다... 왜 이랬을까요? 아마 그쪽에서도 귀찮아서가 아닐까 싶습니다. 어쨌든 evaluation을 해야 하니, 정의를 해 보도록 하겠습니다.
1250 |
1251 | ---
1252 |
1253 | Now, let's validate with the morpheme-based feaftures!
1254 |
1255 | ```python
1256 | validate_cnnrnn(fci_conv,fci_rec,fci_label,32,32,128,class_weights_fci,'model/modelfci/charcnn+bilstm')
1257 | ```
1258 |
1259 | **We can see that the performance has degenerated even compared with the vanilla BiLSTM approach. Well, not always the bigger model results in the better performance. We infer that this originates in the distortion that comes from the difference in the way that CNN and RNN solve the problem. Next, we try another module, *attention network*, by introducing the backend of Keras.**
1260 |
1261 | ```properties
1262 | # CONSOLE RESULT
1263 | __________________________________________________________________________________________________
1264 | Layer (type) Output Shape Param # Connected to
1265 | ==================================================================================================
1266 | input_7 (InputLayer) (None, 30, 100, 1) 0
1267 | __________________________________________________________________________________________________
1268 | conv2d_7 (Conv2D) (None, 28, 1, 32) 9632 input_7[0][0]
1269 | __________________________________________________________________________________________________
1270 | max_pooling2d_4 (MaxPooling2D) (None, 14, 1, 32) 0 conv2d_7[0][0]
1271 | __________________________________________________________________________________________________
1272 | conv2d_8 (Conv2D) (None, 12, 1, 32) 3104 max_pooling2d_4[0][0]
1273 | __________________________________________________________________________________________________
1274 | input_8 (InputLayer) (None, 30, 100) 0
1275 | __________________________________________________________________________________________________
1276 | flatten_4 (Flatten) (None, 384) 0 conv2d_8[0][0]
1277 | __________________________________________________________________________________________________
1278 | bidirectional_3 (Bidirectional) (None, 64) 34048 input_8[0][0]
1279 | __________________________________________________________________________________________________
1280 | dense_11 (Dense) (None, 128) 49280 flatten_4[0][0]
1281 | __________________________________________________________________________________________________
1282 | dense_12 (Dense) (None, 128) 8320 bidirectional_3[0][0]
1283 | __________________________________________________________________________________________________
1284 | concatenate_3 (Concatenate) (None, 256) 0 dense_11[0][0]
1285 | dense_12[0][0]
1286 | __________________________________________________________________________________________________
1287 | dense_13 (Dense) (None, 128) 32896 concatenate_3[0][0]
1288 | __________________________________________________________________________________________________
1289 | dropout_5 (Dropout) (None, 128) 0 dense_13[0][0]
1290 | __________________________________________________________________________________________________
1291 | dense_14 (Dense) (None, 128) 16512 dropout_5[0][0]
1292 | __________________________________________________________________________________________________
1293 | dropout_6 (Dropout) (None, 128) 0 dense_14[0][0]
1294 | __________________________________________________________________________________________________
1295 | dense_15 (Dense) (None, 7) 903 dropout_6[0][0]
1296 | ==================================================================================================
1297 | Total params: 154,695
1298 | Trainable params: 154,695
1299 | Non-trainable params: 0
1300 | __________________________________________________________________________________________________
1301 | Train on 55129 samples, validate on 6126 samples
1302 | Epoch 1/30
1303 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.6263 - acc: 0.7916— val_f1: 0.730286 — val_precision: 0.768703 — val_recall: 0.705825
1304 | — val_f1_w: 0.843637 — val_precision_w: 0.842989 — val_recall_w: 0.847698
1305 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.6261 - acc: 0.7916 - val_loss: 0.4664 - val_acc: 0.8477
1306 | Epoch 2/30
1307 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.4405 - acc: 0.8548— val_f1: 0.758616 — val_precision: 0.786346 — val_recall: 0.740990
1308 | — val_f1_w: 0.856354 — val_precision_w: 0.860634 — val_recall_w: 0.857982
1309 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.4405 - acc: 0.8548 - val_loss: 0.4282 - val_acc: 0.8580
1310 | Epoch 3/30
1311 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3859 - acc: 0.8731— val_f1: 0.759942 — val_precision: 0.804874 — val_recall: 0.729082
1312 | — val_f1_w: 0.861656 — val_precision_w: 0.863499 — val_recall_w: 0.865002
1313 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.3859 - acc: 0.8731 - val_loss: 0.3992 - val_acc: 0.8650
1314 | Epoch 4/30
1315 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3492 - acc: 0.8831— val_f1: 0.781057 — val_precision: 0.795622 — val_recall: 0.769233
1316 | — val_f1_w: 0.868542 — val_precision_w: 0.869711 — val_recall_w: 0.869899
1317 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.3493 - acc: 0.8831 - val_loss: 0.3879 - val_acc: 0.8699
1318 | Epoch 5/30
1319 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3177 - acc: 0.8927— val_f1: 0.779071 — val_precision: 0.829605 — val_recall: 0.746311
1320 | — val_f1_w: 0.865831 — val_precision_w: 0.870711 — val_recall_w: 0.868919
1321 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.3177 - acc: 0.8927 - val_loss: 0.4028 - val_acc: 0.8689
1322 | Epoch 6/30
1323 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2919 - acc: 0.9011— val_f1: 0.783441 — val_precision: 0.804650 — val_recall: 0.768078
1324 | — val_f1_w: 0.872711 — val_precision_w: 0.873444 — val_recall_w: 0.873980
1325 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.2919 - acc: 0.9011 - val_loss: 0.3968 - val_acc: 0.8740
1326 | Epoch 7/30
1327 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2667 - acc: 0.9091— val_f1: 0.784232 — val_precision: 0.819105 — val_recall: 0.761072
1328 | — val_f1_w: 0.870747 — val_precision_w: 0.870663 — val_recall_w: 0.873164
1329 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.2667 - acc: 0.9091 - val_loss: 0.4189 - val_acc: 0.8732
1330 | Epoch 8/30
1331 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2447 - acc: 0.9174— val_f1: 0.781662 — val_precision: 0.811667 — val_recall: 0.758755
1332 | — val_f1_w: 0.868505 — val_precision_w: 0.869532 — val_recall_w: 0.871205
1333 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.2447 - acc: 0.9174 - val_loss: 0.4401 - val_acc: 0.8712
1334 | Epoch 9/30
1335 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2226 - acc: 0.9235— val_f1: 0.777220 — val_precision: 0.781946 — val_recall: 0.782025
1336 | — val_f1_w: 0.873209 — val_precision_w: 0.874389 — val_recall_w: 0.873980
1337 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.2227 - acc: 0.9235 - val_loss: 0.4419 - val_acc: 0.8740
1338 | Epoch 10/30
1339 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2055 - acc: 0.9280— val_f1: 0.776107 — val_precision: 0.795161 — val_recall: 0.763229
1340 | — val_f1_w: 0.867720 — val_precision_w: 0.867430 — val_recall_w: 0.869246
1341 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.2055 - acc: 0.9280 - val_loss: 0.4681 - val_acc: 0.8692
1342 | Epoch 11/30
1343 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.1893 - acc: 0.9344— val_f1: 0.788884 — val_precision: 0.796599 — val_recall: 0.782946
1344 | — val_f1_w: 0.875588 — val_precision_w: 0.876054 — val_recall_w: 0.875612
1345 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.1892 - acc: 0.9345 - val_loss: 0.4614 - val_acc: 0.8756
1346 | Epoch 12/30
1347 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.1749 - acc: 0.9389— val_f1: 0.784650 — val_precision: 0.809236 — val_recall: 0.767474
1348 | — val_f1_w: 0.871932 — val_precision_w: 0.871065 — val_recall_w: 0.874306
1349 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.1750 - acc: 0.9389 - val_loss: 0.4874 - val_acc: 0.8743
1350 | Epoch 13/30
1351 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.1613 - acc: 0.9439— val_f1: 0.790170 — val_precision: 0.813151 — val_recall: 0.771651
1352 | — val_f1_w: 0.874813 — val_precision_w: 0.874452 — val_recall_w: 0.876755
1353 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.1613 - acc: 0.9439 - val_loss: 0.5269 - val_acc: 0.8768
1354 | ```
1355 |
1356 | 어... vanilla BiLSTM보다 성능이 떨어졌네요 ㅎㅎ 뭐 그럴수도 있죠... 아무래도 CNN과 BiLSTM이 infer하는 과정에서 양상이 달라, jointly training에서는 혼선이 생긴 것이 아닌가 싶습니다. model을 merge하는 것이 꼭 좋은 결과를 가져오지는 않는 것 같아요. 다음 장에서는 backend인 tensorflow를 끌어 와서 self-attentive BiLSTM을 구현해 보도록 하겠습니다.
1357 |
1358 | ## 10. Self-attentive BiLSTM
1359 |
1360 | **The second to the final step is applying 'the' attention mechanism, which has shifted paradigm of deep learning architectures, and still shifting... Many will be familiar with the [attention model](https://arxiv.org/abs/1409.0473) which came out along with [RNN encoder-decoder](https://arxiv.org/abs/1406.1078), or the self-attention which was suggested in [Transformer](http://papers.nips.cc/paper/7181-attention-is-all-you-need), which deals with seq2seq-style problems such as machine translation. Though the inherit philosophy may be consistent, here, we introduce a [structured self attentive embedding](https://arxiv.org/abs/1703.03130) which was suggested for an effective sentence classification.**
1361 |
1362 | **The key idea in this paper is to train an additional attention layer that assigns weight to each hidden layer of the BiLSTM structure. For this, a context vector of the size same as the hidden layer width, i.e. 64 as will be implemented, is separately defined and jointly trained, in the manner that *it is column-wisely multiplied with the MLP from each hidden layer to yield the attention vector*. The attention vector is recursively multiplied to the hidden layers so that the weighted sum becomes the final summarization for the fine-tuning. The code is implemented below; with the new function *lambda* which enables us to customize the layers in somewhat sophiscated ways. We also need to import the backend of Keras, here TensorFlow, for the layer-level and specific operations.**
1363 |
1364 | ```python
1365 | from keras.layers import Lambda
1366 | import keras.backend as K
1367 |
1368 | def validate_rnn_self_drop(x_rnn,x_y,hidden_lstm,hidden_con,hidden_dim,cw,val_sp,bat_size,filename):
1369 | char_r_input = Input(shape=(len(x_rnn[0]),len(x_rnn[0][0])),dtype='float32')
1370 | r_seq = Bidirectional(LSTM(hidden_lstm,return_sequences=True))(char_r_input)
1371 | r_att = Dense(hidden_con, activation='tanh')(r_seq)
1372 | att_source = np.zeros((len(x_rnn),hidden_con))
1373 | att_test = np.zeros((len(x_rnn),hidden_con))
1374 | att_input = Input(shape=(hidden_con,), dtype='float32')
1375 | att_vec = Dense(hidden_con,activation='relu')(att_input)
1376 | att_vec = Dropout(0.3)(att_vec)
1377 | att_vec = Dense(hidden_con,activation='relu')(att_vec)
1378 | att_vec = Lambda(lambda x: K.batch_dot(*x, axes=(1,2)))([att_vec,r_att])
1379 | att_vec = Dense(len(x_rnn[0]),activation='softmax')(att_vec)
1380 | att_vec = layers.Reshape((len(x_rnn[0]),1))(att_vec)
1381 | r_seq = layers.multiply([att_vec,r_seq])
1382 | r_seq = Lambda(lambda x: K.sum(x, axis=1))(r_seq)
1383 | r_seq = Dense(hidden_dim, activation='relu')(r_seq)
1384 | r_seq = Dropout(0.3)(r_seq)
1385 | r_seq = Dense(hidden_dim, activation='relu')(r_seq)
1386 | r_seq = Dropout(0.3)(r_seq)
1387 | main_output = Dense(int(max(x_y)+1),activation='softmax')(r_seq)
1388 | model = Sequential()
1389 | model = Model(inputs=[char_r_input,att_input],outputs=[main_output])
1390 | model.summary()
1391 | model.compile(optimizer=adam_half,loss="sparse_categorical_crossentropy",metrics=["accuracy"])
1392 | filepath=filename+"-{epoch:02d}-{val_acc:.4f}.hdf5"
1393 | checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, mode='max')
1394 | callbacks_list = [metricsf1macro_2input,checkpoint]
1395 | model.fit([x_rnn,att_source],x_y,validation_split=val_sp,epochs=50,batch_size= bat_size ,callbacks=callbacks_list,class_weight=cw)
1396 | ```
1397 |
1398 | **At the very beginning of the code, BiLSTM module is defined so that each hidden layer can be fed as an input of MLP (here a single layer was utilized though) whose final size is the same as *the context vector*, 64 (*hidden_con*), to make up *r_att*. Next, from an attention source *zeros*, an attention vector *att_vec* is yielded by MLP, with the size of *hidden_con*, and is multiplied column-wisely to *r_att*! This finally makes up an attention vector that is recursively multiplied to the hidden layer sequence to yield the weighted sum. The rest are the same, but due to the model being large, we raised the number of epochs to 50. The same callback functions were utilized as in the last chapter since we utilized two inputs, RNN dataset and attention source (zeros).**
1399 |
1400 |
1402 | (image from Lin 2017)
1403 |
1404 | 요즘, 아니 꽤 오랫동안 NLP와 ML에서 혁신을 가져왔던 attention mechanism을 이제서야 만나볼 수 있게 되었습니다. 많은 분들이 익숙하신 내용은 주로 machine translation과 함께 사용되었던 [attention model](https://arxiv.org/abs/1409.0473)이나 Transformer의 [self attention](http://papers.nips.cc/paper/7181-attention-is-all-you-need)일 텐데요, 여기서는 비슷한 시기에 sentence classification task를 위해 등장한 structured self-attentive embedding을 살펴보도록 하겠습니다. self attention이 나온다기는 좀 뭐하지만, attention vector을 활용하여 sentence classification에 사용되는 latent variable들에 정보를 주는데, 그 source가 자기 자신이라는 점이 self-attention과 유사한 측면이 있다고 생각됩니다.
1405 |
1406 | 아주 러프하게 말하면, 전체 procedure는 BiLSTM의 hidden layer sequence에 column-wise하게 곱해지는 attention vector를 얻는 데에 있어, context vector라는 개념을 도입하는 데에 있습니다. Context vector의 dimension을 *hidden_con*이라고 한다면, 우선 BiLSTM에서 MLP를 통해 *hidden_con*의 크기를 가진 dense layer을 각 hidden layer에 대해 뽑아낸 후, context vector와 column-wise한 inner product를 통해 attention vector을 얻는 것이죠. 어떻게 말해도 복잡하네요... 위에 있는 그림을 보는 것이 좀 더 이해가 빠를 것입니다 ㅎㅎ 여튼 여기서 중요한 점은, 케라스를 이용해서도 이 과정을 구현할 수 있다, 그런데 구현하려면 backend와 lambda라는 녀석들이 필요하다! 이 정도인 것 같네요.
1407 |
1408 | ---
1409 |
1410 | **The implementation might not be optimum, but well, we followed the description in the paper! The result shows that the self-attentive BiLSTM outperforms the vanilla one and the CNN-RNN concatenation, and matches with the character-based approach (which incorporates pretrained word embedding)! But it's not sure if we can push through the wall of 90% ...**
1411 |
1412 | ```properties
1413 | >>> validate_rnn_self_drop(fci_rec,fci_label,32,64,256,class_weights_fci,0.1,16,'model/tutorial/rec_self_drop')
1414 |
1415 | __________________________________________________________________________________________________
1416 | Layer (type) Output Shape Param # Connected to
1417 | ==================================================================================================
1418 | input_12 (InputLayer) (None, 64) 0
1419 | __________________________________________________________________________________________________
1420 | dense_20 (Dense) (None, 64) 4160 input_12[0][0]
1421 | __________________________________________________________________________________________________
1422 | input_11 (InputLayer) (None, 30, 100) 0
1423 | __________________________________________________________________________________________________
1424 | dropout_8 (Dropout) (None, 64) 0 dense_20[0][0]
1425 | __________________________________________________________________________________________________
1426 | bidirectional_5 (Bidirectional) (None, 30, 64) 34048 input_11[0][0]
1427 | __________________________________________________________________________________________________
1428 | dense_21 (Dense) (None, 64) 4160 dropout_8[0][0]
1429 | __________________________________________________________________________________________________
1430 | dense_19 (Dense) (None, 30, 64) 4160 bidirectional_5[0][0]
1431 | __________________________________________________________________________________________________
1432 | lambda_1 (Lambda) (None, 30) 0 dense_21[0][0]
1433 | dense_19[0][0]
1434 | __________________________________________________________________________________________________
1435 | dense_22 (Dense) (None, 30) 930 lambda_1[0][0]
1436 | __________________________________________________________________________________________________
1437 | reshape_1 (Reshape) (None, 30, 1) 0 dense_22[0][0]
1438 | __________________________________________________________________________________________________
1439 | multiply_1 (Multiply) (None, 30, 64) 0 reshape_1[0][0]
1440 | bidirectional_5[0][0]
1441 | __________________________________________________________________________________________________
1442 | lambda_2 (Lambda) (None, 64) 0 multiply_1[0][0]
1443 | __________________________________________________________________________________________________
1444 | dense_23 (Dense) (None, 256) 16640 lambda_2[0][0]
1445 | __________________________________________________________________________________________________
1446 | dropout_9 (Dropout) (None, 256) 0 dense_23[0][0]
1447 | __________________________________________________________________________________________________
1448 | dense_24 (Dense) (None, 256) 65792 dropout_9[0][0]
1449 | __________________________________________________________________________________________________
1450 | dropout_10 (Dropout) (None, 256) 0 dense_24[0][0]
1451 | __________________________________________________________________________________________________
1452 | dense_25 (Dense) (None, 7) 1799 dropout_10[0][0]
1453 | ==================================================================================================
1454 | Total params: 131,689
1455 | Trainable params: 131,689
1456 | Non-trainable params: 0
1457 | __________________________________________________________________________________________________
1458 | Train on 55129 samples, validate on 6126 samples
1459 | Epoch 1/50
1460 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.6279 - acc: 0.7886— val_f1: 0.711039 — val_precision: 0.807089 — val_recall: 0.670628
1461 | — val_f1_w: 0.831240 — val_precision_w: 0.842004 — val_recall_w: 0.839700
1462 | 55129/55129 [==============================] - 86s 2ms/step - loss: 0.6279 - acc: 0.7886 - val_loss: 0.4957 - val_acc: 0.8397
1463 | Epoch 2/50
1464 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.4680 - acc: 0.8442— val_f1: 0.754434 — val_precision: 0.791817 — val_recall: 0.731321
1465 | — val_f1_w: 0.848612 — val_precision_w: 0.848852 — val_recall_w: 0.851126
1466 | 55129/55129 [==============================] - 88s 2ms/step - loss: 0.4680 - acc: 0.8442 - val_loss: 0.4490 - val_acc: 0.8511
1467 | Epoch 3/50
1468 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.4244 - acc: 0.8578— val_f1: 0.771808 — val_precision: 0.812655 — val_recall: 0.743364
1469 | — val_f1_w: 0.858047 — val_precision_w: 0.858865 — val_recall_w: 0.861410
1470 | 55129/55129 [==============================] - 86s 2ms/step - loss: 0.4243 - acc: 0.8578 - val_loss: 0.4134 - val_acc: 0.8614
1471 | Epoch 4/50
1472 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3948 - acc: 0.8673— val_f1: 0.751910 — val_precision: 0.806796 — val_recall: 0.719961
1473 | — val_f1_w: 0.856063 — val_precision_w: 0.856820 — val_recall_w: 0.860431
1474 | 55129/55129 [==============================] - 87s 2ms/step - loss: 0.3948 - acc: 0.8673 - val_loss: 0.4073 - val_acc: 0.8604
1475 | Epoch 5/50
1476 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3688 - acc: 0.8747— val_f1: 0.772794 — val_precision: 0.823605 — val_recall: 0.740154
1477 | — val_f1_w: 0.860423 — val_precision_w: 0.866368 — val_recall_w: 0.863206
1478 | 55129/55129 [==============================] - 91s 2ms/step - loss: 0.3688 - acc: 0.8747 - val_loss: 0.4034 - val_acc: 0.8632
1479 | Epoch 6/50
1480 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3482 - acc: 0.8801— val_f1: 0.782679 — val_precision: 0.834986 — val_recall: 0.751268
1481 | — val_f1_w: 0.867117 — val_precision_w: 0.871998 — val_recall_w: 0.871041
1482 | 55129/55129 [==============================] - 89s 2ms/step - loss: 0.3482 - acc: 0.8801 - val_loss: 0.3943 - val_acc: 0.8710
1483 | Epoch 7/50
1484 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3285 - acc: 0.8869— val_f1: 0.775507 — val_precision: 0.834404 — val_recall: 0.740948
1485 | — val_f1_w: 0.866852 — val_precision_w: 0.870674 — val_recall_w: 0.871205
1486 | 55129/55129 [==============================] - 86s 2ms/step - loss: 0.3284 - acc: 0.8869 - val_loss: 0.4053 - val_acc: 0.8712
1487 | Epoch 8/50
1488 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.3166 - acc: 0.8914— val_f1: 0.789958 — val_precision: 0.832346 — val_recall: 0.761638
1489 | — val_f1_w: 0.874285 — val_precision_w: 0.877032 — val_recall_w: 0.877408
1490 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.3166 - acc: 0.8914 - val_loss: 0.3843 - val_acc: 0.8774
1491 | Epoch 9/50
1492 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.3011 - acc: 0.8956— val_f1: 0.796802 — val_precision: 0.811898 — val_recall: 0.786651
1493 | — val_f1_w: 0.872073 — val_precision_w: 0.872473 — val_recall_w: 0.873653
1494 | 55129/55129 [==============================] - 91s 2ms/step - loss: 0.3011 - acc: 0.8956 - val_loss: 0.3862 - val_acc: 0.8737
1495 | Epoch 10/50
1496 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2873 - acc: 0.9008— val_f1: 0.786752 — val_precision: 0.821892 — val_recall: 0.765839
1497 | — val_f1_w: 0.874281 — val_precision_w: 0.877892 — val_recall_w: 0.877408
1498 | 55129/55129 [==============================] - 93s 2ms/step - loss: 0.2873 - acc: 0.9008 - val_loss: 0.3845 - val_acc: 0.8774
1499 | Epoch 11/50
1500 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2755 - acc: 0.9048— val_f1: 0.796917 — val_precision: 0.856046 — val_recall: 0.760149
1501 | — val_f1_w: 0.877631 — val_precision_w: 0.881019 — val_recall_w: 0.881815
1502 | 55129/55129 [==============================] - 92s 2ms/step - loss: 0.2755 - acc: 0.9048 - val_loss: 0.3812 - val_acc: 0.8818
1503 | Epoch 12/50
1504 | 55088/55129 [============================>.] - ETA: 0s - loss: 0.2633 - acc: 0.9076— val_f1: 0.793011 — val_precision: 0.825051 — val_recall: 0.769426
1505 | — val_f1_w: 0.878768 — val_precision_w: 0.878530 — val_recall_w: 0.881325
1506 | 55129/55129 [==============================] - 90s 2ms/step - loss: 0.2633 - acc: 0.9076 - val_loss: 0.3828 - val_acc: 0.8813
1507 | Epoch 13/50
1508 | 55104/55129 [============================>.] - ETA: 0s - loss: 0.2515 - acc: 0.9121— val_f1: 0.791692 — val_precision: 0.792037 — val_recall: 0.792989
1509 | — val_f1_w: 0.873094 — val_precision_w: 0.872973 — val_recall_w: 0.873980
1510 | 55129/55129 [==============================] - 91s 2ms/step - loss: 0.2515 - acc: 0.9122 - val_loss: 0.3975 - val_acc: 0.8740
1511 | Epoch 14/50
1512 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2413 - acc: 0.9150— val_f1: 0.799131 — val_precision: 0.802153 — val_recall: 0.797500
1513 | — val_f1_w: 0.878161 — val_precision_w: 0.877972 — val_recall_w: 0.878877
1514 | 55129/55129 [==============================] - 94s 2ms/step - loss: 0.2413 - acc: 0.9150 - val_loss: 0.3853 - val_acc: 0.8789
1515 | Epoch 15/50
1516 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2295 - acc: 0.9190— val_f1: 0.800154 — val_precision: 0.813464 — val_recall: 0.789562
1517 | — val_f1_w: 0.876815 — val_precision_w: 0.876335 — val_recall_w: 0.877897
1518 | 55129/55129 [==============================] - 96s 2ms/step - loss: 0.2294 - acc: 0.9190 - val_loss: 0.3951 - val_acc: 0.8779
1519 | Epoch 16/50
1520 | 55120/55129 [============================>.] - ETA: 0s - loss: 0.2200 - acc: 0.9227— val_f1: 0.791979 — val_precision: 0.816055 — val_recall: 0.775612
1521 | — val_f1_w: 0.879622 — val_precision_w: 0.879747 — val_recall_w: 0.881978
1522 | 55129/55129 [==============================] - 97s 2ms/step - loss: 0.2200 - acc: 0.9227 - val_loss: 0.4043 - val_acc: 0.8820
1523 | ```
1524 |
1525 | 어찌어찌하여 character-level embedding과 비슷한 결과가 나오긴 했네요 ㅎㅎ 힘들었습니다...만 여기서 그나마 얻은 것은 케라스에서도 이런 layer 단위의 빡센 아키텍쳐 수립이 가능하다는 점이었네요 ㅎㅎ 이게 베스트 구현인지는 모르겠습니다만 어쨌든 논문에서 하라는 대로 다 구현도 했고 성능도 아까에 비해서는 올랐습니다! ㅠㅠ 그런데 과연 우린 90%의 벽을 넘을 수 있을까요? 그 전에 89% 벽을 넘을 수 있을까요..? 지금까지의 feature-based approach들과 차별화된 뭔가 다른 접근방법이 필요한 것은 아닐까요....?
1526 |
1527 | ## 11. Transformer, BERT, and after
1528 |
--------------------------------------------------------------------------------
/Requirements.txt:
--------------------------------------------------------------------------------
1 | fasttext==0.8.3
2 | Keras==2.1.2
3 | konlpy==0.4.4
4 | nltk==3.3
5 | numpy==1.14.3
6 | scikit-learn==0.19.1
7 | tensorflow-gpu==1.4.1
8 |
--------------------------------------------------------------------------------
/image/alexnet2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/image/alexnet2.png
--------------------------------------------------------------------------------
/image/char2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/image/char2.PNG
--------------------------------------------------------------------------------
/image/rnn.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/image/rnn.jpg
--------------------------------------------------------------------------------
/image/selfAA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/image/selfAA.png
--------------------------------------------------------------------------------
/image/skipgram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/image/skipgram.png
--------------------------------------------------------------------------------
/image/tfidf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/image/tfidf.png
--------------------------------------------------------------------------------
/image/ykim14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/image/ykim14.png
--------------------------------------------------------------------------------
/kor_char.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warnikchow/dlk2nlp/ea650390f750cce35acec9768857b7a71d6a4aac/kor_char.npy
--------------------------------------------------------------------------------