본문 바로가기

머신러닝,딥러닝/자연어처리(NLP)

word2vec 이해를 위한 개념 정리 part1

word2vec 을 이해하기 위한 개념들을 모아서 정리하는 포스팅입니다. part 몇까지 진행될지는 모르겠습니다.

gensim 튜토리얼을 공부하다가 word2vec 개념을 정리할 필요가 있다고 느껴서 정리하는 포스팅입니다. 

 

출처는 제일 하단에 밝혔으며, 개인적으로 자연어처리 공부하시는 분들은 한 번 이상 보는 자료들이라고 생각합니다. 

이를 정리하는데 초점을 두었고, 좀 더 자세한 내용이 궁금한 분들은 출처에 있는 링크나 책을 통해 더 학습하시면 좋을 것 같습니다 

 

1. 희소 표현(Sparse Represents)

 

희소행렬(sparse matrix)은 행렬의 값이 대부분 0인 경우를 가리키는 표현이다.

 

출처:https://bit.ly/38ZatV3

희소 표현은 희소행렬에서 나온 개념으로 보시면 됩니다. 원-핫 인코딩을 통해서 나온 원-핫 벡터들은 표현하고자 하는 단어의 인덱스의 값만 1이고, 나머지 인덱스에는 전부 0으로 표현되는 벡터 표현 방법이었습니다. 이렇게 벡터 또는 행렬(matrix)의 값이 대부분이 0으로 표현되는 방법을 희소 표현(sparse representation)이라고 합니다. 그러니까 원-핫 벡터희소 벡터(sparse vector)입니다.

 

이러한 희소 벡터의 문제점단어의 개수가 늘어나면 벡터의 차원이 한없이 커진다는 점입니다. 원-핫 벡터로 표현할 때는 갖고 있는 코퍼스에 단어가 10,000개였다면 벡터의 차원은 10,000이어야만 했습니다. 심지어 그중에서 단어의 인덱스에 해당되는 부분만 1이고 나머지는 0의 값을 가져야만 했습니다. 단어 집합이 클수록 고차원의 벡터가 됩니다. 예를 들어 단어가 10,000개 있고 강아지란 단어의 인덱스는 5였다면 원 핫 벡터는 이렇게 표현되어야 했습니다.

 

Ex) 강아지 = [ 0 0 0 0 1 0 0 0 0 0 0 0 ... 중략... 0] # 이때 1 뒤의 0의 수는 9995개.

 

이러한 벡터 표현은 공간적 낭비를 불러일으킵니다. 잘 생각해보면, 공간적 낭비를 일으키는 것은 원-핫 벡터뿐만은 아닙니다. 희소 표현의 일종인 DTM과 같은 경우에도 특정 문서에 여러 단어가 다수 등장하였으나, 다른 많은 문서에서는 해당 특정 문서에 등장했던 단어들이 전부 등장하지 않는다면 역시나 행렬의 많은 값이 0이 되면서 공간적 낭비를 일으킵니다. 뿐만 아니라, 원-핫 벡터는 단어의 의미를 담지 못한다는 단점을 갖고있습니다.

 

2. 밀집 표현(Dense Representation) 

이러한 희소 표현과 반대되는 표현이 있으니, 이를 밀집 표현(dense representation)이라고 합니다. 밀집 표현은 벡터의 차원을 단어 집합의 크기로 상정하지 않습니다. 사용자가 설정한 값으로 모든 단어의 벡터 표현의 차원을 맞춥니다. 또한, 이 과정에서 더 이상 0과 1만 가진 값이 아니라 실수 값을 가지게 됩니다. 다시 희소 표현의 예를 가져와봅시다.

 

Ex) 강아지 = [ 0 0 0 0 1 0 0 0 0 0 0 0... 중략... 0] # 이때 1 뒤의 0의 수는 9995개. 차원은 10,000

 

예를 들어 10,000개의 단어가 있을 때 강아지란 단어를 표현하기 위해서는 위와 같은 표현을 사용했습니다. 하지만 밀집 표현을 사용하고, 사용자가 밀집 표현의 차원을 128로 설정한다면, 모든 단어의 벡터 표현의 차원은 128로 바뀌면서 모든 값이 실수가 됩니다.

 

Ex) 강아지 = [0.2 1.8 1.1 -2.1 1.1 2.8... 중략...] # 이 벡터의 차원은 128

 

이 경우 벡터의 차원이 조밀해졌다고 하여 밀집 벡터(dense vector)라고 합니다.

 

핵심은 희소 행렬을 차원 감소를 통해(예를 들어 SVD와 같은 특이값 분해) 중요한 축을 찾아내어 더 적은 차원으로 다시 표현하는 것(=밀집 벡터)입니다. 

 

+)  Neural Probabilistic Language Model(NPLM)

 

NPLM은 단어들의 연쇄가 주어졌을 때 다음 단어가 무엇인지 맞추는 과정에서 분산 표상된 단어 벡터들을 만드는 방법론입니다.

 

ex) 발 없는 말이 천리 ?

 

위와 같이 ‘발’, ‘없는’, ‘말이’, ‘천리’ 네 개 단어가 주어졌다고 합시다. 그다음에 올 단어는 무엇일까요? 수많은 동사가 올 수 있겠지만 ‘간다’라는 단어가 등장할 확률이 높을 겁니다. 우리는 ‘발 없는 말이 천리 간다’는 속담을 자주 쓰는 편이니까요. 이를 수식으로 쓰면 아래와 같습니다.

 

NPLM은 위 식의 조건부 확률을 최대화하는 방향으로 학습을 하게 됩니다. 즉, P(간다P(간다|발,없는,말이,천리)발,없는,말이,천리)를 높이고 싶은 거죠. 바꿔 말하면 ‘발’, ‘없는’, ‘말이’, ‘천리’ 네 개 단어로 ‘간다’를 맞추자는 겁니다. 이처럼 NNLM은 직전까지 등장한 n1n−1개 단어들로 nn번째 단어를 맞추는 N-gram 모델이 그 본질입니다.

 

출처: https://ratsgo.github.io/from%20frequency%20to%20semantics/2017/03/29/NNLM/

 

3. 워드 임베딩(Word Embedding)

단어를 밀집 벡터(dense vector)의 형태로 표현하는 방법을 워드 임베딩(word embedding)이라고 합니다. 그리고 이 밀집 벡터를 워드 임베딩 과정을 통해 나온 결과라고 하여 임베딩 벡터(embedding vector)라고도 합니다.

 

워드 임베딩 방법론으로는 LSA, Word2Vec, FastText, Glove 등이 있습니다. 케라스에서 제공하는 도구인 Embedding()는 앞서 언급한 방법들을 사용하지는 않지만, 단어를 랜덤한 값을 가지는 밀집 벡터로 변환한 뒤에, 인공 신경망의 가중치를 학습하는 것과 같은 방식으로 단어 벡터를 학습하는 방법을 사용합니다. 아래의 표는 앞서 배운 원-핫 벡터와 지금 배우고 있는 임베딩 벡터의 차이를 보여줍니다.

 

 

4. 분산 표현(Distributed Representatin)

 

분산 표현(distributed representation) 방법은 기본적으로 분포 가설(distributional hypothesis)이라는 가정 하에 만들어진 표현 방법입니다. 이 가정은 '비슷한 위치에서 등장하는 단어들은 비슷한 의미를 가진다'라는 가정입니다. 강아지란 단어는 귀엽다, 예쁘다, 애교 등의 단어가 주로 함께 등장하는데 분포 가설에 따라서 저런 내용을 가진 텍스트를 벡터화한다면 저 단어들은 의미적으로 가까운 단어가 됩니다. 분산 표현은 분포 가설을 이용하여 단어들의 셋을 학습하고, 벡터에 단어의 의미를 여러 차원에 분산하여 표현합니다.

 

이렇게 표현된 벡터들은 원-핫 벡터처럼 벡터의 차원이 단어 집합(vocabulary)의 크기일 필요가 없으므로, 벡터의 차원이 상대적으로 저차원으로 줄어듭니다. 예를 들어 단어가 10,000개 있고 인덱스가 1부터 시작한다고 하였을 때 강아지란 단어의 인덱스는 5였다면 강아지란 단어를 표현하는 원-핫 벡터는 다음과 같았습니다.

 

Ex) 강아지 = [ 0 0 0 0 1 0 0 0 0 0 0 0 ... 중략 ... 0]

 

1이란 값 뒤에는 0이 9,995개가 있는 벡터가 됩니다. 하지만 Word2Vec로 임베딩 된 벡터는 굳이 벡터의 차원이 단어 집합의 크기가 될 필요가 없습니다. 강아지란 단어를 표현하기 위해 사용자가 설정한 차원을 가지는 벡터가 되면서 각 차원은 실수형의 값을 가집니다.

 

Ex) 강아지 = [0.2 0.3 0.5 0.7 0.2 ... 중략 ... 0.2]

 

요약하면 희소 표현이 고차원에 각 차원이 분리된 표현 방법이었다면, 분산 표현은 저차원에 단어의 의미를 여러 차원에다가 분산하여 표현합니다. 이런 표현 방법을 사용하면 단어 간 유사도를 계산할 수 있습니다.

이를 위한 학습 방법으로는 NNLM, RNNLM 등이 있으나 요즘에는 해당 방법들의 속도를 대폭 개선시킨 Word2Vec가 많이 쓰이고 있습니다.

5. 신경망에서의 단어 처리

자연어처리에서는 단어를 벡터로 바꾸는 기법 중 2가지로 나뉩니다. '통계 기반 기법'과 '추론 기반 기법'입니다. 단어의 의미를 얻는 방식은 서로 크게 다르지만, 그 배경에는 모두 분포 가설이 있습니다. 분포 가설이란 ‘단어의 의미는 주변 단어에 의해 형성된다’는 가설입니다. 

 

출처:http://www.hanbit.co.kr/media/channel/view.html?cms_code=CMS1232324353

[그림 2]처럼 추론 문제를 풀고 학습하는 것이 ‘추론 기반 기법’이 다루는 문제입니다. 이러한 추론 문제를 반복해서 풀면서 단어의 출현 패턴을 학습하는 것이죠. ‘모델 관점’에서 보면, 이 추론 문제는 [그림 3]처럼 보입니다.

 

출처: http://www.hanbit.co.kr/media/channel/view.html?cms_code=CMS1232324353

[그림 3]처럼 추론 기반 기법에는 어떠한 모델이 등장합니다. 우리는 이 모델로 신경망을 사용합니다. 모델은 맥락 정보를 입력받아 (출현할 수 있는) 각 단어의 출현 확률을 출력합니다. 이러한 틀 안에서 말뭉치를 사용해 모델이 올바른 추측을 내놓도록 학습시킵니다. 그리고 그 학습의 결과로 단어의 분산 표현을 얻는 것이 추론 기반 기법의 전체 그림입니다.

 

지금부터 신경망을 이용해 ‘단어’를 처리합니다. 그런데 신경망은 “you”와 “say” 등의 단어를 있는 그대로 처리할 수 없으니 단어를 ‘고정 길이의 벡터’로 변환해야 합니다. 이때 사용하는 대표적인 방법이 단어를 원핫one-hot 표현(또는 원핫 벡터)으로 변환하는 것입니다.  “You say goodbye and I say hello.”라는 한 문장짜리 말뭉치를 예로 설명하겠습니다. 이 말뭉치에는 어휘가 총 7개 등장합니다(“you”, “say”, “goodbye”, “and”, “i”, “hello”, “.”). 이중 두 단어의 원핫 표현을 [그림 4]에 나타내봤습니다.

 

[그림 4]처럼 단어는 텍스트, 단어 ID, 그리고 원핫 표현 형태로 나타낼 수 있습니다. 여기서 단어를 원핫 표현으로 변환하는 방법은 이렇습니다. 먼저 총 어휘 수만큼의 원소를 갖는 벡터를 준비하고, 인덱스가 단어 ID와 같은 원소를 1로, 나머지는 모두 0으로 설정합니다. 이처럼 단어를 고정 길이 벡터로 변환하면 우리 신경망의 입력층은 [그림 5]처럼 뉴런의 수를 ‘고정’할 수 있습니다.

 

[그림 5]처럼 입력층의 뉴런은 총 7개입니다. 이 7개의 뉴런은 차례로 7개의 단어들에 대응합니다(첫 번째 뉴런은 “you”에, 두 번째 뉴런은 “say”에, ...). 이제 이야기가 간단해졌습니다. 단어를 벡터로 나타낼 수 있고, 신경망을 구성하는 ‘계층’들은 벡터를 처리할 수 있습니다. 다시 말해, 단어를 신경망으로 처리할 수 있다는 뜻이죠. 예컨대 [그림 3-6]은 원핫 표현으로 된 단어 하나를 완전연결계층을 통해 변환하는 모습을 보여줍니다.

 

 [그림 6]의 신경망은 완전연결계층이므로 각각의 노드가 이웃 층의 모든 노드와 화살표로 연결되어 있습니다. 이 화살표에는 가중치(매개변수)가 존재하여, 입력층 뉴런과의 가중합weighted sum이 은닉층 뉴런이 됩니다. 참고로 이번 장에서 사용하는 완전연결계층에서는 편향을 생략했습니다. (이 부분은 뒤의 word2vec 설명 때 이야기하겠습니다).

 

편향을 이용하지 않는 완전연결계층은 ‘행렬 곱’ 계산에 해당합니다. 그래서 이 책의 경우, 완전연결계층은 1장에서 구현한 MatMul 계층과 같아지죠. 참고로 딥러닝 프레임워크들은 일반적으로 완전연결계층을 생성할 때 편향을 이용할지 선택할 수 있도록 해줍니다.

 

자, [그림 6]에서는 뉴런 사이의 결합을 화살표로 그렸습니다만, 이후로는 가중치를 명확하게 보여주기 위해 [그림 7]처럼 그리겠습니다.

 

이 코드는 단어 ID가 0인 단어를 원핫 표현으로 표현한 다음 완전연결계층을 통과시켜 변환하는 모습을 보여줍니다. 복습해보자면, 완전연결계층의 계산은 행렬 곱으로 수행할 수 있고, 행렬 곱은 넘파이의 np.matmul( )이 해결해줍니다(편향은 생략). 이 코드에서 입력 데이터(c)의 차원 수(ndim)는 2입니다. 이는 미니배치 처리를 고려한 것으로, 최초의 차원(0번째 차원)에 각 데이터를 저장합니다.

 

앞의 코드에서 주목할 곳은 c와 W의 행렬 곱 부분입니다. 자, c는 원핫 표현이므로 단어 ID에 대응하는 원소만 1이고 그 외에는 0인 벡터입니다. 따라서 앞 코드의 c와 W의 행렬 곱은 결국 [그림 8]처럼 가중치의 행벡터 하나를 ‘뽑아낸’ 것과 같습니다.

 

그저 가중치로부터 행벡터를 뽑아낼 뿐인데 행렬 곱을 계산하는 건 비효율적이라고 생각되시죠? 이 점은 ‘4.1 word2vec 개선 ①’ 절에서 개선할 예정입니다. 또한, 앞의 코드로 수행한 작업은 (1장에서 구현한) MatMul 계층으로도 수행할 수 있습니다. 다음 코드처럼 말이죠.

 

 

MatMul class 에서 각각의 매개변수와 파라미터가 매직 메서드를 통해 인스턴스의 속성으로 변하고 이가 위의 수식대로 전개되는 것을 확인할 수 있습니다. 클래스 전개되는 부분이 이해가 안된다면 파이썬 초중급편 '클래스' 포스팅들을 참고해주세요!

 

part1은 여기까지 입니다 :) 

 

----자료 출처----

 

1) '딥러닝을 이용한 자연어 처리 입문'

https://wikidocs.net/book/2155

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

2) '한국어 임베딩' 저자 이기창 선생님의 블로그

https://ratsgo.github.io/from%20frequency%20to%20semantics/2017/03/29/NNLM/

 

Neural Probabilistic Language Model · ratsgo's blog

이번 포스팅에선 단어의 분산표상(distributed representation) 방식 가운데 하나인 Neural Probabilistic Language Model(NPLM)에 대해 살펴보도록 하겠습니다. NPLM은 Bengio(2003)에서 제안된 모델인데요, 단어를 벡터로 바꾸는 뉴럴네트워크 기반 방법론으로 주목을 받았습니다. 이번 글은 고려대 강필성 교수님 강의와 네이버랩스 박은정 박사의 발표원고를 바탕으로 정리했음을 먼저 밝힙니다. 자 그럼 시작

ratsgo.github.io

3) 한빛미디어, <밑바닥부터 시작하는 딥러닝2> 

http://www.hanbit.co.kr/media/channel/view.html?cms_code=CMS1232324353