余弦相似度计算

先来一段废话

为了计算文本的相似度,我们需要一些算法,比如这个余弦相似度算法。

具体怎么算呢?

简单一点就是如下:

  1. 分词
  2. 将所有的词放在一个集合中
  3. 根据集合位置给词编码
  4. 将两个语句化为向量,维度为所有词的数量,每个维度的数值为这个词在此句子中出现的次数
  5. 计算两个向量的余弦值,越大代表越接近

具体操作

分词

我们可以用jieba分词,因为它分得一定比我好

把所有的词放在集合中

遍历一下即可

根据集合位置给词编码

变为一个dict并且对应的位置置为数量

转向量

先将分词结果替换为数字数组,数字为dict中的值。
统计不同位置出现的次数,存在向量中。

计算余弦值

使用如下式子计算:

$$
\cos(\theta)=\frac{\sum\limits_{i=1}^n(x_i\times{y_i})}{\sqrt{\sum\limits_{i=1}^n(x_i)^2}\times\sqrt{\sum\limits_{i=1}^n(y_i)^2}}
$$

其实就是点积和长度的乘积的比值。

具体影响:

如果两个句子相同的词越多,点积就会越大,相似度也会越高。
如果两个句子在相同的词不变的情况下,句子长度越长,相似度则会越低。

如果动脑子想一想,确实还是有道理的,但是似乎有点太简单了一些。

举个栗子

首先我们拿到两句话:

这里有一根比较长的棍子
这里有一根比较短的棍子

使用jiaba分词的结果:

1
2
['这里', '有', '一根', '比较', '长', '的', '棍子', '。']
['这里', '有', '一根', '比较', '短', '的', '棍子', '。']

(所以标点符号也算进去了)

然后我们变成这样的集合:

1
['这里', '有', '一根', '比较', '长', '的', '棍子', '。', '短']

然后标个号:

1
2
3
4
5
6
7
8
9
10
11
{
'这里': 0,
'有': 1,
'一根': 2,
'比较': 3,
'长': 4,
'的': 5,
'棍子': 6,
'。': 7,
'短' 8
}

然后我们变成两个向量:

[1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 0, 1, 1, 1, 1],

最后求得余弦值为$0.875$

此时我们再掏出一个句子:

这里有一根棍子,它有点长

分个词:

1
['这里', '有', '一根', '棍子', ',', '它', '有点', '长', '。']

我们用第一个句子和第二个句子来计算相似度,那么我们得到的集合是:

1
['这里', '有', '一根', '比较', '长', '的', '棍子', '。', ',', '它', '有点']

向量:

1
2
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0]
[1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1]

余弦值约等于$0.707$

结果也就是说,比较和第二,第三句的相似度,第一句和第二句的相似度是比较高的。

虽然它们结构上确实是……比较接近的

但是意思显然…不太对

一个可能有效的改进

假设我们有一个反义词集,里面有记录 长/短 是一对反义词,那么我们似乎可以记录一个负值:

比如在计算前两句的相似度时,我们认为长和短在同一个维度,但方向相反,我们如下的位置集合:

1
2
3
4
5
6
7
8
9
10
11
{
'这里': [0, 1],
'有': [1, 1],
'一根': [2, 1],
'比较': [3, 1],
'长': [4, 1],
'的': [5, 1],
'棍子': [6, 1],
'。': [7, 1],
'短' [4, -1]
}

那么我们在这个情况下,计算的两个向量为:

[1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, -1, 1, 1, 1, 1]

余弦值只有$0.75$了…感觉总归是有一点效果的,对于当前这种,一个位置意思相反的情况。

要是冒出一个双重否定怎么办呢…也许我们需要加入一些结构分析

感觉会有人用了一些考虑了不同的词的相关性的算法…而不是这样简单粗暴地分成多个维度 或者只是加一个反向

不过暂时应该是够用了

代码实现

没有实现