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
感谢,知识分享推动世界进步!