当前位置:   article > 正文

langchain 文本向量化存储,并检索相似 topK,检索方法汇总

langchain 文本向量化存储,并检索相似 topK,检索方法汇总

目录

chroma 检索

faiss 检索

检索器

相似性

最大相关性mmr

相似数阈值

多角度查询

上下文压缩

混合检索

检索后上下文重新排序

父文档检索器

自查询

时间权重检索

TF-IDF检索

KNN检索

目录

chroma 检索

faiss 检索

检索器

相似性

最大相关性mmr

相似数阈值

多角度查询

上下文压缩

混合检索

检索后上下文重新排序

父文档检索器

自查询

时间权重检索

TF-IDF检索

KNN检索

RAG全流程模块


txt 有多行,我的这份数据有 67 行,样例如下:

字段1\t值1\n

字段2\t值2\n

...

chroma 检索

pip install langchain-chroma

在本地下载了 embedding 模型,使用去向量化,并检索 top3

指定向量化后的数据库保存到哪里 persist_directory

  1. from langchain.text_splitter import CharacterTextSplitter
  2. from langchain.embeddings import HuggingFaceEmbeddings
  3. from langchain_community.document_loaders import TextLoader
  4. from langchain_community.vectorstores import FAISS
  5. from langchain.vectorstores import Chroma
  6. filepath = 'data/专业描述.txt'
  7. raw_documents = TextLoader(filepath, encoding='utf8').load()
  8. # 按行分割块
  9. text_splitter = CharacterTextSplitter(
  10. chunk_size=100,
  11. chunk_overlap=20,
  12. separator="\n",
  13. length_function=len,
  14. is_separator_regex=True,
  15. )
  16. documents = text_splitter.split_documents(raw_documents)
  17. # 加载本地 embedding 模型
  18. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  19. # 创建向量数据库
  20. db = Chroma.from_documents(documents, embedding, persist_directory=r"./chroma/")
  21. db.persist() # 确保嵌入被写入磁盘
  22. '''
  23. 如果已经创建好了,可以直接读取
  24. db = Chroma(persist_directory=persist_directory, embedding_function=embedding)
  25. '''
  26. # 直接传入文本
  27. query = "材料科学与工程是一门研究材料的组成、性质、制备、加工及应用的多学科交叉领域。它涵盖了金属、无机非金属"
  28. docs = db.similarity_search(query, k=3)
  29. # docs = db.similarity_search_with_score(query, k=3) # 带分数的
  30. print(docs[0].page_content)
  31. # 传入向量去搜索
  32. embedding_vector = embedding.embed_query(query)
  33. docs = db.similarity_search_by_vector(embedding_vector, k=3)
  34. print(docs[0].page_content)

faiss 检索

pip install faiss-cpu

感觉 faiss 向量化要快一些

  1. from langchain.text_splitter import CharacterTextSplitter
  2. from langchain.embeddings import HuggingFaceEmbeddings
  3. from langchain_community.document_loaders import TextLoader
  4. from langchain_community.vectorstores import FAISS
  5. from langchain.vectorstores import Chroma
  6. filepath = 'data/专业描述.txt'
  7. raw_documents = TextLoader(filepath, encoding='utf8').load()
  8. # 按行分割块
  9. text_splitter = CharacterTextSplitter(
  10. chunk_size=100,
  11. chunk_overlap=20,
  12. separator="\n",
  13. length_function=len,
  14. is_separator_regex=True,
  15. )
  16. documents = text_splitter.split_documents(raw_documents)
  17. # 加载本地 embedding 模型
  18. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  19. # 创建向量数据库
  20. db = FAISS.from_documents(documents, embedding)
  21. # 保存
  22. db.save_local("./faiss_index")
  23. '''
  24. 如果已经创建好了,可以直接读取
  25. db = FAISS.load_local("./faiss_index", embeddings)
  26. '''
  27. # 直接传入文本
  28. query = "材料科学与工程是一门研究材料的组成、性质、制备、加工及应用的多学科交叉领域。它涵盖了金属、无机非金属"
  29. docs = db.similarity_search(query, k=3)
  30. # docs = db.similarity_search_with_score(query, k=3) # 带分数的
  31. print(docs[0].page_content)
  32. # 传入向量去搜索
  33. embedding_vector = embedding.embed_query(query)
  34. docs = db.similarity_search_by_vector(embedding_vector, k=3)
  35. print(docs[0].page_content)

检索器

相似性

在上面默认情况下,向量存储检索器使用相似性搜索

我们在用上面的例子,使用 faiss 已经创建好了向量数据库,我们在最后面修改检索的代码

选取 top30

  1. from langchain.text_splitter import CharacterTextSplitter
  2. from langchain.embeddings import HuggingFaceEmbeddings
  3. from langchain_community.document_loaders import TextLoader
  4. from langchain_community.vectorstores import FAISS
  5. from langchain.vectorstores import Chroma
  6. filepath = 'data/专业描述.txt'
  7. raw_documents = TextLoader(filepath, encoding='utf8').load()
  8. # 按行分割块
  9. text_splitter = CharacterTextSplitter(
  10. chunk_size=100,
  11. chunk_overlap=20,
  12. separator="\n",
  13. length_function=len,
  14. is_separator_regex=True,
  15. )
  16. documents = text_splitter.split_documents(raw_documents)
  17. # 加载本地 embedding 模型
  18. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  19. # # 创建向量数据库
  20. # db = FAISS.from_documents(documents, embedding)
  21. # # 保存
  22. # db.save_local("./faiss_index")
  23. # 如果已经创建好了,可以直接读取
  24. db = FAISS.load_local("./faiss_index", embedding, allow_dangerous_deserialization=True)
  25. # 直接传入文本
  26. query = "材料科学与工程是一门研究材料的组成、性质、制备、加工及应用的多学科交叉领域。它涵盖了金属、无机非金属"
  27. retriever = db.as_retriever(search_kwargs={'k': 30}) # 构建检索器
  28. docs = retriever.get_relevant_documents(query)
  29. print(docs)

最大相关性mmr

直接比较使用相似性,相似度方法,可能会有重复数据,使用 mmr 不会有重复的检索结果

retriever = db.as_retriever(search_type="mmr", search_kwargs={'k': 30})  # 构建检索器

会发现我指定 top30,只返回了 20 个

fetch_k 默认是 20,数据库提取的候选文档数量,理解为 mmr 算法使用时内部操作的参数就可以了

想取出 30 那,只需要设置大于 30 即可

retriever = db.as_retriever(search_type="mmr", search_kwargs={'k': 30, 'fetch_k': 50})  # 构建检索器

相似数阈值

相似度大于 0.5 的拿出来

retriever = db.as_retriever(search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.5})  # 构建检索器

多角度查询

基于向量距离的检索可能因微小的询问词变化或向量无法准确表达语义而产生不同结果;

使用大预言模型自动从不同角度生成多个查询,实现提示词优化;

对用户查询生成表达其不同方面的多个新查询(也就是query利用大模型生成多个表述),对每个表述进行检索,去结果的并集;

优点是生成的查询多角度,可以覆盖更全面的语义和信息需求;

  1. from langchain.text_splitter import CharacterTextSplitter
  2. from langchain.embeddings import HuggingFaceEmbeddings
  3. from langchain_community.document_loaders import TextLoader
  4. from langchain_community.vectorstores import FAISS
  5. from langchain.vectorstores import Chroma
  6. import os
  7. from dotenv import load_dotenv
  8. from langchain_community.llms import Tongyi
  9. load_dotenv('key.env') # 指定加载 env 文件
  10. key = os.getenv('DASHSCOPE_API_KEY') # 获得指定环境变量
  11. DASHSCOPE_API_KEY = os.environ["DASHSCOPE_API_KEY"] # 获得指定环境变量
  12. model = Tongyi(temperature=1)
  13. filepath = 'data/专业描述.txt'
  14. raw_documents = TextLoader(filepath, encoding='utf8').load()
  15. # 按行分割块
  16. text_splitter = CharacterTextSplitter(
  17. chunk_size=100,
  18. chunk_overlap=20,
  19. separator="\n",
  20. length_function=len,
  21. is_separator_regex=True,
  22. )
  23. documents = text_splitter.split_documents(raw_documents)
  24. # 加载本地 embedding 模型
  25. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  26. # 如果已经创建好了,可以直接读取
  27. db = FAISS.load_local("./faiss_index", embedding, allow_dangerous_deserialization=True)
  28. # 直接传入文本
  29. query = "材料科学与工程是一门研究材料的组成、性质、制备、加工及应用的多学科交叉领域。它涵盖了金属、无机非金属"
  30. # MultiQueryRetriever 检索
  31. from langchain.retrievers.multi_query import MultiQueryRetriever
  32. retriever_from_llm = MultiQueryRetriever.from_llm(
  33. retriever=db.as_retriever(search_kwargs={'k': 8}), llm=model
  34. )
  35. unique_docs = retriever_from_llm.get_relevant_documents(query=query)
  36. print(unique_docs)

上下文压缩

使用给定查询的上下文来压缩检索的输出,以便只返回相关信息,而不是立即按照原样返回检索到的文档

相当于提取每个检索结果的核心,简化每个文档,利用大模型的能力

这里我们就选择 top1,可以看到检索结果跟 query 一模一样了,是同一句话

  1. from langchain.text_splitter import CharacterTextSplitter
  2. from langchain.embeddings import HuggingFaceEmbeddings
  3. from langchain_community.document_loaders import TextLoader
  4. from langchain_community.vectorstores import FAISS
  5. from langchain.vectorstores import Chroma
  6. import os
  7. from dotenv import load_dotenv
  8. from langchain_community.llms import Tongyi
  9. load_dotenv('key.env') # 指定加载 env 文件
  10. key = os.getenv('DASHSCOPE_API_KEY') # 获得指定环境变量
  11. DASHSCOPE_API_KEY = os.environ["DASHSCOPE_API_KEY"] # 获得指定环境变量
  12. model = Tongyi(temperature=1)
  13. filepath = 'data/专业描述.txt'
  14. raw_documents = TextLoader(filepath, encoding='utf8').load()
  15. # 按行分割块
  16. text_splitter = CharacterTextSplitter(
  17. chunk_size=100,
  18. chunk_overlap=20,
  19. separator="\n",
  20. length_function=len,
  21. is_separator_regex=True,
  22. )
  23. documents = text_splitter.split_documents(raw_documents)
  24. # 加载本地 embedding 模型
  25. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  26. # 如果已经创建好了,可以直接读取
  27. db = FAISS.load_local("./faiss_index", embedding, allow_dangerous_deserialization=True)
  28. # 传入文本
  29. query = "材料科学与工程是一门研究材料的组成、性质、制备、加工及应用的多学科交叉领域。它涵盖了金属、无机非金属"
  30. # 检索
  31. from langchain.retrievers import ContextualCompressionRetriever
  32. from langchain.retrievers.document_compressors import LLMChainExtractor
  33. retriever = db.as_retriever(search_kwargs={'k': 1})
  34. compressor = LLMChainExtractor.from_llm(model)
  35. compression_retriever = ContextualCompressionRetriever(
  36. base_compressor=compressor, base_retriever=retriever
  37. )
  38. unique_docs = compression_retriever.get_relevant_documents(query)
  39. print(unique_docs)

上面这个我是只取了 top1,但是我把全部结果打出来,发现有重复的,我用了下面检索代码,就去重了;官网的意思是:

LLMChainFilter 使用 LLM 链来决定过滤掉最初检索到的文档中的哪些以及返回哪些文档,而无需操作文档内容。

  1. # 检索
  2. from langchain.retrievers import ContextualCompressionRetriever
  3. from langchain.retrievers.document_compressors import LLMChainExtractor
  4. from langchain.retrievers.document_compressors import LLMChainFilter
  5. _filter = LLMChainFilter.from_llm(model)
  6. retriever = db.as_retriever(search_kwargs={'k': 10})
  7. compression_retriever = ContextualCompressionRetriever(
  8. base_compressor=_filter, base_retriever=retriever
  9. )
  10. unique_docs = compression_retriever.get_relevant_documents(query)
  11. print(unique_docs)

对每个检索到的文档进行额外的 LLM 调用既昂贵又缓慢。EmbeddingsFilter通过嵌入文档和查询并仅返回那些与查询具有足够相似嵌入的文档

相当于少调用 llm 去判断相关的文档,改用 embedding 模型

  1. # 检索
  2. from langchain.retrievers import ContextualCompressionRetriever
  3. from langchain.retrievers.document_compressors import EmbeddingsFilter
  4. retriever = db.as_retriever(search_kwargs={'k': 10})
  5. embeddings_filter = EmbeddingsFilter(embeddings=embedding, similarity_threshold=0.76)
  6. compression_retriever = ContextualCompressionRetriever(
  7. base_compressor=embeddings_filter, base_retriever=retriever
  8. )
  9. compressed_docs = compression_retriever.get_relevant_documents(query)
  10. print(compressed_docs)

还有一种,是把文档分割为再小块一些的,再去做 embedding

  1. def contextual_compression_by_embedding_split(cls, db, query, embedding_model, topk=5, similarity_threshold=0.76,
  2. chunk_size=300, chunk_overlap=0, separator=". "):
  3. """
  4. https://python.langchain.com/docs/modules/data_connection/retrievers/contextual_compression/
  5. 上下文压缩检索器,embedding 模型,会对结果去重,将文档分割成更小的部分
  6. 使用给定查询的上下文来压缩检索的输出,以便只返回相关信息,而不是立即按照原样返回检索到的文档
  7. 利用 embedding 来计算
  8. :param db:
  9. :param query:
  10. :param embedding_model:
  11. :param topk: 不生效,默认是 4 个
  12. :return:
  13. """
  14. retriever = db.as_retriever(search_kwargs={'k': topk})
  15. splitter = CharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap, separator=separator)
  16. redundant_filter = EmbeddingsRedundantFilter(embeddings=embedding_model)
  17. relevant_filter = EmbeddingsFilter(embeddings=embedding_model, similarity_threshold=similarity_threshold)
  18. pipeline_compressor = DocumentCompressorPipeline(
  19. transformers=[splitter, redundant_filter, relevant_filter]
  20. )
  21. compression_retriever = ContextualCompressionRetriever(
  22. base_compressor=pipeline_compressor, base_retriever=retriever
  23. )
  24. retriever_docs = compression_retriever.get_relevant_documents(query)
  25. return retriever_docs

混合检索

通过利用不同算法的优势, EnsembleRetriever可以获得比任何单一算法更好的性能

最常见的模式是将稀疏检索器(如 BM25)与密集检索器(如嵌入相似性)相结合,因为它们的优势是互补的。它也被称为“混合搜索”。稀疏检索器擅长根据关键词查找相关文档,而密集检索器擅长根据语义相似度查找相关文档。

  1. from langchain.retrievers import EnsembleRetriever
  2. from langchain_community.retrievers import BM25Retriever
  3. from langchain_community.vectorstores import FAISS
  4. from langchain.embeddings import HuggingFaceEmbeddings
  5. doc_list_1 = [
  6. "I like apples",
  7. "I like oranges",
  8. "Apples and oranges are fruits",
  9. ]
  10. # initialize the bm25 retriever and faiss retriever
  11. bm25_retriever = BM25Retriever.from_texts(
  12. doc_list_1, metadatas=[{"source": 1}] * len(doc_list_1)
  13. )
  14. bm25_retriever.k = 2
  15. doc_list_2 = [
  16. "You like apples",
  17. "You like oranges",
  18. ]
  19. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  20. faiss_vectorstore = FAISS.from_texts(
  21. doc_list_2, embedding, metadatas=[{"source": 2}] * len(doc_list_2)
  22. )
  23. faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})
  24. # initialize the ensemble retriever
  25. ensemble_retriever = EnsembleRetriever(
  26. retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
  27. )
  28. docs = ensemble_retriever.invoke("apples")
  29. print(docs)

检索后上下文重新排序

  1. from langchain.text_splitter import CharacterTextSplitter
  2. from langchain.embeddings import HuggingFaceEmbeddings
  3. from langchain_community.document_loaders import TextLoader
  4. from langchain_community.vectorstores import FAISS
  5. from langchain.vectorstores import Chroma
  6. import os
  7. from dotenv import load_dotenv
  8. from langchain_community.llms import Tongyi
  9. load_dotenv('key.env') # 指定加载 env 文件
  10. key = os.getenv('DASHSCOPE_API_KEY') # 获得指定环境变量
  11. DASHSCOPE_API_KEY = os.environ["DASHSCOPE_API_KEY"] # 获得指定环境变量
  12. model = Tongyi(temperature=1)
  13. filepath = 'data/专业描述.txt'
  14. raw_documents = TextLoader(filepath, encoding='utf8').load()
  15. # 按行分割块
  16. text_splitter = CharacterTextSplitter(
  17. chunk_size=100,
  18. chunk_overlap=20,
  19. separator="\n",
  20. length_function=len,
  21. is_separator_regex=True,
  22. )
  23. documents = text_splitter.split_documents(raw_documents)
  24. # 加载本地 embedding 模型
  25. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  26. # 如果已经创建好了,可以直接读取
  27. db = FAISS.load_local("./faiss_index", embedding, allow_dangerous_deserialization=True)
  28. # 传入文本
  29. query = "材料科学与工程是一门研究材料的组成、性质、制备、加工及应用的多学科交叉领域。它涵盖了金属、无机非金属"
  30. # 检索
  31. from langchain_community.document_transformers import LongContextReorder
  32. retriever = db.as_retriever(search_type="mmr", search_kwargs={'k': 10, 'fetch_k': 50}) # 构建检索器
  33. docs = retriever.get_relevant_documents(query)
  34. # 对检索结果重新排序
  35. reordering = LongContextReorder()
  36. reordered_docs = reordering.transform_documents(docs)
  37. print(reordered_docs)

父文档检索器

大文档拆分成小文档(比如大文档指多个 txt 或文件)

小文档快通过向量空间建模,实现更准确的语义检索,大块提供跟完整的语义内容

检索小的,最后返回大的对应 id 进行返回

  1. from langchain.storage import InMemoryStore
  2. from langchain.embeddings import HuggingFaceEmbeddings
  3. from langchain_community.document_loaders import TextLoader
  4. from langchain_chroma import Chroma
  5. from langchain_text_splitters import RecursiveCharacterTextSplitter
  6. from langchain.retrievers import ParentDocumentRetriever
  7. loaders = [
  8. TextLoader("data/专业描述.txt", encoding="utf-8"),
  9. TextLoader("data/专业描述_copy.txt", encoding="utf-8"),
  10. ]
  11. docs = []
  12. for loader in loaders:
  13. docs.extend(loader.load())
  14. # 加载本地 embedding 模型
  15. embedding = HuggingFaceEmbeddings(model_name='bge-small-zh-v1.5')
  16. # This text splitter is used to create the child documents
  17. child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
  18. # The vectorstore to use to index the child chunks
  19. vectorstore = Chroma(
  20. collection_name="full_documents", embedding_function=embedding
  21. )
  22. # The storage layer for the parent documents
  23. store = InMemoryStore()
  24. retriever = ParentDocumentRetriever(
  25. vectorstore=vectorstore,
  26. docstore=store,
  27. child_splitter=child_splitter,
  28. )
  29. retriever.add_documents(docs, ids=None)
  30. # 会有两个键,添加了两个文档
  31. # print(list(store.yield_keys()))
  32. # 传入文本
  33. query = "材料科学与工程是一门研究材料的组成、性质、制备、加工及应用的多学科交叉领域。它涵盖了金属、无机非金属"
  34. # 检索小块
  35. sub_docs = vectorstore.similarity_search(query)
  36. print(sub_docs[0].page_content)
  37. # 检索大块
  38. retrieved_docs = retriever.get_relevant_documents("justice breyer")
  39. print(retrieved_docs)

如果文档还是太大,可先把父文档文档分割,参考:

https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/481802

推荐阅读
相关标签
  

闽ICP备14008679号