본문 바로가기

컴린이 탈출기/NLP

이론부터 실습까지, Word2Vec의 (아마 거의) 모든 것

반응형

Contents

1. 임베딩
2. Word2Vec 모델의 기본 구조 
    2-1. CBOW
    2-2. Skip-gram
3. Word2Vec 모델의 학습 최적화 
    3-1. Negative sampling
    3-2. Subsampling
4. 영화 리뷰를 이용한 실습

 


1. 임베딩

자연어 처리(NLP, Natural Language Processing) 분야에서 '임베딩'이란, 사람이 쓰는 자연어를 기계가 이해할 수 있는 벡터로 바꾼 결과 (혹은 그 과정)을 의미합니다. 임베딩은 크게 단어 임베딩과 문장 임베딩으로 나뉘는데, 오늘 공부할 Word2Vec은 이름에서도 알 수 있듯이 단어 임베딩에 속합니다. 따라서 Word2Vec의 결과는 아래 표와 같이, 단어를 기준으로 나타나게 됩니다.

사과 0.3 1.92 -0.68 -1.34
냄비 0.9 1.3 0.5 0.42
패딩 -1.2 2.17 2.8 -0.5

 

2. Word2Vec 모델의 기본 구조

Word2Vec은 2013년 구글 연구 팀이 발표한 기법으로 현재까지도 가장 널리 쓰이고 있는 단어 임베딩 모델입니다. Word2Vec 기법은 두 개의 논문으로 나누어 발표가 되었는데, 첫 번째 논문에서는 CBOWSkip-gram이라는 모델이, 두번째 논문에서는 네거티브 샘플링 등 학습을 최적화하는 기법이 제안되었습니다.

CBOW와 Skip-Gram 모델의 기본 구조는 아래 그림과 같습니다.

CBOW는 주변에 있는 문맥 단어(Context Word)를 가지고 타깃 단어(Target Word)를 맞히는 과정에서 학습됩니다. 반대로, Skip-gram은 타깃 단어를 가지고 주변 문맥 단어가 무엇일지 예측하는 과정에서 학습됩니다. 두 모델의 큰 메커니즘은 같습니다. CBOW 모델을 뒤집으면 SKip-gram 모델이 되기 때문에 어느 하나만 이해하면 다른 하나를 쉽게 이해할 수 있습니다.

각각의 방식에 대해 알아보기 전, 두 모델에서 모두 등장하는 '윈도우(window)'라는 개념입니다. 우리는 타깃 단어를 중심으로 앞뒤 몇 개의 단어를 살펴볼 것인지(= 문맥 단어로 둘 것인지)를 정해주는데, 그 범위를 윈도우라고 부릅니다. 여러 단어로 구성된 문장에서 첫 번째 단어부터 한 단어씩 옆으로 옮겨가며 타깃 단어를 바꾸어 나갑니다. 이러한 방식을 슬라이딩 윈도우(sliding window)라고 부릅니다.

Sliding window (window size = 1)

 

2-1. CBOW (Continuous Bag of Words)

"No other magic would compare."라는 문장을 예로 들어봅시다. sliding window 기법을 이용하기 때문에 No부터 compare까지 차례로 모두 타겟 단어가 됩니다. 'magic'을 타겟 단어로 두고, 윈도우 사이즈는 2인 경우를 생각해봅시다. 이 때는 {No, other, would, compare}가 input 데이터가 됩니다. 랜덤으로 초기화된 상태였던 Hidden layer, 즉 파라미터는 출력 값이 {magic}이 되는 방향으로 학습이 됩니다. (=back propagation)

이 방식을 조금 더 수학적으로 이해해봅시다.

Word2Vec의 input 데이터와 맞혀야 하는 정답 데이터는 모두 one-hot encoding 된 벡터입니다. 따라서 input 데이터는 1 * |V| (= 어휘 집합의 크기)의 크기를 갖는 벡터입니다. 

W는 W'와 함께 모델에서 학습되는 파라미터 행렬로, 둘의 크기는 |V| * d (= 임베딩 차원수)로 동일합니다. input 데이터가 One-hot vector이기 때문에 그림에서 h로 표현되는 두 행렬의 곱은 W 행렬의 한 행과 동일합니다. CBOW 모델의 경우 input 데이터가 여러 단어 벡터로 이루어져 있기 때문에 최종적으로 h는 각 단어 벡터와 W 행렬을 곱한 값들의 평균을 사용합니다. 

hidden layer인 h를 출력 레이어로 바꿔주기 위해서는 W와는 크기만 같은 또 다른 행렬 W'가 필요합니다. 두 행렬의 곱을 통해 우리는 모든 단어(|V|개)에 대해 점수를 계산할 수 있습니다. 마지막으로 이 예측 점수를 확률 값으로 바꾸어주기 위해 softmax를 취해줍시다. (각 단어의 점수에 비례해, 점수를 확률로 바꾸어줍니다.) 

 

2-2. Skip-gram

다시 "No other magic would compare."라는 문장을 생각해봅시다. 타깃 단어가 input 데이터가 되기 때문에 CBOW와는 데이터셋 구성이 조금 다릅니다.  아까와 같이 타깃 단어는 'magic', 윈도우 사이즈는 2라고 해봅시다. 이 경우 {magic | No}, {magic | other}, {magic | would}, {magic | compare} 각각이 input 데이터가 됩니다. 각각이 input 데이터가 되기 때문에, 타겟 단어인 magic은 총 4번의 학습 기회를 갖게 됩니다. 반면 CBOW 모델은 같은 윈도우에 대해 학습이 1번밖에 일어나지 않게 됩니다. 같은 크기의 말뭉치에 대해 Skip-gram의 학습량이 더 크기 때문에(=임베딩 품질이 더 좋다!), CBOW보다는 대부분 Skip-gram 방식을 사용합니다.

 

위에서 언급한 것과 같이 CBOW 모델을 뒤집은 것이 Skip-gram 모델로 큰 메커니즘이 같습니다. Skip-gram 모델의 input 데이터와 정답 데이터는 모두 1*|V| 크기의 one-hot encoding된 벡터입니다. W와 W' 역시 CBOW 모델에서와 같이 |V| * d 크기를 갖는 행렬입니다. 

Skip-gram은 아래의 식을 최대화하는 방향으로 학습을 진행합니다. 아래 식의 좌변은 조건부 확률로, 타겟 단어(c)가 주어졌을 때, 문맥 단어(o)가 나타날 확률을 뜻합니다. 이 식을 최대화하기 위해서는 우변의 분자는 키우고, 분모는 줄여야 합니다. 

u는 W'의 열벡터, v는 W의 행벡터이다.

우변의 분자값을 키운다는 것은 타깃 단어에 해당하는 벡터와 문맥 단어에 해당하는 벡터의 내적을 키운다는 뜻입니다.  벡터의 내적은 곧 코사인 값과 연관되기 때문에, 두 단어의 유사도가 높이는 것으로도 생각할 수 있습니다. 반면 분모를 줄인다는 것은 윈도우 내에 등장하지 않는 단어들을 타깃 단어와의 유사도를 감소시키는 것으로 이해할 수 있습니다.

이런 학습 과정을 거쳐 조정된 파라미터 행렬 W는 단어 임베딩 벡터가 모인 것으로, Word2Vec의 최종 결과물입니다.

 

3. Word2Vec 모델의 학습 최적화

Word2Vec에서 학습되는 파라미터는 크기가 각각 |V| * d , d * |V|인 행렬 W와 W'입니다. 타깃 단어를 입력받아 문맥 단어를 출력하는 모델을 학습한다는 것은 정답 문맥 단어가 나타날 확률은 높이고 나머지 단어들은 확률을 낮춰주는 과정입니다. 단어수가 늘어날수록 계산량이 증가하게 된다. 이런 문제점을 해결하기 위한 방법이 Word2Vec의 두 번째 논문에서 발표되었습니다.

3-1. Negative sampling  

네거티브 샘플링 방식에 대해 이해하기 위해서는 우선, 네거티브 샘플과 포지티브 샘플이 무엇인지를 알아야합니다. 이름에서 오는 그 느낌이 맞습니다! 위에서 계속 언급했던 문장("No other magic would compare")을 기준으로, {magic | No}, {magic | other}, {magic | would}, {magic | compare} 은 포지티브 샘플이 됩니다. 반면, {magic | orange}, {magic | pencil}, {magic | cookie}와 같이 문장에서 magic(타깃 단어)의 주변에 등장하지 않은 단어 쌍을 네거티브 샘플이라고 합니다. 

네거티브 샘플에서 타깃 단어의 짝은 말뭉치 전체에서 추출합니다. 말뭉치에 자주 등장하지 않는 희귀한 단어가 네거티브 샘플로 조금 더 잘 뽑힐 수 있도록 설계되었습니다. 확률 수식은 아래와 같습니다. f(w_) 는 (해당 단어 빈도 / 전체 단어 수)를 의미한다. 

네거티브 샘플링 방식으로 학습을 하게 되면 1개의 포지티브 샘플과 k개의 네거티브 샘플에 대해서만 계산이 이루어집니다. 1 스텝에 전체 단어를 모두 계산하는 기존 방법보다 계산량이 훨씬 적습니다. 논문에 따르면 작은 데이터에는 k를 5~20, 큰 말뭉치에서는 2~5로 하는 것이 성능이 좋았다고 합니다.

 

3-2. Subsampling

네거티브 샘플링과 함께 자주 등장하는 단어는 학습에서 제외하는 서브샘플링 기법도 이용합니다. 고빈도 단어는 샘플로 뽑힐 때마다 모두 학습시키는 것이 비효율적이라고 보았기 때문입니다. 서브 샘플링 확률 수식은 아래와 같습니다.

논문에서는 t를 10^-5로 설정했습니다. f(w_)이 0.01로 나타나는 빈도 높은 단어 (ex. 조사 은/는)는 위 식으로 계산하면 P값이 약 0.97이 됩니다. 따라서 f(w_)가 0.01인 어떤 단어는 100번의 학습 기회 중 97번 정도는 학습에서 제외되게 됩니다. 반대로 등장 빈도가 매우 낮은 단어는 샘플로 뽑힐 때마다 빠짐없이 학습을 하게 됩니다.

 

4. 영화 리뷰를 이용한 실습

이제 이론은 여기까지! 15만개의 영화 리뷰 데이터와 구글 코랩을 이용하여 Word2vec 모델을 직접 돌려봅시다. 

4-0. Matplotlib에서 한글 사용하기

본격적인 시작에 앞서 해야할 것이 있습니다. Word2vec 모델을 돌린 후, 임베딩 결과를 matplotlib을 이용해 시각화해서 살펴볼 예정인데, matplotlib에서는 한글 폰트를 기본으로 지원하지 않기 때문에 별도의 설정이 필요합니다. 파일 설치 후 런타임을 다시 시작해야 하기 때문에 미리 설치하고 시작하는 것이 좋습니다!

위의 코드를 실행한 후, 런타임 - 런타임 다시 시작을 눌러줍시다. 그리고 위의 코드를 다시 실행하면 좀 전과 달리 이미 설치되어 있다는 내용의 메시지가 뜹니다.

한글 설정 전에는 ㅁ로 뜨던 한글이 잘 뜨는 것을 확인할 수 있습니다. 

 

4-1. 데이터 전처리

1) 결측값 제거 및 형태소 분석(토큰화) 

train 데이터는 위와 같은 형태로, 짤막한 영화 리뷰와 해당 리뷰가 대체로 긍정(1)인지 부정(0)인지를 나타내는 라벨로 구성되어있습니다. 오늘은 감성 분석 단계까지 가지 않고, Word2Vec 모델을 돌리는 것이 목표이기 때문에 'document'열만 이용할 예정입니다.

형태소 분석을 하기 전 Null 값이 있는지 체크해봅시다. 그 결과 5개의 Null 값이 있는 것을 확인할 수 있습니다.

같은 행에서 어떤 하나의 열이라도 Null 값을 갖게 되면 해당 행을 drop 해주었습니다.

결측값이 모두 없어진 것을 확인할 수 있습니다.

형태소 분석기로는 konlpy의 Okt을 이용하였습니다. (konlpy는 별도의 설치 과정이 필요합니다.) 

pos()는 텍스트를 형태소 단위로 나누고, 나눠진 각 형태소를 그에 해당하는 품사와 함께 리스트화해 리턴합니다. norm 파라미터는 문장 정규화에 관여합니다. 문장 정규화는 표현 방법이 다른 단어들을 통합시켜서 같은 단어로 만드는 것을 의미합니다. (ex. US와 USA를 같은 단어로 취급) stem은 어간 추출과 관련된 파라미터입니다. stem=False 였던 첫 번째 예시에서 '짜증나네요'로 분석되었던 것이 두 번째 예시에서는 '짜증나다'로 분석된 것을 확인할 수 있습니다. 마지막 예시에서 알 수 있듯이 join=True는 형태소와 품사를 하나의 문자열로 묶어 반환합니다. 

위와 같이 파라미터를 설정해 토큰화하는 함수를 만들어주었습니다.

토큰화 결과. 위의 예시처럼 형태소 분석이 제대로 된 문장도 있지만, 아래의 예시처럼 오타가 있는 경우에는 완전히 틀린 분석을 내놓는다는 것을 알 수 있습니다.

위의 예시를 다시 보면 '..'과 같은 구두점 또한 하나의 형태소로 분석하고 있다는 사실을 알 수 있습니다. 하지만 구두점까지 벡터화를 할 필요는 없기 때문에 이를 제거해주도록 합시다.

2) 정규표현식

정규표현식을 이용해 한글 문자와 공백을 제외한 구두점, 특수문자 등을 제거합시다.

train 데이터의 일부를 출력한 결과, '..'과 같은 구두점이 제거된 것을 확인할 수 있습니다.

3) 불용어 (stopwords)

불용어는 자주 등장하지만 분석을 하는 데에 있어서는 도움이 되지 않는 단어를 의미합니다. '나', '너'와 같은 단어나 조사, 접미사는 문장에서 자주 등장하지만 실제로 의미 분석을 하는 데에는 거의 도움이 되지 않습니다. 영화 리뷰 감성 분석과 같은 태스크에서는 좋은 성능을 내기 위해서는 불용어 제거가 반드시 필요합니다. 우리의 목표는 단순히 단어를 벡터화하는 것이지만, 일부 불용어를 제거해보도록 합시다.

불용어 사전을 구축한 뒤, X_train 리스트에 불용어를 제외한 '형태소/품사' 데이터가 담기게 했습니다.

 

4-2. Word2Vec 모델

Gensim 패키지의 Word2vec 모듈을 이용했습니다. 임베딩 결과는 300차원이 되게 설정했고, 윈도우 사이즈는 3으로 설정했습니다. 등장 빈도가 5회 이하(min_count)인 단어는 취급하지 않도록 했습니다.

model.wv.vocab은 (형태소/품사)와 300차원의 벡터를 리턴합니다. vocabs 변수에는 keys를 통해 (형태소/품사) 데이터를 저장했고, list에는 벡터를 저장하였습니다. 이 두 변수(데이터)를 이용해 임베딩 결과를 시각화해봅시다.

 

4-3. 임베딩 결과 시각화

1) PCA

300차원 벡터를 시각화하기 위해서는 2차원 혹은 3차원으로 벡터를 축소하는 과정이 필요합니다. 이런 방식을 '차원 축소'라고 부르고, PCA는 대표적인 차원 축소 방식 중 하나 입니다. 

사이킷런의 PCA 모듈을 이용했다. n_components는 input 데이터를 몇 차원으로 축소시킬 것인지를 의미합니다. 2차원 벡터의 x 값은 xs에, y 값은 ys에 저장했습니다.

PCA 결과를 2차원 그래프로 나타낸 결과는 아래와 같습니다.

단어 벡터가 너무 몰려있다보니 품사는 제외하고 형태소만 나타나게 했다.

많은 벡터가 가운데에 모여 있는 것을 확인할 수 있습니다. 리뷰 양도 10000개로 적고, 300차원의 벡터를 2차원에 나타내다 보니 결과가 아주 우수하다고 보기는 어려운 것이 사실입니다. : (

 

2) t-SNE

PCA가 자주 이용되는 차원 축소 방식이긴 하지만, 위의 결과와 같이 PCA는 군집의 변별력을 죽인다는 단점이 있습니다. (자세한 내용은 여기서 확인할 수 있다.) 이러한 단점을 개선한 방식이 t-SNE 차원축소 방식입니다. 실제로 임베딩 결과는 t-SNE로 나타내는 경우가 많습니다.

t-SNE로 나타낸 단어 임베딩 결과

 

마찬가지로 사이킷런에서 TSNE 모듈을 이용했습니다. PCA와 코드는 크게 다르지 않습니다.

위에서 언급한 이유들로 임베딩 결과가 우수한 편은 아니라는 점을 감안하고 봅시다. 그럼에도 군데군데에서 괜찮은 임베딩 결과를 찾아볼 수 있습니다. (야동, 섹시하다, 섹시), (첫사랑, 풋풋하다) 등이 그 예이다. 

짝짝! 이로써 Word2Vec의 이론부터 실습까지 (NLP 초보 기준) 거의 모든 것을 살펴보았습니다. 상상만큼 좋은 임베딩 결과를 얻진 못한 점이 아쉽지만, Word2Vec의 전반적인 흐름을 파악하는 데에는 무리가 없었을 것으로 생각됩니다. 위 실습 코드 전체는 이곳에서 확인할 수 있으며, 잘못된 부분에 대한 지적은 댓글로 부탁드립니다. :) 

 

Reference

https://ratsgo.github.io/from%20frequency%20to%20semantics/2017/03/30/word2vec/

https://dreamgonfly.github.io/machine/learning,/natural/language/processing/2017/08/16/word2vec_explained.html

https://wikidocs.net/44249

반응형