如何计算两个文本文档之间的相似度?

我正在寻找一个NLP项目,在任何编程语言(尽管Python将是我的偏好)。

我想取两个文档并确定它们有多相似。

256921 次浏览

常见的方法是将文档转换为TF-IDF向量,然后计算它们之间的余弦相似度。任何关于信息检索(IR)的教科书都涵盖了这一点。参见esp. 信息检索导论 .,它可以在网上免费获得。

Computing Pairwise similarity

TF-IDF(以及类似的文本转换)在Python包Gensimscikit-learn中实现。在后一个包中,计算余弦相似度非常简单

from sklearn.feature_extraction.text import TfidfVectorizer


documents = [open(f).read() for f in text_files]
tfidf = TfidfVectorizer().fit_transform(documents)
# no need to normalize, since Vectorizer will return normalized tf-idf
pairwise_similarity = tfidf * tfidf.T

或者,如果文档是普通字符串,

>>> corpus = ["I'd like an apple",
...           "An apple a day keeps the doctor away",
...           "Never compare an apple to an orange",
...           "I prefer scikit-learn to Orange",
...           "The scikit-learn docs are Orange and Blue"]
>>> vect = TfidfVectorizer(min_df=1, stop_words="english")
>>> tfidf = vect.fit_transform(corpus)
>>> pairwise_similarity = tfidf * tfidf.T

尽管Gensim在这类任务中可能有更多选择。

另见这个问题

[免责声明:我参与了scikit-learn TF-IDF的实现。]

解读结果

.

从上面来看,pairwise_similarity是一个方形的Scipy 稀疏矩阵,其行数和列数等于语料库中文档的数量。

>>> pairwise_similarity
<5x5 sparse matrix of type '<class 'numpy.float64'>'
with 17 stored elements in Compressed Sparse Row format>

你可以通过.toarray().A将稀疏数组转换为NumPy数组:

>>> pairwise_similarity.toarray()
array([[1.        , 0.17668795, 0.27056873, 0.        , 0.        ],
[0.17668795, 1.        , 0.15439436, 0.        , 0.        ],
[0.27056873, 0.15439436, 1.        , 0.19635649, 0.16815247],
[0.        , 0.        , 0.19635649, 1.        , 0.54499756],
[0.        , 0.        , 0.16815247, 0.54499756, 1.        ]])

假设我们想要找到与最终文档最相似的文档,“the scikit-learn docs are Orange and Blue"该文档在corpus中有索引4。你可以通过取该行的argmax值,但首先需要屏蔽1,1表示每个文档与其自身的相似度找到最相似文档的索引。你可以通过np.fill_diagonal()来实现后者,通过np.nanargmax()来实现前者:

>>> import numpy as np
                                                                                                                                                                                                                                  

>>> arr = pairwise_similarity.toarray()
>>> np.fill_diagonal(arr, np.nan)
                                                                                                                                                                                                                 

>>> input_doc = "The scikit-learn docs are Orange and Blue"
>>> input_idx = corpus.index(input_doc)
>>> input_idx
4


>>> result_idx = np.nanargmax(arr[input_idx])
>>> corpus[result_idx]
'I prefer scikit-learn to Orange'

注意:使用稀疏矩阵的目的是为大型语料库节省(大量的空间)&词汇量。你可以这样做,而不是转换为NumPy数组:

>>> n, _ = pairwise_similarity.shape
>>> pairwise_similarity[np.arange(n), np.arange(n)] = -1.0
>>> pairwise_similarity[input_idx].argmax()
3
通常使用两个文档之间的余弦相似度作为文档相似度的度量。在Java中,你可以使用Lucene(如果你的集合相当大)或LingPipe来做到这一点。基本概念是计算每个文档中的项,并计算项向量的点积。这些库确实提供了对这种通用方法的一些改进,例如使用逆文档频率和计算tf-idf向量。如果您想要做一些复杂的事情,LingPipe还提供了计算文档之间LSA相似度的方法,这比余弦相似度的结果更好。 对于Python,可以使用NLTK.

这里有一个小应用程序让你开始…

import difflib as dl


a = file('file').read()
b = file('file1').read()


sim = dl.get_close_matches


s = 0
wa = a.split()
wb = b.split()


for i in wa:
if sim(i, wb):
s += 1


n = float(s) / float(len(wa))
print '%d%% similarity' % int(n * 100)

你可能想试试这个在线服务,为余弦文档相似度http://www.scurtu.it/documentSimilarity.html

import urllib,urllib2
import json
API_URL="http://www.scurtu.it/apis/documentSimilarity"
inputDict={}
inputDict['doc1']='Document with some text'
inputDict['doc2']='Other document with some text'
params = urllib.urlencode(inputDict)
f = urllib2.urlopen(API_URL, params)
response= f.read()
responseObject=json.loads(response)
print responseObject

与@larsman相同,但有一些预处理

import nltk, string
from sklearn.feature_extraction.text import TfidfVectorizer


nltk.download('punkt') # if necessary...




stemmer = nltk.stem.porter.PorterStemmer()
remove_punctuation_map = dict((ord(char), None) for char in string.punctuation)


def stem_tokens(tokens):
return [stemmer.stem(item) for item in tokens]


'''remove punctuation, lowercase, stem'''
def normalize(text):
return stem_tokens(nltk.word_tokenize(text.lower().translate(remove_punctuation_map)))


vectorizer = TfidfVectorizer(tokenizer=normalize, stop_words='english')


def cosine_sim(text1, text2):
tfidf = vectorizer.fit_transform([text1, text2])
return ((tfidf * tfidf.T).A)[0,1]




print cosine_sim('a little bird', 'a little bird')
print cosine_sim('a little bird', 'a little bird chirps')
print cosine_sim('a little bird', 'a big dog barks')

这是一个老问题,但我发现这可以很容易地用宽大的来完成。读取文档后,可以使用简单的api similarity来查找文档向量之间的余弦相似度。

首先安装包并下载模型:

pip install spacy
python -m spacy download en_core_web_sm

然后用like so:

import spacy
nlp = spacy.load('en_core_web_sm')
doc1 = nlp(u'Hello hi there!')
doc2 = nlp(u'Hello hi there!')
doc3 = nlp(u'Hey whatsup?')


print (doc1.similarity(doc2)) # 0.999999954642
print (doc2.similarity(doc3)) # 0.699032527716
print (doc1.similarity(doc3)) # 0.699032527716

如果你对测量两段文本的语义相似性更感兴趣,我建议你看看这个gitlab项目。你可以把它作为服务器运行,也有一个预先构建的模型,你可以很容易地使用它来测量两段文本的相似性;尽管它主要用于测量两个句子的相似度,但你仍然可以在你的情况下使用它。它是用java编写的,但您可以将其作为RESTful服务运行。

另一个选项是DKPro相似,它是一个库,有各种算法来测量文本的相似性。然而,它也是用java编写的。

代码示例:

// this similarity measure is defined in the dkpro.similarity.algorithms.lexical-asl package
// you need to add that to your .pom to make that example work
// there are some examples that should work out of the box in dkpro.similarity.example-gpl
TextSimilarityMeasure measure = new WordNGramJaccardMeasure(3);    // Use word trigrams


String[] tokens1 = "This is a short example text .".split(" ");
String[] tokens2 = "A short example text could look like that .".split(" ");


double score = measure.getSimilarity(tokens1, tokens2);


System.out.println("Similarity: " + score);

如果你正在寻找一些非常精确的东西,你需要使用一些比tf-idf更好的工具。通用句子编码器是找到任何两段文本之间相似性的最准确的方法之一。谷歌提供了预训练的模型,您可以将其用于自己的应用程序,而不需要从头开始训练任何东西。首先,你必须安装tensorflow和tensorflow-hub:

    pip install tensorflow
pip install tensorflow_hub

下面的代码允许您将任何文本转换为固定长度的向量表示,然后您可以使用点积来找出它们之间的相似性

import tensorflow_hub as hub
module_url = "https://tfhub.dev/google/universal-sentence-encoder/1?tf-hub-format=compressed"


# Import the Universal Sentence Encoder's TF Hub module
embed = hub.Module(module_url)


# sample text
messages = [
# Smartphones
"My phone is not good.",
"Your cellphone looks great.",


# Weather
"Will it snow tomorrow?",
"Recently a lot of hurricanes have hit the US",


# Food and health
"An apple a day, keeps the doctors away",
"Eating strawberries is healthy",
]


similarity_input_placeholder = tf.placeholder(tf.string, shape=(None))
similarity_message_encodings = embed(similarity_input_placeholder)
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
message_embeddings_ = session.run(similarity_message_encodings, feed_dict={similarity_input_placeholder: messages})


corr = np.inner(message_embeddings_, message_embeddings_)
print(corr)
heatmap(messages, messages, corr)

绘图的代码:

def heatmap(x_labels, y_labels, values):
fig, ax = plt.subplots()
im = ax.imshow(values)


# We want to show all ticks...
ax.set_xticks(np.arange(len(x_labels)))
ax.set_yticks(np.arange(len(y_labels)))
# ... and label them with the respective list entries
ax.set_xticklabels(x_labels)
ax.set_yticklabels(y_labels)


# Rotate the tick labels and set their alignment.
plt.setp(ax.get_xticklabels(), rotation=45, ha="right", fontsize=10,
rotation_mode="anchor")


# Loop over data dimensions and create text annotations.
for i in range(len(y_labels)):
for j in range(len(x_labels)):
text = ax.text(j, i, "%.2f"%values[i, j],
ha="center", va="center", color="w",
fontsize=6)


fig.tight_layout()
plt.show()

结果将是: 文本对之间的相似度矩阵 < / p >

正如你所看到的,最相似的是文本本身和意义相近的文本之间。

重要的:第一次运行代码时,它会很慢,因为它需要下载模型。如果你想防止它再次下载模型并使用本地模型,你必须为缓存创建一个文件夹,并将其添加到环境变量中,然后在第一次运行后使用该路径:

tf_hub_cache_dir = "universal_encoder_cached/"
os.environ["TFHUB_CACHE_DIR"] = tf_hub_cache_dir


# pointing to the folder inside cache dir, it will be unique on your system
module_url = tf_hub_cache_dir+"/d8fbeb5c580e50f975ef73e80bebba9654228449/"
embed = hub.Module(module_url)

更多信息:https://tfhub.dev/google/universal-sentence-encoder/2

句法相似性 有3种简单的方法来检测相似性

  • Word2Vec
  • 手套
  • Tfidf或countvectorizer

语义相似度 可以使用BERT嵌入和尝试不同的词池策略来获得文档嵌入,然后在文档嵌入上应用余弦相似度。< / p > 一种先进的方法可以使用BERT SCORE来获得相似度。 BERT SCORE < / p >

研究论文链接:https://arxiv.org/abs/1904.09675

为了用更少的数据集找到句子的相似性,并获得更高的精度,你可以使用下面的python包,它使用预训练的BERT模型,

pip install similar-sentences

我把@FredFoo和@Renaud的答案结合起来。我的解决方案是能够在@FredFoo的文本语料库上应用@Renaud的预处理,然后显示相似度大于0的成对相似度。我先安装python和pip,在Windows上运行这段代码。PIP是作为python的一部分安装的,但您可能必须显式地重新运行安装包,选择modify,然后选择PIP。我使用命令行来执行保存在文件“similarity.py”中的python代码。我必须执行以下命令:

>set PYTHONPATH=%PYTHONPATH%;C:\_location_of_python_lib_
>python -m pip install sklearn
>python -m pip install nltk
>py similarity.py

similar .py的代码如下:

from sklearn.feature_extraction.text import TfidfVectorizer
import nltk, string
import numpy as np
nltk.download('punkt') # if necessary...


stemmer = nltk.stem.porter.PorterStemmer()
remove_punctuation_map = dict((ord(char), None) for char in string.punctuation)


def stem_tokens(tokens):
return [stemmer.stem(item) for item in tokens]


def normalize(text):
return stem_tokens(nltk.word_tokenize(text.lower().translate(remove_punctuation_map)))


corpus = ["I'd like an apple",
"An apple a day keeps the doctor away",
"Never compare an apple to an orange",
"I prefer scikit-learn to Orange",
"The scikit-learn docs are Orange and Blue"]


vect = TfidfVectorizer(tokenizer=normalize, stop_words='english')
tfidf = vect.fit_transform(corpus)
                                                                                                                                                                                                                    

pairwise_similarity = tfidf * tfidf.T


#view the pairwise similarities
print(pairwise_similarity)


#check how a string is normalized
print(normalize("The scikit-learn docs are Orange and Blue"))
我们可以使用句子转换器来完成这个任务 链接 < / p >

下面是一个来自sbert的简单示例:

from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer('all-MiniLM-L6-v2')
# Two lists of sentences
sentences1 = ['The cat sits outside']
sentences2 = ['The dog plays in the garden']
#Compute embedding for both lists
embeddings1 = model.encode(sentences1, convert_to_tensor=True)
embeddings2 = model.encode(sentences2, convert_to_tensor=True)
#Compute cosine-similarities
cosine_scores = util.cos_sim(embeddings1, embeddings2)
#Output the pairs with their score
for i in range(len(sentences1)):
print("{} \t\t {} \t\t Score: {:.4f}".format(sentences1[i],
sentences2[i], cosine_scores[i][i]))

这里是Simphile NLP文本相似性Python包的创建者。Simphile包含几种文本相似度方法,它们与语言无关,并且比语言嵌入占用的cpu更少。

安装:

pip install simphile

选择你最喜欢的方法。这个例子显示了三点:

from simphile import jaccard_similarity, euclidian_similarity, compression_similarity


text_a = "I love dogs"
text_b = "I love cats"


print(f"Jaccard Similarity: {jaccard_similarity(text_a, text_b)}")
print(f"Euclidian Similarity: {euclidian_similarity(text_a, text_b)}")
print(f"Compression Similarity: {compression_similarity(text_a, text_b)}")
  • 压缩Similairty -利用压缩算法的模式识别
  • 欧几里得的相似性 -将文本视为多维空间中的点,并计算它们的紧密度
  • Jaccard Similairy -文字重叠越多,文本越相似