从0到1,了解NLP中的文本相似度 (3)

结合我们上文的几种距离,其中欧几里德距离、曼哈顿距离和余弦距离等适合应用于词向量,汉明距离应属于基于字符的文本相似度的度量方法。本文接下来将重点介绍基于余弦复杂度的文本相似度比较算法,和适用于海量数据的simhash文本相似度算法,并给予一定的工程实现方案。

余弦复杂度

对于多个不同的文本或者短文本对话消息要来计算他们之间的相似度如何,一个好的做法就是将这些文本中词语,映射到向量空间,形成文本中文字和向量数据的映射关系,再通过计算几个或者多个不同的向量的差异的大小,来计算文本的相似度。下面介绍一个详细成熟的向量空间余弦相似度方法计算相似度算法。

原理

枯燥的原理不如示例来的简单明了,我们将以一个简单的示例来介绍余弦复杂度的原理。现在有下面这样的两句话,从我们直觉感官来看,说的是一模一样的内容,那么我们通过计算其余弦距离来看看其相似度究竟为多少。

S1: "为什么我的眼里常含泪水,因为我对这片土地爱得深沉" S2: "我深沉的爱着这片土地,所以我的眼里常含泪水"

第一步,分词:

我们对上述两段话分词分词并得到下面的词向量:

S1: [为什么 我 的 眼里 常含 泪水 因为 我 对 这片 土地 爱得 深沉 ,] S2: [我 深沉 的 爱 着 这片 土地 所以 我 的 眼里 常含 泪水 ,]

第二步,统计所有词组:

将S1和S2中出现的所有不同词组融合起来,并得到一个词向量超集,如下:

[眼里 这片 为什么 我 的 常含 因为 对 所以 爱得 深沉 爱 着 , 泪水 土地]

第三步,获取词频:

对应上述的超级词向量,我们分别就S1的分词和S2的分词计算其出现频次,并记录:

S1: [1 1 1 2 1 1 1 1 0 1 1 0 0 1 1 1] S2: [1 1 0 2 2 1 0 0 1 0 1 1 1 1 1 1]

第四步,复杂度计算:

通过上述的准备工作,现在我们可以想象在空间中存在着两条线段:SA和SB,二者均从原点([0, 0, ...])出发,指向不同的方向,并分别终结于点A [1 1 1 2 1 1 1 1 0 1 1 0 0 1 1 1]和点B[1 1 0 2 2 1 0 0 1 0 1 1 1 1 1 1],其中点A和点B的坐标与我们上述的词频一致。到了这一步,我们可以发现,对于句子S1和S2的相似度问题,已经被我们抽象到如何计算上述两个向量的相似问题了。

通过上文介绍的余弦定理,我们知道当两条线段之间形成一个夹角,如果夹角为0度,意味着方向相同、线段重合,我们就认定这是表示两个向量代表的文本完全相等;如果夹角为90度,意味着形成直角,方向完全不相似。因此,我们可以通过夹角的大小,来判断向量的相似程度。夹角越小,就代表越相似。

那么对于上述给定的两个属性向量A 和B,其余弦相似性θ由点积和向量长度给出,其余弦相似度的计算如下所示:

img

余弦相似度计算公式

实现

下面我们将通过golang来实现一个简单的余弦相似度算法。

func CosineSimilar(srcWords, dstWords []string) float64 { // get all words allWordsMap := make(map[string]int, 0) for _, word := range srcWords { if _, found := allWordsMap[word]; !found { allWordsMap[word] = 1 } else { allWordsMap[word] += 1 } } for _, word := range dstWords { if _, found := allWordsMap[word]; !found { allWordsMap[word] = 1 } else { allWordsMap[word] += 1 } } // stable the sort allWordsSlice := make([]string, 0) for word, _ := range allWordsMap { allWordsSlice = append(allWordsSlice, word) } // assemble vector srcVector := make([]int, len(allWordsSlice)) dstVector := make([]int, len(allWordsSlice)) for _, word := range srcWords { if index := indexOfSclie(allWordsSlice, word); index != -1 { srcVector[index] += 1 } } for _, word := range dstWords { if index := indexOfSclie(allWordsSlice, word); index != -1 { dstVector[index] += 1 } } // calc cos numerator := float64(0) srcSq := 0 dstSq := 0 for i, srcCount := range srcVector { dstCount := dstVector[i] numerator += float64(srcCount * dstCount) srcSq += srcCount * srcCount dstSq += dstCount * dstCount } denominator := math.Sqrt(float64(srcSq * dstSq)) return numerator / denominator } 结果 --- PASS: TestCosineSimilar (0.84s) similarity_test.go:23: CosineSimilar score: 0.7660323462854266

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyxygj.html