所以你想尝试向量搜索,但不想支付 OpenAI 的费用,也不想使用 Huggingface,并且不想支付向量数据库公司的费用。我来帮助你。让我们在你自己的机器上免费启动向量搜索。
文章缩略图图像
我们在做什么?让我们稍微回退一步,谈谈我们正在做什么以及为什么要这样做。基于AI嵌入的向量搜索是一种基于概念创建搜索的方法。例如,搜索“宠物”可能会同时返回狗和猫的结果。这非常有价值,因为它意味着您的客户可以获得更好的搜索结果。
为了实现这一点,我们首先将要搜索的文本发送给AI,让它创建一个“嵌入”。嵌入是一个长的浮点数值数组,通常包含大约300到1500个数字。
猫、狗和宠物的嵌入值会比较相似。所以如果你比较猫和狗,它们会很接近,而狗和披萨则不会接近。
向量数据库允许你存储这些向量及其相关联的数据(可能是原始的数据文本)。一旦数据被存储,你就可以用一个新的向量查询数据库以获取任何附近的匹配结果。例如,如果我们把“猫”和“狗”及其嵌入向量存储在数据库中,然后我们再输入文本“宠物”,创建该文本的嵌入向量,再用这个向量查询数据库,我们很可能会得到“猫”和“狗”作为返回结果。
为什么是Postgres和OpenLlama?Postgres 是一个出色的数据库,你可以轻松地在本地安装和运行它。并且通过 pgvector
扩展,你可以在 Postgres 中创建可以在 SQL 查询中使用的向量字段。
在你的机器上安装 Postgres 有多种方式。在我的 Mac 上,我使用 Postgres.app 来安装 Postgres。
OpenLlama 是一种非常简单的方式,可以在本地安装和运行 AI 模型。我使用 Homebrew 安装了 OpenLlama,命令为 brew install ollama
。
对于我们的简单测试应用程序,我们将加载来自1986年恐怖电影《异形》的所有台词。
设置环境有许多模型可供选择,详情请见这里。对于这个应用,我选择了 [snowflake-arctic-embed](https://ollama.com/library/snowflake-arctic-embed)
,因为它非常适合快速生成嵌入。安装时我使用了命令 ollama pull snowflake-arctic-embed
。
设置的最后一步是创建一个本地Postgres数据库。你可以给它起任何你喜欢的名字,我选择将它命名为lines
,因为我们正在搜索电影中的台词。
有了数据库后,我们可以使用 psql
命令来运行一些命令。第一个命令是向数据库中添加 vector
扩展。这将启用 vector
字段类型。为此,我使用了 create extension 命令:
CREATE EXTENSION vector;
现在我们需要创建一个表来存储行文本以及向量,以下是创建表以及在 position
值上创建索引的命令,position
是行在脚本中的位置。
CREATE TABLE lines (
id bigserial PRIMARY KEY,
position INT,
text TEXT,
embedding VECTOR(1024)
);
CREATE UNIQUE INDEX position_idx ON lines (position);
需要注意的是向量的大小。不同的模型会产生不同大小的向量。在我们的雪flake模型中,嵌入大小为1,024个数字,所以我们设置向量大小为这个数值。
你希望在存储和查询时使用相同的嵌入式AI。如果你使用不同的模型,那么数字将无法对齐。
创建向量索引正如你可以想象的,比较两个包含1,024个浮点数值的数组可能会很耗时。而比较大量的数组则会非常耗时。因此,这些新的向量数据库提出了不同的索引模型来提高效率。Postgres的向量支持有不同类型的索引,我们将使用Hierarchical Navigable Small Worlds(HNSW)类型来创建三个不同的索引:
创建索引 ON lines 使用 hnsw (embedding vector_ip_ops);
创建索引 ON lines 使用 hnsw (embedding vector_cosine_ops);
创建索引 ON lines 使用 hnsw (embedding vector_l1_ops);
为什么是三种?因为比较两个向量有多种方式。常用的是余弦相似度,适合做概念上的比较。还有欧几里得距离和点积比较。Postgres 支持所有这些方法(以及其他方法)。
无论你使用哪种方法,都确保启用了索引,以便能够进行高速查询。
加载数据库模型下载完成后,以及Postgres设置好之后,我们现在可以开始用我们的电影台词及其嵌入向量来填充数据库了。我已经在Github上发布了整个项目,该项目还包括一个NextJS App Router UI。脚本位于load-embeddings目录。原始数据来自这个脚本页面。
在你可以加载数据之前,你需要将 .env.example
文件复制为 .env
,然后将值更改为与你的 Postgres 连接详情匹配。
要将嵌入加载到Postgres中,请使用Node 20或更高版本运行node loader.mjs
。
脚本的关键部分是嵌入生成:
import ollama from "ollama";
...
const response = await ollama.embeddings({
model: "snowflake-arctic-embed",
prompt: text,
});
在哪里我们使用 ollama
库逐行调用 snowflake 嵌入模型。
我们然后使用 INSERT
语句将该行插入数据库中:
await sql`INSERT INTO lines
(position, text, embedding)
VALUES
(${position}, ${text}, ${`[${response.embedding.join(",")}]`})
`;
这里唯一棘手的地方是如何格式化嵌入,方法是将所有数字连接成一个字符串,并用括号包裹起来。
将所有数据加载到数据库中后,现在是时候进行查询了。
制作我们的第一个查询为了确保这能正常工作,有一个 [test-query.mjs](https://github.com/jherr/aliens-vector-search/blob/main/load-embeddings/test-query.mjs)
文件位于 [load-embeddings](https://github.com/jherr/aliens-vector-search/blob/main/load-embeddings/test-query.mjs)
目录中。为了进行向量查询,我们首先运行模型将查询转换为向量,如下所示:
const response = await ollama.embeddings({
model: "snowflake-arctic-embed",
prompt: "食物",
});
在这种情况下,提示词是 food
,我们使用与加载脚本中相同的过程将其转换为嵌入。
我们然后使用 SQL SELECT
语句查询带有该向量的数据库:
const query = await sql`SELECT
位置, 文本
FROM
线条
ORDER BY
嵌入向量 <#> ${`[${response.embedding.join(",")}]`}
LIMIT 10`;
我们使用 ORDER BY
按数据库中记录与给定嵌入的相似度对记录进行排序,然后使用 LIMIT
获取最相似的前 10 条记录。
<#>
语法在 ORDER BY
中很重要,因为它定义了使用哪种比较算法。根据文档,我们的选项有:
<-> - L2 距离
<#> - (负) 内积
<=> - 余弦距离
<+> - L1 距离 (在 0.7.0 版本中添加)
你可以自己决定哪种比较方法能为你的应用提供最佳输出,但请确保根据所选的比较方法正确地为表建立索引。
在我的机器上,这个测试查询返回了其他一些内容:
317 看来她也不喜欢玉米面包。
哪一句是电影中提到的一种食物(玉米面包)的经典台词。
一个用户界面的搭建通过稍微多一点的努力,我在上面添加了一个 NextJS App Router 接口,你可以在项目根目录下通过运行 pnpm dev
来体验它,前提是数据库已经加载并且 .env
文件已经正确设置。
这个 NextJS 应用使用完全相同的 SELECT
操作从数据库查询行。
显然你不会用一个搜索《异形》剧本的应用程序来投入生产。但从我这里展示的内容来看,你可以搜索文本内容、产品描述、评论,几乎任何类型的文本。
享受吧!
一个简单的解释 🚀感谢你成为In Plain English社区的一员!在你离开之前:
- 确保 点赞 并 关注 作者 👏
- 关注我们:X | LinkedIn | YouTube | Discord | Newsletter
- 访问我们的其他平台:CoFeed | Differ
- 更多内容请访问 PlainEnglish.io
共同学习,写下你的评论
评论加载中...
作者其他优质文章