2019.06.18
출처 : https://wikidocs.net/book/2155
핵심키워드
- OOV(Out Of Vocabulary)
- FastText
패스트텍스트(FastText)
단어를 벡터로 만드는 또 다른 방법으로는 페이스북에서 개발한 패스트텍스트(FastText)가 있다. Word2Vec 이 후에 나온 것이기 때문에, 메커니즘 자체는 Word2Vec의 확장이라고 볼 수 있다. Word2Vec와 패스트텍스트와의 가장 큰 차이점은 Word2Vec은 단어를 쪼개질 수 없는 단위로 생각한다면, 패스트텍스트는 하나의 단어 안에도 여러 단어들이 존재하는 것으로 간주한다. 즉 내부 단어(subword)를 고려하여 학습한다.
1. 모르는 단어(Out Of Vocabulary, OOV)에 대한 대응
패스트텍스트에서는 우선 각 단어는 글자들의 n-gram으로 나타낸다. n을 몇으로 결정하는지에 따라서 단어들이 얼마나 분리되는지 결정한다. 예를 들어서 n을 3으로 잡은 트라이그램(tri-gram)의 경우, apple은 app, ppl, ple로 분리하고 이들 또한 임베딩을 한다. 더 정확히는 시작과 끝을 의미하는 <, >를 도입하여 아래의 5개를 임베딩한다.
<ap, app, ppl, ple, le> # n = 3 이므로 길이가 3
그리고 여기에 추가적으로 하나를 더 임베딩 한다.
<apple> # 특별 토큰
실제 사용할 때는 n의 최소값과 최대값을 설정할 수 있는데, 기본값으로는 각각 3과 6으로 설정되어져 있다. 패스트텍스트의 인공 신경망을 학습한 후에는 데이터 셋의 모든 단어의 각 n-gram에 대해서 워드 임베딩이 된다. 이렇게 되면 장점은 데이터 셋만 충분하다면 위와 같은 내부 단어(subword)를 통해 모르는 단어(OOV)에 대해서도 다른 단어와의 유사도를 계산할 수 있다는 점이다.
가령, 패스트텍스트에서 birthplace(출생지)란 단어를 학습하지 않은 상태라고 해보자. 하지만 다른 단어의 n-gram으로서 birth와 place를 학습한 적이 있다면 birthplace의 임베딩 벡터(Embedding Vector)를 만들어낼 수 있다. 이는 모르는 단어에 대처할 수 없었던 Word2Vec와는 다른 점이다.
2. 단어 집합 내 빈도 수가 적었던 단어(Rare Word)에 대한 대응
Word2Vec의 경우에는 단어 집합에서 등장 빈도 수가 높으면 비교적 정확하게 임베딩 되지만, 등장 빈도 수가 적은 단어(Rare word)에 대해서는 임베딩의 정확도가 높지 않다는 단점이 있다. 참고할 수 있는 경우의 수가 적다보니 정확하게 임베딩이 되지 않는 것이다.
하지만 패스트텍스트는 등장 빈도 수가 적은 단어라 하더라도, n-gram으로 임베딩을 하는 특성상 참고할 수 있는 경우의 수가 많아지므로 Word2Vec와 비교하여 정확도가 높은 경향이 있다.
패스트텍스트가 노이즈가 많은 코퍼스에서 강점을 가진 것 또한 이와 같은 이유이다. 모든 훈련 코퍼스에 오타(Typo)나 맞춤법이 틀린 단어가 없으면 이상적이겠지만, 실제 많은 비정형 데이터에는 오타가 섞여 있다. 그리고 오타가 섞인 단어는 당연히 등장 빈도수가 매우 적으므로 일종의 희귀 단어가 된다. 즉, Word2Vec에서는 오타가 섞인 단어는 임베딩이 제대로 되지 않지만 패스트텍스트는 이에 대해서도 일정 수준의 성능을 보인다.
3. 실습으로 비교하는 Word2Vec Vs. FastText
간단한 실습을 통해 Word2Vec와 패스트텍스트의 차이를 비교해보자. 단, 사용하는 코드는 Word2Vec을 실습하기 위해 사용했던 이전 챕터의 동일한 코드를 사용한다.
(1) Word2Vec
우선, 이전 챕터의 전처리 코드와 Word2Vec 학습 코드를 그대로 수행했음을 가정한다.
입력 단어에 대해서 유사한 단어를 찾아내는 코드에 이번에는 electrofishing이라는 단어를 넣어본다.
model = Word2Vec.load('./model/190617_word2vec.csv')a = model.wv.most_similar('electrofishing')KeyError: "word 'electrofishing' not in vocabulary"
해당 코드는 작동하지 않고, ‘not in vocabulary’라는 에러를 발생시킨다.
에러 메시지는 단어 집합(Vocabulary)에 eletrofishing이 존재하지 않는다고 한다. 이처럼 Word2Vec는 학습 데이터에 존재하지 않는 단어. 즉, 모르는 단어에 대해서는 임베딩 벡터가 존재하지 않기 때문에 단어의 유사도를 계산할 수 없다.
(2) FastText
이번에는 전처리 코드는 그대로 사용하고, Word2Vec 학습 코드만 패스트텍스트 학습 코드로 변경하여 실행해보자.
### 전처리 코드
import re
from lxml import etree
import nltk
from nltk.tokenize import word_tokenize, sent_tokenizetargetXML = open('./ted_en-20160408.xml', 'r', encoding='UTF8')
target_text = etree.parse(targetXML)
parse_text = '\n'.join(target_text.xpath('//content/text()'))
# xml 파일로부터 <content>와 </content> 사이의 내용만 가져온다.content_text = re.sub(r'\([^)]*\)', '', parse_text)
# 정규 표현식의 sub 모듈을 통해 content 중간에 등장하는 (Audio), (Laughter) 등의 배경음 부분을 제거.
sent_text = sent_tokenize(content_text)
# 입력 코퍼스에 대해서 NLTK를 이용하여 문장 토큰화를 수행한다.
normalized_text = []
for string in sent_text:
tokens = re.sub(r"[^a-z0-9]+", " ", string.lower())
normalized_text.append(tokens)result = []
result = [word_tokenize(sentence) for sentence in normalized_text]### FastText 학습
from gensim.models import FastText
ft_model = FastText(result, size=100, window=5, min_count=5, workers=4, sg=1)
학습이 진행되었다면, 이제 electrofishing에 대해서 유사 단어를 찾아보자.
[('electrolux', 0.7934642434120178), ('electrolyte', 0.78279709815979), ('electro', 0.779127836227417), ('electric', 0.7753111720085144), ('airbus', 0.7648627758026123), ('fukushima', 0.7612422704696655), ('electrochemical', 0.7611693143844604), ('gastric', 0.7483425140380859), ('electroshock', 0.7477173805236816), ('overfishing', 0.7435552477836609)]
Word2Vec은 학습하지 않은 단어에 대해서 유사한 단어를 찾아내지 못 했지만, 패스트텍스트는 유사한 단어를 계산해서 출력하고 있음을 볼 수 있다.
4. 사전 훈련 된 FastText 임베딩(Pre-trained FastText Embedding) 사용하기
보통 현업에서는 임베딩 벡터를 처음부터 만드는 것 보다, 사전에 훈련도니 워드 임베딩(Pre-trained Word Embedding)을 갖고 와서 사용하거나, 갖고 온 것에 추가 학습을 하는 방식으로 사용한다. 이는 Word2Vec으로 학습을 하든, 패스트텍스트로 학습을 하든, 글로브로 학습을 하든 마찬가지다.
페이스북의 패스트텍스트는 294개 언어에 대하여 위키피디아로 학습한 사전 훈련된 벡터들을 제공한다. 한국어 또한 제공 대상이다.
다운로드 : https://fasttext.cc/docs/en/pretrained-vectors.html
위 링크에서지원되는 언어를 확인 가능하고, 사전 훈련된 벡터들을 다운로드 받을 수 있다.
5. 한국어에 적용가능한 패스트텍스트
책에서는 별도 실습을 하지 않으나, 한국어의 경우에도 OOV 문제를 해결하기 위해 패스트텍스트를 적용하고자 하는 시도들이 있었다.
(1) 글자 단위
예를 들어서 글자(Character) 단위의 임베딩의 경우에 n=3일때 ‘자연어처리’라는 단어에 대해 n-gram을 만들어보면 다음과 같다.
<자연, 자연어, 연어처, 어처리, 처리>
(2) 자모 단위
하지만 이제 더 나아가 자모 단위(초성, 중성, 종성 단위)로 임베딩하는 시도들 또한 있었다. 글자 단위가 아니라, 자모 단위로 가게 되면 오타나 노이즈 측면에서 더 강한 임베딩을 기대해볼 수 있다. 예를 들어 ‘자연어처리’라는 단어에 대해서 초성, 중성, 종성을 분리하고, 만약, 종성이 존재하지 않는다면 ‘_’라는 토큰을 사용한다고 가정한다면 ‘자연어처리’라는 단어는 아래와 같이 분리가 가능하다.
분리된 결과 : ㅈ ㅏ _ ㅇ ㅕ ㄴ ㅇ ㅓ _ ㅊ ㅓ _ ㄹ ㅣ _
그리고 분리된 결과에 대해서 n=3일 때, n-gram을 적용하여, 임베딩을 한다면 다음과 같다.
<ㅈ ㅏ, ㅈ ㅏ _, ㅏ _ ㅇ, ... 중략>