大模型笔记(四):向量库/检索召回的不同方式-Chroma/FAISS

目录

一、llm接口的一般形式

二、token、chunk

2.1 文本拆分器的类型

2.2 按特定字符拆分

2.3 语义组块 Semantic Chunking

2.4 按token拆分 

三、向量库→检索器/检索方式

3.1 Chroma读取文件后返回类对象的方法 as_retriever

3.2 FAISS读取文件后返回类对象的方法 as_retriever

3.3 高级检索方法

四、embedding模型


一、llm接口的一般形式

填入你的api_key,如果是中转api,还需要base_url。

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    openai_api_key = "sk-JIs8b5bDG2CJezw4295f8287512245F2BdC45fCeAb6d85Cd",
    openai_api_base = "http://192.2.22.55:3001/v1",
    model="deepseek-chat")

不同类型文件导入

# URL 网页加载器生成文档
def load_docs_url():
    from langchain_community.document_loaders import WebBaseLoader
    loader = WebBaseLoader(
        web_paths=("https://blogs.nvidia.com/blog/what-is-retrieval-augmented-generation/",),
        bs_kwargs=dict(
            parse_only=bs4.SoupStrainer(
                class_=("entry-content", "entry-header", "entry-title")
            )
        ),
    )
    docs = loader.load()
    return docs


# TXT文本文件加载器加载生成文档
def load_docs_csv():
    from langchain_community.document_loaders.csv_loader import CSVLoader
    loader = CSVLoader(file_path='data/专业描述.csv', csv_args={
        'delimiter': ',',
        'quotechar': '"',
        'fieldnames': ['专业', '描述']
    }, encoding='utf8', source_column='专业')
    docs = loader.load()
    print(docs)
    return docs


# Word文件加载器加载生成文档
def load_docs_word():
    from langchain_community.document_loaders.word_document import UnstructuredWordDocumentLoader
    loader = UnstructuredWordDocumentLoader(file_path="data/demo.docx")
    # loader = UnstructuredWordDocumentLoader(file_path='data/demo.docx', mode="elements",strategy="fast", )
    docs = loader.load()
    print(docs)
    return docs

# PDF文件加载器加载生成文档
def load_docs_pdf():
    from langchain_community.document_loaders.pdf import UnstructuredPDFLoader
    loader = UnstructuredPDFLoader(file_path="data/demo.pdf")
    # loader = UnstructuredPDFLoader(file_path='data/demo.pdf', mode="elements",strategy="fast", )
    docs = loader.load()
    print(docs)
    return docs

# PowerPoint文件加载器加载生成文档
def load_docs_ppt():
    from langchain_community.document_loaders.powerpoint import UnstructuredPowerPointLoader
    loader = UnstructuredPowerPointLoader(file_path="data/demo.ppt")
    # loader = UnstructuredPowerPointLoader(file_path='data/demo.pptx', mode="elements",strategy="fast", )
    docs = loader.load()
    print(docs)
    return docs

二、token、chunk

transformers库里面一些bert_base模型的输入是tokenizer之后的句子;目前大模型都是一次性可以输入几千token,所以使用textspliter分chunk就可以

而常用的分词token方法主要有:

  1. 基于词典匹配
  2. 基于统计
  3. 基于深度学习

Tokenization(分词) 在自然语言处理(NLP)的任务中是最基本的一步,把文本内容处理为最小基本单元即token(标记,令牌,词元,没有准确的翻译),基本思想是构建一个词表通过词表一一映射进行分词,但如何构建合适的词表呢?以分词粒度的角度有:word(词)粒度、char(字符)粒度、subword(子词)粒度【WordPiece、Byte-Pair Encoding (BPE)、Byte-level BPE(BBPE)】

将一个长文档分割成更小的块,以便适合模型的上下文窗口。LangChain有许多内置的文档转换器,可以轻松地拆分、组合、过滤和操作文档。

2.1 文本拆分器的类型

  • Name:文本拆分器的名称
  • Splits on:此文本拆分器如何拆分文本
  • Adds Metadata:此文本拆分器是否添加关于每个块来源的元数据。
  • Description:拆分器的描述,包括何时使用它的建议。
NameSplits OnAdds MetadataDescription
RecursiveA list of user defined characters递归拆分文本。递归分割文本的目的是试图将相关的文本片段保持在一起。这是开始拆分文本的推荐方式。
HTMLHTML specific characters基于特定于HTML的字符拆分文本。值得注意的是,这添加了关于该块来自何处的相关信息(基于HTML)
MarkdownMarkdown specific characters基于特定于Markdown的字符拆分文本。值得注意的是,这增加了关于该块来自哪里的相关信息(基于降价)
CodeCode (Python, JS) specific characters基于特定于编码语言的字符拆分文本。有15种不同的语言可供选择。
TokenTokens拆分令牌上的文本。有几种不同的方法来衡量token。
CharacterA user defined character基于用户定义的字符拆分文本。一种更简单的方法。
[Experimental] Semantic ChunkerSentences首先对句子进行拆分。然后,如果它们在语义上足够相似,就将它们组合在一起。取自Greg Kamradt

2.2 按特定字符拆分

from langchain_text_splitters import CharacterTextSplitter
 
text_splitter = CharacterTextSplitter(
    separator="\n\n",
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,
    is_separator_regex=False,
)
 
texts = text_splitter.create_documents([state_of_the_union])
print(texts[0])

2.2 按字符递归拆分

from langchain_text_splitters import RecursiveCharacterTextSplitter
 
# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:
    state_of_the_union = f.read()
text_splitter = RecursiveCharacterTextSplitter(
    # Set a really small chunk size, just to show.
    chunk_size=100,
    chunk_overlap=20,
    length_function=len,
    is_separator_regex=False,
)
 
texts = text_splitter.create_documents([state_of_the_union])
print(texts[0])
print(texts[1])

2.3 语义组块 Semantic Chunking

基于语义相似性拆分文本。

# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:
    state_of_the_union = f.read()
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
 
text_splitter = SemanticChunker(OpenAIEmbeddings())
docs = text_splitter.create_documents([state_of_the_union])
print(docs[0].page_content)

2.4 按token拆分 

1、tiktoken

from langchain_text_splitters import CharacterTextSplitter
# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:
    state_of_the_union = f.read()
使用from_tiktoken_encoder()方法采用model_name作为一个参数(例如gpt-4)。所有附加参数,如chunk_size, chunk_overlap,以及separators用于实例化CharacterTextSplitter:

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    model_name="gpt-4", chunk_size=100, chunk_overlap=0
)
texts = text_splitter.split_text(state_of_the_union)
   

from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    model_name="gpt-4",
    chunk_size=100,
    chunk_overlap=0,
)
        

from langchain_text_splitters import TokenTextSplitter
text_splitter = TokenTextSplitter(chunk_size=10, chunk_overlap=0)
 
texts = text_splitter.split_text(state_of_the_union)
print(texts[0])

2、spaCy:如何测量块大小:通过字符数。

from langchain_text_splitters import SpacyTextSplitter
# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:
    state_of_the_union = f.read()
text_splitter = SpacyTextSplitter(chunk_size=1000)
 
texts = text_splitter.split_text(state_of_the_union)
print(texts[0])

3、SentenceTransformers
默认行为是将文本分割成适合您想要使用的句子转换器模型的标记窗口的块。

from langchain_text_splitters import SentenceTransformersTokenTextSplitter
splitter = SentenceTransformersTokenTextSplitter(chunk_overlap=0)
text = "Lorem "
count_start_and_stop_tokens = 2
text_token_count = splitter.count_tokens(text=text) - count_start_and_stop_tokens
print(text_token_count)
token_multiplier = splitter.maximum_tokens_per_chunk // text_token_count + 1
 
# `text_to_split` does not fit in a single chunk
text_to_split = text * token_multiplier
 
print(f"tokens in text to split: {splitter.count_tokens(text=text_to_split)}")
text_chunks = splitter.split_text(text=text_to_split)
 
print(text_chunks[1])

4、NLTK如何测量块大小:通过字符数。

from langchain_text_splitters import NLTKTextSplitter
 
# This is a long document we can split up.
with open("./demo_static/splitters_test.txt") as f:
    state_of_the_union = f.read()
text_splitter = NLTKTextSplitter(chunk_size=1000)
 
texts = text_splitter.split_text(state_of_the_union)
print(texts[0])

三、向量库→检索器/检索方式

嵌入向量需要高效索引器

3.1 Chroma读取文件后返回类对象的方法 as_retriever

在 client = chromadb.Client()中,client 是一个 chromadb.Client 类的实例化对象。

client 对象通常用于执行以下操作:连接到数据库、对数据库执行各种查询操作,如插入数据、检索数据、更新数据、删除数据等、管理集合:你可以使用 client 对象来创建、删除或操作集合(类似于表格的概念)。

vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)
retriever = vectorstore.as_retriever()

Chroma.from_documents:会创建一个向量存储,将文档转换为嵌入向量,并将它们存储在向量数据库中。

as_retriever:将 vectorstore 转换为一个检索器对象(retriever),该对象可以用于从存储的向量中检索最相似的向量。使用了 基于相似度的检索方法,具体来说,用余弦相似度来召回。

retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 5})

召回过程:当你使用 retriever 来检索时,通常会指定 k 的值,即返回前 k 个最相似的向量。然后,retriever 会根据余弦相似度或其他可能的距离度量方法(如欧氏距离、内积等),返回与查询向量最相似的文档或片段。如果你需要具体调整检索方法或使用不同的相似度度量,可以在调用 as_retriever() 时通过 search_typesearch_kwargs 参数进行配置。

3.2 FAISS读取文件后返回类对象的方法 as_retriever

FAISS(Facebook AI Similarity Search)来创建和保存一个向量存储库

  • 这一步将 FAISS 向量索引保存到本地磁盘,保存的路径为 "faiss_db".
  • 这个保存的索引可以稍后重新加载,以便你在不同的会话或程序中重复使用这个向量索引,而不必重新计算所有的嵌入向量。
  • 通过这种方式,你可以在不同的会话中复用已构建的向量索引,而无需重新处理所有数据。
from langchain.agents import AgentExecutor, create_tool_calling_agent
text_chunks = ["Text chunk 1", "Text chunk 2", "Text chunk 3"]
embeddings = YourEmbeddingModel()  # 用于生成嵌入向量的模型
vector_store = FAISS.from_texts(text_chunks, embedding=embeddings)
vector_store.save_local("faiss_db")
loaded_vector_store = FAISS.load_local("faiss_db", embedding=embeddings)
retriever = loaded_vector_store.as_retriever(search_type="similarity", search_kwargs={"k": 5})

也可以没有save和load的操作。

使用检索器查询相关文档:

docs = retriever.get_relevant_documents("知乎是什么") 
prine(docs)

3.3 高级检索方法

  • 最大边际相关性检索

最大边际相关性检索(Maximum Marginal Relevance, MMR)是一种平衡相关性(relevance)与新颖性(novelty)的文档检索方法。该方法旨在解决在检索过程中可能出现的结果冗余问题,即避免返回给用户过多相似或重复的信息。通过同时考虑待检索文档与查询的相关性以及待检索文档之间的相异度,MMR能够在保证信息相关性的同时,增加检索结果的多样性。

默认情况下,Vector Store-backed Retriever 使用相似性搜索。如果底层向量存储支持最大边际相关性搜索,你可以指定这种搜索类型:

retriever = db.as_retriever(search_type="mmr")
docs = retriever.get_relevant_documents("知乎是什么") 
  • 相似度分数阈值检索

相似度分数阈值检索是一种基于相似度分数设定阈值的检索方法。在这种检索策略下,只有当文档与查询的相似度分数高于某个预设的阈值时,该文档才会被检索并返回给用户。这种方法有利于过滤掉与查询相关性较低的文档,确保返回的结果具有较高的相关性。

相似度分数阈值检索适用于对结果质量要求较高的场景,通过调整阈值的大小,可以灵活控制检索结果的精确度和召回率之间的平衡。阈值设置较高时,可以获得更为精确但数量可能较少的结果;阈值设置较低时,则可以获得更多但可能包含一些相关性较低的结果:

retriever = db.as_retriever(
    search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.5}
)
docs = retriever.get_relevant_documents("知乎是什么") 
  • 指定Top K

指定Top K检索是一种简单直观的检索方法,即在所有检索结果中,仅返回相似度分数最高的前K个文档。这种方法非常适用于需要快速获取最相关信息的场景,比如用户仅关注最相关的几条信息,而不需要查看大量可能相关的文档。

通过指定K的值,可以直接控制返回结果的数量,使得检索结果更加精简和专注。这种方法的优势在于操作简单、执行效率高,尤其适合在大规模文档集合中快速定位最相关的信息:

retriever = db.as_retriever(search_kwargs={"k": 1})
docs = retriever.get_relevant_documents("知乎是什么")
print(len(docs))

总结来说,这三种检索策略各有特点和适用场景。最大边际相关性检索强调结果的相关性和多样性,适用于需要综合考虑信息全面性和避免重复的场景;相似度分数阈值检索侧重于过滤掉低相关性结果,适用于对结果质量要求较高的情况;而指定Top K检索则提供了一种快速获取最相关结果的简便方法,适用于需要迅速获取关键信息的场景。

四、embedding模型

bge-base-zh-v1.5

参考

LangChain学习笔记:检索器-矢量存储检索器

LangChain教程 | langchain 文本拆分器 | Text Splitters全集

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值