先来一段废话
为了计算文本的相似度,我们需要一些算法,比如这个余弦相似度算法。
具体怎么算呢?
简单一点就是如下:
- 分词
- 将所有的词放在一个集合中
- 根据集合位置给词编码
- 将两个语句化为向量,维度为所有词的数量,每个维度的数值为这个词在此句子中出现的次数
- 计算两个向量的余弦值,越大代表越接近
具体操作
分词
我们可以用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 | ['这里', '有', '一根', '比较', '长', '的', '棍子', '。'] |
(所以标点符号也算进去了)
然后我们变成这样的集合:
1 | ['这里', '有', '一根', '比较', '长', '的', '棍子', '。', '短'] |
然后标个号:
1 | { |
然后我们变成两个向量:
[1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 0, 1, 1, 1, 1],
最后求得余弦值为$0.875$
此时我们再掏出一个句子:
这里有一根棍子,它有点长
分个词:
1 | ['这里', '有', '一根', '棍子', ',', '它', '有点', '长', '。'] |
我们用第一个句子和第二个句子来计算相似度,那么我们得到的集合是:
1 | ['这里', '有', '一根', '比较', '长', '的', '棍子', '。', ',', '它', '有点'] |
向量:
1 | [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0] |
余弦值约等于$0.707$
结果也就是说,比较和第二,第三句的相似度,第一句和第二句的相似度是比较高的。
虽然它们结构上确实是……比较接近的
但是意思显然…不太对
一个可能有效的改进
假设我们有一个反义词集,里面有记录 长/短 是一对反义词,那么我们似乎可以记录一个负值:
比如在计算前两句的相似度时,我们认为长和短在同一个维度,但方向相反,我们如下的位置集合:
1 | { |
那么我们在这个情况下,计算的两个向量为:
[1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, -1, 1, 1, 1, 1]
余弦值只有$0.75$了…感觉总归是有一点效果的,对于当前这种,一个位置意思相反的情况。
要是冒出一个双重否定怎么办呢…也许我们需要加入一些结构分析
感觉会有人用了一些考虑了不同的词的相关性的算法…而不是这样简单粗暴地分成多个维度 或者只是加一个反向
不过暂时应该是够用了
代码实现
没有实现