【基础算法 】文本相似度计算

article/2025/9/11 8:29:26

在自然语言处理中,文本相似度是一种老生常谈而又应用广泛的基础算法模块,可用于地址标准化中计算与标准地址库中最相似的地址,也可用于问答系统中计算与用户输入问题最相近的问题及其答案,还可用于搜索中计算与输入相近的结果,扩大搜索召回,等等。

基于此,现将几种常见的文本相似度计算方法做一个简单总结,以便后续查阅,本文所有源码均已上传到github。

1.字符串相似度

字符串相似度指的是比较两个文本相同字符个数,从而得出其相似度。
python为我们提供了一个difflib包用于计算两个文本序列的匹配程度,我们可以将其视为两个文本字符串的相似度,其代码实现很简单,如果需要得到两个文本之间相同部分或不同部分,可参考Github:https://github.com/tianyunzqs/pynotes/tree/master/text_diff

import difflib
difflib.SequenceMatcher(None, string1, string2).ratio()

2.simhash相似度

simhash最早是由google在文章《detecting near-duplicates for web crawling》中提出的一种用于网页去重的算法。simhash是一种局部敏感hash,计算速度快,对海量网页文本可实现快速处理。
以下内容主要来源于:https://www.cnblogs.com/xlturing/p/6136690.html,介绍通俗易懂,故摘抄过来。

传统的Hash算法只负责将原始内容尽量均匀随机地映射为一个签名值,原理上仅相当于伪随机数产生算法。传统的hash算法产生的两个签名,如果原始内容在一定概率下是相等的;如果不相等,除了说明原始内容不相等外,不再提供任何信息,因为即使原始内容只相差一个字节,所产生的签名也很可能差别很大。所以传统的Hash是无法在签名的维度上来衡量原内容的相似度,而SimHash本身属于一种局部敏感哈希算法,它产生的hash签名在一定程度上可以表征原内容的相似度。

我们主要解决的是文本相似度计算,要比较的是两个文章是否相似,当然我们降维生成了hash签名也是用于这个目的。看到这里估计大家就明白了,我们使用的simhash就算把文章中的字符串变成 01 串也还是可以用于计算相似度的,而传统的hash却不行。

我们可以来做个测试,两个相差只有一个字符的文本串,
“你妈妈喊你回家吃饭哦,回家罗回家罗”
“你妈妈叫你回家吃饭啦,回家罗回家罗”。
通过simhash计算结果为:
1000010010101101111111100000101011010001001111100001001011001011
1000010010101101011111100000101011010001001111100001101010001011
通过传统hash计算为:
0001000001100110100111011011110
1010010001111111110010110011101

通过上面的例子我们可以很清晰的发现simhash的局部敏感性,相似文本只有部分01变化,而hash值很明显,即使变化很小一部分,也会相差很大。

基本流程

  1. 分词,把需要判断文本分词形成这个文章的特征单词。最后形成去掉噪音词的单词序列并为每个词加上权重,我们假设权重分为5个级别(1~5)。比如:“ 美国“51区”雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”,括号里是代表单词在整个句子里重要程度,数字越大越重要。
  2. hash,通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样我们的字符串就变成了一串串数字,还记得文章开头说过的吗,要把文章变为数字计算才能提高相似度计算性能,现在是降维过程进行时。
  3. 加权,通过 2步骤的hash生成结果,需要按照单词的权重形成加权数字串,比如“美国”的hash值为“100101”,通过加权计算为“4 -4 -4 4 -4 4”;“51区”的hash值为“101011”,通过加权计算为 “ 5 -5 5 -5 5 5”。
  4. 合并,把上面各个单词算出来的序列值累加,变成只有一个序列串。比如 “美国”的 “4 -4 -4 4 -4 4”,“51区”的 “ 5 -5 5 -5 5 5”, 把每一位进行累加, “4+5 -4±5 -4+5 4±5 -4+5 4+5” ==》 “9 -9 1 -1 1 9”。这里作为示例只算了两个单词的,真实计算需要把所有单词的序列串累加。
  5. 降维,把4步算出来的 “9 -9 1 -1 1 9” 变成 0 1 串,形成我们最终的simhash签名。 如果每一位大于0 记为 1,小于0 记为 0。最后算出结果为:“1 0 1 0 1 1”。

整个过程的流程图为:
hashcode生成过程

simhash的主要思想是将高维的特征向量(文本可转换为高维向量表示)映射成低维的特征向量,通过计算两个向量的汉明距离(Hamming Distance)来确定文本的相似度。
其中,汉明距离,表示在两个等长字符串中对应位置不同字符的个数。如,1011100 与 1001000 之间的汉明距离是 2。而字符串的编辑距离则是汉明距离的扩展。

根据以上simhash算法的流程描述,利用tfidf值表示词语的权重,实现simhash计算文本相似度代码如下

# -*- coding: utf-8 -*-
# @Time        : 2019/6/25 15:58
# @Author      : tianyunzqs
# @Description : import codecs
import numpy as np
import jieba.posseg as psegdef load_stopwords(path):return set([line.strip() for line in open(path, "r", encoding="utf-8").readlines() if line.strip()])stopwords = load_stopwords(path='stopwords.txt')def string_hash(source):if not source:return 0x = ord(source[0]) << 7m = 1000003mask = 2 ** 128 - 1for c in source:x = ((x * m) ^ ord(c)) & maskx ^= len(source)if x == -1:x = -2x = bin(x).replace('0b', '').zfill(64)[-64:]return str(x)def load_idf(path):words_idf = dict()with codecs.open(path, 'r', encoding='utf-8') as f:lines = f.readlines()for line in lines:parts = line.strip().split('\t')if len(parts) != 2:continueif parts[0] not in words_idf:words_idf[parts[0]] = float(parts[1])return words_idfwords_idf = load_idf(path=r'idf.txt')def compute_tfidf(text):words_freq = dict()words = pseg.lcut(text)for w in words:if w.word in stopwords:continueif w.word not in words_freq:words_freq[w.word] = 1else:words_freq[w.word] += 1text_total_words = sum(list(words_freq.values()))words_tfidf = dict()for word, freq in words_freq.items():if word not in words_idf:continueelse:tfidf = words_idf[word] * (freq / text_total_words)words_tfidf[word] = tfidfreturn words_tfidfdef get_keywords(text, topk):words_tfidf = compute_tfidf(text)words_tfidf_sorted = sorted(words_tfidf.items(), key=lambda x: x[1], reverse=True)return [item[0] for item in words_tfidf_sorted[:topk]]def hamming_distance(simhash1, simhash2):ham = [s1 == s2 for (s1, s2) in zip(simhash1, simhash2)]return ham.count(False)def text_simhash(text):total_sum = np.array([0 for _ in range(64)])keywords = get_keywords(text, topk=2)for keyword in keywords:v = int(words_idf[keyword])hash_code = string_hash(keyword)decode_vec = [v if hc == '1' else -v for hc in hash_code]total_sum += np.array(decode_vec)simhash_code = [1 if t > 0 else 0 for t in total_sum]return simhash_codedef simhash_similarity(text1, text2):simhash_code1 = text_simhash(text1)simhash_code2 = text_simhash(text2)print(simhash_code1, simhash_code2)return hamming_distance(simhash_code1, simhash_code2)if __name__ == '__main__':print(simhash_similarity('在历史上有著许多数学发现', '在历史上有著许多科学发现'))

而simhash算法已有对应python包——simhash,安装即可实现simhash相似度计算

pip install simhash

利用simhash包,计算文本相似度示例代码

# -*- coding: utf-8 -*-
# @Time        : 2019/6/25 15:58
# @Author      : tianyunzqs
# @Description : from simhash import Simhashdef simhash_similarity(text1, text2):""":param text1: 文本1:param text2: 文本2:return: 返回两篇文章的相似度"""aa_simhash = Simhash(text1)bb_simhash = Simhash(text2)max_hashbit = max(len(bin(aa_simhash.value)), (len(bin(bb_simhash.value))))# 汉明距离distince = aa_simhash.distance(bb_simhash)similar = 1 - distince / max_hashbitreturn similarif __name__ == '__main__':print(simhash_similarity('在历史上有著许多数学发现', '在历史上有著许多科学发现'))

3.word2vec相似度

word2vec是对词语进行向量化的一种无监督算法,具体介绍与tensorflow实现可参考:【基础算法】word2vec词向量
word2vec相似度是指利用word2vec算法将文本向量化,进而利用余弦距离计算两个向量的余弦相似度作为两字符串的相似度。

    def sentence_similarity_word2vec(self, sentence1, sentence2):sentence1 = sentence1.strip()sentence2 = sentence2.strip()if sentence1 == sentence2:return 1.0vec1 = self.get_sentence_vector(sentence1)vec2 = self.get_sentence_vector(sentence2)return self.cosine_similarity(vec1, vec2)

word2vec对文本的向量化是将文本分词后,得到各词语的向量化表示,然后对向量的每个维度进行加权相加,形成文本向量,进而可利用余弦距离计算文本的相似度。

    def get_sentence_vector(self, sentence):words = self.text_segment(sentence)words_vec = [self.get_word_vector(word) for word in words]return np.mean(words_vec, axis=0)@staticmethoddef cosine_similarity(vec1, vec2):tmp1, tmp2 = np.dot(vec1, vec1), np.dot(vec2, vec2)if tmp1 and tmp2:return np.dot(vec1, vec2) / (np.sqrt(tmp1) * np.sqrt(tmp2))return 0.0

完整代码,可参考Github

4.神经网络相似度

利用神经网络进行相似度计算的一种思路是将输入X编码为中间向量V,然后对中间结果进行解码得到输出Y,其中损失函数的计算方式就是尽可能减少XY之间的偏差,理想情况就是中间向量V能完全解码还原为原始输入X。网络训练完成后,我们也就得到了输入句子所表示的语义特征向量。

根据上述思路,我们自然可以联想到最基础的自编码器(AutoEncoder, AE),当然还有其他更复杂的自编码器( 如:栈式自编码器(Stacked Autoencoder, SAE)、变分自编码器(Variational auto-encoder, VAE)等)。

AutoEncoder能很好的编码句子中所包含的语义信息,可以在一定程度上解决字符串相似度计算中所缺乏的语义理解问题和word2vec相似度计算中所缺乏的词序问题。本文基于tensorflow实现了基础版本的自编码器,模型代码如下,完整的项目代码可参考Github

class AutoEncoder(object):def __init__(self,embedding_size,num_hidden_layer,hidden_layers):assert num_hidden_layer == len(hidden_layers), 'num_hidden_layer not match hidden_layers'self.embedding_size = embedding_sizeself.num_hidden_layer = num_hidden_layerself.hidden_layers = hidden_layersself.input_x = tf.placeholder(tf.float32, [None, embedding_size])new_hidden_layers1 = [embedding_size] + hidden_layers + hidden_layers[::-1][1:]new_hidden_layers2 = hidden_layers + hidden_layers[::-1][1:] + [embedding_size]encoder_weights, encoder_biases, decoder_weights, decoder_biases = [], [], [], []for i, (hidden1, hidden2) in enumerate(zip(new_hidden_layers1, new_hidden_layers2)):if i < int(len(new_hidden_layers1) / 2.0):encoder_weights.append(tf.Variable(tf.random_normal([hidden1, hidden2])))encoder_biases.append(tf.Variable(tf.random_normal([hidden2])))else:decoder_weights.append(tf.Variable(tf.random_normal([hidden1, hidden2])))decoder_biases.append(tf.Variable(tf.random_normal([hidden2])))with tf.name_scope('output'):self.encoder_output = self.encoder_or_decode(self.input_x, encoder_weights, encoder_biases)self.decoder_output = self.encoder_or_decode(self.encoder_output, decoder_weights, decoder_biases)with tf.name_scope('loss'):self.loss = tf.reduce_mean(tf.pow(self.input_x - self.decoder_output, 2))self.global_step = tf.Variable(0, name="global_step", trainable=False)tf.summary.scalar('loss', self.loss)self.merge_summary = tf.summary.merge_all()self.saver = tf.train.Saver()@staticmethoddef encoder_or_decode(input_data, encoder_weights, encoder_biases):layer_output = [input_data]for weight, biase in zip(encoder_weights, encoder_biases):layer_output.append(tf.nn.sigmoid(tf.add(tf.matmul(layer_output[-1], weight), biase)))return layer_output[-1]

http://chatgpt.dhexx.cn/article/Cfd5H6om.shtml

相关文章

文本相似度计算——HanLP分词+余弦相似度算法

一、余弦相似度简介 余弦相似度&#xff08;又称为余弦相似性&#xff09;&#xff1a;是通过测量两个向量的夹角的余弦值来度量它们之间的相似性。余弦值接近1&#xff0c;夹角趋于0&#xff0c;表明两个向量越相似&#xff1b;余弦值接近于0&#xff0c;夹角趋于90度&#x…

文本相似度算法Jaccard相似度(杰卡德相似度)java实现

文本相似度算法 杰卡德相似度&#xff0c;指的是文本A与文本B中交集的字数除以并集的字数&#xff0c;公式非常简单&#xff1a; java代码 import java.util.HashSet; import java.util.Scanner; import java.util.Set;public class StrJaccard {public static void main(Str…

python中文相似度_基于TF-IDF、余弦相似度算法实现文本相似度算法的Python应用

基于TF-IDF算法、余弦相似度算法实现相似文本推荐——文本相似度算法,主要应用于文本聚类、相似文本推荐等场景。 设计说明 使用jieba切词,设置自定义字典 使用TF-IDF算法,找出文章的关键词; 每篇文章各取出若干个关键词(比如20个),合并成一个集合,计算每篇文章对于…

NLP文本相似度算法LCS

目录 一、什么是LCS子序列最长公共子序列 二、LCS的应用场景三、LCS的查找方法1. 动态规划法计算LCS的长度和两字符串的相似度2. 回溯算法查找LCS 四、代码实现 一、什么是LCS 子序列 子序列:一个序列S任意删除若干个字符得到的新序列T&#xff0c;则T叫做S的子序列 最长公共…

文本相似度算法对比分析,判断内容相似的算法有

有哪些算法用于比较两个字符串的相似程度 终于知道怎么判断字符串相似度了 一直不理解&#xff0c;为什么要计算两个字符串的相似度呢rfid。什么叫做两个字符串的相似度。经常看别人的博客&#xff0c;碰到比较牛的人&#xff0c;然后就翻了翻&#xff0c;终于找到了比较全面…

文本相似度算法对比分析,短文本相似度主流算法

如何检查多个word文档内容的相似度 工具/材料&#xff1a;电脑、WORD。第一步&#xff0c;打开电脑进入桌面&#xff0c;打开软件进界面。第二步&#xff0c;打开软件进入后&#xff0c;打开相应的文档。第三步&#xff0c;找到上方菜单栏的审阅点击。第四步&#xff0c;点击后…

文本相似度算法总结

文本匹配算法主要用于搜索引擎&#xff0c;问答系统等&#xff0c;是为了找到与目标文本最相关的文本。例如信息检索可以归结成查询项和文档的匹配&#xff0c;问答系统可以归结为问题和候选答案的匹配&#xff0c;对话系统可以归结为对话和回复的匹配。 一、传统模型 基于字面…

计算文本相似度

&#xfeff;&#xfeff; 计算文本相似度 推荐 2 收藏 简单讲解 上一章有提到过[基于关键词的空间向量模型]的算法&#xff0c;将用户的喜好以文档描述并转换成向量模型&#xff0c;对商品也是这么处理&#xff0c;然后再通过计算商品文档和用户偏好文档的余弦相似度。 文本相…

计算文本相似度的常用算法

文章目录 1. 余弦相似度2. TF-IDF模型2.1 词频TF的计算方法2.2 反文档频率IDF的计算方法2.3 TF-IDF的计算方法 3. 基于语义相似度的计算 —— DSSM4. LSI/LSA模型5. LDA模型6. 编辑距离计算7. 杰卡德系数计算8. Word2Vec计算9. BM25 NLP、数据挖掘领域中&#xff0c;文本分析是…

LCS、LIS

LCS是最长公共子序列的表示&#xff1a; 题目链接 代码也十分简单就不敲了。 LIS是最长上升子序列的英文缩写。 (动态规划) O(n2)O(n2) 状态表示&#xff1a;f[i]表示从第一个数字开始算&#xff0c;以w[i]结尾的最大的上升序列。(以w[i]结尾的所有上升序列中属性为最大值的…

LCMV相关的算法

写这个程序的时候&#xff0c;我们遵循如下的设计过程&#xff1a; 第一&#xff1a;设计LCMV&#xff1b; 第二&#xff1a;降秩LCMV&#xff1b; 第三&#xff1a;自适应降秩LCMV&#xff1b; 下面我首先给你捋一下整个算法的大致思路&#xff1a; 输入信号&#xff1a; …

SCC算法求强连通分量简单讲解证明及实现

目录 强连通分量SCC算法简介两个概念dfs结束时间转置图 SCC算法伪代码描述SCC算法正确性证明引理1&#xff1a;引理2&#xff1a;SCC证明不错找不漏找 代码实现 强连通分量 连通分量要求任意两点可达&#xff0c;而强连通分量要求任意两点互相可达&#xff0c;即必须存在a->…

最长公共子串LCS (Longest Common Subsequence) 算法

三个方法都有所借鉴&#xff0c;但代码部分是自己试着写出来的&#xff0c;虽然最后的运行结果都是正确的&#xff0c;但此过程中难免会有考虑不周全的地方&#xff0c;如发现代码某些地方有误&#xff0c;欢迎指正。同时有新的想法&#xff0c;也可以提出! 采用顺序结构存储串…

常考的经典算法--最长公共子序列(LCS)与最长公共子串(DP)

《1》最长公共子序列&#xff08;LCS&#xff09;与最长公共子串(DP) http://blog.csdn.net/u012102306/article/details/53184446 https://segmentfault.com/a/1190000007963594 http://www.cppblog.com/mysileng/archive/2013/05/14/200265.html 1. 问题描述 子串应该比…

LCS算法学习

LCS的定义 最长公共子序列&#xff0c;即Longest Common Subsequence&#xff0c;LCS。一个序列S任意删除若干个字符得到新序列T&#xff0c;则T叫做S的子序列&#xff1b;两个序列X和Y的公共子序列中&#xff0c;长度最长的那个&#xff0c;定义为X和Y的最长公共子序列。 字符…

算法系列之六:最长公共子序列(LCS)问题(连续子序列)的三种解法

最长公共子序列&#xff08;LCS&#xff09;问题有两种方式定义子序列&#xff0c;一种是子序列不要求不连续&#xff0c;一种是子序列必须连续。上一章介绍了用两种算法解决子序列不要求连续的最终公共子序列问题&#xff0c;本章将介绍要求子序列必须是连续的情况下如何用算法…

Hirschberg的LCS算法实现

解决Longest Common Subsequence(LCS)问题最常用的算法是Dyanmic programing&#xff0c;细节可以参考Ch15.4 of Introduction of Algorithm(2ED), MIT press, p 350。这个算法最大的问题是他的空间复杂度是O(m*n)。这样&#xff0c;当两个序列达到上万个节点时&#xff0c;内存…

SLIC算法

基础知识 在介绍SLIC之前&#xff0c;先来介绍以下Lab颜色空间的介绍。 Lab色彩模型是由亮度(L)要素和与有关色彩的a,b要素组成&#xff0c;L的值由0(黑色)到100(白色)&#xff0c;a表示从洋红色至绿色的范围(a为负值表示绿色而正值表示品红)&#xff0c;b表示从黄色至蓝色的…

动态规划之LCS算法

一、前言 LCS是Longest Common Subsequence的缩写&#xff0c;即最长公共子序列。一个序列&#xff0c;如果是两个或多个已知序列的子序列&#xff0c;且是所有子序列中最长的&#xff0c;则为最长公共子序列。 另外还有个分支问题&#xff1a;最长公共子串。子串的字符位置必…

LCS算法的C++实现

这两天忙里偷闲看了July的团队提供的LCS算法视频&#xff0c;真的如视频标题一样&#xff0c;十分钟搞定LCS算法。 感谢July大神&#xff0c;感谢其团队的邹博。 这里附上视频链接&#xff1a;http://www.julyedu.com/video/play?course17 说是十分钟搞定&#xff0c;其实是…