[R] 코사인 거리 (Cosine Distance), 코사인 유사도 (Cosine Similarity) : R proxy dist(x, method = "cosine")
R 분석과 프로그래밍/R 군집분석(Clustering) 2017. 6. 5. 11:40예전 포스팅에서는 연속형 변수들 간의 거리를 측정하는 Measure로서 맨하탄 거리, 유클리드 거리, 표준화 거리, 마할라노비스 거리 등에 대해서 소개하였습니다.
이전 포스팅에서는 명목형 데이터를 원소로 가지는 두 집합 X, Y의 특징들 간의 공통 항목들의 비율 (교집합의 개수 / 합집합의 개수)을 가지고 두 집합 간 유사성을 측정하는 Jaccard Index 와 (1 - Jaccard Index)로 두 집합 간 거리(비유사성)을 측정하는 Jaccard Distance에 대해서 알아보았습니다.
이번 포스팅에서는 문서를 유사도를 기준으로 분류 혹은 그룹핑을 할 때 유용하게 사용할 수 있는 코사인 거리(Cosine Distance)에 대해서 소개하겠습니다.
코사인 거리를 계산할 때는 먼저 문서(Document, Text)에 포함된 단어들을 단어별로 쪼갠 후에, 단어별로 개수를 세어 행렬로 만들어주는 전처리가 필요합니다. (대소문자 처리라든지, 일상적으로 쓰이는 별로 중요하지 않은 단어 처리라든지... 이게 좀 시간이 오래걸리고, 단어 DB랑 처리 노하우가 필요한 부분입니다)
이번 포스팅에서는 이런 전처리가 다 되어있다고 가정하고, 코사인 거리 (혹은 코사인 유사도)의 정의와 계산 방법, R로 자동계산하는 방법을 소개하는데 집중하겠습니다.
아래의 '참고 1'에서와 같이 코사인 유사도(Cosine Similarity)는 두 개의 문서별 단어별 개수를 세어놓은 특징 벡터 X, Y 에 대해서 두 벡터의 곱(X*Y)을 두 벡터의 L2 norm (즉, 유클리드 거리) 의 곱으로 나눈 값입니다.
그리고 코사인 거리(Cosine Distance)는 '1 - 코사인 유사도(Cosine Similarity)' 로 계산합니다.
(유사도 측정 지표인 Jaccard Index 와 비유사도 측정 지표인 Jaccard Distance 와 유사합니다)
[ 참고 1 : 코사인 유사도 (Cosine Similarity) vs. 코사인 거리 (Cosine Distance) ]
위의 공식만 봐서는 쉽게 이해가 안갈 수도 있을 것 같은데요, 아주 간단한 예를 가지고 좀더 자세하게 설명해 보겠습니다.
Document 1, Document 2, Document 3 라는 3개의 문서가 있다고 해보겠습니다.
그리고 각 문서에 'Life', 'Love', 'Learn' 이라는 3개의 단어가 포함되어 있는 개수를 세어보았더니 다음과 같았습니다.
[ Table 1 : 3개의 문서별 단어별 출현 회수 (number of presence by words in each documents) ]
Corpus Text |
Life |
Love |
Learn |
Document 1 |
1 |
0 |
5 |
Document 2 |
4 |
7 |
3 |
Document 3 |
40 |
70 |
30 |
(예 : Document 2에서는 'Life'라는 단어가 4번, 'Love'라는 단어가 7번, 'Learn'이라는 단어가 3번 출현함(포함됨))
위의 'Table 1'의 각 문서별 출현하는 단어별 회수를 특징 벡터로 하는 벡터를 가지고 'Document 1'과 'Document 2' 간의 코사인 거리(Cosine Distance)를 사용해서 각 문서 간 비유사도를 계산해보겠습니다.
[ 참고 2 : 'Document 1'과 'Document 2' 간의 코사인 거리 (cosine distance b/w doc. 1 and doc. 2) ]
코사인 거리(Cosine Distance)를 계산할 때 사용하는 코사인 유사도(Cosine Similarity) 의 분자, 분모를 보면 유추할 수 있는데요, 두 특징 벡터의 각 차원이 동일한 배수로 차이가 나는 경우에는 코사인 거리는 '0'이 되고 코사인 유사도는 '1'이 됩니다.
위의 'Table 1'의 예에서 'Document 2'와 'Document 3'의 각 단어 (Life, Love, Learn)별 출현 회수가 동일하게 '10배'씩 차이가 나고 있는데요, 바로 이런 경우를 말하는 것입니다. Document 23 가 Document 2보다 쪽수가 더 많고 두꺼워서 각 단어별 출현 빈도는 더 높을 지 몰라도 각 단어가 출현하는 비율은 좀더 얇은 Document 2나 더 두꺼운 Document 3가 동일(유사)하므로 두 문서는 유사한 특성을 가지고 있다고 코사인 거리는 판단하는 것입니다. 이처럼 단위에 상관없이 코사인 거리를 사용할 수 있으므로 꽤 편리하고 합리적입니다.
[ 참고 3 : 'Document 2'과 'Document 3' 간의 코사인 거리 (cosine distance b/w doc. 2 and doc. 3) ]
이제부터는 R의 proxy package의 dist(x, method = "cosine") 함수를 사용해서 코사인 거리를 구하는 방법을 소개합니다.
(1) proxy 패키지를 설치하고 불러오기
## installing and loading proxy package install.packages("proxy") library(proxy)
|
(2) 문서별 단어별 출현 회수를 특징 벡터로 가지는 행렬 (Term Document Matrix) 만들기
위에서 설명했던 3개 문서의 'Life', 'Love', 'Learn'의 3개 단어 예제를 그대로 사용합니다.
> # making Term Document Matrix > Doc_1 <- c(1, 0, 5) > Doc_2 <- c(4, 7, 3) > Doc_3 <- c(40, 70, 30) > > Doc_corpus <- rbind(Doc_1, Doc_2, Doc_3) # matrix > colnames(Doc_corpus) <- c("Life", "Love", "Learn") > Doc_corpus Life Love Learn Doc_1 1 0 5 Doc_2 4 7 3 Doc_3 40 70 30
|
(3) proxy 패키지의 dist(x, method = "cosine") 함수로 코사인 거리 계산하고, as.matrix() 함수를 사용해서 코사인 거리 계산 결과를 행렬로 반환하기
> # calculating cosine distance between documents using proxy package > cosine_dist_Doc_mat <- as.matrix(dist(Doc_corpus, method = "cosine")) > cosine_dist_Doc_mat Doc_1 Doc_2 Doc_3 Doc_1 0.0000000 0.5668373 0.5668373 Doc_2 0.5668373 0.0000000 0.0000000 Doc_3 0.5668373 0.0000000 0.0000000
|
위의 코사인 거리 계산 결과를 세로로 긴 형태 (long format) 로 저장하려면 아래의 for loop 문과 indexing을 사용한 코드를 참고하시기 바랍니다.
n <- ncol(cosine_dist_Doc_mat) # number of columns
|
proxy package를 사용하지 않을 거면, 위의 '참고 1'의 공식을 사용하여 아래처럼 함수를 직접 짜서 코사인 거리를 계산할 수도 있습니다. 참고하세요.
> # cosine distance function > cosine_Dist <- function(x){ + as.dist(1 - x%*%t(x)/(sqrt(rowSums(x^2) %*% t(rowSums(x^2))))) + } > > cosine_Dist(Doc_corpus) Doc_1 Doc_2 Doc_2 0.5668373 Doc_3 0.5668373 0.0000000
|
많은 도움이 되었기를 바랍니다.
이번 포스팅이 도움이 되었다면 아래의 '공감 ~ '를 꾸욱 눌러주세요. ^^
다음 포스팅에서는 문자열 편집거리(edit distance, Levenshtein metric)에 대해서 알아보겠습니다.