Alguém pode dar um exemplo de similaridade de cosseno, de uma maneira gráfica muito simples?

Respostas:

463

Aqui estão dois textos muito curtos para comparar:

  1. Julie loves me more than Linda loves me

  2. Jane likes me more than Julie loves me

Queremos saber quão semelhantes são esses textos, puramente em termos de contagem de palavras (e ignorando a ordem das palavras). Começamos fazendo uma lista das palavras dos dois textos:

me Julie loves Linda than more likes Jane

Agora contamos o número de vezes que cada uma dessas palavras aparece em cada texto:

   me   2   2
 Jane   0   1
Julie   1   1
Linda   1   0
likes   0   1
loves   2   1
 more   1   1
 than   1   1

No entanto, não estamos interessados ​​nas próprias palavras. Estamos interessados ​​apenas nesses dois vetores verticais de contagens. Por exemplo, existem duas instâncias de 'eu' em cada texto. Vamos decidir o quão próximos esses dois textos estão um do outro, calculando uma função desses dois vetores, a saber, o cosseno do ângulo entre eles.

Os dois vetores são, novamente:

a: [2, 0, 1, 1, 0, 2, 1, 1]

b: [2, 1, 1, 0, 1, 1, 1, 1]

O cosseno do ângulo entre eles é de cerca de 0,822.

Esses vetores são 8-dimensionais. Uma virtude do uso da semelhança de cosseno é claramente o fato de converter uma questão que está além da capacidade humana de visualizar em uma que possa ser. Nesse caso, você pode pensar nisso como o ângulo de cerca de 35 graus, que é uma "distância" de zero ou concordância perfeita.

Bill Bell
fonte
12
Era exatamente isso que eu estava procurando. Exatamente. Isso é considerado a forma mais simples de "modelo de espaço vetorial"?
TIMEX
2
Estou realmente feliz que isso tenha sido útil para você, Alex. Desculpe pelo atraso na resposta. Não visitei o StackOverflow há algum tempo. Na verdade, este é um exemplo de um "espaço interno do produto". Há uma discussão básica na wikipedia.
Bill Sino
1
Existe alguma maneira de normalizar o comprimento do documento?
sinθ
1
Você precisa usar a normalização do comprimento e, antes disso, tentar usar a ponderação da frequência do log em todos os vetores de termos. Se o seu já lidar com vetores normalizados, então é o produto escalar de AB
Ali Gajani
4
Exemplo mais detalhado com uso de normalização comprimento e TF-IDF: site.uottawa.ca/~diana/csi4107/cosine_tf_idf_example.pdf
Mike B.
121

Suponho que você esteja mais interessado em obter algumas dicas sobre " por que " a semelhança de cosseno funciona (por que fornece uma boa indicação de semelhança), em vez de " como " é calculada (as operações específicas usadas para o cálculo). Se o seu interesse é no último, consulte a referência indicada por Daniel nesta postagem, bem como uma pergunta relacionada ao SO .

Para explicar o como e, mais ainda, o porquê, é útil, a princípio, simplificar o problema e trabalhar apenas em duas dimensões. Depois que você obtém isso em 2D, é mais fácil pensar em três dimensões e, é claro, mais difícil de imaginar em muitas outras dimensões, mas então podemos usar a álgebra linear para fazer os cálculos numéricos e também para nos ajudar a pensar em termos de linhas / vetores / "planos" / "esferas" em n dimensões, mesmo que não possamos desenhá-las.

Assim, em duas dimensões : com relação à semelhança do texto, isso significa que nos concentraremos em dois termos distintos, digamos as palavras "Londres" e "Paris" e contamos quantas vezes cada uma dessas palavras é encontrada em cada uma delas. os dois documentos que queremos comparar. Isso nos dá, para cada documento, um ponto no plano xy. Por exemplo, se o Doc1 tivesse Paris uma vez e Londres quatro vezes, um ponto em (1,4) apresentaria este documento (com relação a essa avaliação diminuta de documentos). Ou, falando em termos de vetores, este documento Doc1 seria uma seta que vai da origem ao ponto (1,4). Com esta imagem em mente, vamos pensar no que significa dois documentos serem semelhantes e como isso se relaciona com os vetores.

Documentos MUITO semelhantes (novamente com relação a esse conjunto limitado de dimensões) teriam o mesmo número de referências a Paris, e o mesmo número de referências a Londres, ou talvez, eles pudessem ter a mesma proporção dessas referências. Um Documento, Doc2, com 2 referências a Paris e 8 referências a Londres, também seria muito semelhante, apenas com talvez um texto mais longo ou de alguma forma mais repetitivo dos nomes das cidades, mas na mesma proporção. Talvez os dois documentos sejam guias sobre Londres, apenas fazendo referências passageiras a Paris (e como essa cidade não é legal ;-) Apenas brincando !!!.

Agora, documentos menos semelhantes também podem incluir referências às duas cidades, mas em proporções diferentes. Talvez o Doc2 cite apenas Paris uma vez e Londres sete vezes.

De volta ao nosso plano xy, se desenharmos esses documentos hipotéticos, veremos que quando eles são MUITO parecidos, seus vetores se sobrepõem (embora alguns vetores possam ser mais longos) e, quando começam a ter menos em comum, esses vetores começam a divergir, para ter um ângulo maior entre eles.

Ao medir o ângulo entre os vetores, podemos ter uma boa idéia de sua similaridade e, para tornar as coisas ainda mais fáceis, usando o cosseno desse ângulo, temos um bom valor de 0 a 1 ou -1 a 1 que é indicativo de essa semelhança, dependendo do que e como representamos. Quanto menor o ângulo, maior (mais próximo de 1) o valor do cosseno e também maior a semelhança.

No extremo, se o Doc1 citar apenas Paris e o Doc2 citar apenas Londres, os documentos não terão absolutamente nada em comum. Doc1 teria seu vetor no eixo x, Doc2 no eixo y, o ângulo 90 graus, cosseno 0. Nesse caso, diríamos que esses documentos são ortogonais entre si.

Adicionando dimensões :
com essa sensação intuitiva de similaridade expressa como um ângulo pequeno (ou cosseno grande), agora podemos imaginar coisas em 3 dimensões, digamos, trazendo a palavra "Amsterdã" para a mistura e visualizar muito bem como um documento com duas as referências a cada um teriam um vetor indo em uma direção específica, e podemos ver como essa direção se compara a um documento que cita Paris e Londres três vezes cada, mas não Amsterdã, etc. Como dito, podemos tentar imaginar a fantasia espaço para 10 ou 100 cidades. É difícil desenhar, mas fácil de conceituar.

Terminarei dizendo algumas palavras sobre a fórmula em si . Como eu disse, outras referências fornecem boas informações sobre os cálculos.

Primeiro em duas dimensões. A fórmula para o cosseno do ângulo entre dois vetores é derivada da diferença trigonométrica (entre o ângulo ae ângulo b):

cos(a - b) = (cos(a) * cos(b)) + (sin (a) * sin(b))

Essa fórmula é muito semelhante à fórmula do produto escalar:

Vect1 . Vect2 =  (x1 * x2) + (y1 * y2)

onde cos(a)corresponde ao xvalor e sin(a)o yvalor, para o primeiro vector, etc. O único problema, é que x, y, etc, não são exactamente os cose sinvalores, para estes valores necessitam de ser lido no círculo unitário. É aí que o denominador da fórmula entra em ação: dividindo pelo produto pelo comprimento desses vetores, as coordenadas xe ysão normalizadas.

mjv
fonte
25

Aqui está minha implementação em 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));
        }
    }
}
tranmq
fonte
isto é incrível obrigado Eu amei como você explicou Magnitude =)
liminal18
Isso é ótimo, mas e se estivermos trabalhando com arquivos ou strings.
Talha
21

Por simplicidade, estou reduzindo o vetor aeb:

Let :
    a : [1, 1, 0]
    b : [1, 0, 1]

Então semelhança de cosseno (Theta):

 (Theta) = (1*1 + 1*0 + 0*1)/sqrt((1^2 + 1^2))* sqrt((1^2 + 1^2)) = 1/2 = 0.5

então o inverso de cos 0,5 é 60 graus.

userPS
fonte
18

Este código Python é minha tentativa rápida e suja de implementar o algoritmo:

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))
Victor Yan
fonte
Você pode explicar por que usou set na linha "all_items = set (counter1.keys ()). Union (set (counter2.keys ()))".
Ghos3t
@ Ghos3t, que é obter lista de palavras distintas de ambos os documentos
Jobs
7

Usando o exemplo de Bill Bell, duas maneiras de fazer isso em [R]

a = c(2,1,0,2,0,1,1,1)

b = c(2,1,1,1,1,0,1,1)

d = (a %*% b) / (sqrt(sum(a^2)) * sqrt(sum(b^2)))

ou aproveitando o desempenho do método crossprod () ...

e = crossprod(a, b) / (sqrt(crossprod(a, a)) * sqrt(crossprod(b, b)))
Andrew U
fonte
5

Este é um Pythoncódigo simples que implementa a similaridade de cosseno.

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]])
Nilani Algiriyage
fonte
3
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 
* @author Xiao Ma
* mail : [email protected]
*
*/
  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));
}
}
user1472571
fonte
3

Código JAVA simples para calcular a semelhança de cosseno

/**
   * 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));
  }
nikoo28
fonte
1
Não é uma "maneira gráfica simples", mas apenas código. Embora outros tenham cometido o mesmo erro também: /
Skrylar
-1

Dois vetores A e B existem em um espaço 2D ou 3D, o ângulo entre esses vetores é semelhança de cos.

Se o ângulo for maior (pode atingir no máximo 180 graus), que é Cos 180 = -1 e o ângulo mínimo é 0 grau. cos 0 = 1 implica que os vetores estão alinhados um com o outro e, portanto, os vetores são semelhantes.

cos 90 = 0 (o que é suficiente para concluir que os vetores A e B não são de todo semelhantes e, como a distância não pode ser negativa, os valores do cosseno serão de 0 a 1. Portanto, mais ângulo implica implica em reduzir a similaridade (visualizando também faz sentido)

Babar
fonte