¿Alguien puede dar un ejemplo de similitud de coseno, de una manera muy simple y gráfica?

Respuestas:

463

Aquí hay dos textos muy cortos para comparar:

  1. Julie loves me more than Linda loves me

  2. Jane likes me more than Julie loves me

Queremos saber qué tan similares son estos textos, simplemente en términos de conteo de palabras (e ignorando el orden de las palabras). Comenzamos haciendo una lista de las palabras de ambos textos:

me Julie loves Linda than more likes Jane

Ahora contamos la cantidad de veces que cada una de estas palabras aparece en 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

Sin embargo, no estamos interesados ​​en las palabras mismas. Solo nos interesan esos dos vectores verticales de conteos. Por ejemplo, hay dos instancias de 'yo' en cada texto. Vamos a decidir qué tan cerca están estos dos textos entre sí calculando una función de esos dos vectores, a saber, el coseno del ángulo entre ellos.

Los dos vectores son, de nuevo:

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

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

El coseno del ángulo entre ellos es de aproximadamente 0.822.

Estos vectores son de 8 dimensiones. Una virtud de usar la similitud del coseno es claramente que convierte una pregunta que está más allá de la capacidad humana de visualizar en una que pueda ser. En este caso, puede pensar en esto como el ángulo de unos 35 grados, que es una "distancia" de cero o un acuerdo perfecto.

Bill Bell
fuente
12
Esto es exactamente lo que estaba buscando. Exactamente. ¿Se considera esta la forma más simple de "modelo de espacio vectorial"?
TIMEX
2
Estoy muy contento de que esto te haya sido útil, Alex. Disculpe la demora en responder. No he visitado StackOverflow en mucho tiempo. En realidad, este es un ejemplo de un "espacio interno de producto". Hay una discusión básica en wikipedia.
Bill Bell
1
¿Hay alguna forma de normalizar la longitud del documento?
sinθ
1
Debe usar la normalización de longitud y antes de eso, intente usar la ponderación de frecuencia de registro en todos los vectores de término. Si ya está tratando con vectores normalizados, entonces es el producto
escalar
44
Ejemplo más detallado con el uso de la normalización de longitud y TF-IDF: site.uottawa.ca/~diana/csi4107/cosine_tf_idf_example.pdf
Mike B.
121

Supongo que está más interesado en obtener una idea de " por qué " la similitud del coseno funciona (por qué proporciona una buena indicación de similitud), en lugar de " cómo " se calcula (las operaciones específicas utilizadas para el cálculo). Si le interesa este último, consulte la referencia indicada por Daniel en esta publicación, así como una pregunta SO relacionada .

Para explicar tanto el cómo y aún más el por qué, es útil, al principio, simplificar el problema y trabajar solo en dos dimensiones. Una vez que obtenga esto en 2D, es más fácil pensarlo en tres dimensiones y, por supuesto, más difícil de imaginar en muchas más dimensiones, pero para entonces podemos usar el álgebra lineal para hacer los cálculos numéricos y también para ayudarnos a pensar en términos de líneas / vectores / "planos" / "esferas" en n dimensiones, aunque no podemos dibujarlas.

Entonces, en dos dimensiones : con respecto a la similitud de texto, esto significa que nos enfocaríamos en dos términos distintos, digamos las palabras "Londres" y "París", y contaríamos cuántas veces se encuentra cada una de estas palabras en cada Los dos documentos que deseamos comparar. Esto nos da, para cada documento, un punto en el plano xy. Por ejemplo, si Doc1 tuvo París una vez, y Londres cuatro veces, un punto en (1,4) presentaría este documento (con respecto a esta evaluación diminuta de documentos). O, hablando en términos de vectores, este documento Doc1 sería una flecha que va desde el origen hasta el punto (1,4). Con esta imagen en mente, pensemos qué significa que dos documentos sean similares y cómo se relaciona esto con los vectores.

Documentos MUY similares (nuevamente con respecto a este conjunto limitado de dimensiones) tendrían la misma cantidad de referencias a París, y la misma cantidad de referencias a Londres, o tal vez, podrían tener la misma proporción de estas referencias. Un documento, Doc2, con 2 referencias a París y 8 referencias a Londres, también sería muy similar, solo con un texto más largo o de alguna manera más repetitivo de los nombres de las ciudades, pero en la misma proporción. Tal vez ambos documentos son guías sobre Londres, solo hacen referencias pasajeras a París (y lo poco interesante que es esa ciudad ;-) ¡¡¡Solo bromeo !!!.

Ahora, documentos menos similares también pueden incluir referencias a ambas ciudades, pero en diferentes proporciones. Quizás Doc2 solo citaría París una vez y Londres siete veces.

De vuelta a nuestro avión xy, si dibujamos estos documentos hipotéticos, vemos que cuando son MUY similares, sus vectores se superponen (aunque algunos vectores pueden ser más largos), y a medida que comienzan a tener menos en común, estos vectores comienzan a divergir, tener un ángulo más amplio entre ellos.

Al medir el ángulo entre los vectores, podemos tener una buena idea de su similitud , y hacer las cosas aún más fáciles, tomando el coseno de este ángulo, tenemos un buen valor de 0 a 1 o de -1 a 1 que es indicativo de esta similitud, dependiendo de qué y cómo contamos. Cuanto más pequeño es el ángulo, más grande (más cercano a 1) el valor del coseno, y también mayor es la similitud.

En el extremo, si Doc1 solo cita París y Doc2 solo cita Londres, los documentos no tienen absolutamente nada en común. Doc1 tendría su vector en el eje x, Doc2 en el eje y, el ángulo 90 grados, Coseno 0. En este caso, diríamos que estos documentos son ortogonales entre sí.

Agregar dimensiones :
con esta sensación intuitiva de similitud expresada como un ángulo pequeño (o coseno grande), ahora podemos imaginar cosas en 3 dimensiones, digamos al incorporar la palabra "Amsterdam" en la mezcla, y visualizar muy bien cómo un documento con dos las referencias a cada uno tendrían un vector en una dirección particular, y podemos ver cómo esta dirección se compararía con un documento que cita París y Londres tres veces cada uno, pero no Amsterdam, etc. Como se dijo, podemos tratar de imaginar esta fantasía espacio para 10 o 100 ciudades. Es difícil de dibujar, pero fácil de conceptualizar.

Terminaré simplemente diciendo algunas palabras sobre la fórmula misma . Como he dicho, otras referencias proporcionan buena información sobre los cálculos.

Primero en dos dimensiones. La fórmula para el coseno del ángulo entre dos vectores se deriva de la diferencia trigonométrica (entre el ángulo a y el ángulo b):

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

Esta fórmula se parece mucho a la fórmula del producto de puntos:

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

donde cos(a)corresponde al xvalor y sin(a)el yvalor, para el primer vector, etc. El único problema, es que x, y, etc, no son exactamente los cosy sinsus valores, de estos valores tienen que ser leídos en el círculo unitario. Ahí es donde entra en juego el denominador de la fórmula: al dividir por el producto de la longitud de estos vectores, las coordenadas xy yse normalizan.

mjv
fuente
25

Aquí está mi implementación en 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
fuente
esto es increíble gracias, me encantó cómo explicaste Magnitud =)
liminal18
Eso es genial, pero ¿qué pasa si estamos trabajando con archivos o cadenas?
Talha
21

Por simplicidad, estoy reduciendo el vector ayb:

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

Entonces similitud coseno (Theta):

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

entonces inverso de cos 0.5 es 60 grados.

usuarioPS
fuente
18

Este código de Python es mi intento rápido y sucio de implementar el 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
fuente
¿Puede explicar por qué usó set en la línea "all_items = set (counter1.keys ()). Union (set (counter2.keys ()))".
Ghos3t
@ Ghos3t, eso es obtener una lista de palabras distintas de ambos documentos
Jobs
7

Usando el ejemplo de @Bill Bell, dos formas de hacer esto en [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)))

o aprovechando el rendimiento del método crossprod () ...

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

Este es un Pythoncódigo simple que implementa la similitud de coseno.

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
fuente
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));
}
}
usuario1472571
fuente
3

Código JAVA simple para calcular la similitud de coseno

/**
   * 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
fuente
1
No es una "forma simple y gráfica", sino solo código. Aunque otros también han cometido el mismo error: /
Skrylar
-1

Existen dos vectores A y B en un espacio 2D o 3D, el ángulo entre esos vectores es cos similar.

Si el ángulo es mayor (puede alcanzar un máximo de 180 grados), que es Cos 180 = -1 y el ángulo mínimo es de 0 grados. cos 0 = 1 implica que los vectores están alineados entre sí y, por lo tanto, los vectores son similares.

cos 90 = 0 (que es suficiente para concluir que los vectores A y B no son similares en absoluto y dado que la distancia no puede ser negativa, los valores del coseno estarán entre 0 y 1. Por lo tanto, más ángulo implica reducir la similitud (visualizarlo también tiene sentido)

Babear
fuente