乐趣区

关于人工智能:20-行代码带你快速构建基础文本搜索引擎

💡 作者:韩信子 @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 整顿的自然语言解决相干教程和文章

  • 深度学习教程:吴恩达专项课程 · 全套笔记解读
  • 深度学习教程 | 自然语言解决与词嵌入
  • NLP 教程 | 斯坦福 CS224n · 课程带学与全套笔记解读
  • NLP 教程 (1) – 词向量、SVD 合成与 Word2Vec
  • NLP 教程 (2) – GloVe 及词向量的训练与评估

① 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)]

参考资料

  • 📘 深度学习教程:吴恩达专项课程 · 全套笔记解读 :https://www.showmeai.tech/tutorials/35
  • 📘 深度学习教程 | 自然语言解决与词嵌入 :https://www.showmeai.tech/article-detail/226
  • 📘 NLP 教程 | 斯坦福 CS224n · 课程带学与全套笔记解读 :https://www.showmeai.tech/tutorials/36
  • 📘 NLP 教程 (1) – 词向量、SVD 合成与 Word2Vec:https://www.showmeai.tech/article-detail/230
  • 📘 NLP 教程 (2) – GloVe 及词向量的训练与评估 :https://www.showmeai.tech/article-detail/232
退出移动版