余弦相似度用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小。余弦值越接近1,就表明夹角越接近0度,也就是两个向量越相似,这就叫"余弦相似性"。
首先,从海量的向量中提取出第1条高维向量 A 1 A_1 A1,然后,计算出第1条向量与基准向量 A 0 A_0 A0的余弦值 c o s 1 cos_1 cos1,将该计算得到的余弦值作为最大余弦值 k = c o s 1 k=cos_1 k=cos1,接着,又从海量的向量中提取出第2条高维向量 A 2 A_2 A2,计算出第2条向量与基准向量的余弦值 c o s 2 cos_2 cos2,继而,将最新的余弦值 c o s 2 cos_2 cos2与最大的余弦值 k k k进行比较 m a x { k , c o s 2 } max\{k,cos_2\} max{k,cos2}得到最新的最大余弦值 k k k,最后,同理从海量的向量中迭代完剩余的向量找出余弦相似度最为相似的向量。
本文为了提高搜索效率,从数学方法证明了两向量的余弦值与他们的单位向量的欧式距离的平方值存在线性负相关关系(见公式14),最后,通过java程序验证了方法的可行性,通过计算部分维度,搜索最小的欧式距离,间接找出余弦相似度最为相似的向量。
已知高维向量 A 0 A_0 A0,请从高维向量集合 S = { A 1 , A 2 , A 3 , … , A m } S=\begin{Bmatrix} A_1,A_2,A_3,\ldots,A_m \end{Bmatrix} S={A1,A2,A3,…,Am}中找到与之最相似的高维向量 A k A_k Ak(用两向量的余弦值来衡量向量间的相似度)。
解:
令:向量 A 0 = [ a 01 , a 02 , a 03 , ⋯ , a 0 n ] A_0=[a_{01},a_{02},a_{03},\cdots,a_{0n}] A0=[a01,a02,a03,⋯,a0n]与集合 S S S中的某个向量 A i = [ a i 1 , a i 2 , a i 3 , ⋯ , a i n ] A_i=[a_{i1},a_{i2},a_{i3},\cdots,a_{in}] Ai=[ai1,ai2,ai3,⋯,ain]之间的相似度函数如下:
f ( i ) = c o s i ( θ ) = A 0 ⋅ A i ∣ A 0 ∣ × ∣ A i ∣ ( 公 式 1 ) f(i)=cos_i(\theta) =\frac {A_0\cdot A_i} { \begin{vmatrix}A_0\end{vmatrix} \times \begin{vmatrix}A_i\end{vmatrix} } \qquad (公式1) f(i)=cosi(θ)=∣∣A0∣∣×∣∣Ai∣∣A0⋅Ai(公式1)
由公式 1 可得到如下函数的表达形式:
f ( i ) = a 01 × a i 1 + a 02 × a i 2 + a 03 × a i 3 + ⋯ + a 0 n × a i n a 01 2 + a 02 2 + a 03 2 + ⋯ + a 0 n 2 × a i 1 2 + a i 2 2 + a i 3 2 + ⋯ + a i n 2 ( 公 式 2 ) f(i)= \frac { a_{01}\times a_{i1}+a_{02}\times a_{i2}+a_{03}\times a_{i3}+\cdots+a_{0n}\times a_{in} } { \sqrt{{a_{01}}^2+{a_{02}}^2+{a_{03}}^2+\cdots+{a_{0n}}^2} \times \sqrt{{a_{i1}}^2+{a_{i2}}^2+{a_{i3}}^2+\cdots+{a_{in}}^2} } \qquad (公式2) f(i)=a012+a022+a032+⋯+a0n2×ai12+ai22+ai32+⋯+ain2a01×ai1+a02×ai2+a03×ai3+⋯+a0n×ain(公式2)
由公式 2 的表达式结构可以视为是如下的两个单位向量计算而来:
B 0 = 1 λ × A 0 = 1 λ × [ a 01 , a 02 , a 03 , ⋯ , a 0 n ] ( 公 式 3 ) B_0=\frac{1}{\lambda}\times A_0= \frac{1}{\lambda}\times [a_{01},a_{02},a_{03},\cdots,a_{0n}] \qquad (公式3) B0=λ1×A0=λ1×[a01,a02,a03,⋯,a0n](公式3)
B i = 1 γ × A i = 1 γ × [ a i 1 , a i 2 , a i 3 , ⋯ , a i n ] ( 公 式 4 ) B_i=\frac{1}{\gamma}\times A_i= \frac{1}{\gamma}\times [a_{i1},a_{i2},a_{i3},\cdots,a_{in}] \qquad (公式4) Bi=γ1×Ai=γ1×[ai1,ai2,ai3,⋯,ain](公式4)
其中
λ = a 01 2 + a 02 2 + a 03 2 + ⋯ + a 0 n 2 ( 公 式 5 ) \lambda=\sqrt{{a_{01}}^2+{a_{02}}^2+{a_{03}}^2+\cdots+{a_{0n}}^2} \qquad (公式5) λ=a012+a022+a032+⋯+a0n2(公式5)
γ = a i 1 2 + a i 2 2 + a i 3 2 + ⋯ + a i n 2 ( 公 式 6 ) \gamma=\sqrt{{a_{i1}}^2+{a_{i2}}^2+{a_{i3}}^2+\cdots+{a_{in}}^2} \qquad (公式6) γ=ai12+ai22+ai32+⋯+ain2(公式6)
由公式 5 和公式 6 可以得到如下的恒等关系
1 = ∑ j = 1 n a 0 j 2 λ 2 ( 公 式 7 ) 1=\sum_{j=1}^{n}\frac{{a_{0j}}^2}{\lambda^2} \qquad (公式7) 1=j=1∑nλ2a0j2(公式7)
1 = ∑ j = 1 n a i j 2 γ 2 ( 公 式 8 ) 1=\sum_{j=1}^{n}\frac{{a_{ij}}^2}{\gamma^2} \qquad (公式8) 1=j=1∑nγ2aij2(公式8)
由公式 2 结合公式 3、公式 4 可以得到如下的表达形式
f ( i ) = 1 λ × 1 γ × ∑ j = 1 n a 0 j × a i j ( 公 式 9 ) f(i)= \frac{1}{\lambda} \times \frac{1}{\gamma} \times \sum_{j=1}^{n}a_{0j} \times a_{ij} \qquad (公式9) f(i)=λ1×γ1×j=1∑na0j×aij(公式9)
令 : 向量 B 0 B_0 B0 与 B i B_i Bi 之间的欧氏距离 r i r_i ri 的平方的函数 g ( i ) g(i) g(i) 如下:
g ( i ) = r i 2 = ∑ j = 1 n ( 1 λ a 0 j − 1 γ a i j ) 2 ⋯ r i ∈ [ 0 2 ] ( 公 式 10 ) g(i)={r_i}^2=\sum_{j=1}^{n} ( \frac{1}{\lambda}a_{0j}-\frac{1}{\gamma}a_{ij} )^2 \qquad\cdots\qquad r_i\in[0\quad 2] \qquad (公式10) g(i)=ri2=j=1∑n(λ1a0j−γ1aij)2⋯ri∈[02](公式10)
由公式 10 可以得到如下的展开表达式:
g ( i ) = ∑ j = 1 n a 0 j 2 λ 2 + ∑ j = 1 n a i j 2 γ 2 + 2 × 1 λ × 1 γ × ∑ j = 1 n a 0 j × a i j ( 公 式 11 ) g(i)= \sum_{j=1}^{n}\frac{{a_{0j}}^2}{\lambda^2} + \sum_{j=1}^{n}\frac{{a_{ij}}^2}{\gamma^2} + 2\times \frac{1}{\lambda} \times \frac{1}{\gamma} \times \sum_{j=1}^{n}a_{0j} \times a_{ij} \qquad (公式11) g(i)=j=1∑nλ2a0j2+j=1∑nγ2aij2+2×λ1×γ1×j=1∑na0j×aij(公式11)
由公式 11 结合公式 7、公式 8和公式 9可以得到如下表达式:
g ( i ) = 2 − 2 × f ( i ) ( 公 式 12 ) g(i)=2-2\times f(i) \qquad (公式12) g(i)=2−2×f(i)(公式12)
由公式10和公式12可以分别得到 f ( i ) f(i) f(i) 关于 r i r_i ri 和 g ( i ) g(i) g(i) 的函数表达式如下:
f ( i ) = 2 − r i 2 2 ⋯ r i ∈ [ 0 2 ] ( 公 式 13 ) f(i)= \frac{2-{r_i}^2}{2} \qquad\cdots\qquad r_i\in[0\quad 2] \qquad (公式13) f(i)=22−ri2⋯ri∈[02](公式13)
f ( i ) = 2 − g ( i ) 2 ⋯ g ( i ) ∈ [ 0 4 ] ( 公 式 14 ) f(i)= \frac{2-g(i)}{2} \qquad\cdots\qquad g(i)\in[0\quad 4] \qquad (公式14) f(i)=22−g(i)⋯g(i)∈[04](公式14)
由公式 14 可得到如下结论:
任意两个向量的余弦值 f f f 跟这两个向量的单位向量的欧氏距离的平方 g g g 成线性负相关关系。 若这两个向量的余弦相似度越高,那么它们的余弦值 f f f 会趋近于 1,欧氏距离的平方 g g g 会趋近于 0。所以可以通过寻找最小的 g g g 值间接找到最大的余弦值 f f f。
由公式 10 可推理出如下计算到第 j 项的递归表达式:
h ( i , j ) = h ( i , j − 1 ) + ( 1 λ a 0 j − 1 γ a i j ) 2 ⋯ j = 1 , 2 , 3 , ⋯ , n ( 公 式 15 ) h(i,j)=h(i,j-1) + ( \frac{1}{\lambda}a_{0j}-\frac{1}{\gamma}a_{ij} )^2 \qquad\cdots\qquad j=1,2,3,\cdots,n \qquad (公式15) h(i,j)=h(i,j−1)+(λ1a0j−γ1aij)2⋯j=1,2,3,⋯,n(公式15)
其中
g ( i ) = h ( i , n ) ( 公 式 16 ) g(i)=h(i,n) \qquad (公式16) g(i)=h(i,n)(公式16)
综上所述,设计寻找向量 A 0 A_0 A0 的余弦相似度最相似向量 A k A_k Ak 的算法如下:
import java.util.Arrays;
public class Test {
/**
* 搜索算法
*/
public static int search(double[] a_0, double[][] a_s) {
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//数据预处理(处理成单位向量)
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
double[] b_0 = unit(a_0);
double[][] b_s = unit(a_s);
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//开始寻找
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//最为相似向量的位置
Integer k = null;
//最小的g值(初始值为4)
double min_g = 4;
for (int i = 0; i < b_s.length; i++) {
//待比较的单位向量
double[] b_i = b_s[i];
//待比较的单位向量的长度
int n = b_i.length;
//h(i,j)的自变量j
int j = 0;
//h(i,j)的函数值
double h_i_j = 0;
//随着j的自增求h(i,j)的函数值
while (j < n) {
h_i_j += (b_0[j] - b_i[j]) * (b_0[j] - b_i[j]);
//若h(i,j)比最小值g大,那么当前向量不是余弦相似度最相似的向量
if (h_i_j > min_g) {
break;
}
j++;
}
//若j等于n,那么当前向量是前i个向量中的余弦相似度最相似的向量
if (j == n) {
min_g = h_i_j;
k = i;
}
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//结束寻找
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
return k;
}
/**
* 转化为单位向量
*/
public static double[] unit(double[] v) {
double[] res = new double[v.length];
double sum = 0.0;
for (int i = 0; i < v.length; i++) {
sum += v[i] * v[i];
}
double sqrt = Math.sqrt(sum);
for (int i = 0; i < v.length; i++) {
res[i] = v[i] / sqrt;
}
return res;
}
/**
* 转化为单位向量
*/
public static double[][] unit(double[][] v) {
double[][] res = new double[v.length][];
for (int i = 0; i < v.length; i++) {
res[i] = unit(v[i]);
}
return res;
}
public static void main(String[] args) {
double[] a_0 = {1, 2, 3};
double[][] a_s = {{0, 1, 2}, {2, 3, 4}, {4, 5, 6}, {1.1, 2, 3}};
int k = search(a_0, a_s);
System.out.println("与向量" + Arrays.toString(a_0) + "余弦相似度最为相似的向量是" + Arrays.toString(a_s[k]));
}