词句相似度计算——余弦相似度

余弦相似度,是一种通过判断两个向量的夹角来判断其相似性的数学方法。

举个栗子:

A:中国工商银行北京分部北京支行

B:中国招商银行广西分部桂林支行

我们用“余弦相似度”的办法来判断这两个句子的相似性

1、分词!

第一步要做的肯定是分词,把一个句子分成一组一组的散词,分词一般我们会用现成的语料库,比如结巴分词是吧,传说中的最好用的中文分词模块包。如果不是专业性特别强的方向,足矣,如果是专项的,比如医学、金融学、药学等专业相关,那肯定得自己构建一套自己的分词系统,不过这些网上也是一堆一堆的。怎么分词,待会咱们代码见!

2、词转向量

假设我们已经分好了词:

A:中国、工商银行、北京、分部、北京、支行

B:中国、招商银行、广西、分部、桂林、支行

这样我们就有了一个局部语料库,bags=['中国', '工商银行', '招商银行', '北京', '广西','支行','桂林', '分部'],这是个有序集合(最好是有序)

                     ['中国', '工商银行', '招商银行', '北京', '广西','支行','桂林', '分部']

对于A而言,                     

向量为:      [1               1                  0                2           0       1         0           1]

对于B而言,

向量为:      [1               0                  1                0           1        1         1           1]

其中,每一个数字表示:局部语料库中的词在A(或B)中出现的次数 

于是,通过这种方法,词就转成了向量

 

3、计算余弦相似度

首相,余弦相似度是这么定义的:

(引自:https://www.cnblogs.com/dsgcBlogs/p/8619566.html)

词句相似度计算——余弦相似度_第1张图片

举个栗子,a = [1,2,3], b=[5,6,7]

a和b的余弦相似度就是

                                          (1*5 + 2 * 6 + 3 * 7)/(\sqrt{1^2 + 2^2 + 3^2} * \sqrt{5^2 + 6^2 + 7^2}) 

于是我们得出“中国工商银行北京分部北京支行”和“中国招商银行广西分部桂林支行”的相似度为

                 \frac{1*1+1*0+0*1+2*0+0*2+0*1+1*1+0*1+1*1}{\sqrt{1^2+1^2+2^2+1^2+1^2}*\sqrt{1^2+1^2+1^2+1^2+1^2+1^2}}=0.433

这个值越接近1,相似度越高

 

那么!这有啥问题!

假设,我们把“桂林”,改成“南宁”,得出来的余弦相似度是一样的!!!

但是,实际上,桂林支行和南宁支行是不一样的,我们用不一样的词得出了一样的结果!!!

那怎么办?

于是乎,人们想出了一个算法,叫TF-IDF

之前我们所有的计算,全部基于当前的“局部语料库”,TF-IDF算法基于更大语料库,暂且命名为“整体语料库”吧。那既然不同的词有不同的结果,我们就给不同的词加一个系数吧,这个系数怎么来?

(引自:https://www.cnblogs.com/pinard/p/6693230.html)

这个系数等于

                                                                                       IDF=log\frac{N}{n}

其中N表示总词数,n表示包含某个词(比如桂林、南宁、苹果等)的总词数,举个栗子:

咱们整体语料库一共有1024个词,而包含桂林两个字的有256个词,而包含南宁两个字的只有16个词,

IDF_{a=guilin}=log\frac{1024}{256}=2,                   IDF_{a=nanning}=log\frac{1024}{16}=6

在假设除开桂林和南宁的系数都是1的前提下:

A         =  [1               1                  0                2           0       1         0           1]

B桂林 =  [1               0                  1                0           1        1         1*2        1]

B南宁 =  [1               0                  1                0           1        1         1*6        1]

注意:只是假设其他的词的系数为1,但是实际上不可能,这里只是为了方便说明

IDF系数越大,说明n越小,n越小说明使用的次数使用的地方越少;

同时,因为IDF系数越大,跟A的余弦值越小(夹角越大),那么相似度就越小

也就是说:用得越频繁的词,可能跟某个句子的相似度就越大;用得越不频繁的词,相似度就可能越小。

这样,就把一个词的使用频率也加进来了。结巴分词,就做了这一点。在结巴分词里,桂林的IDF要小于南宁的IDF,也就是说:在某一程度上,桂林和北京的关联性要比南宁跟北京的关联性要强一些

 

于是乎,余弦相似度进一步升级为加入了相关系数的乘法计算。以下附python代码

import numpy as np
import jieba
import jieba.analyse
from collections import Counter

def word_2_vec(words1, words2):
    # 词向量
    words1_info = jieba.analyse.extract_tags(words1, withWeight=True)
    words2_info = jieba.analyse.extract_tags(words2, withWeight=True)
    # 转成counter不需要考虑0的情况
    words1_dict = Counter({i[0]: i[1] for i in words1_info})
    words2_dict = Counter({i[0]: i[1] for i in words2_info})
    bags = set(words1_dict.keys()).union(set(words2_dict.keys()))
    # 转成list对debug比较方便吗,防止循环集合每次结果不一致
    bags = sorted(list(bags))
    vec_words1 = [words1_dict[i] for i in bags]
    vec_words2 = [words2_dict[i] for i in bags]
    # 转numpy
    vec_words1 = np.asarray(vec_words1, dtype=np.float)
    vec_words2 = np.asarray(vec_words2, dtype=np.float)
    return vec_words1, vec_words2


def cosine_similarity(v1, v2):
    # 余弦相似度
    v1, v2 = np.asarray(v1, dtype=np.float), np.asarray(v2, dtype=np.float)
    up = np.dot(v1, v2)
    down = np.linalg.norm(v1) * np.linalg.norm(v2)
    return round(up / down, 3)

cosine_similarity(*word_2_vec('中国工商银行北京分部北京支行', '中国招商银行广西分部南宁支行'
))

 

你可能感兴趣的:(python,算法)