句子相似度计算方法

摘要:
v2=句子_向量(s1),句子_向量,(s2)returnnp.dot(v1,v2)/(norm(v1)*norm(v2))s1=“你在做什么”s2=“你正在做什么”print(vector_stability(s1,add_space(s2)#转换为TF矩阵cv=CountVectorizer(tokenizer=lambdas:

W~J~T~E

一、基本方法

 在做自然语言处理的过程中,我们经常会遇到需要找出相似语句的场景,或者找出句子的近似表达,那么求句子相似度方法有哪些呢?

  • 编辑距离计算
  • 杰卡德系数计算
  • TF 计算
  • TFIDF 计算
  • Word2Vec 计算

 1)Word2Vec:其实就是将每一个词转换为向量的过程

  这里我们可以直接下载训练好的 Word2Vec 模型,模型的链接地址为:news_12g_baidubaike_20g_novel_90g_embedding_64.bin提取码:l9r4

import gensim
import jieba
import numpy as np
from scipy.linalg import norm
 
model_file = './word2vec/news_12g_baidubaike_20g_novel_90g_embedding_64.bin'
model = gensim.models.KeyedVectors.load_word2vec_format(model_file, binary=True)
 
def vector_similarity(s1, s2):
          
    def sentence_vector(s):
        words = jieba.lcut(s)
        v = np.zeros(64)
        for word in words:
            v += model[word]
        v /= len(words)
        return v
    
    v1, v2 = sentence_vector(s1), sentence_vector(s2)
    return np.dot(v1, v2) / (norm(v1) * norm(v2))

s1 = '你在干嘛'
s2 = '你正做什么'
print(vector_similarity(s1, s2))

strings = [
    '你在干什么',
    '你在干啥子',
    '你在做什么',
    '你好啊',
    '我喜欢吃香蕉'
]
 
target = '你在干啥'
for string in strings:
    print(string, vector_similarity(string, target))

  在获取Sentence Vector 的时候,我们首先对句子进行分词,然后对分好的每一个词获取其对应的 Vector,然后将所有 Vector 相加并求平均,这样就可得到 Sentence Vector 了,然后再计算其夹角余弦值即可

  代码示例结果:

0.6701133967824016

你在干什么 0.8785495016487205
你在干啥子 0.9789649689827054
你在做什么 0.8781992402695276
你好啊 0.5174225914249864
我喜欢吃香蕉 0.5829908414506211

  可以看到相近的语句相似度都能到 0.8 以上,而不同的句子相似度都不足 0.6,这个区分度就非常大了,可以说有了 Word2Vec 我们可以结合一些语义信息来进行一些判断,效果明显也好很多

 2)杰卡德系数

  杰卡德系数,英文叫做 Jaccard index,又称为 Jaccard 相似系数,用于比较有限样本集之间的相似性与差异性。Jaccard 系数值越大,样本相似度越高

  实际上它的计算方式非常简单,就是两个样本的交集除以并集得到的数值,当两个样本完全一致时,结果为 1,当两个样本完全不同时,结果为 0

  下面我们来看看代码:

from sklearn.feature_extraction.text import CountVectorizer
import numpy as np
 
def jaccard_similarity(s1, s2):
    def add_space(s):
        return ' '.join(s)
    
    # 将字中间加入空格
    s1, s2 = add_space(s1),  add_space(s2)
    # 转化为TF矩阵
    cv = CountVectorizer(tokenizer=lambda s: s.split())
    corpus = [s1, s2]
    vectors = cv.fit_transform(corpus).toarray()
    print(cv.get_feature_names())
    print(vectors)
    # 求交集
    numerator = np.sum(np.min(vectors, axis=0))
    print(np.min(vectors, axis=0))
    # 求并集
    denominator = np.sum(np.max(vectors, axis=0))
    print(np.max(vectors, axis=0))
    # 计算杰卡德系数
    return 1.0 * numerator / denominator
 
s1 = '你在干嘛呢'
s2 = '你在干什么呢'
print(jaccard_similarity(s1, s2))

  这里值得学习的有 CountVectorizer 的用法,通过它的 fit_transform() 方法我们可以将字符串转化为词频矩阵

  例如这里有两句话“你在干嘛呢”和“你在干什么呢”,首先 CountVectorizer 会计算出不重复的有哪些字,会得到一个字的列表,结果为:(cv.get_feature_names()

['么', '什', '你', '呢', '嘛', '在', '干']

  接下来通过转化之后,vectors 变量就变成了:

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

  交集大小和并集大小,然后作商即可:

0.5714285714285714

  这个数值越大,代表两个字符串越接近,否则反之,因此我们也可以使用这个方法,并通过设置一个相似度阈值来进行筛选

 3)TF 计算

  第三种方案就是直接计算 TF 矩阵中两个向量的相似度了,实际上就是求解两个向量夹角的余弦值:点乘积除以二者的模长,公式如下

cosθ=a·b/|a|*|b|

  方法2中我们已经获得了 TF 矩阵,下面我们只需要求解两个向量夹角的余弦值就好了,代码如下:

  代码如下:

from sklearn.feature_extraction.text import CountVectorizer
import numpy as np
from scipy.linalg import norm
 
def tf_similarity(s1, s2):
          
    def add_space(s):
        return ' '.join(s)
    
    # 将字中间加入空格
    s1, s2 = add_space(s1), add_space(s2)
    # 转化为TF矩阵
    cv = CountVectorizer(tokenizer=lambda s: s.split())
    corpus = [s1, s2]
    vectors = cv.fit_transform(corpus).toarray()
    # 计算TF系数
    return np.dot(vectors[0], vectors[1]) / (norm(vectors[0]) * norm(vectors[1]))
 
s1 = '你在干嘛呢'
s2 = '你在干什么呢'
print(tf_similarity(s1, s2))

  在这里我们使用了 np.dot() 方法获取了向量的点乘积,然后通过 norm() 方法获取了向量的模长,经过计算得到二者的 TF 系数,结果如下:

0.7302967433402214

  

 4)TFIDF 计算

  另外除了计算 TF 系数我们还可以计算 TFIDF 系数,TFIDF 实际上就是在词频 TF 的基础上再加入 IDF 的信息,IDF 称为逆文档频率

  下面我们还是借助于 Sklearn 中的模块 TfidfVectorizer 来实现,代码如下:

from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
from scipy.linalg import norm

def tfidf_similarity(s1, s2):
    def add_space(s):
        return ' '.join(s)
    
    # 将字中间加入空格
    s1, s2 = add_space(s1), add_space(s2)
    # 转化为TF矩阵
    cv = TfidfVectorizer(tokenizer=lambda s: s.split())
    corpus = [s1, s2]
    vectors = cv.fit_transform(corpus).toarray()
    # 计算TF系数
    return np.dot(vectors[0], vectors[1]) / (norm(vectors[0]) * norm(vectors[1]))
 
s1 = '你在干嘛呢'
s2 = '你在干什么呢'
print(tfidf_similarity(s1, s2))

  代码示例结果:

0.5803329846765686

  所以通过 TFIDF 系数我们也可以进行相似度的计算

 5)编辑距离

  编辑距离,英文叫做 Edit Distance,又称 Levenshtein 距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数

  如果它们的距离越大,说明它们越是不同。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符

  例如我们有两个字符串:string 和 setting,如果我们想要把 string 转化为 setting,需要这么两步:

  • 第一步,在 s 和 t 之间加入字符 e
  • 第二步,把 r 替换成 t

  所以它们的编辑距离差就是 2,这就对应着二者要进行转化所要改变(添加、替换、删除)的最小步数。

  那么用 Python 怎样来实现呢,我们可以直接使用 distance 库:

import distance
 
def edit_distance(s1, s2):
    return distance.levenshtein(s1, s2)
 
s1 = 'string'
s2 = 'setting'
print(edit_distance(s1, s2))

  代码示例结果:2

  这样如果我们想要获取相似的文本的话可以直接设定一个编辑距离的阈值来实现,如设置编辑距离为 2,下面是一个样例:

import distance
 
def edit_distance(s1, s2):
    return distance.levenshtein(s1, s2)
 
strings = [
    '你在干什么',
    '你在干啥子',
    '你在做什么',
    '你好啊',
    '我喜欢吃香蕉'
]
 
target = '你在干啥'
results = list(filter(lambda x: edit_distance(x, target) <= 2, strings))
print(results)

  代码示例结果:

['你在干什么', '你在干啥子']

  通过这种方式我们可以大致筛选出类似的句子,但是发现一些句子例如“你在做什么” 就没有被识别出来,但他们的意义确实是相差不大的

  因此,编辑距离并不是一个好的方式,但是简单易用

二、感谢

 本文参考:https://cuiqingcai.com/6101.html

 感谢,知识分享推动世界进步!

免责声明:文章转载自《句子相似度计算方法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇测试百度地图输入GPS经纬度显示位置APIoracle函数,查询,事务下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

流量取证-流量中提取文件

以前整理的一些东西,拿出来做备忘 PCAP 报文就是抓取实际在网络中传输的图片,视频等数据,然后以PCAP 格式存储形成的文件。工作中对离线的数据包进行回溯分析,有时会遇到将 PCAP 中的码流还原成相应的图片、视频、邮件等原有格式的需求。 从流量中取证文件大部分情况下是为了提取流量中的可执行程序。 1、 tcpxtract 安装: apt-get ins...

Lanproxy 遍历目录漏洞 CVE-2021-3019 附批量POC

一、概述 Lanproxy内网穿透工具,支持tcp流量转发,可支持任何tcp上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面等等)今天给大家带来的是 Lanproxy 遍历漏洞 (CVE-2021-3019)通过../绕过读取任意文件。 该漏洞允许读取/../conf/config.properties来获取到内部网连接的账户密码。 二、漏洞...

蓝桥杯 2014本科C++ B组 奇怪的分式 暴力枚举

蓝桥杯 枚举 奇怪的分式 标题:奇怪的分式     上小学的时候,小明经常自己发明新算法。一次,老师出的题目是:     1/4 乘以 8/5     小明居然把分子拼接在一起,分母拼接在一起,答案是:18/45 (参见图1.png)     老师刚想批评他,转念一想,这个答案凑巧也对啊,真是见鬼!     对于分子、分母都是 1~9 中的一位数的情况,还...

nginx发布的nginScript

nginx发布的nginScript 背景 2015年9月,nginx宣布支持类JavaScript语言。这意味着开发者可以更轻松、自由的控制全球最优秀的HTTP及反向代理服务器,并在此之上可以衍生出更多有用、好玩的创意。  Nginx介绍: 1、工作在TCP第七层,可以对HTTP协议的所有内容进行分析和处理。 2、支持lua,perl,JavaSc...

HDU-2647 Reward(链式前向星+拓扑排序)

Reward Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 16602 Accepted Submission(s): 5308 Problem Description Dandelion's unc...

Python subprocess.Popen communicate() 和wait()使用上的区别

之所以会纠结到这个问题上是因为发现在调用Popen的wait方法之后程序一直没有返回。google发现wait是有可能产生死锁的。为了把这个问题彻底弄清楚,搜索一些资料过来看看: 原文链接:http://blog.csdn.net/carolzhang8406/article/details/22286913 看到别人的例子: 今天遇到的一个问题。简单说就...