머신러닝&딥러닝/자연어처리

Embedding 이란 무엇인가 이해하기

Like_Me 2019. 12. 20. 18:31
반응형

인간의 언어(자연어)는 수치화되어 있지 않은 데이터이기 때문에 머신러닝, 딥러닝 기법을 바로 사용할 수가 없다.

(수치화되어있는 데이터의 예로는 Mnist나 꽃의 종류처럼 숫자로 분류가 가능한 것들을 말함.)

 

그래서 자연어 처리에서 특징 추출을 통해 수치화를 해줘야 하는데 이때 사용하는 것이 "언어의 벡터화"이다.

이런 벡터화의 과정을 Word Embedding이라고 한다.

 

가장 기본적인 벡터화의 방법은 One-hot encoding 방법이다. 예를 들어, 남자와 여자를 표현하는 벡터를 만든다고 할 때 각각을 [1,0] [0,1]로 만드는 방법이다. 그런데 이 방법은 단어가 많아지면 벡터 공간이 매우 커지고 실제 1인 값은 한 개뿐 이므로 매우 비효율적이다. 또 이런 표현방식은 단어가 뭔지만을 알려줄 뿐 어떤 특징을 표출해주지는 못한다. 이런 방식을 단어 벡터의 크기가 너무 크고 값이 1이 되는 값은 거의 없어 Sparse(희소)한 표현법이라고 한다. 

 

따라서, 이를 해결하기 위해 Dense 한 표현법이 제시되었다. Dense 한 표현방식은 단어의 수에 상관없이 특정 차원의 벡터로 변환시켜 단어의 특성이나 유사성을 나타내 준다. 예를 들어 남자, 여자를 [0.2,0.5] [0.1,0.4]의 실수 형태 벡터로 표현해주는 것이다. 

 


벡터화의 방법은 사이킷런의 CountVectorizer, TfidfVectorizer이나 Word2Vec이 대표적이다.

 

CountVectorizer는 단순히 각 텍스트에서 횟수를 기준으로 특징을 추출하는 방법이다.

예를 들어 '나는 매일매일 일기를 쓴다'를 벡터화할 때 단어 사전을 먼저 정의해야 하는데 단어 사전이 [나는, 매일 , 혼자, 일기를, 쓴다] 일 때, [1, 2, 0, 1, 1]로 되는 것이다.

이 방법은 단순히 횟수만을 특징으로 잡기 때문에 큰 의미가 없고 자주 쓰이는 조사가 높은 특징 값을 가진다는 단점이 있다. 이를 해결하기 위해 사용하는 것이 TfidfVectorizer이다.

 

TfidfVectorizer는 TF-IDF를 이용해 텍스트 데이터의 특징을 추출하는 것이다. TF는 Term Frequency로 특정 단어가 글 안에서 나오는 횟수를 말하며 IDF는 Inverse Document Frequency로 특정 단어가 여러 글에 얼마나 자주 나오는지 알려주는 지표의 Inverse(반대) 값이다. 다른 글에서 지시대명사나 조사가 많이 나오므로 IDF는 값이 반대로 낮은 값을 갖게 된다. 이것이 TF-IDF의 장점으로 의미가 없는 조사나 지시대명사를 제외한 단어들의 임베딩 값을 얻을 수 있는 것이다. TF-IDF는 TF와 IDF를 곱한 값으로 다른 글에서 자주 나오지 않고 해당 문서에 많이 등장할수록 더 높은 값을 갖게 된다.


위의 두 방법은 단어의 빈도를 기준으로 단어의 벡터화를 하여 특징을 추출하는데 단어 사이의 유사도를 나타내기는 힘들다.

단어의 특징과 유사도를 나타내 주는 (진정한) embedding은 Word2Vec과 같은 학습을 통한 예측 기반 방법이다.

이때 분포 가설(Distributed hypothesis)이 등장한다. 분포 가설은 같은 문맥의 단어, 즉 비슷한 위치에 나오는 단어는 비슷한 의미를 가진다 라는 의미이다. 따라서 어떤 글의 비슷한 위치에 존재하는 단어는 단어 간의 유사도를 높게 측정할 것이다.

 

Word2Vec은 CBow와 Skip-gram이 있다. CBow는 어떤 단어를 문맥 안의 주변 단어들을 통해 예측하는 방법이고 Skip-gram은 반대로 어떤 단어를 가지고 특정 문맥 안의 주변 단어들을 예측하는 과정이다.

 

CBow의 예를 들면, 나는 오늘 __ 를 갔다. 의 문장에서 __를 예측하는 것이다. 학습은 다음과 같이 진행된다.

 

1. 주변 단어들을 one-hot 벡터로 만들어 입력값으로 사용한다.(input layer)

2. 가중치 행렬을 one-hot벡터에 곱해서 n-차원 벡터를 만든다.(hidden layer)

3. 만들어진 n-차원 벡터를 모두 더해 더한 개수로 나눠 평균 n-차원 벡터를 만든다.(output layer)

4. n-차원 벡터에 다시 가중치 행렬을 곱해서 one-hot 벡터와 같은 차원의 벡터로 만들고 실제 예측하려고 하는 단어의 one-hot 벡터와 비교해서 학습한다.(optimize)

 

위의 학습 과정을 모두 끝낸 후 가중치 행렬의 각 행을 단어 벡터로 사용한다.

 

이런 과정을 거쳐서 만들어진 단어 벡터는 단어 간의 유사도를 잘 측정하며 복잡한 특징까지도 잘 잡아낸다. 예를 들어

엄마 아빠, 여자 남자라는 단어의 벡터 사이의 거리가 같게 나오는 것이다. 

 

  이런 임베딩은 언어뿐 아니라 2-D image 등에서도 적용되는데 예를 들어 GAN이나 CNN 모델로 Input을 Manifold 하는 과정에서 사용된다. 이에 관해서는 다음 Auto Encoder란? - Manifold와 차원 축소를 참고하면 된다.


자연어처리 관련 코드를 짤 때 tensorflow keras의 embedding을 많이 사용한다. 그렇다면 keras의 embedding layer는 어떻게 동작할까??

 

우선 Tokenizer를 통해 데이터를 단어별로 나누는 과정을 거쳐 input으로 넣어주는 것까지 했다고 생각한다.

(Tokenize는 data를 문장 혹은 단어 등의 단위로 나누는 것을 말한다.)

 

밑에 요약해놓은 Embdding layer의 소스코드를 보면 Embedding객체가 있고 build에서 add_weight 함수가 훈련 가능한 가중치 행렬을 만들어준다. 그리고 call에서 lookup함수를 통해 해당 단어의 벡터를 계산하여 return 해준다. 

class Embedding(Layer):
  def __init__(self,
input_dim,output_dim,embeddings_initializer='uniform',embeddings_regularizer=None,activity_regularizer=None,embeddings_constraint=None,
mask_zero=False,input_length=None,**kwargs):
    self.input_dim = input_dim
    self.output_dim = output_dim
    self.embeddings_initializer = initializers.get(embeddings_initializer)
    self.embeddings_regularizer = regularizers.get(embeddings_regularizer)
    self.activity_regularizer = regularizers.get(activity_regularizer)
    self.embeddings_constraint = constraints.get(embeddings_constraint)
    self.mask_zero = mask_zero
    self.supports_masking = mask_zero
    self.input_length = input_length
    self._supports_ragged_inputs = True

  @tf_utils.shape_type_conversion
  def build(self, input_shape):
        self.embeddings = self.add_weight(
            shape=(self.input_dim, self.output_dim),
            initializer=self.embeddings_initializer,
            name='embeddings',
            regularizer=self.embeddings_regularizer,
            constraint=self.embeddings_constraint)

  def call(self, inputs):
    out = embedding_ops.embedding_lookup(self.embeddings, inputs)
    return out

예를 들어, vocab_size가 4019이고(해당 data의 총단어가 4019라는 뜻) Embedding_size는 8이며(차원이 8인 벡터를 만든다는 뜻), 어떤 문장이 Tokenize를 통해 길이가 13인 정수의 array가 되어 아래와 같이 되었다고 가정해서 코드를 작성해 보았다

import numpy as np
import tensorflow as tf

# len(sentence) = 13
sentence=np.array([ 281,  197,  974,  199,  330, 1, 4, 3053, 4018,  450, 1024,21, 0, ])

W=tf.Variable(
    tf.random.uniform([4019,8], -1.0, 1.0) ,name="W")
    
result=tf.nn.embedding_lookup(W,sentence)

이때의 result 값은, shape=(13,8)인 실수형 array로 나오며, 0번째 인덱스의 값을 예시로 보면 다음과 같다.

array([ 0.65594006, 0.83988833, 0.44662547, 0.22759342, -0.3911364 , 0.08832693, -0.5317943 , -0.76355004], dtype=float32)  즉, Vocab 281번에 있는 단어는 위와 같은 shape=8인 array로 embedding 된 것이다!!

 

Random 하게 생성된 Weight은 결과적으로 training을 할 때 다른 params처럼 학습될 것이고 최적의 Embedding을 찾을 것이다.

 

keras의 Embedding layer까지 이해해보았다.

 

 

참고 문헌 : 텐서플로와 머신러닝으로 시작하는 자연어 처리 : 로지스틱 회귀부터 트랜스포머 챗봇까지 - 전창욱, 최태균, 조중현

반응형