说在前面
上一节,我们介绍了语义搜索的基础知识,并做了一些实践案例,可以看到在有些情况下效果不错,但同时也能看到存在一些边缘情况。本节将介绍 检索(Retrieval)以及讲解一些解决这些边缘案例的高级方法。(视频时长11:48)
Main Content
检索(Retrieval)是检索增强生成(Retrieval Augmented Generation,RAG)的核心,指根据用户的问题去向量数据库中搜索与问题相关的文档内容。当我们访问和查询向量数据库时可能会运用到以下技术:
- 基本语义相似度(Basic semantic similarity)
- 最大边际相关性(Maximum marginal relevance,MMR)
- 过滤元数据
- LLM辅助检索
本节将介绍几种检索方法,以及解决前述’Failure modes‘的技巧。
前置工作
1.导入环境变量和 OPENAI API
import os
import openai
import sys
sys.path.append('../..')
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
openai.api_key = os.environ['OPENAI_API_KEY']
Vectorstore retrieval
Similarity Search
1.导入 Chroma 和 OpenAIEmbeddings,并做好初始化,文件内容使用的是上一节存储在 'docs/chroma/'
中的讲义数据。
from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings
persist_directory = 'docs/chroma/'
embedding = OpenAIEmbeddings()
vectordb = Chroma(
persist_directory=persist_directory,
embedding_function=embedding
)
2.向量存储中一共有 209 个内容。
print(vectordb._collection.count())
3.下面设定 texts
,并将其进行向量存储到 smalldb
中。用来做该部分的演示。
texts = [
"""The Amanita phalloides has a large and imposing epigeous (aboveground) fruiting body (basidiocarp).""",
"""A mushroom with a large fruiting body is the Amanita phalloides. Some varieties are all-white.""",
"""A. phalloides, a.k.a Death Cap, is one of the most poisonous of all known mushrooms.""",
]
smalldb = Chroma.from_texts(texts, embedding=embedding)
4.设定问题。
question = "Tell me about all-white mushrooms with large fruiting bodies"
5.在 samllbd
中做语义相似查询,k 设定为 2 返回两条相似度最高的 chunk。
smalldb.similarity_search(question, k=2)
我们可以看到,相似性搜索 similarity_search 返回两个文档,是texts的第一句和第二句。它们都与用户问题有关,且含义非常相近,其实只返回其中一句足以满足要求。
Addressing Diversity: Maximum marginal relevance
在 smallbd
中做最大边际相关搜索 MMR,设定返回的 chunk 数为 k=2
。
smalldb.max_marginal_relevance_search(question,k=2, fetch_k=3)
最大边际相关(Maximum Marginal Relevance)是实现多样性检索的常用算法。
MMR的介绍
MMR会平衡结果的相关性(relevance)和多样性(diversity),以避免返回内容冗余的结果。
LangChain 的 max_marginal_relevance_search
函数使用MMR算法初步检索 fetch_k
个文档,选择前k个返回。
下面是一个MMR与SS(语义相似度搜索)的效果对比。
question = "what did they say about matlab?"
docs_ss = vectordb.similarity_search(question,k=3)
docs_ss[0].page_content[:100]
docs_ss[1].page_content[:100]
docs_mmr = vectordb.max_marginal_relevance_search(question,k=3)
docs_mmr[0].page_content[:100]
docs_mmr[1].page_content[:100]
Addressing Specificity: working with metadata
上一节的 Failure modes中要求只在Lecture03中搜索答案,但是结果中额外包含 Lecture01 和Lecture02 的内容。我们可以通过过滤元数据(metadata)的方式实现精准搜索,从而解决这个问题。
1.设定问题,要求查找 Lecture03 中的内容。
question = "what did they say about regression in the third lecture?"
2.初始化语义相似度搜索,添加过滤器 filter
设定对 metadata 进行过滤,要求只能来源只能是 Lecture03。
docs = vectordb.similarity_search(
question,
k=3,
filter={"source":"docs/cs229_lectures/MachineLearning-Lecture03.pdf"}
)
3.打印查找到的文档可以看到过滤后,就都来自 Lecture03。
for d in docs:
print(d.metadata)
Addressing Specificity: working with metadata using self-query retriever
上面的 filter
中的过滤条件是手动设置的,有没有方法可以准确识别问题中的语义从而实现元数据自动过滤呢?LangChain提供 SelfQueryRetriever
解决这个问题,它使用LLM提取query 和 filter。
1.导入 SelfQueryRetriever
。
from langchain.llms import OpenAI
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
2.定义元数据过滤条件。metadata_field_info
参数用于描述元数据中的字段及其类型和含义。这个参数的设置对检索器如何解析查询并匹配元数据非常关键。根据 metadata_field_info
信息,LLM会自动从用户问题中提取 query 和 filter,然后向量数据库基于这两项去搜索相关内容。
metadata_field_info = [
AttributeInfo(
name="source",
description="The lecture the chunk is from, should be one of `docs/cs229_lectures/MachineLearning-Lecture01.pdf`, `docs/cs229_lectures/MachineLearning-Lecture02.pdf`, or `docs/cs229_lectures/MachineLearning-Lecture03.pdf`",
type="string",
),
AttributeInfo(
name="page",
description="The page from the lecture",
type="integer",
),
]
document_content_description = "Lecture notes"
llm = OpenAI(model='gpt-3.5-turbo-instruct', temperature=0)
retriever = SelfQueryRetriever.from_llm(
llm,
vectordb,
document_content_description,
metadata_field_info,
verbose=True
)
3.LLM提取 query 和filter,query=‘regression’ 来自用户问题,filter是 LLM 根据 metadata_field_info和用户问题设定的过滤条件。
question = "what did they say about regression in the third lecture?"
docs = retriever.get_relevant_documents(question)
4.可以看到成功的按照我们的要求进行了查找。
for d in docs:
print(d.metadata)
Additional tricks: compression
另一种提高检索文档质量的方法是压缩。向量数据库会返回与问题相关的 chunk 中的所有内容,其中包含大量不相关的信息。LangChain提供 ContextualCompressionRetriever
对返回的完整chunk进行压缩,提取与用户问题相关的内容。可以有效提升输出质量,减少计算资源的浪费。
1.导入 ContextualCompressionRetriever
和 LLMChainExtractor
。
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
def pretty_print_docs(docs):
print(f"\n{'-' * 100}\n".join([f"Document {i+1}:\n\n" + d.page_content for i, d in enumerate(docs)]))
3.设定压缩器。
# Wrap our vectorstore
llm = OpenAI(temperature=0, model="gpt-3.5-turbo-instruct")
compressor = LLMChainExtractor.from_llm(llm)
4.进行压缩并打印。
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectordb.as_retriever()
)
question = "what did they say about matlab?"
compressed_docs = compression_retriever.get_relevant_documents(question)
pretty_print_docs(compressed_docs)
我们定义了压缩器LLMChainExtractor,负责从向量数据库返回的chunk中提取信息。ContextualCompressionRetriever有两个参数,一个是压缩器LLMChainExtractor实例,另一个是vectordb检索器。
Combining various techniques
这里我们把上面的几种方法结合一下(Compression + metadata)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectordb.as_retriever(search_type = "mmr")
)
question = "what did they say about matlab?"
compressed_docs = compression_retriever.get_relevant_documents(question)
pretty_print_docs(compressed_docs)
Other types of retrieval
vectordb并不是LangChain唯一的检索器,LangChain还提供了其他检索文档的方式,例如TF-IDF、SVM。
from langchain.retrievers import SVMRetriever
from langchain.retrievers import TFIDFRetriever
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Load PDF
loader = PyPDFLoader("docs/cs229_lectures/MachineLearning-Lecture01.pdf")
pages = loader.load()
all_page_text=[p.page_content for p in pages]
joined_page_text=" ".join(all_page_text)
# Split9
text_splitter = RecursiveCharacterTextSplitter(chunk_size = 1500,chunk_overlap = 150)
splits = text_splitter.split_text(joined_page_text)
# Retrieve
svm_retriever = SVMRetriever.from_texts(splits,embedding)
tfidf_retriever = TFIDFRetriever.from_texts(splits)
question = "What are major topics for this class?"
docs_svm=svm_retriever.get_relevant_documents(question)
docs_svm[0]
question = "what did they say about matlab?"
docs_tfidf=tfidf_retriever.get_relevant_documents(question)
docs_tfidf[0]
总结
本节介绍了关于 Retrieval 的内容。这部分的内容也是现在的 RAG 方向比较新的和充满挑战的方向,学习这部分可以多去阅读最新的文献去了解技术的动向。