Deep Learning/NLP

04. One- Hot Encoding, Similarlity

해파리냉채무침 2023. 3. 13. 01:45

One-Hot Encoding

단어를 숫자로 표현함

원숭이 = [1,0,0]

바나나 = [0,1,0]

사과 =     [0,0,1]

새로운 단어가 등장할 때마다 차원이 하나씩 추가됨. 단어 수 만큼의 차원이 필요

 

 

ex. 원숭이 = [1,0,0]

차원수는 3개, 각각 숫자를 인덱스라고 부름.

원 핫 인코딩의 한계점 : 의미를 담지 못하는 문제가 있음 , 단어간의 유사성이 있는지는 판단할 수 없음

단어간의 유사성을 (-1,1) 사이 값으로 나타냄

직각으로 된 좌표의 코사인 유사도를 구했을 때 0이 나옴, 원핫인코딩의 성질은 직교한다는 성질이 있음. 

즉, 어떤 단어들간의 코사인 유사도를 구하여도 0이 나온다 -> 따라서 의미를 분간하기 어려움

출처: https://github.com/insightcampus/sesac-nlp

원핫 인코딩의 한계를 극복하기 위해 단어 임베딩 (word embedding)을 한다.

단어 임베딩은 단어의 의미를 밀집 벡터로 표현 1차형 공간에 실수 형태로 표현한다.

새로운 단어를 추가해도 차원을 늘릴 필요가 없어 연산을 줄일 수 있음.

 

그럼 단어 의미를 담지 못하는 문제는 어떻게 해결할까?

밀집 벡터를 만들 때 분포가설을 전제를 한다. 분포 가설이란 같은 문맥에서 등장하는 단어는 유사한 의미를 지니는 것을 의미한다. 

즉 같은 문맥이 등장하는 단어를 가깝게 표현한다.

code

# 인코딩 대상 단어들을 담은 리스트
word_ls = ['원숭이','바나나','사과','개', '고양이']
# 단어별 인덱스 지정
from collections import defaultdict #단어 입력하면 dict으로 변환 
import numpy as np
from collections import defaultdict #단어 입력하면 dict으로 변환 
import numpy as np 
word2id_dic = defaultdict(lambda:len(word2id_dic))
for w in word_ls :
    word2id_dic[w]
    #고유한 단어수
n_unique_words = len(word2id_dic) # 고유한 단어의 갯수
one_hot_vectors = np.zeros((len(word_ls), n_unique_words)) # 원핫-벡터를 만들기 위해 비어있는 벡터 생성 행은 단어길이만큼, 열은 단어갯수
for i,word in enumerate(word_ls): #몇번째 함수인지 알려주는 함수 enumerate
        index = word2id_dic[word] # 해당 단어의 고유 인덱스
        one_hot_vectors[i, index] = 1 # 해당 단어의 고유 인덱스에만 1을 더해줌
print(word2id_dic)
print(n_unique_words)
print(index)
print(one_hot_vectors)
defaultdict(<function <lambda> at 0x000001C8408C11F0>, {'원숭이': 0, '바나나': 1, '사과': 2, '개': 3, '고양이': 4})
5
4
[[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]

Sklearn을 사용한 원핫 인코딩 코드

word_ls = ['원숭이','바나나','사과','코끼리','코끼리','개']
from numpy import array
from sklearn.preprocessing import LabelEncoder #단어별로 숫자를 부여
from sklearn.preprocessing import OneHotEncoder #라벨인코더를 원핫인코더로 변환

label_enc = LabelEncoder()
int_enc = label_enc.fit_transform(word_ls)
int_enc.reshape(len(int_enc),1) #가나다순 인코딩
array([[3],
       [1],
       [2],
       [4],
       [4],
       [0]], dtype=int64)
onehot_enc = OneHotEncoder(sparse=False) #앞에 라벨인코딩 가지고 nx1행렬로 변환
int_enc = int_enc.reshape(len(int_enc),1)
onehot_vectors = onehot_enc.fit_transform(int_enc)
array([[0., 0., 0., 1., 0.],
       [0., 1., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 1.],
       [0., 0., 0., 0., 1.],
       [1., 0., 0., 0., 0.]])

 

 

Similarity

유사성을 계산하는 방법은 많은 방법이 있음.

1. 유클리디안 거리(Euclidean distance)

직선거리를 계산, 각 단어들을 벡터변환하여 벡터좌표들의 거리를 측정함.

피타고라스 정리를 생각하면 된다. 자연어처리에서는 많이 사용하지 않음

출처: 위키백과

많이 사용하지 않는 이유는 그 한계점에 있는데, 

원숭이와 바나나는 가깝게 계산되지만 원숭이와 바나나바나나바나나와 거리가 매우 멀게 계산될 수 있다.

바나나나 바나나x4는 같은 의미로 비슷하게 계산되어야 할 것 같지만, 좌표를 보면 다르게 계산된다.

유클리디안을 보완하기 위해 코사인 유사도를 사용한다.

code

word_embedding_dic = {
    '사과' : [1.0, 0.5],
    '바나나' : [0.9, 1.2],
    '원숭이' : [0.5, 1.5]
}
import numpy as np
a=word_embedding_dic['사과']
b=word_embedding_dic['바나나']

def euclidean_dist(a,b):

    x = np.array(a)
    y = np.array(b)

    return np.sqrt(np.sum((x-y)**2)) #두개 각 좌표를 뺴서 제곱한것을 더해줌
x = np.array(a)
y = np.array(b)
np.sqrt(np.sum((x-y)**2))
0.7071067811865475

 

2. 자카드 유사도(Jaccard index)

문서간 혹은 문장간 얼마나 유사한지 판단하는 지표다.

출처: 위키백과

A와 B의 교집합이 있을때, 전체문서 중에 겹치는 토큰이 얼마나 있는지 측정한다.

code

s1 = '대부분 원숭이는 바나나를 좋아합니다.'
s2 = '코주부 원숭이는 바나나를 싫어합니다.'
def jaccard_index(a1,a2):
    token_s1 = s1.split()
    token_s2 = s2.split()

    union = set(token_s1).union(set(token_s2)) #합집합 결과
    intersection = set(token_s1).intersection(set(token_s2)) #교집합 결과

    return len(intersection)/len(union) #두문장의 유사도는 33%
jaccard_index(s1,s2)
0.3333333333333333

3. 코사인 유사도(Cosine Similarity)

두 벡터 좌표 사이의 각도를 측정한다.

0도일때 1(같은방향 일때), 90도일 때 (직각일때)는 1, 180도 (완전히 다른방향일때) -1의 값을 가짐.

1에 가까울 수록 유사도가 높다. 

출처: 위키백과

code

import numpy as np
n=np.dot(a,b) #a와 b 내적
d= np.linalg.norm(a) * np.linalg.norm(b) #각 벡터의 길이 계산 
n/d
0.8944271909999159

출처: https://github.com/insightcampus/sesac-nlp