자연어처리(NLP) Word2Vec, fastText

생각하는 마리오네트·2021년 8월 20일
0

딥러닝

목록 보기
4/5
post-thumbnail

이전에는 텍스트를 벡터화 시키는 방법중 등장횟수 기반의 단어표현(Count-based Representation) 으로 벡터화 하는것을 알아보았습니다.

이번에는 분포기반의 단어표현(Distributed Representation)에 대해서 학습해보겠습니다.


Distributed Representation

분포기반의 단어표현은 단어 자체를 벡터화하는 방법을 사용한다.

  • 분포기반의 단어표현은 Word2Vec과 fastText두가지 방법이 있다. 이방법의 차이는 무엇이고, 어떻게 사용하는지 알아보자!! 그전에 두가지 방법은 모두 우리가 벡터로 표현하고자 하는 타겟단어가 해당 단어 주변에 있는 단어에 의해 결정된다. 이러한 방법으로 정하는 이유는 분포가설(Distribution hypothesis)때문이다.

  • 분포가설이란? "비슷한 위치에 등장하는 단어들은 비슷한 의미를 지닌다." 라는 뜻이다.

  • 예를 들어 두개의 문장이 있다고 하자. I found beautiful laptop/ I found good laptop

  • 두 문장은 해당 단어의 주변에 분포한 단어가 유사하기 때문에 비슷한 의미를 지닐것이다. 라고 가정하는것을 말한다.


원핫인코딩을 사용하지 못하는 이유

사실 단어를 벡터화할때 선택할 수 있는 가장 쉬운 방법은 원핫인코딩이다. 하지만 이것을 사용하게 되면 단어간의 유사도를 구할 수 없다는 단점이 있습니다. 단어간 유사도를 계산할 때에는 "코사인 유사도" 라는것을 사용하게 되는데 원핫으로인코딩한 두 벡터의 내적은 항상 0이 되기때문에 유사도를 구하더라도 항상 0이 된다. 따라서 원핫인코딩을 사용할 수 없다.

  • 코사인 유사도의 수식은 다음과 같다.
Cosine similarity=abab\large \text{Cosine similarity} = \frac{\vec{a} \cdot \vec{b} }{\vert \vec{a} \vert \vert \vec{b} \vert }

임베딩(Embedding)

이러한 단점을 해결하기 위해서 나온것이 임베딩(Embedding)입니다.
임베딩은 단어를 고정길이의 벡터(차원이 일정한 벡터) 로 나타내기 때문에 '임베딩'이라고 부른다.

Word2Vec

  • 단어를 벡터로 나타내는 방법으로 가장 널리 사용되는 임베딩 방법 중 하나입니다. Word2Vec은 특정 단어 양 옆에 있는 한 단어(window size = 1)의 관계를 활용한다.

  • Word2Vec은 두가지 방법이 있는데 CBow와 Skip-gram이다.

CBoW와 Skip-gram

CBoW는 주변 단어에 대한 정보를 기반으로 중심 단어의 정보를 예측하는 모델이고, Skip-gram은 중심 단어의 정보를 기반으로 주변 단어의 정보를 에측하는 모델이다.

위의 그림처럼 주변단어들로 중심단어를 예측하느냐, 중심단어로 주변단어를 예측하는것인가 에 대한 차이이다.

애국가를 예를들어보겠습니다. "...백두산이 마르고 닳도록 하느님이 보우하사..."

  • CBoW의 경우에는
    "..백두산이 마르고[...]하느님이 보우하사.."
    ".. ... 마르고 닳도록 [...] 보우하사.."

  • Skip-gram의 경우에는
    "..[...]백두산이[...][...][....].."
    "..[...][....]마르고[...][....].."
    "..[...][....][...]닳도록[....].."

다시한번 차이를 비교해 보면 CBoW의 경우 주변단어로 중심단어를 예측하는 모델이고, Skip-gram의 경우 중심단어로 주변단어를 예측하는 모델이다.

겉으로 봤을때에는 당연히 CBoW의 성능이 더 좋아보인다, 하지만 역전파 개념으로 생각했을때 더 학습이 많이되어 Skip-gram이 성능이 조금 더 좋게 나타난다. 그만큼 계산량이 많기때문에 Skip-gram에 필요한 리소스가 더 큰것은 어쩔 수 없다.


Word2Vec모델 구조

  • 입력층 : Word2Vec의 입력은 원-핫 인코딩이 된 단어벡터이다.
  • 은닉충 : 임베딩 벡터의 차원수 만큼 노드를 구성된다.(은닉층 1개인 신경망)
  • 출력층 : 단어 개수 만큼의 노드로 이루어져 있고, 활성화 함수는 소프트맥스를 사용한다.

코드실습

# 최신버전 업그레이드
!pip install gensim --upgrade # 코렙기준 업그레이드 코드

# import 및 버전확인
import gensim
gensim.__version__

# 구글뉴스 말뭉치로 학습된 word2vec다운
import gensim.downloader as api

wv = api.load('word2vec-google-news-300')

# 임베딩 벡터의 차원과 값을 확인해보기

vec_king = wv['king']

print(f"Embedding dimesion is : {vec_king.shape}\n")
print(f"Embedding vector of 'king' is \n\n {vec_king}")

>>>
Embedding dimesion is : (300,)

Embedding vector of 'king' is 

 [ 1.25976562e-01  2.97851562e-02  8.60595703e-03  1.39648438e-01
 -2.56347656e-02 -3.61328125e-02  1.11816406e-01 -1.98242188e-01
  5.12695312e-02  3.63281250e-01 -2.42187500e-01 -3.02734375e-01
 -1.77734375e-01 -2.49023438e-02 -1.67968750e-01 -1.69921875e-01
  3.46679688e-02  5.21850586e-03  4.63867188e-02  1.28906250e-01
  1.36718750e-01  1.12792969e-01  5.95703125e-02  1.36718750e-01
  1.01074219e-01 -1.76757812e-01 -2.51953125e-01  5.98144531e-02
  3.41796875e-01 -3.11279297e-02  1.04492188e-01  6.17675781e-02
  1.24511719e-01  4.00390625e-01 -3.22265625e-01  8.39843750e-02
  3.90625000e-02  5.85937500e-03  7.03125000e-02  1.72851562e-01
  1.38671875e-01 -2.31445312e-01  2.83203125e-01  1.42578125e-01........

Word2Vec의 한계

위에서 다운받은 구글 뉴스 말뭉치를 데이터에 cameroon이라는 구글 뉴스 말뭉치에 등장하지 않는 단어를 임베딩 벡터화 해보겠습니다.

unk = 'cameroon'

try:
    vec_unk = wv[unk]
except KeyError:
    print(f"The word #{unk} does not appear in this model")
>>>
The word #cameroon does not appear in this model

Word2Vec의 한계는 바로 코퍼스(말뭉치)에 포함되지 않은 단어는 벡터화 할 수 없다는 단점이 있습니다.

단어간의 유사도 파악하기

gensim패키지에 있는 .similarity를 사용하면 단어들 간의 유사도를 파악할 수 있다.


pairs = [
    ('car', 'minivan'),   
    ('car', 'bicycle'),  
    ('car', 'airplane')
]
    

for w1, w2 in pairs:
    print(f'{w1} ==={w2}\t  {wv.similarity(w1, w2):.2f}')
>>>
car === inivan	  0.69
car === bicycle	  0.54
car === airplane  0.42

.most_similar메서드를 사용해 보겠습니다.

for i, (word, similarity) in enumerate(wv.most_similar(positive=['car', 'minivan'], topn=5)):
    print(f"Top {i+1} : {word}, {similarity}")
>>>
Top 1 : SUV, 0.8532192707061768
Top 2 : vehicle, 0.8175783753395081
Top 3 : pickup_truck, 0.7763688564300537
Top 4 : Jeep, 0.7567334175109863
Top 5 : Ford_Explorer, 0.7565720081329346
  • 'car'벡터에 'minivan'벡터를 더한 벡터와 가장 유사한 5개 단어를 뽑은 것입니다. 만약에 특정 벡터를 빼려면 negative=[' '] 를 사용하면 됩니다.
  • .doesnt_match 메서드를 사용하여 리스트중 가장 관계없는 단어도 뽑아볼 수 있다.
 print(wv.doesnt_match(['fire', 'water', 'land', 'sea', 'air', 'car']))
 >>>
 car

fastText

fastText는 Word2Vec방식에서 철자 기반의 임베딩 방식을 더해준 새로운 임베딩 방식이다.
해당 방식은 Word2Vec의 한계였던 기존에 말뭉치에 존재하지 않았던 단어들은 벡터화할 수 없다는 점이었습니다.

따라서 이를 보완하기 위해 만들어졌습니다.

OOV(Out of Vocabulary)문제

Word2Vec에서 나온 한계점을 표현하면 OOV문제라고 할 수 있습니다. 즉, 기존 말뭉치에 등장하지 않은 단어를 출력하면 에러가 나왔습니다. 말뭉치에없거나 적게등장한 단어는 임베딩 벡터를 생성해내지 못하는데 말뭉치에 없는경우를 OOV라고 부릅니다.

철자단위 임베딩

철자단위 임베딩이란 모델이 학습하지 못한 단어더라도 잘 쪼개고 보면 말뭉치에서 등장했던 단어를 통해 유추해 볼 수 있다는 것이다.

예를 들어 "맞벌이"라는 단어가 학습되어 있지 않다고 하더라도 아래 단어들을 알면 대략적으로 유추해 볼 수 있다.

  1. "맞선, 맞절, 맞대다, 맞들다, 맞바꾸다, 맞서다, 맞잡다, 맞장구치다"
  2. "벌다, 벌어, 벌고"
  3. "먹이, 깊이, 넓이"
  • fastText는 단어를 3~6개 단위로 잘라서 임베딩 한다. 예를들어 "eat"이라는 단어가 있다면
    <ea , eat, at> 이런식으로 접두사와 접미사에는 표시를 위해 <>와 같은 표시를 한다. 이과정을 거치면 eat단어 외에 3개의 철자가 더 추가된다.

  • 이러한 방식으로 임베딩 벡터를 생성하고 원래 있던 eat의 임베딩 벡터와 함께 사용하는 방식이다.

  • 이렇게 얻어진 n-gram들의 임베딩 벡터를 모두 구하면된다.

  • 단순히 봤을때 Word2Vec과 달리 유추를 통해 학습되지않은 단어의 벡터를 구할 수 있지만, 연산이 엄청많아 시간이 많이 걸릴것이라고 짐작해 볼 수 있다. 하지만 알고리즘 자체를 워낙 효율적으로 만들었기 때문에 시간상으로 그렇게 많은 차이는 나지 않는다.

코드실습

from pprint import pprint as print
from gensim.models.fasttext import FastText
from gensim.test.urils import datapath

# 데이터 파일 이름 설정 및 모델 설정
corpus_file = datapath('lee_background.cor')

model = FastText(vector_size =100)

# 모델에 단어 집합으로 생성
model.build_vocab(corpus_file = corpus_file)

# 학습하기
model.train(corpus_file = corpus_file, epochs = model.epochs,
		total_examples = model.corpus_count, total_words = model.corpus_total_words)
#  night와 nights가 각각 있는지 확인하기
ft = model.wv
print(f"{'night' in ft.key_to_index}")
print(f"{'nights' in ft.key_to_index}")

>>>
True
False
# 없는 단어이지만 유사도를 확인해 볼 수 있다.
print(ft.similarity('night', 'nights'))
>>> 
0.9999918
# 사전에 없는 단어인 nights 와 가장 비슷한 단어는 어떤것이 있는지 확인하기
print(ft.most_similar('nights'))

>>>
[('night', 0.9999917149543762),
 ('rights', 0.9999875426292419),
 ('flights', 0.9999871850013733),
 ('overnight', 0.9999868273735046),
 ('fighters', 0.9999852776527405),
 ('fighting', 0.9999851584434509),
 ('entered', 0.9999849796295166),
 ('fight', 0.999984860420227),
 ('fighter', 0.9999845027923584),
 ('night.', 0.9999843835830688)]

profile
문제를해결하는도구로서의"데이터"

0개의 댓글