ใครสามารถยกตัวอย่างความคล้ายคลึงของโคไซน์ในรูปแบบที่เรียบง่ายและกราฟิกได้ไหม?


201

บทความคล้ายคลึงกันของโคไซน์ใน Wikipedia

คุณสามารถแสดงเวกเตอร์ที่นี่ (ในรายการหรืออะไรก็ได้) แล้วทำคณิตศาสตร์และให้เราดูว่ามันทำงานอย่างไร

ฉันเป็นมือใหม่


1
ลองหยิบสำเนาของเรขาคณิตและความหมายโดย Widdows ( press.uchicago.edu/presssite/ ...... ) ฉันอ่านมันย้อนหลังไปแล้วอยากให้ฉันมีเวลาหลายปีที่ผ่านมาข้อความเกริ่นนำที่ยอดเยี่ยม
นาธานโฮเวล

คำตอบ:


463

นี่คือข้อความสั้น ๆ สองข้อที่จะเปรียบเทียบ:

  1. Julie loves me more than Linda loves me

  2. 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

เราไม่ได้สนใจในคำว่าตัวเอง เราสนใจเฉพาะค่าเวกเตอร์แนวตั้งสองตัวนั้นเท่านั้น ตัวอย่างเช่นมีสองอินสแตนซ์ของ 'ฉัน' ในแต่ละข้อความ เราจะตัดสินใจว่าข้อความทั้งสองนี้ใกล้กันมากเพียงใดโดยการคำนวณหนึ่งฟังก์ชันของเวกเตอร์สองตัวนั่นคือโคไซน์ของมุมระหว่างพวกเขา

เวกเตอร์สองตัวคืออีกครั้ง:

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

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

มุมโคไซน์ของมุมระหว่างพวกมันประมาณ 0.822

เวกเตอร์เหล่านี้มีขนาด 8 มิติ ข้อดีของการใช้ความคล้ายคลึงกันของโคไซน์นั้นชัดเจนว่ามันแปลงคำถามที่เกินความสามารถของมนุษย์ ในกรณีนี้คุณสามารถคิดว่านี่เป็นมุมประมาณ 35 องศาซึ่งเป็น 'ระยะทาง' จากศูนย์หรือข้อตกลงที่สมบูรณ์แบบ


12
นี่คือสิ่งที่ฉันกำลังมองหา เผง นี่ถือว่าเป็นรูปแบบที่ง่ายที่สุดของ "โมเดลอวกาศเวกเตอร์" หรือไม่?
TIMEX

2
ฉันดีใจจริงๆที่มีประโยชน์กับคุณอเล็กซ์ ขออภัยในความล่าช้าในการตอบกลับ ฉันไม่ได้เข้าชม StackOverflow ในระยะเวลาหนึ่ง ที่จริงนี่เป็นตัวอย่างของ "พื้นที่ผลิตภัณฑ์ภายใน" มีการอภิปรายขั้นพื้นฐานเกี่ยวกับวิกิพีเดีย
Bill Bell

1
มีวิธีใดที่จะทำให้ปกติสำหรับความยาวของเอกสาร?
sinθ

1
คุณต้องใช้การทำให้เป็นมาตรฐานความยาวและก่อนหน้านั้นลองใช้น้ำหนักความถี่บันทึกในเวกเตอร์ทุกเทอม หากคุณจัดการกับเวกเตอร์ที่ได้มาตรฐานแล้วนั่นคือผลคูณของ AB
Ali Gajani

4
ตัวอย่างที่มีรายละเอียดเพิ่มเติมเกี่ยวกับการใช้การปรับความยาวและ TF-IDF: site.uottawa.ca/~diana/csi4107/cosine_tf_idf_example.pdf
Mike B.

121

ฉันเดาว่าคุณสนใจที่จะรับข้อมูลเชิงลึกเกี่ยวกับ " ทำไม " ความเหมือนโคไซน์ทำงาน (ทำไมมันถึงบ่งบอกถึงความคล้ายคลึงกันได้ดี) แทนที่จะคำนวณ" วิธี " (การปฏิบัติการเฉพาะที่ใช้สำหรับการคำนวณ) ถ้าหากคุณสนใจของคุณอยู่ในหลังโปรดดูข้อมูลอ้างอิงที่ระบุโดยแดเนียลในโพสต์นี้เช่นเดียวกับกิจการที่เกี่ยวข้องกันดังนั้นคำถาม

ในการอธิบายทั้งวิธีการและสาเหตุอื่น ๆ เพิ่มเติมสาเหตุที่เป็นประโยชน์ในตอนแรกเพื่อทำให้ปัญหาง่ายขึ้นและใช้งานได้ในสองมิติเท่านั้น เมื่อคุณได้รับในรูปแบบ 2D มันจะง่ายกว่าที่จะคิดในสามมิติและแน่นอนยากที่จะจินตนาการในมิติอื่น ๆ อีกมากมาย แต่จากนั้นเราสามารถใช้พีชคณิตเชิงเส้นเพื่อทำการคำนวณเชิงตัวเลขและช่วยเราคิดในแง่ ของเส้น / เวกเตอร์ / "ระนาบ" / "ทรงกลม" ใน n มิติแม้ว่าเราจะวาดมันไม่ได้

ดังนั้นในสองมิติด้วยความคล้ายคลึงกันกับข้อความนี่หมายความว่าเราจะมุ่งเน้นไปที่คำสองคำที่แตกต่างกันพูดคำว่า "ลอนดอน" และ "ปารีส" และเราจะนับว่าคำเหล่านี้พบได้กี่ครั้งในแต่ละคำ เอกสารสองรายการที่เราต้องการเปรียบเทียบ สิ่งนี้ทำให้เรามีจุดหนึ่งในระนาบ xy ตัวอย่างเช่นหาก Doc1 มีปารีสครั้งเดียวและลอนดอนสี่ครั้งจุดที่ (1,4) จะนำเสนอเอกสารนี้ (เกี่ยวกับการประเมินเอกสารฉบับย่อนี้) หรือหากพูดในแง่ของเวกเตอร์เอกสาร Doc1 นี้จะเป็นลูกศรที่ไปจากจุดเริ่มต้นไปยังจุดที่หนึ่ง ด้วยภาพนี้ในใจลองคิดดูว่าเอกสารสองฉบับมีความคล้ายคลึงกันอย่างไรและมันเกี่ยวข้องกับเวกเตอร์อย่างไร

เอกสารที่คล้ายกันมาก (อีกครั้งเกี่ยวกับชุดข้อมูลที่มีขนาด จำกัด นี้) จะมีหมายเลขอ้างอิงไปยังปารีสจำนวนเท่ากันและมีจำนวนอ้างอิงถึงลอนดอนเท่ากันหรืออาจจะมีอัตราส่วนเดียวกันกับเอกสารอ้างอิงเหล่านี้ เอกสาร Doc2 ที่มี 2 refs ไปปารีสและ 8 refs ไปลอนดอนก็จะคล้ายกันมากอาจมีเพียงข้อความยาวหรือซ้ำชื่อของเมือง แต่ในสัดส่วนเดียวกัน บางทีเอกสารทั้งสองเป็นแนวทางเกี่ยวกับลอนดอนเพียงส่งผ่านการอ้างอิงถึงปารีสเท่านั้น (และวิธีที่เมืองที่ไม่สงบเป็น ;-) แค่ล้อเล่น !!!

ตอนนี้เอกสารที่คล้ายกันน้อยกว่าอาจรวมถึงการอ้างอิงไปยังเมืองทั้งสอง แต่ในสัดส่วนที่แตกต่างกัน บางที Doc2 อาจอ้างถึงปารีสเพียงครั้งเดียวและลอนดอนถึงเจ็ดครั้ง

กลับไปที่ระนาบ xy ของเราถ้าเราวาดเอกสารสมมุติเหล่านี้เราจะเห็นว่าเมื่อพวกมันคล้ายกันมากเวกเตอร์ของพวกเขาทับซ้อนกัน (แม้ว่าเวกเตอร์บางตัวอาจยาวกว่า) และเมื่อพวกเขาเริ่มมีสามัญน้อยกว่าเวกเตอร์เหล่านี้ก็เริ่มแตก เพื่อให้ได้มุมที่กว้างขึ้นระหว่างพวกเขา

โดยการวัดมุมระหว่างเวกเตอร์เราจะได้ความคิดที่ดีเกี่ยวกับความคล้ายคลึงกันของพวกมันและทำให้สิ่งต่าง ๆ ง่ายขึ้นโดยการเอาโคไซน์ของมุมนี้เรามีค่า 0 ถึง 1 หรือ -1 ต่อ 1 ที่บ่งบอกถึง ความคล้ายคลึงกันนี้ขึ้นอยู่กับสิ่งที่และบัญชีของเรา ยิ่งมุมเล็กเท่าไหร่ก็ยิ่งมีค่าโคไซน์มากขึ้นและใกล้เคียงกันมากขึ้นเท่านั้น

ในที่สุดหาก Doc1 อ้างถึงปารีสและ Doc2 อ้างถึงลอนดอนเท่านั้นเอกสารก็ไม่มีอะไรเหมือนกัน Doc1 จะมีเวกเตอร์บนแกน x, Doc2 บนแกน y, มุม 90 องศา, โคไซน์ 0 ในกรณีนี้เราบอกว่าเอกสารเหล่านี้เป็นมุมฉากต่อกัน

การเพิ่มมิติ :
ด้วยความรู้สึกที่เข้าใจง่ายสำหรับความคล้ายคลึงที่แสดงออกมาเป็นมุมเล็ก ๆ (หรือโคไซน์ใหญ่) ตอนนี้เราสามารถจินตนาการถึงสิ่งต่าง ๆ ใน 3 มิติโดยการนำคำว่า "อัมสเตอร์ดัม" มาผสมกัน การอ้างอิงถึงเวกเตอร์แต่ละตัวจะมีทิศทางที่เฉพาะเจาะจงและเราสามารถดูได้ว่าทิศทางนี้จะเปรียบเทียบกับเอกสารที่อ้างถึงปารีสและลอนดอนสามครั้งในแต่ละครั้ง แต่ไม่ใช่อัมสเตอร์ดัมเป็นต้นตามที่กล่าวไว้เราสามารถลองนึกภาพแฟนซีนี้ พื้นที่สำหรับ 10 หรือ 100 เมือง วาดยาก แต่คิดง่าย

ฉันจะตัดขึ้นเพียงแค่พูดคำไม่กี่คำเกี่ยวกับสูตรของตัวเอง อย่างที่ฉันได้กล่าวไปแล้วการอ้างอิงอื่น ๆ ให้ข้อมูลที่ดีเกี่ยวกับการคำนวณ

ที่หนึ่งในสองมิติ สูตรสำหรับโคไซน์ของมุมระหว่างสองเวกเตอร์นั้นมาจากความแตกต่างตรีโกณมิติ (ระหว่างมุม a และมุม b):

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

สูตรนี้ดูคล้ายกับสูตรผลิตภัณฑ์ dot:

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

ที่cos(a)สอดคล้องกับxคุณค่าและค่าสำหรับเวกเตอร์แรก ฯลฯ ปัญหาเดียวก็คือว่า, ฯลฯ จะไม่ตรงและค่าสำหรับค่าเหล่านี้จะต้องมีการอ่านบนยูนิทวงกลม นั่นคือสิ่งที่ตัวหารของสูตรเริ่มต้น: โดยการหารด้วยผลคูณของความยาวของเวกเตอร์เหล่านี้, และพิกัดกลายเป็นมาตรฐานsin(a)yxycossinxy


25

นี่คือการใช้งานของฉันใน 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));
        }
    }
}

นี่มันยอดเยี่ยมมากขอบคุณฉันรักที่คุณอธิบาย Magnitude =)
liminal18

เยี่ยมมาก แต่ถ้าเราทำงานกับไฟล์หรือสตริง
Talha

21

เพื่อความง่ายฉันลดเวกเตอร์ a และ b:

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

ความคล้ายคลึงกันของโคไซน์ (Theta):

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

ค่าผกผันของ cos 0.5 เท่ากับ 60 องศา


18

รหัส Python นี้เป็นความพยายามที่รวดเร็วและสกปรกของฉันในการใช้อัลกอริทึม:

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))

คุณช่วยอธิบายได้ไหมว่าทำไมคุณถึงใช้ set ในบรรทัด "all_items = set (counter1.keys ()). union (set (counter2.keys ()))"
Ghos3t

@ Ghos3t นั่นคือการรับรายการคำที่แตกต่างจากเอกสารทั้งสอง
งาน

7

ใช้ตัวอย่าง @Bill Bell สองวิธีในการทำเช่นนี้ใน [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)))

หรือใช้ประโยชน์จากประสิทธิภาพของวิธีการ crossprod ()

e = crossprod(a, b) / (sqrt(crossprod(a, a)) * sqrt(crossprod(b, b)))

5

นี่คือ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]])

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

3

รหัส 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));
  }

1
มันไม่ได้เป็น "วิธีที่เรียบง่ายกราฟิก" แต่ยังเป็นเพียงแค่รหัส แม้ว่าคนอื่น ๆ ก็ทำผิดพลาดเหมือนกันเช่นกัน: /
Skrylar

-1

เวกเตอร์ A และ B สองตัวมีอยู่ในพื้นที่ 2D หรือพื้นที่ 3D มุมระหว่างเวกเตอร์เหล่านั้นคือความคล้ายคลึงกัน

หากมุมมีมากขึ้น (สามารถเข้าถึงสูงสุด 180 องศา) ซึ่งเป็น Cos 180 = -1 และมุมต่ำสุดคือ 0 องศา cos 0 = 1 หมายถึงเวกเตอร์อยู่ในแนวเดียวกันและดังนั้นเวกเตอร์จึงมีความคล้ายคลึงกัน

cos 90 = 0 (ซึ่งเพียงพอที่จะสรุปได้ว่าเวกเตอร์ A และ B ไม่เหมือนกันเลยและเนื่องจากระยะทางไม่สามารถลบได้ค่าโคไซน์จะอยู่ระหว่าง 0 ถึง 1 ดังนั้นมุมที่มากกว่าหมายถึงการลดความคล้ายคลึงกัน มีเหตุผล)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.