Может ли кто-нибудь привести пример сходства косинусов очень простым графическим способом?
Статья Cosine Similarity в Википедии
Можете ли вы показать векторы здесь (в списке или что-то в этом роде), а затем выполнить математику, и давайте посмотрим, как это работает?
Я новичок
12 ответов
Вот два очень коротких текста для сравнения:
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
Мы не заинтересованы в самих словах, хотя. Нас интересуют только эти два вертикальных вектора отсчетов. Например, в каждом тексте есть два экземпляра "я". Мы собираемся решить, насколько близки эти два текста друг к другу, вычислив одну функцию этих двух векторов, а именно косинус угла между ними.
Два вектора, опять же:
a: [2, 0, 1, 1, 0, 2, 1, 1]
b: [2, 1, 1, 0, 1, 1, 1, 1]
Косинус угла между ними составляет около 0,822.
Эти векторы 8-мерны. Преимущество использования косинусного сходства состоит в том, что он превращает вопрос, который выше человеческой способности визуализировать, в тот, который может быть. В этом случае вы можете думать об этом как о углу около 35 градусов, что является некоторым "расстоянием" от нуля или идеальным соглашением.
Я предполагаю, что вы больше заинтересованы в том, чтобы получить представление о том, " почему " работает косинусное сходство (почему оно дает хорошее представление о сходстве), а не о том, " как " оно вычисляется (конкретные операции, используемые для вычисления). Если вас интересует последнее, см. Ссылку, указанную Даниэлем в этом посте, а также соответствующий вопрос SO.
Чтобы объяснить как и, более того, почему, полезно, во-первых, упростить задачу и работать только в двух измерениях. Как только вы получите это в 2D, вам будет легче думать об этом в трех измерениях, и, конечно, сложнее представить это во многих других измерениях, но к тому времени мы сможем использовать линейную алгебру для выполнения численных расчетов, а также, чтобы помочь нам мыслить в терминах линий / векторов / "плоскостей" / "сфер" в n измерениях, хотя мы не можем их нарисовать.
Итак... в двух измерениях: в отношении сходства текста это означает, что мы сосредоточимся на двух разных терминах, скажем, слова "Лондон" и "Париж", и посчитаем, сколько раз каждое из этих слов находится в каждый из двух документов мы хотим сравнить. Это дает нам для каждого документа точку в плоскости xy, например, если у Doc1 был Париж один раз, а Лондон четыре раза, точка (1,4) представила бы этот документ (в отношении этой уменьшительной оценки документов), Или, говоря с точки зрения векторов, этот документ Doc1 будет стрелкой, идущей от начала координат к точке (1,4). Имея это в виду, давайте подумаем, что значит быть похожим для двух документов и как это соотносится с векторами.
ОЧЕНЬ похожие документы (опять же в отношении этого ограниченного набора измерений) будут иметь такое же количество ссылок на Париж, И то же самое количество ссылок на Лондон, или, может быть, они могут иметь одинаковое соотношение этих ссылок (скажем, Документ Doc2, с 2 ссылками на Париж и 8 ссылками на Лондон, также будет очень похож, только возможно более длинный текст или как-то более повторяющийся из названий городов, но в той же пропорции: Возможно, оба документа являются путеводителями по Лондону, только мимолетные ссылки на Париж (и как не круто этот город;-) Шучу!!!). Теперь менее похожие документы могут включать ссылки на оба города, но в разных пропорциях. Возможно, Doc2 будет ссылаться только на Париж Один раз и Лондон 7 раз.
Возвращаясь к нашей плоскости xy, если мы нарисуем эти гипотетические документы, мы увидим, что когда они ОЧЕНЬ похожи, их векторы перекрываются (хотя некоторые векторы могут быть длиннее), и, поскольку они начинают иметь меньше общего, эти векторы начинают расходиться, иметь больший угол между ними.
Бам! измеряя угол между векторами, мы можем получить хорошее представление об их сходстве, и, чтобы сделать вещи еще проще, взяв косинус этого угла, мы получим хорошие значения от 0 до 1 (или от -1 до 1, в зависимости от того, что и как мы учитываем) значение, которое указывает на это сходство. Чем меньше угол, тем больше (ближе к 1) значение косинуса, а также тем больше сходство.
В крайнем случае, если Doc1 только ссылается на Париж, а Doc2 только ссылается на Лондон, документы не имеют абсолютно ничего общего. Doc1 будет иметь свой вектор на оси x, Doc2 на оси y, угол 90 градусов, косинус 0. (Кстати , именно это мы имеем в виду, когда говорим, что две вещи ортогональны друг другу)
Добавление размеров:
Благодаря этому интуитивному ощущению сходства, выраженному в виде небольшого угла (или большого косинуса), мы можем теперь представить вещи в трех измерениях, например, введя слово "Амстердам" в миксе. И наглядно представьте, как документ с двумя ссылками на каждую будет иметь вектор, идущий в определенном направлении, и мы можем увидеть, как это направление будет сравниваться с документом, в котором каждый раз упоминается Париж и Лондон, но не Амстердам и т. Д. Как сказано мы можем попытаться представить себе это фантастическое пространство для 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));
}
}
}
Для простоты я сокращаю вектор a и b:
Let :
a : [1, 1, 0]
b : [1, 0, 1]
Тогда косинус сходства (тета):
(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 градусам.
Этот код 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))
Используя пример @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)))
Это простой 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));
}
Позвольте мне попытаться объяснить это с помощью кода Python и некоторых графических математических формул.
Предположим, в нашем коде есть два очень коротких текста:
texts = ["I am a boy", "I am a girl"]
И мы хотим сравнить следующий текст запроса, чтобы увидеть, насколько он близок к приведенным выше текстам, используя быстрые оценки сходства косинуса:
query = ["I am a boy scout"]
Как нам вычислить оценки сходства косинусов? Во-первых, давайте построим на Python матрицу tfidf для этих текстов:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(texts)
Затем давайте проверим значения нашей матрицы tfidf и ее словаря:
print(tfidf_matrix.toarray())
# output
array([[0.57973867, 0.81480247, 0. ],
[0.57973867, 0. , 0.81480247]])
Здесь мы получаем матрицу tfidf со значениями tfidf 2 x 3 или 2 документа / текст x 3 термина. Это наша матрица терминов документа tfidf. Посмотрим, что это за 3 термина, позвонив
vectorizer.vocabulary_
print(vectorizer.vocabulary_)
# output
{'am': 0, 'boy': 1, 'girl': 2}
Это говорит нам, что наши 3 члена в нашей матрице tfidf - это «am», «boy» и «girl». «am» находится в столбце 0, «мальчик» - в столбце 1, а «девушка» - в столбце 2. Термины «I» и «a» были удалены векторизатором, поскольку они являются игнорируемыми словами.
Теперь у нас есть матрица tfidf, мы хотим сравнить текст нашего запроса с нашими текстами и посмотреть, насколько близок наш запрос к нашим текстам. Для этого мы можем вычислить оценки косинусного сходства запроса по сравнению с матрицей текстов tfidf. Но сначала нам нужно вычислить tfidf нашего запроса:
query = ["I am a boy scout"]
query_tfidf = vectorizer.transform([query])
print(query_tfidf.toarray())
#output
array([[0.57973867, 0.81480247, 0. ]])
Здесь мы вычислили tfidf нашего запроса. Наш query_tfidf имеет вектор значений tfidf, который мы будем использовать для вычисления наших оценок умножения схожести косинуса. Если не ошибаюсь, значения query_tfidf или
vectorizer.transform([query])
значения получаются путем простого выбора строки или документа из tfidf_matrix, в котором наибольшее количество слов соответствует запросу. Например, строка 1 или документ / текст 1 tfidf_matrix имеет наибольшее совпадение слов с текстом запроса, который содержит «am» (0,57973867) и «мальчик» (0,81480247), следовательно, строка 1 tfidf_matrix
[0.57973867, 0.81480247, 0. ]
значения выбираются как значения для query_tfidf. (Примечание: если бы кто-то мог помочь объяснить это, это было бы хорошо)
После вычисления нашего query_tfidf, мы можем теперь матрично умножить или умножить наш вектор query_tfidf на наш текст tfidf_matrix, чтобы получить оценки косинусного сходства.
Напомним, что оценка или формула косинусного сходства равна следующему:
cosine similarity score = (A . B) / ||A|| ||B||
Здесь A = наш вектор query_tfidf и B = каждая строка нашего tfidf_matrix
Обратите внимание: А. B = A * B ^T, или скалярное произведение B = A умножить на B Транспонировать.
Зная формулу, давайте вручную вычислим наши оценки косинусного сходства для query_tfidf, а затем сравним наш ответ со значениями, предоставленными функцией cosine_similarity sklearn.metrics. Рассчитаем вручную:
query_tfidf_arr = query_tfidf.toarray()
tfidf_matrix_arr = tfidf_matrix.toarray()
cosine_similarity_1 = np.dot(query_tfidf_arr, tfidf_matrix_arr[0].T) /
(np.linalg.norm(query_tfidf_arr) * np.linalg.norm(tfidf_matrix_arr[0]))
cosine_similarity_2 = np.dot(query_tfidf_arr, tfidf_matrix_arr[1].T) /
(np.linalg.norm(query_tfidf_arr) * np.linalg.norm(tfidf_matrix_arr[1]))
manual_cosine_similarities = [cosine_similarity_1[0], cosine_similarity_2[0]]
print(manual_cosine_similarities)
#output
[1.0, 0.33609692727625745]
Наши рассчитанные вручную оценки косинусного сходства дают значения
[1.0, 0.33609692727625745]
. Давайте сравним наш вычисленный вручную показатель сходства косинуса со значением ответа, предоставленным функцией cosine_similarity sklearn.metrics:
from sklearn.metrics.pairwise import cosine_similarity
function_cosine_similarities = cosine_similarity(query_tfidf, tfidf_matrix)
print(function_cosine_similarities)
#output
array([[1.0 , 0.33609693]])
Оба выходных значения одинаковы! Значения подобия косинуса, вычисленные вручную, такие же, как значения подобия косинуса, вычисленные функцией!
Следовательно, это простое объяснение показывает, как вычисляются значения косинусного сходства. Надеюсь, вы нашли это объяснение полезным.
Два вектора A и B существуют в 2D-пространстве или 3D-пространстве, угол между этими векторами равен cos подобию.
Если угол больше (может достигать макс. 180 градусов), то есть Cos 180=-1, а минимальный угол составляет 0 градусов. cos 0 =1 подразумевает, что векторы выровнены друг с другом и, следовательно, векторы похожи.
cos 90 = 0 (этого достаточно, чтобы сделать вывод, что векторы A и B совсем не похожи, и, поскольку расстояние не может быть отрицательным, значения косинуса будут лежать в диапазоне от 0 до 1. Следовательно, больший угол подразумевает уменьшение сходства (визуализируя также его имеет смысл)
Вот простой код Python для вычисления косинусного сходства:
import math
def dot_prod(v1, v2):
ret = 0
for i in range(len(v1)):
ret += v1[i] * v2[i]
return ret
def magnitude(v):
ret = 0
for i in v:
ret += i**2
return math.sqrt(ret)
def cos_sim(v1, v2):
return (dot_prod(v1, v2)) / (magnitude(v1) * magnitude(v2))