Codong's Development Diary RSS 태그 관리 글쓰기 방명록
2021-05-11 21:16:53

개요


딥러닝을 이용한 자연어 처리 입문
기계가 문서의 유사도를 구할때 문서들 간에 동일한 단어 또는 비슷한 단어가 얼마나 공통적으로 많이 사용되었는지에 의존한다. 그렇기에 유사도의 성능은 단어들을 어떤 방법으로 수치화하여 표현했는지(DTM, Word2Vec 등), 문서 간의 단어들의 차이를 어떤 방법으로 계산(유클리드 거리, 코사인 유사도 등)했는지에 따라 다르다.



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


단어를 수치화할 수 있다면 수치를 통해 계산을 할 수 있다. 유사도를 구하는 계산법에 대해 알아보자.


1.1 코사인 유사도란?

두 벡터 간의 코사인 각도를 이용하여 구할 수 있는 두 벡터의 유사도를 의미한다. 두 벡터의 방향이 완전히 동일한 경우에는 1의 값을 가지고, 90도의 각을 이루면 0의 값, 180도로 반대의 방향을 가지면 -1의 값을 가지게 된다. 즉, -1 이상 1이하의 값을 가지며 값이 1에 가까울수록 유사도가 높다고 판단할 수 있다. 직관적으로 이해하자면 두 벡터가 가르키는 방향이 얼마나 유사한지를 의미한다.

식으로 표현하면 다음과 같다.
$similarity = \cos (\Theta) = \frac{A \cdot B}{\parallel A \parallel \parallel B \parallel} = \frac{\sum_{i=1}^n A_i \times B_i}{\sqrt{\sum_{i=1}^n(A_i)^2} \times \sqrt{\sum_{i=1}^n(B_i)^2}}$

(왼쪽)스칼라곱 , (오른쪽)코사인, 사인함수 그래프

방향이 얼마나 유사한지를 판단하는 것을 두 벡터 사이의 각도를 구하는 것으로 접근했다. 왼쪽 그림은 두 벡터 a, b의 스칼라곱($A \cdot B$)을 표현한 그림이다. $\theta$ 값이 작아진다면 같은 방향을 향한다고 볼 수 있지 않는가? 오른쪽 그림을 통해 $\theta$값이 작아지면 자연스럽게 $\cos{\theta}$ 의 값이 1에 가까워 짐을 알 수 있다.(파란 점선이 코사인 함수. 각도가 0일 때 1임을 알 수 있다.) 그래서 처음 설명에서 1에 가까울 수록 두 벡터 간의 각도가 0에 가까우니 같은 방향을 향하므로 유사도가 높다고 말할 수 있는 것이다.

그리고 왜 뚱딴지 같이 스칼라곱이 나왔나 싶을 수 있다. 스칼라곱 공식 살펴보자.
$ A \cdot B = \cos{\theta} \parallel A \parallel \parallel B \parallel $
사실 스칼라곱 공식에서 $ \parallel A \parallel \parallel B \parallel $을 이항시킴으로 $\cos{\theta}$를 구하는 공식이 유도 됨을 알 수 있다.

➕ $\parallel A \parallel$ 이것은 norm 이라고 하는데 구글에 검색하면 더욱 자세히 알 수 있다.


그래도 감이 안 올땐 역시 코드를 보자. 코드로 보면(numpy를 이용하여) 쉽게 구현할 수 있다.

from numpy import dot
from numpy.linalg import norm
import numpy as np
def cos_sim(A, B):
       return dot(A, B)/(norm(A)*norm(B))

이것을 이용해 보기 위해 간단한 예를 들어 다음과 같은 백터가 있다고 생각해보자.

doc1=np.array([0,1,1,1])
doc2=np.array([1,0,1,1])
doc3=np.array([2,0,2,2])

각각의 문서 벡터들의 유사도를 구해본다면

print(cos_sim(doc1, doc2)) #문서1과 문서2의 코사인 유사도
print(cos_sim(doc1, doc3)) #문서1과 문서3의 코사인 유사도
print(cos_sim(doc2, doc3)) #문서2과 문서3의 코사인 유사도
0.67
0.67
1.00

결과는 위와 같다. 신기한 것은 문서1과 문서2의 코사인 유사도문서1과 문서3의 코사인 유사도가 같다는 것이다. 코사인 유사도는 특정 문서의 단어 빈도수가 일정하게 더 높아질 때 다른 문서보다 유사도가 높게 나오는 것을 방지할 수 있다. 즉, 코사인 유사도는 크기보다 방향으로 유사도를 따지기에 앞서 말한 동일한 패턴으로의 크기가 증가하는 경우와 같을 때 공정한 비교를 할 수 있도록 도와준다.



2. 여러가지 유사도 기법


코사인 유사도 외의 여러가지 유사도 기법에 대해 알아보자.


2.1 유클리드 거리(Euclidean distance)

유클리드 거리는 문서 유사도를 구할 때 자카드 유사도나 코사인 유사도만큼 유용한 방법은 아니다. 하지만 여러 가지 방법을 이해하고, 시도해보는 것 자체만으로 다른 개념들을 이해할 때 도움이 되므로 의미가 있다.

다차원 공간에서 두개의 점 p와 q가 각각 $p = (p_1,p_2,p_3,...,p_n)$ 과 $q = (q_1,q_2,q_3,...,q_n)$ 의 좌표를 가질 때 두 점 사이의 거리를 계산하는 유클리드 거리 공식은 다음과 같다.

$\sqrt{(q_1-p_1)^2 + (q_2-p_2)^2 + ... + (q_n-p_n)^2} = \sqrt{\sum_{i=1}^n(q_i-p_i)^2}$

다차원 공간이라 복잡해 보이지만, 2차원 공간이라 가정한다면 피타고라스의 정리라고 생각하면 된다.
numpy를 이용해 docQ와 가장 유사한(가까운) 문서를 찾는 간단한 예제를 구현해보자.

import numpy as np
def dist(x,y):   
    return np.sqrt(np.sum((x-y)**2))

doc1 = np.array((2,3,0,1))
doc2 = np.array((1,2,3,1))
doc3 = np.array((2,1,2,2))
docQ = np.array((1,1,0,1))

print(dist(doc1,docQ)) # 2.23606797749979
print(dist(doc2,docQ)) # 3.1622776601683795
print(dist(doc3,docQ)) # 2.449489742783178

문서 1이 제일 가까우므로 유사하다고 볼 수 있다.


2.2 자카드 유사도(Jaccard similarity)

A와 B 두개의 집합이 있다고 하자. 합집합에서 교집합의 비율을 구한다면 두 집합 A와 B의 유사도를 구할 수 있지 않을까?라는 아이디어가 바로 자카드 유사도의 아이디어이다. 자카드 유사도는 0과 1사이의 값을 가지게 되는데, 동일하면 1의 값을 가지고 공통원소가 없다면 0의 값을 가진다.

J를 자카드 유사도 함수라고 하였을 때, 수식으로 표현하면 다음과 같다.
$J(A,B) = \frac{|A \cap B|}{|A \cup B|} = \frac{|A \cap B|}{|A|+|B|-||A \cap B||}$

코드로는 set을 이용한다면 간단히 구현할 수 있다.

doc1 = "apple banana everyone like likey watch card holder"
doc2 = "apple banana coupon passport love you"

# 토큰화를 수행합니다.
tokenized_doc1 = doc1.split()
tokenized_doc2 = doc2.split()

문서 별 토큰화 결과를 set으로 만들어 합집합과 교집합을 구하여 계산한다.

union = set(tokenized_doc1).union(set(tokenized_doc2))
print(union) 
{'card', 'holder', 'passport', 'banana', 'apple', 'love', 'you', 'likey', 'coupon', 'like', 'watch', 'everyone'}
intersection = set(tokenized_doc1).intersection(set(tokenized_doc2))
print(intersection) # {'banana', 'apple'}

print(len(intersection)/len(union)) # 0.166666666

이렇게 구한 값은 자카드 유사도이자, 두 문서에서의 총 단어 집합 중 공통적으로 등장한 단어의 비율이라고도 할 수 있다.


2.3 레벤슈타인 거리(Levenshtein distance)

  • 레벤슈타인 거리는 편집 거리라고도 하는데, 두 문자열이 얼마나 다른지를 나타내는 거리 중 하나이다
  • 문자를 삽입, 삭제, 치환하여 다른 문자열로 변형하는데 필요한 최소 횟수를 구하여, 이것을 점수로 나타낼 수 있다.
  • 철자검사기 등에서 두 문자열이 어느 정도 유사한지 값으로 나타내는 방법의 하나
  • 1965년 러시아의 브라디미르 레벤슈타인이 고안함

2.4 n-gram을 이용한 유사도

이전포스팅 [자연어처리 입문] 2. 언어 모델 에서 다뤘던 부분이라 구체적인 부분은 참고 바란다.
이 n-gram을 이용하여 두 문장이 같은 문자를 사용하지만 순서가 바뀌거나 오타교정 등에 많이 사용될 수 있을 것 같다.


reference