热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python余弦相似度文本分类_【基础算法】文本相似度计算

在自然语言处理中,文本相似度是一种老生常谈而又应用广泛的基础算法模块,可用于地址标准化中计算与标准地址库中最相似的地址,也可用于问答系统中

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

基于此,现将几种常见的文本相似度计算方法做一个简单总结,以便后续查阅,本文所有源码均已上传到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值很明显,即使变化很小一部分,也会相差很大。

基本流程

分词,把需要判断文本分词形成这个文章的特征单词。最后形成去掉噪音词的单词序列并为每个词加上权重,我们假设权重分为5个级别(1~5)。比如:“ 美国“51区”雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”,括号里是代表单词在整个句子里重要程度,数字越大越重要。

hash,通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样我们的字符串就变成了一串串数字,还记得文章开头说过的吗,要把文章变为数字计算才能提高相似度计算性能,现在是降维过程进行时。

加权,通过 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”,“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”。这里作为示例只算了两个单词的,真实计算需要把所有单词的序列串累加。

降维,把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 pseg

def 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 0

x &#61; ord(source[0]) <<7

m &#61; 1000003

mask &#61; 2 ** 128 - 1

for c in source:

x &#61; ((x * m) ^ ord(c)) & mask

x ^&#61; len(source)

if x &#61;&#61; -1:

x &#61; -2

x &#61; bin(x).replace(&#39;0b&#39;, &#39;&#39;).zfill(64)[-64:]

return str(x)

def load_idf(path):

words_idf &#61; dict()

with codecs.open(path, &#39;r&#39;, encoding&#61;&#39;utf-8&#39;) as f:

lines &#61; f.readlines()

for line in lines:

parts &#61; line.strip().split(&#39;\t&#39;)

if len(parts) !&#61; 2:

continue

if parts[0] not in words_idf:

words_idf[parts[0]] &#61; float(parts[1])

return words_idf

words_idf &#61; load_idf(path&#61;r&#39;idf.txt&#39;)

def compute_tfidf(text):

words_freq &#61; dict()

words &#61; pseg.lcut(text)

for w in words:

if w.word in stopwords:

continue

if w.word not in words_freq:

words_freq[w.word] &#61; 1

else:

words_freq[w.word] &#43;&#61; 1

text_total_words &#61; sum(list(words_freq.values()))

words_tfidf &#61; dict()

for word, freq in words_freq.items():

if word not in words_idf:

continue

else:

tfidf &#61; words_idf[word] * (freq / text_total_words)

words_tfidf[word] &#61; tfidf

return words_tfidf

def get_keywords(text, topk):

words_tfidf &#61; compute_tfidf(text)

words_tfidf_sorted &#61; sorted(words_tfidf.items(), key&#61;lambda x: x[1], reverse&#61;True)

return [item[0] for item in words_tfidf_sorted[:topk]]

def hamming_distance(simhash1, simhash2):

ham &#61; [s1 &#61;&#61; s2 for (s1, s2) in zip(simhash1, simhash2)]

return ham.count(False)

def text_simhash(text):

total_sum &#61; np.array([0 for _ in range(64)])

keywords &#61; get_keywords(text, topk&#61;2)

for keyword in keywords:

v &#61; int(words_idf[keyword])

hash_code &#61; string_hash(keyword)

decode_vec &#61; [v if hc &#61;&#61; &#39;1&#39; else -v for hc in hash_code]

total_sum &#43;&#61; np.array(decode_vec)

simhash_code &#61; [1 if t > 0 else 0 for t in total_sum]

return simhash_code

def simhash_similarity(text1, text2):

simhash_code1 &#61; text_simhash(text1)

simhash_code2 &#61; text_simhash(text2)

print(simhash_code1, simhash_code2)

return hamming_distance(simhash_code1, simhash_code2)

if __name__ &#61;&#61; &#39;__main__&#39;:

print(simhash_similarity(&#39;在历史上有著许多数学发现&#39;, &#39;在历史上有著许多科学发现&#39;))

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

pip install simhash

利用simhash包&#xff0c;计算文本相似度示例代码

# -*- coding: utf-8 -*-

# &#64;Time : 2019/6/25 15:58

# &#64;Author : tianyunzqs

# &#64;Description :

from simhash import Simhash

def simhash_similarity(text1, text2):

"""

:param text1: 文本1

:param text2: 文本2

:return: 返回两篇文章的相似度

"""

aa_simhash &#61; Simhash(text1)

bb_simhash &#61; Simhash(text2)

max_hashbit &#61; max(len(bin(aa_simhash.value)), (len(bin(bb_simhash.value))))

# 汉明距离

distince &#61; aa_simhash.distance(bb_simhash)

similar &#61; 1 - distince / max_hashbit

return similar

if __name__ &#61;&#61; &#39;__main__&#39;:

print(simhash_similarity(&#39;在历史上有著许多数学发现&#39;, &#39;在历史上有著许多科学发现&#39;))

3.word2vec相似度

word2vec是对词语进行向量化的一种无监督算法&#xff0c;具体介绍与tensorflow实现可参考&#xff1a;【基础算法】word2vec词向量

word2vec相似度是指利用word2vec算法将文本向量化&#xff0c;进而利用余弦距离计算两个向量的余弦相似度作为两字符串的相似度。

def sentence_similarity_word2vec(self, sentence1, sentence2):

sentence1 &#61; sentence1.strip()

sentence2 &#61; sentence2.strip()

if sentence1 &#61;&#61; sentence2:

return 1.0

vec1 &#61; self.get_sentence_vector(sentence1)

vec2 &#61; self.get_sentence_vector(sentence2)

return self.cosine_similarity(vec1, vec2)

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

def get_sentence_vector(self, sentence):

words &#61; self.text_segment(sentence)

words_vec &#61; [self.get_word_vector(word) for word in words]

return np.mean(words_vec, axis&#61;0)

&#64;staticmethod

def cosine_similarity(vec1, vec2):

tmp1, tmp2 &#61; 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

完整代码&#xff0c;可参考Github

4.神经网络相似度

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

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

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

class AutoEncoder(object):

def __init__(self,

embedding_size,

num_hidden_layer,

hidden_layers):

assert num_hidden_layer &#61;&#61; len(hidden_layers), &#39;num_hidden_layer not match hidden_layers&#39;

self.embedding_size &#61; embedding_size

self.num_hidden_layer &#61; num_hidden_layer

self.hidden_layers &#61; hidden_layers

self.input_x &#61; tf.placeholder(tf.float32, [None, embedding_size])

new_hidden_layers1 &#61; [embedding_size] &#43; hidden_layers &#43; hidden_layers[::-1][1:]

new_hidden_layers2 &#61; hidden_layers &#43; hidden_layers[::-1][1:] &#43; [embedding_size]

encoder_weights, encoder_biases, decoder_weights, decoder_biases &#61; [], [], [], []

for i, (hidden1, hidden2) in enumerate(zip(new_hidden_layers1, new_hidden_layers2)):

if i

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(&#39;output&#39;):

self.encoder_output &#61; self.encoder_or_decode(self.input_x, encoder_weights, encoder_biases)

self.decoder_output &#61; self.encoder_or_decode(self.encoder_output, decoder_weights, decoder_biases)

with tf.name_scope(&#39;loss&#39;):

self.loss &#61; tf.reduce_mean(tf.pow(self.input_x - self.decoder_output, 2))

self.global_step &#61; tf.Variable(0, name&#61;"global_step", trainable&#61;False)

tf.summary.scalar(&#39;loss&#39;, self.loss)

self.merge_summary &#61; tf.summary.merge_all()

self.saver &#61; tf.train.Saver()

&#64;staticmethod

def encoder_or_decode(input_data, encoder_weights, encoder_biases):

layer_output &#61; [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]



推荐阅读
  • 【MicroServices】【Arduino】装修甲醛检测,ArduinoDart甲醛、PM2.5、温湿度、光照传感器等,数据记录于SD卡,Python数据显示,UI5前台,微服务后台……
    这篇文章介绍了一个基于Arduino的装修甲醛检测项目,使用了ArduinoDart甲醛、PM2.5、温湿度、光照传感器等硬件,并将数据记录于SD卡,使用Python进行数据显示,使用UI5进行前台设计,使用微服务进行后台开发。该项目还在不断更新中,有兴趣的可以关注作者的博客和GitHub。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • php支持中文文件名
    2019独角兽企业重金招聘Python工程师标准大家可能遇到过上传中文文件名的文件,或者读取中文目录时不能读取,出现错误的情况这种情况是因为php自动将中文字符转成了utf8 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 2022年的风口:你看不起的行业,真的很挣钱!
    本文介绍了2022年的风口,探讨了一份稳定的副业收入对于普通人增加收入的重要性,以及如何抓住风口来实现赚钱的目标。文章指出,拼命工作并不一定能让人有钱,而是需要顺应时代的方向。 ... [详细]
  • 小程序自动授权和手动接入的方式及操作步骤
    本文介绍了小程序支持的两种接入方式:自动授权和手动接入,并详细说明了它们的操作步骤。同时还介绍了如何在两种方式之间切换,以及手动接入后如何下载代码包和提交审核。 ... [详细]
author-avatar
文女2010_532
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有