hat, cat
위와 같은 문자열 2개가 있다고 해보자. 둘은 얼마나 유사할까?
단순히 문자간의 배열이 같은지, 의미상의 유사성 등 다양한 방식으로 유사한 정도를 표현할 수 있을 것이다.
이처럼 '유사성'이란 '비슷한 정도'를 수치적으로 표현한 것을 의미한다.
자연어 처리에서 일반적으로 사용하는 몇가지 유사도 기법이 있다.
이번 글에서는 그에 대해 설명해보고자 한다.
코사인 유사도는 말그대로 코사인의 특성을 그대로 가져온 것이다.
코사인 값은 두 벡터 사이의 각도로 결정된다.
학습에 앞서 Raw Data를 전처리하는 과정에서 자연어를 단어, 어간/어미 단위로 쪼갠 뒤에 이를 정수에 대치하는 인코딩을 한다.
하나의 문장을 최소단위로 토큰화 및 정수 인코딩을 진행하여 정수로 표현한 것을 Zero Padding을 해서 길이를 맞춰주게 되면 하나의 정수 벡터를 만들 수 있다.
이것을 바탕으로 각 벡터(문장)간의 코사인 값을 구하는 것을 코사인 유사도라고 할 수 있다.
계산을 위한 수식은 아래와 같다.
$similarity = cos(\Theta) = \frac{{A \cdot B}}{||A||\ ||B||} = \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}}$
코사인의 치역(출력)은 -1~1이므로 벡터(문장)가 일치할수록 절댓값이 1에 가까울 것이고, 벡터가 다를수록 0에 가까워질 것이다.
단순한 예시를 살펴보도록 하자
import numpy as np
from numpy import dot
from numpy.linalg import norm
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('doc1 & doc2 : ',cos_sim(doc1, doc2))
print('doc2 & doc3 : ',cos_sim(doc2, doc3))
print('doc3 & doc1 : ',cos_sim(doc3, doc1))
"""
output :
doc1 & doc2 : 0.6666666666666667
doc2 & doc3 : 1.0000000000000002
doc3 & doc1 : 0.6666666666666667
"""
위 코드는 이미 단어집합에 맞춰 카운트로 인코딩된 문장 doc1, doc2, doc3 이 있다는 가정 하에 코사인 유사도를 계산한 것이다.
numpy.linalg.norm() 함수를 이용해 원점에서 벡터까지의 거리를 구한 뒤 수식에 대입하였다.
doc2, doc3은 벡터의 크기가 2배가 되었을 뿐이므로 문장이 동일하다고 표기되고 있다.
반대로 doc1, doc2 / doc1, doc3은 모두 한자리씩 달라서 코사인값이 0.67이 된 모습을 확인할 수 있다.
다차원 공간에서 두 점간의 거리를 비교하는 것이다. 많이 쓰이지는 않는다.
수식은 다음과 같다.
$\sqrt{(q_1 - p_1)^{2} + ... + (q_n - p_n)^{2}} = \sqrt{\sum_{i=1}^n (q_i - p_i)^2}$
자카드 유사도는 두 문장을 이루는 단어들을 원소로하는 집합을 만든 뒤에 두 문장의 합집합과 교집합의 비율을 표시하는 기법이다.
예를들어 문장 2개가 있을 때, 각 문장을 단어집합에 들어가는 단어 단위로 토큰화를 한다.
그렇게 만들어진 각 문장의 단어로 이루어진 집합을 각각 A, B라고 하자.
A와 B의 교집합이 있을 수도 있고, 없을 수도 있다.
이 경우, 다음 수식을 따른다.
$Jaccard Similarity = \frac{P(A \cap B)}{P \cup B}$
비슷한 주제의 문장을 다루는 경우에 효과적이라고 생각된다.
레벤슈타인 유사도는 문자열이 얼마나 비슷한지를 나타낸다.
말그대로 문자열을 얼마나 바꾸면 목표하는 문자열이 되는지를 계산한 것이다.
예를들어,
hat, here
두 문자열이 있다고 해보자. 이들의 편집거리(레벤슈타인 거리)는 3이다
h -> h (동일, 편집+0회)
a -> e (수정, 편집+1회)
t -> r (수정, 편집+1회)
-> e (삽입, 편집+1회)
총 편집 횟수) 3회
이같은 방법으로 유사도를 찾는 것이다.
영어같은 경우에는 단순하게 코드를 작성 할 수 있지만 한글의 경우는 고려할 점이 있다.
신라면, 푸라면
이 두 문자열에 대해서는 '신'과 '푸'가 다르다는 것을 알 수 있다.
그렇다면 이 문자열은 몇번 편집해야 하는가? 라고 묻는다면 3회라고 해야한다.
따라서 이를 해결하기 위해 한글 유니코드표를 대조하여 레벤슈타인 거리를 계산하는 방법을 취해야만 정확한 편집거리를 구할 수 있다.