这里写目录标题
流程概述
实施嵌入技术和向量存储以进行语义搜索的工作流程包括以下步骤:
1. 文档分割
初始步骤涉及将原始文档集合分解成更小、更易于管理的部分,这些部分在语义上是连贯的。这一过程称为文档分割,对于提高搜索结果的粒度至关重要。每个片段或段落应理想地代表单一的主题或概念,以确保后续步骤生成的嵌入准确捕捉文本的语义本质。这一步增强了系统匹配文档特定部分与查询的能力,而不是检索可能只部分相关的整个文档。
2. 嵌入生成
一旦文档被分割成语义连贯的片段,接下来的步骤就是将这些片段转化为嵌入。嵌入生成涉及使用机器学习模型将文本映射到高维向量。这些向量代表文本的语义特征,使得意义相似的文本片段由向量空间中相互接近的向量表示。这一步是将文本信息转换为计算系统可以高效处理和比较的格式的基础。
3. 向量存储索引
在为每个文档片段生成嵌入后,这些嵌入随后被存储在一个向量存储中。向量存储是一个专门设计用于高效存储和检索高维向量数据的数据库。通过在向量存储中对嵌入进行索引,系统可以快速执行相似性搜索,找到与给定查询向量最相似的向量。这一能力是实现快速且精确地检索与用户搜索查询相关的文档片段的关键。
4. 查询处理
当用户提交查询时,系统使用与文档片段相同的过程生成查询的嵌入。然后使用此查询嵌入在向量存储中搜索与之最相似的嵌入。相似性搜索可以基于各种距离度量,如欧几里得距离或余弦相似度,以识别那些与查询嵌入距离最短或相似度最高的文档片段。这一步确保搜索结果在语义上与查询相关,提高了检索信息的相关性。
5. 响应生成
最后一步涉及将检索到的文档片段传递给大型语言模型 (LLM),同时附上原始查询。LLM 使用来自文档片段和查询的信息生成连贯且语境相关的响应。这一过程利用了 LLM 理解和生成自然语言的能力,为用户提供不仅相关而且易于理解的答案。这一步对于通过基于向量存储检索到的语义相关文档片段提供精确且信息丰富的答案来提升用户体验至关重要。
环境设置
在深入探讨嵌入技术和向量存储的复杂性之前,准备开发环境至关重要。这涉及导入必要的库、设置 API 密钥,并确保系统正确配置。
import os
import openai
import sys
from dotenv import load_dotenv, find_dotenv
# 扩展系统路径以包括项目目录
sys.path.append('../..')
# 加载环境变量
load_dotenv(find_dotenv())
# 配置 OpenAI API 密钥
openai.api_key = os.environ['OPENAI_API_KEY']
文档加载与分割
工作流程的初始阶段涉及加载文档并将它们分割成更小、语义有意义的片段。这一步对于更有效地管理数据和准备嵌入至关重要。
加载文档
为了演示目的,从一系列讲座视频中加载了一系列 PDF 文档。这包括有意重复一个文档以模拟数据杂乱的情况。
# 导入 langchain 库中的 PyPDFLoader 类
from langchain.document_loaders import PyPDFLoader
# 初始化一个 PDF 加载器列表,每个代表一个具体的讲座文档
pdf_document_loaders = [
PyPDFLoader("docs/doc1.pdf"),
PyPDFLoader("docs/doc2.pdf"),
PyPDFLoader("docs/doc3.pdf"),
]
# 创建一个空列表来存储每个已加载文档的内容
loaded_documents_content = []
# 遍历列表中的每个 PDF 加载器来加载文档
for document_loader in pdf_document_loaders:
# 使用每个 PyPDFLoader 实例的 load 方法来加载文档内容
# 并将结果扩展到 loaded_documents_content 列表中
loaded_documents_content.extend(document_loader.load())
# 此时,loaded_documents_content 包含所有指定 PDF 的内容
分割文档
加载后,文档被分割成更小的片段,以增强后续过程的可管理性和效率。
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 配置并应用文本分割器
document_splitter = RecursiveCharacterTextSplitter(
chunk_size=1500,
chunk_overlap=150
)
document_splits = document_splitter.split_documents(loaded_documents_content) # 注意这里的参数应为 loaded_documents_content 而不是 documents
请注意,在实际代码中,split_documents
方法的参数应该是 loaded_documents_content
而不是 documents
。这是因为 loaded_documents_content
包含实际加载后的文档内容,而 documents
在此上下文中未定义。
生成嵌入
为每个文档片段创建嵌入,将文本信息转化为数值向量,这些向量捕捉文本的语义本质。
from langchain.embeddings.openai import OpenAIEmbeddings
import numpy as np
# 初始化嵌入生成器
embedding_generator = OpenAIEmbeddings()
# 示例句子用于生成嵌入
sentence_examples = ["我喜欢狗", "我喜欢犬类", "外面的天气很糟糕"]
# 为每个句子生成嵌入
embeddings = [embedding_generator.embed_query(sentence) for sentence in sentence_examples]
# 通过点积展示相似性
similarity_dog_canine = np.dot(embeddings[0], embeddings[1])
similarity_dog_weather = np.dot(embeddings[0], embeddings[2])
向量存储以实现高效检索
生成嵌入后,下一步是在向量存储中对这些向量进行索引以方便高效的相似性搜索。
设置 Chroma 作为向量存储
选择 Chroma 是因为其轻量级和内存特性,适合演示用途。
from langchain.vectorstores import Chroma
# 定义用于持久化向量存储的目录
persist_directory = 'docs/chroma/'
# 清除持久化目录中的现有数据
!rm -rf ./docs/chroma
# 初始化并填充向量存储,使用文档片段及其嵌入
vector_database = Chroma.from_documents(
documents=document_splits,
embedding=embedding_generator,
persist_directory=persist_directory
)
进行相似性搜索
嵌入和向量存储的主要用途是通过相似性搜索来检索与给定查询最相关的文档片段。
# 示例查询
query = "我可以询问帮助的电子邮件是什么?"
# 检索最相关的前3个文档片段
retrieved_documents = vector_database.similarity_search(query, k=3)
# 查看最相关结果的内容
print(retrieved_documents[0].page_content)
处理失败模式并改进搜索
虽然基本的相似性搜索非常有效,但某些边缘情况和失败模式需要进一步的改进。
识别和处理失败模式
重复条目和包含其他讲座中的无关文档是常见的问题,这些问题会削弱语义搜索的有效性。
# 示例失败模式查询
query_matlab = "他们对 MATLAB 说了什么?"
# 识别检索结果中的重复片段
retrieved_documents_matlab = vector_database.similarity_search(query_matlab, k=5)
未来的讨论将探索处理这些失败模式的策略,确保检索到既相关又独特的片段。
结论
嵌入和向量存储为大规模文本集合提供了强大的语义搜索工具。通过仔细地将文本处理成嵌入并利用高效的向量检索机制,开发者可以创建出能够理解和响应复杂查询的复杂系统。探索失败模式和改进策略进一步增强了这些系统的稳健性和准确性。
进一步阅读
- OpenAI API 文档:使用 OpenAI 模型生成嵌入的深入指南。
- 向量数据库技术:各种向量存储及其在语义搜索和检索系统中的应用的比较。
理论问题
- 将文本信息转化为嵌入的主要目的是什么?
- 嵌入如何帮助实现单词或句子之间的语义相似性?
- 描述创建词嵌入的过程以及上下文在此过程中的重要性。
- 嵌入如何在语义搜索中改善搜索结果,与传统的基于关键词的搜索相比?
- 描述文档嵌入和查询嵌入在语义搜索过程中的作用。
- 什么是向量存储,为什么它在嵌入的上下文中很重要?
- 在为特定应用选择向量存储时应该考虑哪些标准?
- 为什么 Chroma 可能适用于快速原型设计和小型数据集,它的局限性是什么?
- 描述在语义搜索中实施嵌入和向量存储的工作流程。
- 文档分割如何增强语义搜索系统中搜索结果的粒度和相关性?
- 描述为文档片段生成嵌入的过程及其在语义搜索中的重要性。
- 向量存储索引在相似性搜索的上下文中为何重要?
- 查询处理在语义搜索中是如何工作的,通常使用哪些度量来比较查询嵌入与文档嵌入?
- 解释工作流程中的响应生成步骤如何增强语义搜索应用中的用户体验。
- 实施嵌入和向量存储以进行语义搜索需要哪些初步步骤?
- 描述一个实际场景,在该场景中,文档加载和分割是处理文本数据以进行语义搜索的关键步骤。
- 生成嵌入如何转换文本信息,举例说明如何展示嵌入之间的相似性。
- 在设置 Chroma 这样的向量存储以实现高效检索时应考虑哪些因素?
- 相似性搜索如何促进语义搜索系统中相关文档片段的检索?
- 识别语义搜索中的潜在失败模式,并解释改进搜索准确性和相关性的策略。
练习问题
基于章节内容,这里有一些与嵌入、向量存储及其在语义搜索中的应用相关的实践编码任务:
- 编写一个名为
generate_embeddings
的 Python 函数,该函数接受字符串列表(句子)作为输入,并返回嵌入列表。使用占位符模型来模拟嵌入生成过程(例如,简单返回每个字符串的长度作为其“嵌入”)。 - 实现一个名为
cosine_similarity
的 Python 函数,该函数计算并返回两个向量之间的余弦相似度。向量可以用数字列表表示。假设两个向量具有相同的维度。 - 创建一个名为
SimpleVectorStore
的 Python 类,该类模拟向量存储的基本功能。该类应支持添加向量(add_vector
方法)和根据余弦相似度检索与给定查询向量最相似的向量(find_most_similar
方法)。 - 编写一个 Python 脚本,从文件加载文本,将文本分割成指定大小的片段(例如,500 个字符),并打印每个片段。假设文件路径和片段大小作为命令行参数提供。
- 开发一个名为
query_processing
的 Python 函数,该函数模拟生成查询嵌入、在SimpleVectorStore
中进行相似性搜索并打印最相关文档片段内容的过程。使用占位符生成查询嵌入。 - 实现一个名为
remove_duplicates
的函数,该函数接收文档片段列表(字符串),并返回一个去重后的新列表。定义考虑片段是否重复的标准(例如,完全匹配或相似性阈值)。 - 编写一个 Python 脚本,初始化一个
SimpleVectorStore
,添加一组文档嵌入(使用占位符),然后针对样本查询执行相似性搜索。打印最相关的前 3 个文档片段的 ID 或内容。 - 创建一个名为
embed_and_store_documents
的函数,该函数接收文档片段列表,为每个片段生成嵌入(使用占位符),并将这些嵌入存储在SimpleVectorStore
中。函数应返回初始化的SimpleVectorStore
。 - 开发一个名为
vector_store_persistence
的 Python 函数,该函数演示如何将SimpleVectorStore
的状态保存到文件中以及从文件中加载。实现向量存储数据的序列化和反序列化方法。 - 编写一个名为
evaluate_search_accuracy
的 Python 函数,该函数接收查询列表及其预期最相关的文档片段。该函数应为每个查询执行相似性搜索,将检索到的片段与预期结果进行比较,并计算搜索结果的准确率。