NLP自然语言 之 余弦相似度 (一 Python代码实现)

前言

如何计算两个句子的相似度度?一般来说,我们可以根据两个句子的用词越相似,它们的内容所表达的意思就应该越相似。即可以使用余弦相似度的方式来计算。

余弦相似度公式

余弦相似度,又称为余弦相似性,是通过计算两个向量的夹角余弦值来评估他们的相似度。余弦相似度将向量根据坐标值,绘制到向量空间中,如最常见的二维空间

  • 余弦相似度用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小。余弦值越接近1,就表明夹角越接近0度,也就是两个向量越相似,这就叫”余弦相似性”

  • 两个向量a,b的夹角很小可以说a向量和b向量有很高的的相似性,极端情况下,a和b向量完全重合。可以认为a和b向量是相等的,也即a,b向量代表的文本是完全相似的,或者说是相等的如下图

  • 如果a和b向量夹角较大,或者反方向。两个向量a,b的夹角很大可以说a向量和b向量有很底的的相似性,或者说a和b向量代表的文本基本不相似如下图

  • 的余弦定值公式为:

  • 向量表示的三角形中,假设a向量是(x1, y1),b向量是(x2, y2),那么可以将余弦定理改写成下面的形式

  • 扩展,如果向量a和b不是二维而是n维,上述余弦的计算法仍然正确。假定a和b是两个n维向量,a是 ,b是 ,则a与b的夹角 的余弦等于

余弦相似度代码实现

1
2
句子1:小孩很爱妈妈,妈妈也喜爱小孩
句子2:小孩不爱妈妈,但是妈妈还是喜爱小孩

基本思路:

  • 分词

    使用jieba为两个语句分词

    jieba(Python https://github.com/fxsjy/jieba )

    jieba(Java https://github.com/huaban/jieba-analysis)

    1
    2
    句子1:小孩 很 爱 妈妈 妈妈 也 喜爱 爱小 小孩
    鱼子2:小孩 不 爱 妈妈 但是 妈妈 还是 喜爱 爱小 小孩
  • 列出所有词

    通过计算合并句子1与句子2所有的词(并集)

    1
    爱 妈妈 也 小孩 喜爱 很 不 还是 爱小 但是
  • 计算词频

    计算句子1与句子2并集词的每个词出现在对应句子当中出现的次数

    1
    2
    句子1:爱/1 妈妈/2 也/1 小孩/2 喜爱/1 很/1 不/0 还是/0 爱小/1 但是/0
    鱼子2:爱/1 妈妈/2 也/0 小孩/2 喜爱/1 很/0 不/1 还是/1 爱小/1 但是/1
  • 每个句子的向量值

    最终形成每个句子的向量空间集

    1
    2
    [1, 2, 1, 2, 1, 1, 0, 0, 1, 0]
    [1, 2, 0, 2, 1, 0, 1, 1, 1, 1]
  • 根据公式计算相似度

    最终结果x等于0.815374248327

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    # coding=utf-8
    # 计算语句向量
    import jieba
    import math

    # 计算一个向量值的平方和然后开更
    def vector_to_pow_with_sqrt(vector):
    vectorPow = 0
    for x in vector:
    vectorPow += math.pow(x, 2)
    return math.sqrt(vectorPow)

    # 转换为向量值
    def word_to_vector(union_set, seg_list):
    data_vector = [0] * len(union_set)
    index=0
    for word in union_set:
    data_vector[index] = seg_list.count(word)
    index += 1
    return data_vector

    # 加载stop word。可以直接通过百度搜索(停用词)
    stopWords = set()
    lines = open("stop-word", 'r')
    for line in lines:
    if len(line.strip()) == 0:
    continue
    stopWords.add(line.strip())

    s1 = "小孩很爱妈妈,妈妈也喜爱小孩"
    s2 = "小孩不爱妈妈,但是妈妈还是喜爱小孩"

    s1_seg_list = [x for x in jieba.cut(s1, True) if x != ""]
    s2_seg_list = [x for x in jieba.cut(s2, True) if x != ""]

    s1_seg_set = set(s1_seg_list)
    s2_seg_set = set(s2_seg_list)
    s1_with_s2_seg_set = s1_seg_set.union(s2_seg_list)

    # 打印语句分词结果
    for x in s1_seg_list:
    print x,
    print
    for x in s2_seg_list:
    print x,
    print

    # 打印句子1与句子2分并集结果
    for x in s1_with_s2_seg_set:
    print x,
    print

    # 分词句子转换成向量值
    s1_vector = word_to_vector(s1_with_s2_seg_set, s1_seg_list)
    s2_vector = word_to_vector(s1_with_s2_seg_set, s2_seg_list)
    print s1_vector
    print s2_vector

    # 根据余弦相似度的计算公式 计算向量值的平方更结果
    s1_vector_sqrt = vector_to_pow_with_sqrt(s1_vector)
    s2_vector_sqrt = vector_to_pow_with_sqrt(s2_vector)

    # 根据余弦相似度的计算公式 计算分母
    consine_top = 0
    for index in range(len(s1_vector)):
    consine_top += s1_vector[index] * s2_vector[index]

    # 计算余弦值
    consine = consine_top/(s1_vector_sqrt * s2_vector_sqrt)

    print consine

总结

余弦的相识度是一种非常有用的算法,只要是计算两个向量的相似度都可以使用此方法。计算流程就是:

  • 找出两篇文章或者两个句子的关键词
  • 每篇文章或句子个取出若干个关键词然后合并成一个集合,然后计算每篇文章或句子的关键词在这个集合中出现的次数
  • 生成两篇文章或句子的词频向量
  • 计算两个向量的余弦相似度,值越大就表示越相似

代码:https://github.com/lishijia/py-data-demo/tree/master/nlp

参考链接

分享到 评论