赞
踩
PyMuPDFLoader 是 PDF 解析器中速度最快的一种,结果会包含 PDF 及其页面的详细元数据,并且每页返回一个文档。
## 安装必要的库
# !pip install rapidocr_onnxruntime -i https://pypi.tuna.tsinghua.edu.cn/simple
# !pip install "unstructured[all-docs]" -i https://pypi.tuna.tsinghua.edu.cn/simple
# !pip install pyMuPDF -i https://pypi.tuna.tsinghua.edu.cn/simple
from langchain.document_loaders import PyMuPDFLoader
# 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
loader = PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf")
# 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
pages = loader.load()
page 中的每一元素为一个文档,变量类型为 langchain.schema.document.Document, 文档变量类型包含两个属性:
● page_content 包含该文档的内容。
● meta_data 为文档相关的描述性数据。
from langchain.document_loaders import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader("../../data_base/knowledge_db/prompt_engineering/1. 简介 Introduction.md")
pages = loader.load()
# 需要先使用whisper将视频转写为文本
from langchain.document_loaders import UnstructuredFileLoader
loader = UnstructuredFileLoader("../../data_base/knowledge_db/easy_rl/强化学习入门指南.txt")
pages = loader.load()
使用LangChain将文本根据 chunk_size(块大小)和chunk_overlap(两个块重叠大小)进行分割
● chunk_size 指每个块包含的字符或 Token (如单词、句子等)的数量
● chunk_overlap 指两个块之间共享的字符数量,用于保持上下文的连贯性,避免分割丢失上下文信息
Langchain 提供多种文档分割方式,区别在怎么确定块与块之间的边界、块由哪些字符/token组成、以及如何测量块大小
● RecursiveCharacterTextSplitter(): 按字符串分割文本,递归地尝试按不同的分隔符进行分割文本。
● CharacterTextSplitter(): 按字符来分割文本。
● MarkdownHeaderTextSplitter(): 基于指定的标题来分割markdown 文件。
● TokenTextSplitter(): 按token来分割文本。
● SentenceTransformersTokenTextSplitter(): 按token来分割文本
● Language(): 用于 CPP、Python、Ruby、Markdown 等。
● NLTKTextSplitter(): 使用 NLTK(自然语言工具包)按句子分割文本。
● SpacyTextSplitter(): 使用 Spacy按句子的切割文本。
#导入文本分割器from langchain.text_splitter import RecursiveCharacterTextSplitter # 知识库中单段文本长度 CHUNK_SIZE = 500 # 知识库中相邻文本重合长度 OVERLAP_SIZE = 50 # 此处使用 PDF 文件作为示例 from langchain.document_loaders import PyMuPDFLoader # 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径 loader = PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf") # 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载 pages = loader.load() page = pages[1] # 使用递归字符文本分割器 from langchain.text_splitter import TokenTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=CHUNK_SIZE, chunk_overlap=OVERLAP_SIZE ) # 分割单个文本 text_splitter.split_text(page.page_content[0:1000]) # 分割文本列表 text_splitter.split_documents(pages)
向量(Embedding)是NLP中用于表征文本的一种技术,该方法可以将离线的文本映射到维度较低且连续的向量空间中,使得具有相似语义的单词或者句子的向量位置很近,语义差距较大的单词或者句子的向量位置很远。
from langchain.embeddings.openai import OpenAIEmbeddings from langchain.embeddings.huggingface import HuggingFaceEmbeddings from zhipuai_embedding import ZhipuAIEmbeddings # 三种加载向量的方法 # embedding = OpenAIEmbeddings() # embedding = HuggingFaceEmbeddings(model_name="moka-ai/m3e-base") embedding = ZhipuAIEmbeddings() query1 = "机器学习" query2 = "强化学习" query3 = "大语言模型" # 通过对应的 embedding 类生成 query 的 embedding。 emb1 = embedding.embed_query(query1) emb2 = embedding.embed_query(query2) emb3 = embedding.embed_query(query3) # 将返回结果转成 numpy 的格式,便于后续计算 emb1 = np.array(emb1) emb2 = np.array(emb2) emb3 = np.array(emb3) import numpy as np from sklearn.metrics.pairwise import cosine_similarity # 第一种计算相关性的方法:点积 # 点积是将两个向量对应位置的元素相乘后求和得到的标量值。点积相似度越大,表示两个向量越相似。 # 点积:计算简单,快速,不需要进行额外的归一化步骤,但丢失了方向信息。 print(f"{query1} 和 {query2} 向量之间的点积为:{np.dot(emb1, emb2)}") print(f"{query1} 和 {query3} 向量之间的点积为:{np.dot(emb1, emb3)}") print(f"{query2} 和 {query3} 向量之间的点积为:{np.dot(emb2, emb3)}") # 第二种计算相关性的方法:余弦相似度 # 余弦相似度将两个向量的点积除以它们的模长的乘积,可以同时比较向量的方向和数量级大小。 print(f"{query1} 和 {query2} 向量之间的余弦相似度为:{cosine_similarity(emb1.reshape(1, -1) , emb2.reshape(1, -1) )}") print(f"{query1} 和 {query3} 向量之间的余弦相似度为:{cosine_similarity(emb1.reshape(1, -1) , emb3.reshape(1, -1) )}") print(f"{query2} 和 {query3} 向量之间的余弦相似度为:{cosine_similarity(emb2.reshape(1, -1) , emb3.reshape(1, -1) )}")
专门用于存储和检索向量的一种数据库,常用的有Faiss、Chroma和Milvus等。
from langchain.vectorstores import Chroma from langchain.document_loaders import PyMuPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.embeddings.openai import OpenAIEmbeddings from langchain.embeddings.huggingface import HuggingFaceEmbeddings from zhipuai_embedding import ZhipuAIEmbeddings from langchain.llms import OpenAI from langchain.llms import HuggingFacePipeline from zhipuai_llm import ZhipuAILLM # 加载 PDF loaders_chinese = [ PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf") # 南瓜书 ] docs = [] for loader in loaders_chinese: docs.extend(loader.load()) # 切分文档 text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150) split_docs = text_splitter.split_documents(docs) # 定义 Embeddings embedding = OpenAIEmbeddings() # embedding = HuggingFaceEmbeddings(model_name=model_name, model_kwargs=model_kwargs) # embedding = ZhipuAIEmbeddings() # 定义向量数据库 persist_directory = '../../data_base/vector_db/chroma' vectordb = Chroma.from_documents( documents=split_docs[:100], # 为了速度,只选择了前 100 个切分的 doc 进行生成。 embedding=embedding, persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上 ) # 将向量保存到本地文件 vectordb.persist() # 加载本地的向量库 vectordb = Chroma( persist_directory=persist_directory, embedding_function=embedding ) print(f"向量库中存储的数量:{vectordb._collection.count()}")
question="什么是机器学习"
sim_docs = vectordb.similarity_search(question,k=3)
print(f"检索到的内容数:{len(sim_docs)}")
for i, sim_doc in enumerate(sim_docs):
print(f"检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
如果只考虑检索出内容的相关性会导致内容过于单一,可能丢失重要信息。
最大边际相关性 (MMR, Maximum marginal relevance) 可以帮助在保持相关性的同时,增加内容的丰富度。
MMR的核心思想是在已经选择了一个相关性高的文档之后,再选择一个与已选文档相关性较低但是信息丰富的文档。这样可以在保持相关性的同时,增加内容的多样性,避免过于单一的结果。
mmr_docs = vectordb.max_marginal_relevance_search(question,k=3)
for i, sim_doc in enumerate(mmr_docs):
print(f"MMR 检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
基于 LangChain,可以构造一个使用 LLM 进行问答的检索式问答链,这是一种通过检索步骤进行问答的方法。可以通过传入一个语言模型和一个向量数据库来创建它作为检索器。然后,可以用问题作为查询调用它,得到一个答案。
# 导入检索式问答链 from langchain.chains import RetrievalQA # 可以使用 HuggingFacePipeline 本地搭建大语言模型 model_id = 'THUDM/chatglm2-6b-int4' # 采用 int 量化后的模型可以节省硬盘占用以及实时量化所需的运算资源 tokenizer = AutoTokenizer.from_pretrained(model_id) model = AutoModel.from_pretrained(model_id, trust_remote_code=True).half().quantize(4).cuda() model = model.eval() pipe = pipeline( "text2text-generation", model=model, tokenizer=tokenizer, max_length=100 ) llm = HuggingFacePipeline(pipeline=pipe) # 声明一个检索式问答链 qa_chain = RetrievalQA.from_chain_type( llm, retriever=vectordb.as_retriever() ) # 可以以该方式进行检索问答 question = "本知识库主要包含什么内容" result = qa_chain({"query": question}) print(f"大语言模型的回答为:{result['result']}") from langchain.prompts import PromptTemplate # Build prompt template = """使用以下上下文片段来回答最后的问题。如果你不知道答案,只需说不知道,不要试图编造答案。答案最多使用三个句子。尽量简明扼要地回答。在回答的最后一定要说"感谢您的提问!" {context} 问题:{question} 有用的回答:""" QA_CHAIN_PROMPT = PromptTemplate.from_template(template) # Run chain qa_chain = RetrievalQA.from_chain_type( llm, retriever=vectordb.as_retriever(), return_source_documents=True, chain_type_kwargs={"prompt": QA_CHAIN_PROMPT} ) question = " 2025 年大语言模型效果最好的是哪个模型" result = qa_chain({"query": question}) print(f"LLM 对问题的回答:{result['result']}")
如果文档太多,无法全部适配到上下文窗口中,可以使用Stuff、Refine、MapReduce或者MapRerank等方法。
RetrievalQA.from_chain_type(
llm,
retriever=vectordb.as_retriever(),
chain_type="map_reduce"
)
使用 ConversationBufferMemory ,它保存聊天消息历史记录的列表,这些历史记录将在回答问题时与问题一起传递给聊天机器人,从而将它们添加到上下文中。关于更多的 Memory 的使用,包括保留指定对话轮数、保存指定 token 数量、保存历史对话的总结摘要等内容,请参考 langchain 的 Memory 部分的相关文档。
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(
memory_key="chat_history", # 与 prompt 的输入变量保持一致。
return_messages=True # 将以消息列表的形式返回聊天记录,而不是单个字符串
)
from langchain.chains import ConversationalRetrievalChain
retriever=vectordb.as_retriever()
qa = ConversationalRetrievalChain.from_llm(
llm,
retriever=retriever,
memory=memory
)
question = "我可以学习到关于强化学习的知识吗?"
result = qa({"question": question})
print(result['answer'])
清晰并不是简短,更长的需求会提供充分的上下文,模型输出结果会更加准确
注意:避免提示词注入,什么是提示词注入?是指用户被允许在提示中增加一些输入,它们可能会向模型发出相互矛盾的指令,这可能会导致模型遵循用户的指示而不是你想做的事情。即提示词看起来是一个指令,但是不应该被执行。
例如要求模型输出HTML或者JSON
如果模型没有满足条件,就需要向模型指出并停止,用户也需要考虑问题的边界情况,告诉模型应该怎么避免这些问题的发生
如果提问模型的任务过于复杂,可能会导致模型急于得出结论而出现推理错误,应当重新设计提问,要求模型在回答之前有一连串的推理,最后再给出答案。
模型对自己知识边界并不十分了解,它可能编造一些看起来有道理但是并不真实的事情,我们称之为幻觉,一个解决办法是先找到相关的信息,然后再让模型引用这些相关的信息回答问题
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。