神经搜索 101:完整指南和分步教程
信息检索技术是现代互联网得以存在的主要技术之一。如今,搜索技术是各种应用的核心。从网页搜索到产品推荐,搜索无处不在。多年来,这项技术没有太大变化,直到神经网络开始发挥作用。
在本指南中,我们将找到以下问题的答案
- 常规搜索和神经搜索有什么区别?
- 哪些神经网络可用于搜索?
- 神经网络搜索在哪些任务中有用?
- 如何分步构建和部署自己的神经搜索服务?
什么是神经搜索?
常规全文搜索(例如 Google 的搜索)是通过搜索文档内的关键词来实现的。因此,算法无法考虑查询和文档的实际含义。许多用户可能感兴趣的文档,由于使用了不同的措辞而无法被找到。
神经搜索试图解决的正是这个问题——它试图实现不是基于关键词而是基于含义的搜索。为了实现这一点,搜索分两步进行。第一步,一个经过特殊训练的神经网络编码器将查询和搜索对象转换为称为嵌入 (embeddings) 的向量表示。编码器必须经过训练,以便相似的对象(例如具有相同含义的文本或相似的图片)获得接近的向量表示。
有了这个向量表示,很容易理解第二步应该是什么。要找到与查询相似的文档,现在只需要找到最近的向量。确定两个向量之间距离最方便的方法是计算余弦距离 (cosine distance)。也可以使用普通的欧几里得距离 (Euclidean distance),但由于维度灾难,其效率不高。
可以使用哪些模型?
理想情况下,应使用经过特殊训练的模型来确定含义的接近度。例如,在语义文本相似度 (STS) 数据集上训练的模型。当前最先进的模型可以在此排行榜上找到。
然而,不仅仅可以使用经过特殊训练的模型。如果模型是在足够大的数据集上训练的,它的内部特征也可以用作嵌入。因此,例如,你可以选取任何在 ImageNet 上预训练的模型,并去掉它的最后一层。通常,在神经网络的倒数第二层会形成最高级别的特征,然而这些特征不对应特定的类别。这一层的输出可以用作嵌入。
神经搜索适用于哪些任务?
神经搜索的最大优势在于查询无法精确表述的领域。查询 SQL 数据库中的表并不是神经搜索的最佳应用场景。
相反,如果查询本身是模糊的,或者无法表述为一组条件,神经搜索可以帮助你。如果搜索查询是图片、音频文件或长文本,神经网络搜索几乎是唯一的选择。
如果你想构建一个推荐系统,神经网络方法也会很有用。用户的行为可以像图片或文本一样被编码到向量空间中。拥有这些向量后,就可以找到语义上相似的用户,并确定用户下一步可能的操作。
使用 Qdrant 的分步神经搜索教程
综上所述,让我们来构建我们的神经网络搜索。举个例子,我决定按创业公司(startups)的描述进行搜索。在这个演示中,我们将看到文本搜索更有效和神经网络搜索更有效的情况。
我将使用来自startups-list.com的数据。每条记录包含名称、描述公司的段落、位置和图片。原始解析数据可以在此链接找到。
步骤 1:为神经搜索准备数据
为了能够在向量空间中搜索我们的描述,我们必须先获取向量。我们需要将描述编码成向量表示。由于描述是文本数据,我们可以使用预训练的语言模型。如前所述,对于文本搜索任务,有一整套专门为语义相似性调整的预训练模型。
我认为,使用预训练语言模型最简单的库之一是 UKPLab 的 sentence-transformers。它提供了一种便捷的方式来下载和使用许多预训练模型,这些模型主要基于 Transformer 架构。Transformer 不是唯一适合神经搜索的架构,但对于我们的任务来说,它已经足够了。
我们将使用一个名为all-MiniLM-L6-v2
的模型。这个模型是一个全能模型,针对多种用例进行了调整。它在一个包含超过 10 亿训练对的大型多样化数据集上训练,并针对低内存消耗和快速推理进行了优化。
包含详细注释的数据准备完整代码可以在Colab Notebook中找到并运行。
步骤 2:引入向量搜索引擎
现在我们已经为所有记录获得了向量表示,我们需要将它们存储在某个地方。除了存储,我们可能还需要添加或删除向量,以及保存与向量相关的额外信息。最重要的是,我们需要一种搜索最近向量的方法。
向量搜索引擎可以处理所有这些任务。它提供了一个便捷的 API 用于搜索和管理向量。在本教程中,我们将使用Qdrant 向量搜索引擎。它不仅支持所有必需的向量操作,还允许你与向量一起存储额外的 payload 数据,并使用它来过滤搜索结果。Qdrant 有一个 Python 客户端,如果你需要从其他语言使用它,它也定义了 API schema。
使用 Qdrant 最简单的方法是运行一个预构建的镜像。因此请确保你的系统上安装了 Docker。
要启动 Qdrant,请按照其主页上的说明进行操作。
从DockerHub下载镜像
docker pull qdrant/qdrant
并在 Docker 容器内运行该服务
docker run -p 6333:6333 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
你应该会看到类似这样的输出
...
[2021-02-05T00:08:51Z INFO actix_server::builder] Starting 12 workers
[2021-02-05T00:08:51Z INFO actix_server::builder] Starting "actix-web-service-0.0.0.0:6333" service on 0.0.0.0:6333
这意味着服务已成功启动并正在监听端口 6333。为了确保,你可以在浏览器中访问http://localhost:6333/并获取 Qdrant 版本信息。
所有上传到 Qdrant 的数据都被保存到./qdrant_storage
目录中,即使你重新创建容器,数据也会被保留。
步骤 3:上传数据到 Qdrant
现在我们已经准备好了向量并运行着搜索引擎,可以开始上传数据了。要从 Python 与 Qdrant 交互,我建议使用一个开箱即用的客户端库。
要安装它,使用以下命令
pip install qdrant-client
至此,我们应该在文件startups.json
中拥有创业公司记录,在文件startup_vectors.npy
中拥有编码后的向量,并在本地机器上运行着 Qdrant。让我们编写一个脚本,将所有创业公司数据和向量上传到搜索引擎。
首先,让我们为 Qdrant 创建一个客户端对象。
# Import client library
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance
qdrant_client = QdrantClient(host='localhost', port=6333)
Qdrant 允许你将具有相同用途的向量组合到集合 (collections) 中。许多独立的向量集合可以同时存在于一个服务上。
让我们为我们的创业公司向量创建一个新集合。
if not qdrant_client.collection_exists('startups'):
qdrant_client.create_collection(
collection_name='startups',
vectors_config=VectorParams(size=384, distance=Distance.COSINE),
)
vector_size
参数非常重要。它告诉服务该集合中向量的大小。集合中的所有向量必须具有相同的大小,否则无法计算它们之间的距离。384
是我们使用的编码器的输出维度。
distance
参数允许指定用于测量两点之间距离的函数。
Qdrant 客户端库定义了一个特殊函数,允许你将数据集加载到服务中。然而,由于数据可能过多而无法放入单个计算机内存中,该函数接受一个数据迭代器作为输入。
让我们为创业公司数据和向量创建一个迭代器。
import numpy as np
import json
fd = open('./startups.json')
# payload is now an iterator over startup data
payload = map(json.loads, fd)
# Here we load all vectors into memory, numpy array works as iterable for itself.
# Other option would be to use Mmap, if we don't want to load all data into RAM
vectors = np.load('./startup_vectors.npy')
最后一步 - 数据上传
qdrant_client.upload_collection(
collection_name='startups',
vectors=vectors,
payload=payload,
ids=None, # Vector ids will be assigned automatically
batch_size=256 # How many vectors will be uploaded in a single request?
)
现在我们已经将向量上传到向量搜索引擎。在下一步中,我们将学习如何实际搜索最近的向量。
本步骤的完整代码可以在这里找到。
步骤 4:构建搜索 API
现在所有准备工作都已完成,让我们开始构建一个神经搜索类。
首先,安装所有依赖项
pip install sentence-transformers numpy
为了处理传入的请求,神经搜索需要两样东西:一个将查询转换为向量的模型和用于执行搜索查询的 Qdrant 客户端。
# File: neural_searcher.py
from qdrant_client import QdrantClient
from sentence_transformers import SentenceTransformer
class NeuralSearcher:
def __init__(self, collection_name):
self.collection_name = collection_name
# Initialize encoder model
self.model = SentenceTransformer('all-MiniLM-L6-v2', device='cpu')
# initialize Qdrant client
self.qdrant_client = QdrantClient(host='localhost', port=6333)
搜索函数尽可能简单
def search(self, text: str):
# Convert text query into vector
vector = self.model.encode(text).tolist()
# Use `vector` for search for closest vectors in the collection
search_result = self.qdrant_client.search(
collection_name=self.collection_name,
query_vector=vector,
query_filter=None, # We don't want any filters for now
top=5 # 5 the most closest results is enough
)
# `search_result` contains found vector ids with similarity scores along with the stored payload
# In this function we are interested in payload only
payloads = [hit.payload for hit in search_result]
return payloads
使用 Qdrant,也可以为搜索添加一些条件。例如,如果我们想搜索某个城市的创业公司,搜索查询可以像这样:
from qdrant_client.models import Filter
...
city_of_interest = "Berlin"
# Define a filter for cities
city_filter = Filter(**{
"must": [{
"key": "city", # We store city information in a field of the same name
"match": { # This condition checks if payload field have requested value
"keyword": city_of_interest
}
}]
})
search_result = self.qdrant_client.search(
collection_name=self.collection_name,
query_vector=vector,
query_filter=city_filter,
top=5
)
...
我们现在有了一个用于执行神经搜索查询的类。让我们将其打包成一个服务。
步骤 5:部署为服务
为了构建该服务,我们将使用 FastAPI 框架。它非常易于使用,并且只需极少的代码编写。
要安装它,使用以下命令
pip install fastapi uvicorn
我们的服务将只有一个 API 端点,并且会像这样:
# File: service.py
from fastapi import FastAPI
# That is the file where NeuralSearcher is stored
from neural_searcher import NeuralSearcher
app = FastAPI()
# Create an instance of the neural searcher
neural_searcher = NeuralSearcher(collection_name='startups')
@app.get("/api/search")
def search_startup(q: str):
return {
"result": neural_searcher.search(text=q)
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
现在,如果你使用以下命令运行该服务:
python service.py
并在浏览器中打开http://localhost:8000/docs,你应该能够看到服务的调试界面。
欢迎自由地试用它,进行查询并查看结果。本教程到此结束。
体验 Qdrant 的免费神经搜索演示
渴望看到神经搜索的实际应用吗?迈出下一步,预订一个与 Qdrant 一起的免费演示!亲身体验这项尖端技术如何改变你的搜索能力。
我们的演示将帮助你建立对神经搜索何时有用的直观认识。演示包含一个可以在神经搜索和全文搜索之间切换的开关。你可以开启或关闭神经搜索,以便与常规全文搜索的结果进行比较。尝试使用创业公司描述来查找相似的公司。
加入我们的Discord 社区,在这里我们讨论向量搜索和相似性学习,并发布神经网络和神经搜索应用的其他示例。