다시 이음

딥러닝(7) - Distributed Representation(분산표현) 본문

AI 일별 공부 정리

딥러닝(7) - Distributed Representation(분산표현)

Taeho(Damon) 2021. 10. 27. 00:08

안녕하세요.

 

오늘은 어제에 이어서 자연어를 벡터화 하는 방법에 대해서 알아보겠습니다.

 

어제 등장 횟수에 따라 벡터화 하는 방법(BoW:TF,TF-IDF)을 알아보았습니다.

 

오늘은 벡터화 하는 다른 방법인 단어 자체를 벡터화 하는 분산 표현에 대해서 알아보겠습니다.

 

 

Distributed Representation

 

1) 원핫인코딩

 

단어를 벡터화하고자 할 때 선택할 수 있는 가장 쉬운 방법입니다.

 

예를 들면 5가지의 단어가 모여 하나의 문장을 이룰 때 이것을 원핫인코딩을 적용하면 5차원을 가진 벡터가 생성됩니다.

 

순서에 따라 [1,0,0,0,0], [0,1,0,0,0]... [0,0,0,0,1]과같이 생성됩니다.

 

원핫인코딩의 단점은 단어간의 유사도를 구할 수 없다는 것입니다.

 

코사인 유사도(cosine similarity)을 따르면 원핫인코딩을 사용한 벡터들은 0의 값을 가지게 되어 유사도를 구할 수 없습니다.

 

 

2) 임베딩(Embedding)

 

사실 임베딩이라는 것은 분산표현에만 국한되지 않고 이전에 배웠던 등장횟수에 따른 벡터화 또한 포함하는 큰 범위의 용어입니다.

 

그러나 우리는 여기서 임베딩 기법중에 하나인 예측기반 임베딩을 알아보려고 합니다.

 

예측기반 임베딩이란?

 

어떤 단어 주변에 특정 단어가 나타날지 예측하거나, 이전 단어들이 주어졌을 때 다음 단어가 무엇일지 예측하거나, 문장 내 일부 단어를 지우고 해당 단어가 무엇일지 맞추는 과정에서 학습하는 방법입니다.

 

- Word2Vec

 

단어를 벡터로(Word to Vector) 나타내는 방법으로 가장 널리 사용되는 임베딩 방법 중 하나입니다.

 

파라미터인 (window size = 2)을 통해 한 단어의 양옆의 몇개(2개)만큼 관계를 활용하게 됩니다.

 

Word2Vec의 두가지방법은

1) 주변 단어에 대한 정보를 기반으로 중심 단어의 정보를 예측하는 모델 ▶️ CBoW(Continuous Bag-of-Words)

2) 중심 단어의 정보를 기반으로 주변 단어의 정보를 예측하는 모델 ▶️ Skip-gram

CBow(왼쪽),Skip-gram(오른쪽)

 

예를 들면 '나는 오늘 저녁을 먹었다'라는 문장에서

'나는 오늘 __ 먹었다'  -- 옆의 단어들을 통해 __부분을 예측하기 (CBow),

'__ __ 저녁을 ___' -- '저녁을'이라는 단어를 통해 __부분을 예측하기 (Skip-gram) 입니다.

 

쉽게 생각하면 CBow는 많은 훈련데이터를 가지고 하나의 검증데이터(모의고사)를 실행하는 것이고,

Skip-gram은 작은 훈련데이터로 많은 검증데이터(모의고사)를 실행하는 것입니다.

 

이럴 경우 Skip-gram이 연산에 많은 시간이 걸리기는 하지만 더 좋은 성능을 보여줍니다.

 

 

- Word2Vec의 결과

 

우리가 Word2Vec를 사용하면 어떠한 결과를 얻을 수 있을까요?

 

예를들어 5000개의 단어(입력값)이 있는 데 이걸 Word2Vec를 사용한다면 어떤 값이 출력될까요?

입력값(5000개) 에 은닉층 노드개수(300개)라면 5000개의 단어에 300개의 뉴런이 있다고 볼 수 있고

Word2Vec을 통과하여 5000개의 단어는 300개의 차원을 가진 벡터가 됩니다.

 

이 벡터는 Word vector lookup table이라고 불리는 색인이 가능한 순람표 형태가 되는 겁니다.

 

Word2Vec을 통해 얻은 임베딩 벡터는 단어 간의 의미적, 문법적 관계를 잘 나타냅니다.

 

 

우리는 주로 이렇게 순람표(사전)이라는 형태를 불러와서 사용합니다. ( 학습이 아니라 불러서 사용하는 겁니다 )

import gensim
gensim.__version__

import gensim.downloader as api
wv = api.load('word2vec-google-news-300')

 

구글에서 나온 gensim이라는 api를 주로 사용합니다.

 

그런데 이 사전에 우리가 알고 싶어하는 단어의 값이 없다면 어떻게 될까요??

 

그 단어를 분석하려고 하자 key error가 발생합니다.

 

이렇게 말뭉치에 등장하지 않는 단어 혹은 자주 등장하지 않아서 학습이 덜 된 단어가 등장하는 문제를 OOV(Out of Vocabulary) 문제라고 합니다.

 

 

3) 철자 단위 임베딩(Character level Embedding)

 

위의 OOV문제를 해결하기 위해 나온 방법입니다.

 

모델이 학습하지 못한 단어더라도 잘 쪼개고 보면 말뭉치에서 등장했던 단어를 통해 유추해 볼 수 있다는 가정아래 생긴 방법입니다.

 

- fastText

 

철자 단위 인베딩을 사용하는 방법에 fastText가 있습니다.

 

fastText 는 3-6개로 묶은 Character 정보(3-6 grams) 단위를 사용합니다.
3-6개 단위로 묶기 이전에 모델이 접두사와 접미사를 인식할 수 있도록 해당 단어 앞뒤로 "<", ">" 를 붙여줍니다.

 

단점으로는 단어의 의미보다는 단어의 형태에 집중하여 유사성을 파악할 경우가 있습니다.

 

 

4) 문장 분류하기

 

위의 임베딩 벡터들을 활용하여 문장을 분류하는 모델을 만들 수 있습니다.

 

예를 들면, 특성에는 강아지를 키우는 사람들의 후기와 고양이를 키우는 사람들의 후기가 수록되어 있고, 타겟값에는 강아지, 고양이 이렇게 분류가 되어 있다면 특성에 적힌 문장을 보고 타겟값을 분류해보는 것입니다. 어떤 후기가 강아지에 대한 후기일지, 고양이에 대한 후기일지

 

import numpy as np
import tensorflow as tf

from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, GlobalAveragePooling1D
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.datasets import imdb #datasets 사용할때만 필요
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, Flatten

##데이터 전처리 과정 생략
# 데이터 불러오기
# 데이터 전처리(중복값,결측값 삭제)
# 데이터 구성 (특성,타겟)
# 인코딩 과정 거치기

sentences = [decode_review(idx) for idx in X_train]
#인코딩 사용했을 때 sentences = encoder.inverse_transform(X_train)

#숫자로 변환되어 있던 데이터를 문자로 변경하여 리스트만들기
tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentences)

#사전(임베딩 벡터)의 행 길이
vocab_size = len(tokenizer.word_index) + 1
print(vocab_size)

#리스트된 문자를 숫자 인덱스 값으로 대치해넣기
X_encoded = tokenizer.texts_to_sequences(sentences)

#사전(임베딩 벡터)의 열 길이 = 인코딩 X의 값중 가장 긴 차원값
max_len = max(len(sent) for sent in X_encoded)
print(max_len)

#패딩처리하기(서로 다른 개수의 단어로 이루어진 문장을 같은 길이로 만들어주기 위해 패딩)
X_train=pad_sequences(X_encoded, maxlen=400, padding='post')
y_train=np.array(y_train)

#임베딩 가중치 행렬 만들기(즉, 사전에 있는 배열만 가져오기 위함)
embedding_matrix = np.zeros((vocab_size, 300))
print(np.shape(embedding_matrix))

#임베딩 가중치 행렬 채워넣기
def get_vector(word):
    """
    해당 word가 word2vec에 있는 단어일 경우 임베딩 벡터를 반환
    """
    if word in wv:
        return wv[word]
    else:
        return None
 
for word, i in tokenizer.word_index.items():
    temp = get_vector(word)
    if temp is not None:
        embedding_matrix[i] = temp

#모델 설정하기
model = Sequential()
#임베딩 레이어 삽입
model.add(Embedding(vocab_size, 300, weights=[embedding_matrix], input_length=max_len, trainable=False))
model.add(GlobalAveragePooling1D()) # 입력되는 단어 벡터의 평균을 구하는 함수입니다.
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])
model.fit(X_train, y_train, batch_size=64, epochs=20, validation_split=0.2)

#테스트 성능확인하기 위해 테스트데이터 변환
test_sentences = [decode_review(idx) for idx in X_test]
X_test_encoded = tokenizer.texts_to_sequences(test_sentences)
X_test=pad_sequences(X_test_encoded, maxlen=400, padding='post')
y_test=np.array(y_test)

#성능 확인
model.evaluate(X_test, y_test)

 

참조 : https://hezma.tistory.com/104

https://codetorial.net/tensorflow/natural_language_processing_in_tensorflow_01.html

 

 

 

!!!

내가 이해한 임베딩 벡터로 문장 분류하기(정확하지는 않습니다..)

 

1. 입력값은 3차원의 행렬이다. (문장의 개수*그 단어를 표현할 sequences(배열)*단어인덱스의 개수)

2. weights는 사전을 보여주는 lookup 행렬이다.

3. 예를들면 2000개의 문장에서 하나의 문장을 뽑아 그 문장을 표현하고 있는 각 단어의 임베딩 값을 배열(400개)하고 그 배열을 위해서 단어 인덱스(1500개)가 같이 있다.(2000*400*1500) -> (1*400*1500)

4. 이 입력값에 가중치를 내적하여 임베딩 벡터 값을 연산한다.

5. 가중치의 값은 (단어인덱스의 개수*은닉층노드개수)

6. 즉 연산을하면 입력값(400*1500)* 가중치값(1500*300) = (400*300)의 값이 도출된다.

7. 이렇게 생성된 임베딩 벡터는 단어를 통해 문장을 분류하기 위해 만들어졌고 이 벡터값을 평균하여 문장을 분류한다.