回答:
比較する2つの非常に短いテキストを次に示します。
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
私たちは言葉自体には興味がありません。これらのカウントの2つの垂直ベクトルのみに関心があります。たとえば、各テキストには「私」の2つのインスタンスがあります。これら2つのベクトルの1つの関数、つまり、2つのテキスト間の角度の余弦を計算することにより、これら2つのテキストが互いにどの程度近いかを判断します。
ここでも、2つのベクトルは次のとおりです。
a: [2, 0, 1, 1, 0, 2, 1, 1]
b: [2, 1, 1, 0, 1, 1, 1, 1]
それらの間の角度の余弦は約0.822です。
これらのベクトルは8次元です。コサイン類似度を使用することの利点は、視覚化する人間の能力を超えた問題を、可能な問題に変換することです。この場合、これは約35度の角度と考えることができます。これは、ゼロからの「距離」または完全な一致からのある程度の距離です。
計算方法(計算に使用される特定の演算)ではなく、コサイン類似性が機能する理由(類似性の優れた指標を提供する理由)についての洞察を得ることに関心があると思います。後者に関心がある場合は、この投稿でダニエルが示した参照と関連するSO質問を参照してください。
方法とその理由の両方を説明するには、最初は問題を単純化し、2次元でのみ機能することが有用です。これを2Dで取得すると、3次元で考えるのが簡単になり、当然、さらに多くの次元で想像するのが難しくなりますが、それまでに線形代数を使用して数値計算を実行し、用語で考えることもできます。これらは描画できませんが、n次元の線/ベクトル/「平面」/「球」の数。
したがって、2次元では、テキストの類似性に関して、これは「ロンドン」と「パリ」という2つの異なる用語に焦点を当てることを意味し、これらの各単語がそれぞれに何回見つかったかをカウントします。比較する2つのドキュメント。これにより、ドキュメントごとにxy平面内のポイントが得られます。たとえば、Doc1にパリが1回、ロンドンが4回あった場合、(1,4)でこのドキュメントが提示されます(このドキュメントの小さめの評価に関して)。または、ベクトルで言えば、このDoc1ドキュメントは原点からポイント(1,4)に向かう矢印になります。このイメージを念頭に置いて、2つのドキュメントが類似していることの意味と、これがベクトルにどのように関係しているかを考えてみましょう。
非常に似たドキュメント(この限られた次元のセットに関して)は、パリへの参照の数とロンドンへの参照の数が非常に同じであるか、またはこれらの参照の比率が同じである可能性があります。パリへの2つの参照とロンドンへの8つの参照を持つドキュメント、Doc2も非常によく似ていますが、テキストが長いか、都市の名前の繰り返しがいくらか多いだけですが、比率は同じです。多分両方のドキュメントはロンドンについてのガイドであり、パリへの言及を渡すだけです(そしてその都市はどれほどクールではないか;-)
現在、類似性の低いドキュメントには両方の都市への参照が含まれている場合がありますが、比率は異なります。たぶんDoc2はパリを1回、ロンドンを7回だけ引用するでしょう。
xy平面に戻ると、これらの架空のドキュメントを描画すると、それらが非常に似ていると、ベクトルが重なり(一部のベクトルは長くなる場合があります)、共通点が少なくなるにつれて、これらのベクトルが発散し始めます。それらの間のより広い角度を持っています。
ベクトル間の角度を測定することにより、それらの類似性の良いアイデアを得ることができます。さらに簡単にするために、この角度のコサインを取得することで、0から1または-1から1の素晴らしい値が得られます。この類似性は、何をどのように説明するかによって異なります。角度が小さいほど、余弦値は大きく(1に近く)、類似性も高くなります。
極端に言えば、Doc1がパリだけを引用し、Doc2がロンドンだけを引用している場合、文書にはまったく共通点がありません。Doc1のベクトルはx軸、Doc2はy軸、角度は90度、コサイン0です。この場合、これらのドキュメントは互いに直交していると言えます。
次元の追加:
小さな角度(または大きな余弦)として表現される類似性の直感的な感覚により、「アムステルダム」という単語を組み合わせて3次元で想像できるようになり、ドキュメントが2つそれぞれへの参照には特定の方向に向かうベクトルがあり、この方向が、アムステルダムやパリなどではなく、パリとロンドンをそれぞれ3回引用しているドキュメントとどのように比較されるかを確認できます。前述のように、 10または100都市のためのスペース。描くのは難しいですが、概念化するのは簡単です。
最後に、数式自体について簡単に説明します。私が言ったように、他の参考文献は計算についての良い情報を提供します。
二次元の最初。2つのベクトル間の角度の余弦の式は、(角度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));
}
}
}
この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))
これは、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));
}