답변:
다음은 비교할 두 가지 매우 짧은 텍스트입니다.
Julie loves me more than Linda loves me
Jane likes me more than Julie loves me
우리는 순전히 단어 수와 단어 순서를 무시하면서이 텍스트들이 얼마나 유사한 지 알고 싶습니다. 우리는 두 텍스트에서 단어 목록을 만드는 것으로 시작합니다.
me Julie loves Linda than more likes Jane
이제 우리는 각 단어가 각 텍스트에 나타나는 횟수를 센다 :
me 2 2
Jane 0 1
Julie 1 1
Linda 1 0
likes 0 1
loves 2 1
more 1 1
than 1 1
우리는 단어 자체에 관심이 없습니다. 우리는이 두 개의 수직 벡터 수에만 관심이 있습니다. 예를 들어, 각 텍스트에는 'me'의 두 인스턴스가 있습니다. 우리는이 두 벡터의 하나의 함수, 즉 그 사이의 각도의 코사인을 계산하여이 두 텍스트가 서로 얼마나 가까운 지 결정할 것입니다.
두 벡터는 다음과 같습니다.
a: [2, 0, 1, 1, 0, 2, 1, 1]
b: [2, 1, 1, 0, 1, 1, 1, 1]
그들 사이의 각도의 코사인은 약 0.822입니다.
이 벡터는 8 차원입니다. 코사인 유사성을 사용한다는 장점은 인간 능력을 넘어서는 질문을 시각화 할 수있는 것으로 변환한다는 것입니다. 이 경우, 이것을 0도 또는 완벽한 일치로부터 '거리'인 약 35 도의 각도로 생각할 수 있습니다.
코사인 유사성이 어떻게 계산 되는지 (계산에 사용 된 특정 연산 )보다는 " 왜 "코사인 유사성이 작동하는지 (유사성에 대한 좋은 표시를 제공하는 이유)에 대한 통찰력을 얻는 데 더 관심이 있다고 생각합니다 . 후자에 관심이 있다면,이 게시물에 Daniel이 표시 한 참조 및 관련 SO Question을 참조하십시오 .
방법과 이유를 설명하기 위해 처음에는 문제를 단순화하고 2 차원에서만 작동하는 것이 유용합니다. 이것을 2D로 가져 오면 3 차원으로 생각하기가 더 쉽고 물론 더 많은 차원으로 상상하기는 어렵지만, 선형 대수를 사용하여 숫자 계산을 수행하고 용어로 생각하는 데 도움을 줄 수 있습니다 우리가 이것을 그릴 수는 없지만 n 차원의 선 / 벡터 / "평면"/ "구"의 집합.
따라서 두 가지 차원에서 : 텍스트 유사성과 관련하여 이것은 "London"과 "Paris"라는 두 가지 용어에 중점을두고 각 단어가 몇 번이나 나오는지를 계산합니다. 비교하고자하는 두 문서. 이것은 각 문서마다 xy 평면의 한 점을 제공합니다. 예를 들어, Doc1이 파리를 한 번, 런던을 네 번 가졌다면 (1,4) 지점이이 문서를 제시 할 것입니다 (문서의이 작은 평가와 관련하여). 또는 벡터 측면에서 말하면이 Doc1 문서는 원점에서 점 (1,4)으로가는 화살표입니다. 이 이미지를 염두에두고 두 문서가 유사하다는 것이 무슨 의미이며 이것이 벡터와 어떤 관련이 있는지 생각해 봅시다.
매우 유사한 문서 (이 제한된 차원 세트와 관련하여)는 파리에 대한 참조 수가 동일하거나 런던에 대한 참조 수가 동일하거나 이러한 참조의 비율이 동일 할 수 있습니다. 파리에 대한 2 개의 참조, 런던에 대한 8 개의 참조를 가진 문서 Doc2도 매우 유사하지만, 더 긴 텍스트 또는 도시 이름에 대해 더 반복적이지만 동일한 비율로만 가능합니다. 어쩌면 두 문서 모두 런던에 대한 안내 일뿐입니다.
이제 덜 유사한 문서에도 두 도시에 대한 참조가 포함될 수 있지만 비율은 다릅니다. 아마도 Doc2는 파리를 한 번만, 런던을 일곱 번만 인용했을 것입니다.
xy 평면으로 돌아가서이 가상의 문서를 그리면 매우 유사 할 때 벡터가 겹치며 (일부 벡터는 더 길어질 수 있지만) 공통성이 떨어지기 시작하면 벡터가 갈라지기 시작합니다. 그들 사이에 더 넓은 각도를 갖도록
벡터 사이의 각도 를 측정함으로써 , 우리는 그들의 유사성에 대한 좋은 아이디어를 얻을 수 있고, 이 각도 의 코사인 을 취함으로써, 일을 더 쉽게 만들 수 있습니다 , 우리는 좋은 0 대 1 또는 -1 대 1 값을 나타냅니다 우리가 무엇을 어떻게 설명 하느냐에 따라 이러한 유사성. 각도가 작을수록 코사인 값이 커지고 (1에 가까워짐) 유사성이 높아집니다.
Doc1이 파리 만 인용하고 Doc2가 런던 만 인용한다면, 문서는 공통점이 없습니다. Doc1은 x 축에 벡터, y 축에 Doc2, 각도 90도, 코사인 0을 갖습니다.이 경우 우리는이 문서들이 서로 직교한다고 말할 것입니다.
치수 추가 :
작은 각도 (또는 큰 코사인)로 표현 된 유사성에 대한 직관적 인 느낌으로 이제 "Amsterdam"이라는 단어를 혼합하여 3 차원으로 사물을 상상할 수 있습니다. 각각에 대한 참조는 특정 방향으로 진행되는 벡터를 가지며,이 방향이 파리와 런던을 각각 3 회 인용하지만 암스테르담 등을 인용하지 않는 문서와 어떻게 비교되는지 볼 수 있습니다. 10 개 또는 100 개 도시를위한 공간. 그리기는 어렵지만 개념화하기는 쉽습니다.
수식 자체에 대해 몇 마디 말함으로써 마무리하겠습니다 . 내가 말했듯이, 다른 참조는 계산에 대한 좋은 정보를 제공합니다.
두 가지 차원에서 처음입니다. 두 벡터 사이의 각도의 코사인 공식은 삼각 차이 (각도 a와 각도 b)에서 파생됩니다.
cos(a - b) = (cos(a) * cos(b)) + (sin (a) * sin(b))
이 공식은 내적 제품 공식과 매우 유사합니다.
Vect1 . Vect2 = (x1 * x2) + (y1 * y2)
여기서 cos(a)
받는 대응 x
값 값, 제 벡터 등 유일한 문제는,이 인 , 등을 정확하게하지 그리고 이들 값은 단위 원에 판독해야위한 값. 이 벡터의 길이의 곱으로 나눔으로써 상기 : 수식 차기의 분모는 곳이다 및 좌표가 정규화가된다.sin(a)
y
x
y
cos
sin
x
y
다음은 C #으로 구현 한 것입니다.
using System;
namespace CosineSimilarity
{
class Program
{
static void Main()
{
int[] vecA = {1, 2, 3, 4, 5};
int[] vecB = {6, 7, 7, 9, 10};
var cosSimilarity = CalculateCosineSimilarity(vecA, vecB);
Console.WriteLine(cosSimilarity);
Console.Read();
}
private static double CalculateCosineSimilarity(int[] vecA, int[] vecB)
{
var dotProduct = DotProduct(vecA, vecB);
var magnitudeOfA = Magnitude(vecA);
var magnitudeOfB = Magnitude(vecB);
return dotProduct/(magnitudeOfA*magnitudeOfB);
}
private static double DotProduct(int[] vecA, int[] vecB)
{
// I'm not validating inputs here for simplicity.
double dotProduct = 0;
for (var i = 0; i < vecA.Length; i++)
{
dotProduct += (vecA[i] * vecB[i]);
}
return dotProduct;
}
// Magnitude of the vector is the square root of the dot product of the vector with itself.
private static double Magnitude(int[] vector)
{
return Math.Sqrt(DotProduct(vector, vector));
}
}
}
이 파이썬 코드는 알고리즘을 구현하려는 빠르고 더러운 시도입니다.
import math
from collections import Counter
def build_vector(iterable1, iterable2):
counter1 = Counter(iterable1)
counter2 = Counter(iterable2)
all_items = set(counter1.keys()).union(set(counter2.keys()))
vector1 = [counter1[k] for k in all_items]
vector2 = [counter2[k] for k in all_items]
return vector1, vector2
def cosim(v1, v2):
dot_product = sum(n1 * n2 for n1, n2 in zip(v1, v2) )
magnitude1 = math.sqrt(sum(n ** 2 for n in v1))
magnitude2 = math.sqrt(sum(n ** 2 for n in v2))
return dot_product / (magnitude1 * magnitude2)
l1 = "Julie loves me more than Linda loves me".split()
l2 = "Jane likes me more than Julie loves me or".split()
v1, v2 = build_vector(l1, l2)
print(cosim(v1, v2))
이것은 Python
코사인 유사성을 구현 하는 간단한 코드입니다.
from scipy import linalg, mat, dot
import numpy as np
In [12]: matrix = mat( [[2, 1, 0, 2, 0, 1, 1, 1],[2, 1, 1, 1, 1, 0, 1, 1]] )
In [13]: matrix
Out[13]:
matrix([[2, 1, 0, 2, 0, 1, 1, 1],
[2, 1, 1, 1, 1, 0, 1, 1]])
In [14]: dot(matrix[0],matrix[1].T)/np.linalg.norm(matrix[0])/np.linalg.norm(matrix[1])
Out[14]: matrix([[ 0.82158384]])
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
*
* @author Xiao Ma
* mail : 409791952@qq.com
*
*/
public class SimilarityUtil {
public static double consineTextSimilarity(String[] left, String[] right) {
Map<String, Integer> leftWordCountMap = new HashMap<String, Integer>();
Map<String, Integer> rightWordCountMap = new HashMap<String, Integer>();
Set<String> uniqueSet = new HashSet<String>();
Integer temp = null;
for (String leftWord : left) {
temp = leftWordCountMap.get(leftWord);
if (temp == null) {
leftWordCountMap.put(leftWord, 1);
uniqueSet.add(leftWord);
} else {
leftWordCountMap.put(leftWord, temp + 1);
}
}
for (String rightWord : right) {
temp = rightWordCountMap.get(rightWord);
if (temp == null) {
rightWordCountMap.put(rightWord, 1);
uniqueSet.add(rightWord);
} else {
rightWordCountMap.put(rightWord, temp + 1);
}
}
int[] leftVector = new int[uniqueSet.size()];
int[] rightVector = new int[uniqueSet.size()];
int index = 0;
Integer tempCount = 0;
for (String uniqueWord : uniqueSet) {
tempCount = leftWordCountMap.get(uniqueWord);
leftVector[index] = tempCount == null ? 0 : tempCount;
tempCount = rightWordCountMap.get(uniqueWord);
rightVector[index] = tempCount == null ? 0 : tempCount;
index++;
}
return consineVectorSimilarity(leftVector, rightVector);
}
/**
* The resulting similarity ranges from −1 meaning exactly opposite, to 1
* meaning exactly the same, with 0 usually indicating independence, and
* in-between values indicating intermediate similarity or dissimilarity.
*
* For text matching, the attribute vectors A and B are usually the term
* frequency vectors of the documents. The cosine similarity can be seen as
* a method of normalizing document length during comparison.
*
* In the case of information retrieval, the cosine similarity of two
* documents will range from 0 to 1, since the term frequencies (tf-idf
* weights) cannot be negative. The angle between two term frequency vectors
* cannot be greater than 90°.
*
* @param leftVector
* @param rightVector
* @return
*/
private static double consineVectorSimilarity(int[] leftVector,
int[] rightVector) {
if (leftVector.length != rightVector.length)
return 1;
double dotProduct = 0;
double leftNorm = 0;
double rightNorm = 0;
for (int i = 0; i < leftVector.length; i++) {
dotProduct += leftVector[i] * rightVector[i];
leftNorm += leftVector[i] * leftVector[i];
rightNorm += rightVector[i] * rightVector[i];
}
double result = dotProduct
/ (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
return result;
}
public static void main(String[] args) {
String left[] = { "Julie", "loves", "me", "more", "than", "Linda",
"loves", "me" };
String right[] = { "Jane", "likes", "me", "more", "than", "Julie",
"loves", "me" };
System.out.println(consineTextSimilarity(left,right));
}
}
코사인 유사성을 계산하는 간단한 Java 코드
/**
* Method to calculate cosine similarity of vectors
* 1 - exactly similar (angle between them is 0)
* 0 - orthogonal vectors (angle between them is 90)
* @param vector1 - vector in the form [a1, a2, a3, ..... an]
* @param vector2 - vector in the form [b1, b2, b3, ..... bn]
* @return - the cosine similarity of vectors (ranges from 0 to 1)
*/
private double cosineSimilarity(List<Double> vector1, List<Double> vector2) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < vector1.size(); i++) {
dotProduct += vector1.get(i) * vector2.get(i);
normA += Math.pow(vector1.get(i), 2);
normB += Math.pow(vector2.get(i), 2);
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}