向量余弦相似度
cosine_similarity
(
x
,
y
)
=
cos
⟨
x
,
y
⟩
=
x
T
y
∥
x
∥
∥
y
∥
\text{ cosine\_similarity}\left( \mathbf{x},\mathbf{y}\right)=\cos \left\langle \mathbf{x},\mathbf{y} \right\rangle=\frac{\mathbf{x}^T\mathbf{y}}{\|\mathbf{x}\|\|\mathbf{y}\|}
cosine_similarity(x,y)=cos⟨x,y⟩=∥x∥∥y∥xTy
其中
⟨
x
,
y
⟩
\left\langle \mathbf{x},\mathbf{y} \right\rangle
⟨x,y⟩表示
x
\mathbf{x}
x和
y
\mathbf{y}
y的角度
余弦相似度矩阵
接下来的问题是考虑矩阵
A
=
(
a
1
T
a
2
T
⋮
a
m
T
)
,
a
i
∈
R
k
,
B
=
(
b
1
T
b
2
T
⋮
b
n
T
)
,
b
i
∈
R
k
\mathbf{A}=\begin{pmatrix} \mathbf{a}_1^T\\ \mathbf{a}_2^T\\ \vdots\\ \mathbf{a}_m^T \end{pmatrix},\mathbf{a}_i\in\mathbb{R}^k,\mathbf{B}=\begin{pmatrix} \mathbf{b}_1^T\\ \mathbf{b}_2^T\\ \vdots\\ \mathbf{b}_n^T \end{pmatrix},\mathbf{b}_i\in\mathbb{R}^k
A=⎝⎜⎜⎜⎛a1Ta2T⋮amT⎠⎟⎟⎟⎞,ai∈Rk,B=⎝⎜⎜⎜⎛b1Tb2T⋮bnT⎠⎟⎟⎟⎞,bi∈Rk
计算余弦相似度矩阵
C
∈
R
m
×
n
\mathbf{C}\in\mathbb{R}^{m\times n}
C∈Rm×n,
其中
c
i
j
=
cosine_similarity
(
a
i
,
b
j
)
c_{ij}=\text{ cosine\_similarity}\left( \mathbf{a}_i,\mathbf{b}_j\right)
cij= cosine_similarity(ai,bj)
第一种
先单位化,这样可以不用单位化
这里要注意防止分母为0,所以有一个eps
torch.einsum就是一种矩阵乘法
def cosine_similarity(a, b, eps=1e-7):
"""
:param a: m*k
:param b: n*k
:return: m*n cosine similarity
"""
a = a / torch.max(torch.norm(a, p=2, dim=-1, keepdim=True), torch.tensor(eps))
b = b / torch.max(torch.norm(b, p=2, dim=-1, keepdim=True), torch.tensor(eps))
return torch.einsum('ni,mi->nm', a, b)
第二种
其实就是最后一步直接矩阵乘法
def cosine_similarity(a, b, eps=1e-7):
"""
:param a: m*k
:param b: n*k
:return: m*n cosine similarity
"""
a = a / torch.max(torch.norm(a, p=2, dim=-1, keepdim=True), torch.tensor(eps))
b = b / torch.max(torch.norm(b, p=2, dim=-1, keepdim=True), torch.tensor(eps))
return torch.mm(a, b.T)
第三种(推荐)
from torch.nn.functional import cosine_similarity
cosine_similarity(a.unsqueeze(1), b.unsqueeze(0), dim=-1)
可以查看这里https://pytorch.org/docs/stable/generated/torch.nn.CosineSimilarity.html?highlight=cosinesimilarity#torch.nn.CosineSimilarity
A
∈
R
m
×
k
\mathbf{A}\in\mathbb{R}^{m\times k}
A∈Rm×k,经过.unsqueeze(1)会变成
m
×
1
×
k
m\times 1\times k
m×1×k的矩阵
B
∈
R
n
×
k
\mathbf{B}\in\mathbb{R}^{n\times k}
B∈Rn×k,经过.unsqueeze(0)会变成
1
×
n
×
k
1\times n\times k
1×n×k的矩阵
dim=-1,这意味着最后会变成一个
m
×
n
m\times n
m×n的矩阵
因为形状不一样,所以会广播成
m
×
n
×
k
m\times n\times k
m×n×k的矩阵
举个例子
将
3
×
2
3\times 2
3×2的矩阵广播成
3
×
4
×
2
3\times 4\times 2
3×4×2
a经过unsqueeze(1)会变成
3
×
1
×
2
3\times 1\times 2
3×1×2的矩阵
最后的+zeros是为了触发广播
最后计算余弦相似度的时候
a[0,0]得到的是一个2个元素的向量
就是
a[0,0]和b[0,0]计算余弦相似度
a[0,0]和b[0,1]计算余弦相似度
…
a[0,1]和b[0,0]计算余弦相似度
a[0,1]和b[0,1]计算余弦相似度
…
a[m,n]和b[m,n]计算余弦相似度
角度矩阵
得到余弦相似度矩阵后计算
arccos
\arccos
arccos,对应pytorch的torch.acos
需要注意的是,pytorch的acos有点问题https://github.com/pytorch/pytorch/issues/8069
如果数值接近-1或1,则会返回nan
解决方法是
±
e
p
s
\pm eps
±eps
eps=1e-7
matrix_cos=matrix_cos.clamp(min=-1+eps,max=1+eps)
matrix_angle=torch.acos(matrix_cos)