20 行代码!带你快速构建基础文本搜索引擎 ⛵

本文涉及的产品
NLP自然语言处理_基础版,每接口每天50万次
NLP自然语言处理_高级版,每接口累计50万次
NLP 自学习平台,3个模型定制额度 1个月
简介: 本文使用tf-idf(词频-逆文件频率)、lsi(潜在语义索引)和 doc2vec(文档向量化嵌入)这3种最基础的NLP文档嵌入技术,对文本进行嵌入操作(即构建语义向量)并完成比对检索,构建一个基础版的文本搜索引擎。

💡 作者: 韩信子@ ShowMeAI
📘 机器学习实战系列https://www.showmeai.tech/tutorials/41
📘 深度学习实战系列https://www.showmeai.tech/tutorials/42
📘 本文地址https://www.showmeai.tech/article-detail/321
📢 声明:版权所有,转载请联系平台与作者并注明出处
📢 收藏 ShowMeAI查看更多精彩内容

在本篇内容中,ShowMeAI将带大家,使用最基础的3种NLP文档嵌入技术:tf-idf、lsi 和 doc2vec(dbow),来对文本进行嵌入操作(即构建语义向量)并完成比对检索,完成一个基础版的文本搜索引擎。

💡 文档嵌入技术

文档嵌入(doc embedding)方法能完成文本的向量化表示,我们可以进而将文本搜索问题简化为计算向量之间相似性的问题。我们把『搜索词条』和『文档』都转换为向量(同一个向量空间中)之后,文本比较与检索变得容易得多。

搜索引擎根据『文档』与『搜索词条』的相似度对文档进行评分与排序,并返回得分最高的文档。 比如我们可以使用余弦相似度:

💡 文档嵌入方法与实现

📌 TFIDF / 词频-逆文件频率

TF-IDF(Term Frequency–Inverse Document Frequency)是一种用于信息检索与文本挖掘的常用加权技术。TF-IDF 是一种统计方法,用以评估一字词对于一个文档集或一个语料库中的其中一份文档的重要程度。字词的重要性随着它在文档中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。简单的解释为,一个单词在一个文档中出现次数很多,同时在其他文档中出现此时较少,那么我们认为这个单词对该文档是非常重要的。

我们可以通过 tfidf 把每个文档构建成长度为 M 的嵌入向量,其中 M 是所有文档中单词构成的词库大小。

一个文档(或查询)d 的 tfidf 向量定义如下:

其中,词频 (term frequency, TF) 指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(一般是词频除以文章总词数), 以防止它偏向长的文件。

逆向文件频率 (inverse document frequency,IDF) 的主要思想是:如果包含词条t的文档越少, IDF越大,则说明词条具有很好的类别区分能力。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。

IDF 的计算公式中分母之所以要加1,是为了避免分母为0。

scikit-learn 包带有 tfidf 的实现。 几行代码就可以构建一个基于 tfidf 的原始搜索引擎。

# 数据集处理与tf-idf计算所需工具库
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.datasets import fetch_20newsgroups

# 训练,即统计词表,构建tf-idf映射器
def train(documents):
  # Input: 文档列表
  # Output: TfidfVectorizer, tfidf矩阵
   vectorizer = TfidfVectorizer()
   tfidf = vectorizer.fit_transform(documents.data)
   return vectorizer, tfidf


documents = fetch_20newsgroups()
vectorizer, tfidf = train(documents.data)
# 使用构建好的tfidf文档向量化表示,构建简易搜索引擎
from sklearn.metrics.pairwise import linear_kernel

# 距离比对与检索
def search(query, N):
  # Input: 检索文本串query, 返回结果条数N
  # Output: 所有文档中最相关的N条结果索引
  transformed_query = vectorizer.transform([query])
  cosine_similarities = linear_kernel(transformed_query, tfidf).flatten()
  idx = np.argpartition(cosine_similarities, -N)[-N:]
  related_docs_indices = idx[np.argsort((-cosine_similarities)[idx])]
  return related_docs_indices, cosine_similarities[related_docs_indices]

# 从索引映射取回原始文档内容
[documents.data[idx] for idx in search('car hunter', 5)[0]]

tfidf 是最经典的信息检索算法,尽管它的原理非常简单,但它仍然被广泛使用。 例如,ElasticSearch 使用了 tfidf 的变体,并且在内存管理、可靠性和检索速度方面比原始版本要好得多。

📌 LSI / 潜在语义索引

上面介绍到的 tfidf 其实只考虑了精确的单词匹配。 然而我们有很多不同的方式来表达同样一个意思,但 tfidf 对于同义词的捕捉能力是很弱的。

优秀的搜索引擎需要处理语义,能在语义层面进行匹配和检索。为了实现这一点,我们需要捕捉文档的语义信息,而LSI可以通过在 tdfidf 矩阵上应用 SVD 来构造这样一个潜在的概念空间。

SVD 将 tfidf 矩阵分解为 3 个较小矩阵的乘积(其中 U 和 V 是正交矩阵,Σ 是 tfidf 矩阵的奇异值的对角矩阵)。

(UΣ) 构建成了我们的文档概念矩阵:它的每一列都带有一个潜在的“主题”。在潜在概念空间中匹配度高的文档,我们认为它们彼此更加接近。

要实现 LSI,我们只需要对上述 tfidf 的实现做一点小改动,在 train 方法中添加一个 svd 步骤。 搜索方法保持完全相同。

# LSI潜在语义索引实现
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.pipeline import Pipeline

# 注意到得到的tfidf矩阵会进行SVD分解
def train(documents):
  vectorizer = TfidfVectorizer()
  svd = TruncatedSVD(n_components=1500, 
                     algorithm='randomized',
                     n_iter=10, random_state=42)
  lsi = Pipeline([('tfidf', vectorizer), 
                  ('svd', svd)])
  matrix = lsi.fit_transform(documents.data)
  return lsi, matrix

不过,一些研究人员指出,在上述因式分解中,从矩阵 V 推断词相似度是不太靠谱的。所以大家在有些地方也会看到应用对称 SVD:

📌 Doc2vec / 文档向量化嵌入

上面提到的SVD方法,在数据量很大时会有时间复杂度太高的问题。通过训练浅层神经网络来构建文档向量,可以很好地解决这个问题,Doc2vec 是最典型的方法之一,它有 2 种风格:DM 和 DBOW。

有兴趣更系统全面了解词向量与文档向量的宝宝,建议阅读ShowMeAI整理的自然语言处理相关教程和文章

① DM(A distributed memory model)

大家可能听说过word2vec训练词向量的方法,训练词向量的核心思想就是说可以根据每个单词的上下文预测,也就是说上下文的单词对是有影响的。训练句向量的方法和词向量的方法非常类似,例如对于一个句子i want to drink water,如果要去预测句子中的单词want,那么不仅可以根据其他单词生成feature, 也可以根据其他单词和句子来生成feature进行预测。因此doc2vec的框架如下所示:

每个段落/句子都被映射到向量空间中,可以用矩阵的一列来表示。每个单词同样被映射到向量空间,可以用矩阵的一列来表示。然后将段落向量和词向量级联或者求平均得到特征,预测句子中的下一个单词。

这个段落向量/句向量也可以认为是一个单词,它的作用相当于是上下文的记忆单元或者是这个段落的主题,所以我们一般叫这种训练方法为Distributed Memory Model of Paragraph Vectors(PV-DM)

在训练的时候我们固定上下文的长度,用滑动窗口的方法产生训练集。段落向量/句向量 在该上下文中共享。

doc2vec的过程可以分为2个核心步骤

  • ① 训练模型,在已知的训练数据中得到词向量W, softmax的参数U和b,以及段落向量/句向量D
  • ② 推断过程(inference stage),对于新的段落,得到其向量表达。具体地,在矩阵D中添加更多的列,在固定W,U,b的情况下,利用上述方法进行训练,使用梯度下降的方法得到新的D,从而得到新段落的向量表达

② DBOW(Paragraph Vector without word ordering: Distributed bag of words)

相比上面提到的DM方法,DBOW训练方法是忽略输入的上下文,让模型去预测段落中的随机一个单词。就是在每次迭代的时候,从文本中采样得到一个窗口,再从这个窗口中随机采样一个单词作为预测任务,让模型去预测,输入就是段落向量。如下所示:

我们使用 gensim 工具可以快速构建 doc2vec。

from gensim.models.doc2vec import Doc2Vec, TaggedDocument
from sklearn.datasets import fetch_20newsgroups


def train(documents):
  # Input: 文档列表
  # Output: Doc2vec模型
  tagged_doc = [TaggedDocument(doc.split(' '), [i]) for i, doc in enumerate(documents)]
  model = Doc2Vec(documents, vector_size=100, window=2, min_count=1, workers=4, dm=0)
  model.build_vocab(documents)
  model.train(documents, total_examples=len(documents), epochs=10)
  return model

# 在训练集上训练
documents = fetch_20newsgroups()
model = train(documents.data)

而 gensim 构建的 doc2vec 模型对象,可以直接进行向量距离比对和排序,所以我们的检索过程可以如下简单实现:

def search(query, N):
  # Input: 检索文本串query, 返回结果条数N
  # Output: 所有文档中最相关的N条结果索引
  inferred_vector = model.infer_vector(query.split(' '))
  return model.dv.most_similar(inferred_vector, topn=N)

# 根据索引映射回原来的文档内容
[documents.data[idx[0]] for idx in search('car hunter', 5)]

参考资料

目录
相关文章
|
存储 安全 网络协议
阿里云SSL数字证书原理、使用、申请流程及部署方式
本文带您了解阿里云数字证书的基本原理、使用、申请流程及部署方式
|
存储 算法 NoSQL
探秘HyperLogLog:Redis中的基数统计黑科技
探秘HyperLogLog:Redis中的基数统计黑科技
567 0
|
机器学习/深度学习 人工智能 项目管理
【机器学习】集成学习——Stacking模型融合(理论+图解)
【机器学习】集成学习——Stacking模型融合(理论+图解)
5409 1
【机器学习】集成学习——Stacking模型融合(理论+图解)
|
人工智能
全方位解析PAI:数据准备、模型开发、模型训练一网打尽
全方位解析PAI:数据准备、模型开发、模型训练一网打尽 随着人工智能技术的飞速发展,越来越多的企业开始关注并投入到AI的研发中。然而,AI的研发并非易事,从数据准备、模型开发、模型训练到模型服务,每一个环节都需要专业的工具和平台来支持。阿里云的PAI(Powered by AI)正是一个涵盖了数据准备、模型开发、模型训练、模型服务全流程的AI工作平台。本文将为您详细介绍PAI的各个子产品的产品线上规格及使用指引。
404 2
|
Linux
如何让linux显示器命令行界面不显示
若需在命令行界面快速关闭屏幕,可选方法包括:使用`setterm --blank force`立即关闭屏幕;利用`sudo vbetool dpms off`将显示器置于低功耗模式;在X server环境下执行`xset dpms force off`关闭屏幕;或是针对笔记本通过`echo 0 | sudo tee /sys/class/backlight/*/brightness`关闭背光。以上任一方法均可实现屏幕关闭,部分操作需要管理员权限,屏幕通常会在按键或移动鼠标后自动恢复。
1366 10
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【7月更文挑战第22天】在大数据领域,Python算法效率至关重要。本文深入解析时间与空间复杂度,用大O表示法衡量执行时间和存储需求。通过冒泡排序(O(n^2)时间,O(1)空间)与快速排序(平均O(n log n)时间,O(log n)空间)实例,展示Python代码实现与复杂度分析。策略包括算法适配、分治法应用及空间换取时间优化。掌握这些,可提升大数据处理能力,持续学习实践是关键。
285 1
|
安全 Java
【JavaSE专栏44】Java常用类Calender解析,科学处理Java日期数据
【JavaSE专栏44】Java常用类Calender解析,科学处理Java日期数据
421 0
|
Kubernetes Cloud Native Java
灰度发布、蓝绿部署、金丝雀都是啥?
在滚动部署中,应用的新版本逐步替换旧版本。实际的部署发生在一段时间内。在此期间,新旧版本会共存,而不会影响功能和用户体验。这个过程可以更轻易的回滚和旧组件不兼容的任何新组件。
灰度发布、蓝绿部署、金丝雀都是啥?
|
存储 Prometheus 监控
公司要上监控,Zabbix 和 Prometheus 怎么选?这么选准没错!
公司要上监控,Zabbix 和 Prometheus 怎么选?这么选准没错!
3012 0
公司要上监控,Zabbix 和 Prometheus 怎么选?这么选准没错!