作者|Masatoshi Nishimura
编译|VK
起源|Towards Data Science

如果你想晓得2020年文档相似性工作的最佳算法,你来对了中央。

在33914篇《纽约时报》文章中,我测试了5种常见的文档相似性算法。从传统的统计办法到古代的深度学习办法。

每个实现少于50行代码。所有应用的模型都来自互联网。因而,你能够在没有数据科学知识的状况下,开箱即用,并且失去相似的后果。

在这篇文章中,你将学习如何实现每种算法以及如何抉择最佳算法。内容如下:

  1. 最佳的定义
  2. 试验指标陈说
  3. 数据设置
  4. 比拟规范
  5. 算法设置
  6. 选出赢家
  7. 对初学者的倡议

你想深刻自然语言解决和人工智能。你想用相干的倡议来减少用户体验。你想降级旧的现有算法。那么你会喜爱这个文章的。

数据科学家主张相对最好

你可能会搜寻术语“最佳文档相似性算法”(best document similarity algorithms)。

而后你将从学术论文,博客,问答中失去搜寻后果。一些侧重于特定算法的教程,而另一些则侧重于实践概述。

在学术论文中,一个题目说,这种算法的准确率达到了80%,而其余算法的准确率仅为75%。好啊。然而,这种差别是否足以让咱们的眼睛留神到它呢?减少2%怎么样?实现这个算法有多容易?科学家偏向于在给定的测试集中谋求最好,而疏忽了实际意义。

在相干的问题问答中,狂热的支持者占据了整个话题。有人说当初最好的算法是BERT。这个算法概念是如此具备革命性,它战胜了所有。另一方面,愤世嫉俗者称所有都取决于工作。有些答案早在深度学习之前就有了。看看这个Stackoverflow(https://stackoverflow.com/que...)。2012年是投票最多的一年,很难判断它对咱们到底意味着什么。

谷歌会很乐意投入数百万美元购买工程师的能力和最新的计算能力,仅仅是为了将他们的搜寻能力进步1%。这对咱们来说可能既不事实也没有意义。

性能增益和实现所需的技术专业知识之间有什么衡量?它须要多少内存?它以起码的预处理能够运行多快?

你想晓得的是一种算法在实际意义上是如何优于另一种算法的。

这篇文章将为你提供一个指导方针,领导你在文档相似性问题应该实现哪种算法。

各种算法,通篇风行文章,预训练模型

本试验有4个指标:

  1. 通过在同一个数据集上运行多个算法,你将看到算法与另一个算法的公平性以及偏心水平。
  2. 通过应用来自风行媒体的全文文章作为咱们的数据集,你将发现理论应用程序的有效性。
  3. 通过拜访文章url,你将可能比拟后果品质的差别。
  4. 通过只应用公开可用的预训练模型,你将可能设置本人的文档相似性并失去相似的输入。
“预训练模型是你的敌人。-Cathal Horan”

数据设置-5篇根底文章

本试验选取了33914篇《纽约时报》的文章。从2018年到2020年6月。数据次要是从RSS中收集的,文章的均匀长度是6500个字符。

从这些文章中抉择5个作为相似性搜寻的根底文章。每一个代表一个不同的类别。

在语义类别的根底上,咱们还将度量书面格局。更多的形容在上面。

  1. Lifestyle, Human Interest:How My Worst Date Ever Became My Best(https://www.nytimes.com/2020/...
  2. Science, Informational:A Deep-Sea Magma Monster Gets a Body Scan(https://www.nytimes.com/2019/...
  3. Business, News:Renault and Nissan Try a New Way After Years When Carlos Ghosn Ruled(https://www.nytimes.com/2019/...
  4. Sports, News:Dominic Thiem Beats Rafael Nadal in Australian Open Quarterfinal(https://www.nytimes.com/2020/...
  5. Politics, News:2020 Democrats Seek Voters in an Unusual Spot: Fox News(https://www.nytimes.com/2019/...

判断规范

咱们将应用5个规范来判断相似性的性质。如果你只想查看后果,请跳过此局部。

  1. 标签的重叠
  2. 大节
  3. 文风
  4. 主题

标签是最靠近人类判断内容相似性的工具。记者本人亲手写下标签。你能够在HTML题目中的news_keywords meta标记处查看它们。应用标签最好的局部是咱们能够主观地测量两个内容有多少重叠。每个标签的大小从1到12不等。两篇文章的标签重叠越多,就越类似。

第二,咱们看这个局部。这就是《纽约时报》在最高级别对文章进行分类的形式:迷信、政治、体育等等。在网址的域名前面会进行显示,例如nytimes.com/…

第二局部是大节。例如,一个版块能够细分为world,或者world能够细分为Australia。并不是所有的文章都蕴含它,它不像以上那2个那么重要。

第四是文风。大多数文档比拟剖析只关注语义。然而,因为咱们是在理论用例中比拟举荐,所以咱们也须要相似的写作格调。例如,你不想在学术期刊的“跑鞋和矫形术”之后,从商业角度浏览“十大跑鞋”。咱们将依据杰斐逊县学校的写作领导准则对文章进行分组。该列表包含人类趣味、共性、最佳(例如:产品评论)、新闻、操作方法、过来的事件和信息。

5个候选算法

这些是咱们将要钻研的算法。

  1. Jaccard
  2. TF-IDF
  3. Doc2vec
  4. USE
  5. BERT

每一个算法对33914篇文章运行,以找出得分最高的前3篇文章。对于每一篇根底文章,都会反复这个过程。

输出的是文章的全文内容。题目被疏忽。

请留神,有些算法并不是为文档相似性而构建的。然而在互联网上有如此不同的意见,咱们将亲眼看到后果。

咱们将不关注概念了解,也不关注具体的代码审查。相同,其目标是展现问题的设置有多简略。如果你不明确以下算法的细节,不要放心,你能够浏览其余优良博客进行了解

你能够在Github repo中找到整个代码库:https://github.com/massanishi...

如果你只想查看后果,请跳过此局部。

Jaccard

Jaccard 在一个多世纪前提出了这个公式。长期以来,这一概念始终是相似性工作的规范。

侥幸的是,你会发现jaccard是最容易了解的算法。数学很简略,没有向量化。它能够让你从头开始编写代码。

而且,jaccard是多数不应用余弦相似性的算法之一。它标记单词并计算交加。

咱们应用NLTK对文本进行预处理。

步骤:

  1. 小写所有文本
  2. 标识化
  3. 删除停用词
  4. 删除标点符号
  5. 词根化
  6. 计算两个文档中的交加/并集
import stringimport nltknltk.download('stopwords')nltk.download('wordnet')nltk.download('punkt')from nltk.corpus import stopwordsfrom nltk.tokenize import word_tokenizefrom nltk.stem import WordNetLemmatizerlemmatizer = WordNetLemmatizer()base_document = "This is an example sentence for the document to be compared"documents = ["This is the collection of documents to be compared against the base_document"]def preprocess(text):    # 步骤:    # 1. 小写字母    # 2. 词根化    # 3. 删除停用词    # 4. 删除标点符号    # 5. 删除长度为1的字符    lowered = str.lower(text)    stop_words = set(stopwords.words('english'))    word_tokens = word_tokenize(lowered)    words = []    for w in word_tokens:        if w not in stop_words:            if w not in string.punctuation:                if len(w) > 1:                    lemmatized = lemmatizer.lemmatize(w)                    words.append(lemmatized)    return wordsdef calculate_jaccard(word_tokens1, word_tokens2):    # 联合这两个标识来找到并集。    both_tokens = word_tokens1 + word_tokens2    union = set(both_tokens)    # 计算交加    intersection = set()    for w in word_tokens1:        if w in word_tokens2:            intersection.add(w)    jaccard_score = len(intersection)/len(union)    return jaccard_scoredef process_jaccard_similarity():    # 标记咱们要比拟的根本文档。    base_tokens = preprocess(base_document)    # 标记每一篇文档    all_tokens = []    for i, document in enumerate(documents):        tokens = preprocess(document)        all_tokens.append(tokens)        print("making word tokens at index:", i)    all_scores = []    for tokens in all_tokens:        score = calculate_jaccard(base_tokens, tokens)        all_scores.append(score)    highest_score = 0    highest_score_index = 0    for i, score in enumerate(all_scores):        if highest_score < score:            highest_score = score            highest_score_index = i    most_similar_document = documents[highest_score_index]    print("Most similar document by Jaccard with the score:", most_similar_document, highest_score)process_jaccard_similarity()

TF-IDF

这是自1972年以来呈现的另一种成熟算法。通过几十年的测试,它是Elasticsearch的默认搜寻实现。

Scikit learn提供了不错的TF-IDF的实现。TfidfVectorizer容许任何人尝试此操作。

利用scikit-learn的余弦类似度计算TF-IDF词向量的后果。咱们将在其余的例子中应用这种余弦相似性。余弦相似性是许多机器学习工作中应用的一个十分重要的概念,可能值得你花工夫相熟一下。

多亏了scikit learn,这个算法产生了最短的代码行。

from sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.metrics.pairwise import cosine_similaritybase_document = "This is an example sentence for the document to be compared"documents = ["This is the collection of documents to be compared against the base_document"]def process_tfidf_similarity():    vectorizer = TfidfVectorizer()    # 要生成对立的向量,首先须要将两个文档合并。    documents.insert(0, base_document)    embeddings = vectorizer.fit_transform(documents)    cosine_similarities = cosine_similarity(embeddings[0:1], embeddings[1:]).flatten()    highest_score = 0    highest_score_index = 0    for i, score in enumerate(cosine_similarities):        if highest_score < score:            highest_score = score            highest_score_index = i    most_similar_document = documents[highest_score_index]    print("Most similar document by TF-IDF with the score:", most_similar_document, highest_score)process_tfidf_similarity()

Doc2vec

Word2vec于2014年面世,这让过后的开发者们另眼相看。你可能据说过十分有名的一个例子:

国王 - 男性 = 女王

Word2vec十分善于了解单个单词,将整个句子向量化须要很长时间。更不用说整个文件了。

相同,咱们将应用Doc2vec,这是一种相似的嵌入算法,将段落而不是每个单词向量化。你能够看看这个博客的介绍:https://medium.com/wisio/a-ge...

可怜的是,对于Doc2vec来说,没有官网预训练模型。咱们将应用其他人的预训练模型。它是在英文维基百科上训练的(数字不详,但模型大小相当于1.5gb):https://github.com/jhlau/doc2vec

Doc2vec的官网文档指出,输出能够是任意长度。一旦标识化,咱们输出整个文档到gensim库。

from gensim.models.doc2vec import Doc2Vecfrom sklearn.metrics.pairwise import cosine_similarityimport stringimport nltknltk.download('stopwords')nltk.download('wordnet')nltk.download('punkt')from nltk.corpus import stopwordsfrom nltk.tokenize import word_tokenizefrom nltk.stem import WordNetLemmatizerlemmatizer = WordNetLemmatizer()base_document = "This is an example sentence for the document to be compared"documents = ["This is the collection of documents to be compared against the base_document"]def preprocess(text):    # 步骤:    # 1. 小写字母    # 2. 词根化    # 3. 删除停用词    # 4. 删除标点符号    # 5. 删除长度为1的字符    lowered = str.lower(text)    stop_words = set(stopwords.words('english'))    word_tokens = word_tokenize(lowered)    words = []    for w in word_tokens:        if w not in stop_words:            if w not in string.punctuation:                if len(w) > 1:                    lemmatized = lemmatizer.lemmatize(w)                    words.append(lemmatized)    return wordsdef process_doc2vec_similarity():    # 这两种事后训练的模型都能够在jhlau的公开仓库中取得。    # URL: https://github.com/jhlau/doc2vec    # filename = './models/apnews_dbow/doc2vec.bin'    filename = './models/enwiki_dbow/doc2vec.bin'    model= Doc2Vec.load(filename)    tokens = preprocess(base_document)    # 只解决呈现在doc2vec预训练过的向量中的单词。enwiki_ebow模型蕴含669549个词汇。    tokens = list(filter(lambda x: x in model.wv.vocab.keys(), tokens))    base_vector = model.infer_vector(tokens)    vectors = []    for i, document in enumerate(documents):        tokens = preprocess(document)        tokens = list(filter(lambda x: x in model.wv.vocab.keys(), tokens))        vector = model.infer_vector(tokens)        vectors.append(vector)        print("making vector at index:", i)    scores = cosine_similarity([base_vector], vectors).flatten()    highest_score = 0    highest_score_index = 0    for i, score in enumerate(scores):        if highest_score < score:            highest_score = score            highest_score_index = i    most_similar_document = documents[highest_score_index]    print("Most similar document by Doc2vec with the score:", most_similar_document, highest_score)process_doc2vec_similarity()

Universal Sentence Encoder (USE)

这是Google最近在2018年5月公布的一个风行算法。 实现细节:https://www.tensorflow.org/hu...。

咱们将应用谷歌最新的官网预训练模型:Universal Sentence Encoder 4(https://tfhub.dev/google/univ...

顾名思义,它是用句子来构建的。但官网文件并没有限度投入规模。没有什么能阻止咱们将它用于文档比拟工作。

整个文档按原样插入到Tensorflow中。没有进行标识化。

from sklearn.metrics.pairwise import cosine_similarityimport tensorflow as tfimport tensorflow_hub as hubbase_document = "This is an example sentence for the document to be compared"documents = ["This is the collection of documents to be compared against the base_document"]def process_use_similarity():    filename = "./models/universal-sentence-encoder_4"    model = hub.load(filename)    base_embeddings = model([base_document])    embeddings = model(documents)    scores = cosine_similarity(base_embeddings, embeddings).flatten()    highest_score = 0    highest_score_index = 0    for i, score in enumerate(scores):        if highest_score < score:            highest_score = score            highest_score_index = i    most_similar_document = documents[highest_score_index]    print("Most similar document by USE with the score:", most_similar_document, highest_score)process_use_similarity()

BERT

这可是个重量级选手。2018年11月谷歌开源BERT算法。第二年,谷歌搜寻副总裁发表了一篇博文,称BERT是他们过来5年来最大的飞跃。

它是专门为了解你的搜寻查问而构建的。当谈到了解一个句子的上下文时,BERT仿佛比这里提到的所有其余技术都要杰出。

最后的BERT工作并不打算解决大量的文本输出。对于嵌入多个句子,咱们将应用UKPLab(来自德国大学)出版的句子转换器开源我的项目(https://github.com/UKPLab/sen...),其计算速度更快。它们还为咱们提供了一个与原始模型相当的预训练模型(https://github.com/UKPLab/sen...)

所以每个文档都被标记成句子。并对后果进行均匀,以将文档示意为一个向量。

import numpy as npfrom sklearn.metrics.pairwise import cosine_similarityfrom nltk import sent_tokenizefrom sentence_transformers import SentenceTransformerbase_document = "This is an example sentence for the document to be compared"documents = ["This is the collection of documents to be compared against the base_document"]def process_bert_similarity():    # 这将下载和加载UKPLab提供的预训练模型。    model = SentenceTransformer('bert-base-nli-mean-tokens')    # 尽管在句子转换器的官网文件中并没有明确的阐明,然而原来的BERT是指一个更短的句子。咱们将通过句子而不是整个文档来提供模型。    sentences = sent_tokenize(base_document)    base_embeddings_sentences = model.encode(sentences)    base_embeddings = np.mean(np.array(base_embeddings_sentences), axis=0)    vectors = []    for i, document in enumerate(documents):        sentences = sent_tokenize(document)        embeddings_sentences = model.encode(sentences)        embeddings = np.mean(np.array(embeddings_sentences), axis=0)        vectors.append(embeddings)        print("making vector at index:", i)    scores = cosine_similarity([base_embeddings], vectors).flatten()    highest_score = 0    highest_score_index = 0    for i, score in enumerate(scores):        if highest_score < score:            highest_score = score            highest_score_index = i    most_similar_document = documents[highest_score_index]    print("Most similar document by BERT with the score:", most_similar_document, highest_score)process_bert_similarity()

算法评估

让咱们看看每种算法在咱们的5篇不同类型的文章中的体现。咱们依据得分最高的三篇文章进行比拟。

在这篇博文中,咱们将只介绍五种算法中性能最好的算法的后果。无关残缺的后果以及个别文章链接,请参阅仓库中的算法目录:https://github.com/massanishi...

1. How My Worst Date Ever Became My Best

BERT胜利

这篇文章是一个人类感兴趣的故事,波及一个50年代离婚妇女的浪漫约会。

这种写作格调没有像名人名字这样的特定名词。它对工夫也不敏感。2010年的一个对于人类趣味的故事在明天可能也同样重要。在比拟中没有一个算法性能特地差。

BERT和USE的较量千钧一发。USE把故事绕到了社会问题,BERT关注浪漫和约会。其余算法则转向了家庭和孩子的话题,可能是因为看到了“ex husband 前夫”这个词。

2. A Deep-Sea Magma Monster Gets a Body Scan

TF-IDF获胜。

这篇迷信文章是对于陆地中活火山的三维扫描。

3D扫描、火山和陆地是常见的术语。所有算法都很好地实现了偏心。

TF-IDF正确地抉择了那些只议论地球陆地内火山的人。USE与它相比也是一个弱小的竞争者,它的重点是火星上的火山而不是陆地。另一些算法则抉择了无关俄罗斯军用潜艇的文章,这些文章与迷信无关,与主题无关。

3. Renault and Nissan Try a New Way After Years When Carlos Ghosn Ruled

TF-IDF获胜。

文章谈到了前首席执行官卡洛斯·戈恩越狱后雷诺和日产的遭逢。

现实的匹配将探讨这3个实体。与前两篇相比,本文更具备事件驱动性和工夫敏感性。相干新闻应与此日期或之后产生(从2019年11月开始)。

TF-IDF正确地抉择了关注日产CEO的文章。其他人则抉择了一些议论通用汽车行业新闻的文章,比方菲亚特克莱斯勒(Fiat Chrysler)和标致(Peugeot)的结盟。

值得一提的是,Doc2vec和USE生成了完全相同的后果。

4. Dominic Thiem Beats Rafael Nadal in Australian Open Quarterfinal

Jaccard、TF-IDF和USE后果类似。

这篇文章是对于网球选手多米尼克·蒂姆在2020年澳大利亚网球公开赛(网球较量)上的文章。

新闻是事件驱动的,对集体来说十分具体。所以现实的匹配是多米尼克和澳大利亚公开赛。

可怜的是,这个后果因为不足足够的数据而受到影响。他们都议论网球。但有些较量是在议论2018年法国网球公开赛的多米尼克。或者,在澳大利亚网球公开赛上对费德勒的认识。

后果是三种算法的后果。这阐明了要害的重要性:咱们须要尽最大致力收集、多样化和扩大数据池,以获得最佳的相似性匹配后果。

5. 2020 Democrats Seek Voters in an Unusual Spot: Fox News

USE胜利。

这篇文章是对于民主党人的,特地关注伯尼·桑德斯在福克斯新闻(Fox News)上为2020年大选出镜。

每一个话题都有本人的大问题。对于民主党候选人和选举的文章很多。因为这个故事的宗旨是新鲜的,所以咱们优先探讨民主党候选人和福克斯的关系。

旁注:在实践中,你要小心看待政治上的倡议。把自在和激进的新闻混合在一起很容易让读者不安。既然咱们是独自和《纽约时报》打交道,那就不用放心了。

USE找到了一些对于伯尼·桑德斯和福克斯、微软全国广播公司等电视频道的文章。其他人则抉择了一些探讨2020年大选中其余民主党候选人的文章。

速度之王

在完结赢家之前,咱们须要谈谈运行工夫。每种算法在速度方面体现得十分不同。

后果是,TF-IDF的施行比任何其余办法都快得多。要在单个CPU上从头到尾计算33914个文档(标识化、向量化和比拟),须要:

  • TF-IDF:1.5分钟。
  • Jaccard:13分钟。
  • Doc2vec:43分钟。
  • USE:62分钟。
  • BERT:50多小时(每个句子都被向量化了)。

TF-IDF只花了一分半钟。这是USE的2.5%。当然,你能够合并多种效率加强。但潜在收益须要探讨。这将使咱们有另一个理由认真扫视相干的利弊衡量。

以下是5篇文章中的每一篇的赢家算法。

  1. BERT
  2. TF-IDF
  3. TF-IDF
  4. Jaccard, TF-IDF和USE
  5. USE

从后果能够看出,对于新闻报道中的文档相似性,TF-IDF是最佳候选。如果你应用它的最小定制,这一点尤其正确。思考到TF-IDF是创造的第二古老的算法,这也令人诧异。相同,你可能会悲观的是,古代先进的人工智能深度学习在这项工作中没有任何意义。

当然,每种深度学习技术都能够通过训练本人的模型和更好地预处理数据来改良。但所有这些都随同着开发成本。你想好好想想,绝对于TF-IDF办法,这种致力会带来额定多大的益处。

最初,能够说咱们应该齐全遗记Jaccard和Doc2vec的文档相似性。与明天的替代品相比,它们没有带来任何益处。

老手举荐

假如你决定从头开始在应用程序中实现相似性算法,上面是我的倡议。

1.先施行TF-IDF

最快的文档相似性匹配是TF-IDF,只管有深度学习的各种宣传,例如深度学习给你一个高质量的后果。然而TFIDF最棒的是,它是闪电般的快。

正如咱们所看到的,将其降级到深度学习办法可能会或不会给你带来更好的性能。在计算衡量时,必须当时思考很多问题。

2.积攒更好的数据

Andrew Ng给出了一个相似的倡议。你不能指望你的车没有油就跑。油必须是好的。

文档相似性依赖于数据的多样性,也依赖于特定的算法。你应该尽你最大的致力找到惟一的数据来加强你的相似性后果。

3.降级到深度学习

仅当你对TF-IDF的后果不称心时,才迁徙到USE或BERT以降级模型。你须要思考计算工夫。你可能会预处理词嵌入,因而你能够在运行时更快地解决相似性匹配。谷歌为此写了一篇教程:https://cloud.google.com/solu...

4.调整深度学习算法

你能够缓缓降级你的模型。训练你本人的模型,将预训练好的常识融入特定的畛域,等等。明天也有许多不同的深度学习模式。你能够一个一个的来看看哪一个最适宜你的具体要求。

文档相似性是许多NLP工作之一

你能够应用各种算法实现文档的相似性:一些是传统的统计办法,另一些是尖端的深度学习办法。咱们曾经在纽约时报的文章中看到了它们之间的比拟。

应用TF-IDF,你能够在本地笔记本电脑上轻松启动本人的文档相似性。不须要低廉的GPU。不须要大内存。你依然能够失去高质量的数据。

诚然,如果你想做情绪剖析或分类等其余工作,深刻学习应该适宜你的工作。然而,当钻研人员试图冲破深度学习效率和问题界线时,咱们要意识到生存在炒作的圈子里是不衰弱的。它给新来的人带来微小的焦虑和不安全感。

保持经验主义能够让咱们看到事实。

心愿这个博客激励你开始本人的NLP我的项目。


参考浏览

  • An article covering TF-IDF and Cosine similarity with examples: “Overview of Text Similarity Metrics in Python“:https://towardsdatascience.co...
  • An academic paper discussing how cosine similarity is used in various NLP machine learning tasks: “Cosine Similarity”:https://www.sciencedirect.com...
  • Discussion of sentence similarity in different algorithms: “Text Similarities : Estimate the degree of similarity between two texts”:https://medium.com/@adriensie...
  • An examination of various deep learning models in text analysis: “When Not to Choose the Best NLP Model”:https://blog.floydhub.com/whe...
  • Conceptual dive into BERT model: “A review of BERT based models”:https://towardsdatascience.co...
  • A literature review on document embeddings: “Document Embedding Techniques”:https://towardsdatascience.co...

原文链接:https://towardsdatascience.co...

欢送关注磐创AI博客站:
http://panchuang.net/

sklearn机器学习中文官网文档:
http://sklearn123.com/

欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/