万链网站做的怎么样?,什么是网络设计制作,体育馆网站建设,信盈达嵌入式培训本文将使用 Transformers 和 LangChain#xff0c;选择在 Retrieval - Chinese 中表现较好的编码模型进行演示#xff0c;即 chuxin-llm/Chuxin-Embedding。 你还将了解 RecursiveCharacterTextSplitter 的递归工作原理。 一份值得关注的基准测试榜单#xff1a;MTEB (M… 本文将使用 Transformers 和 LangChain选择在 Retrieval - Chinese 中表现较好的编码模型进行演示即 chuxin-llm/Chuxin-Embedding。 你还将了解 RecursiveCharacterTextSplitter 的递归工作原理。 一份值得关注的基准测试榜单MTEB (Massive Text Embedding Benchmark) Leaderboard。 代码文件下载 文章目录 前言环境配置RA文档导入文本处理递归拆分的过程 加载编码模型建立向量数据库保存和加载向量数据库可选创建检索器 G加载文本生成模型创建管道集成到 LangChain定义提示词模版 构建问答链进行 QA参考链接附录完整代码RecursiveCharacterTextSplitter 源码 前言
RAGRetrieval-Augmented Generation检索增强生成并不是一项高深的技术你可以将其拆分为 RA检索增强和 G生成来理解。
RA 的通俗解释是在询问模型之前通过相似度匹配从文档中检索相关内容将其与当前问题结合。
举个例子。
假设你正在负责图书答疑有读者询问某本书中特定主题的问题 情景 1读者直接向你提问但你并不知道他所说的是哪本书不过凭借丰富的知识储备你还是给出了一个回答。 情景 2读者先向你的助理提问助理从书架上找出了相关的书籍检索到了他认为相关的段落并将这些段落和问题一起交给你基于这些具体的信息你提供了一个更加准确、相关且详细的回答。
情景 2 就是 RAG 的工作方式在模型回答之前先检索相关的信息提供给模型以增强其回答的准确性和相关性。
因此RA 更像是一种工程上的操作或者说是对 Prompt 的增强并不会影响模型本身的参数。通过在 Prompt 中加入检索到的相关信息模型可以在回答特定文档的问题时表现得更好。有点像将 Zero-shot Prompting 扩充为 Few-shot Prompting所以在特定文档的问答中会有提升。
而 G 就是大家熟悉的文本生成或者说生成式模型的调用本文不会涉及模型训练。
环境配置
pip install langchain langchain-community langchain-huggingface unstructured
pip install pandas
pip install transformers sentence-transformers accelerate
pip install numpy2.0# 处理图片tesseract 进行 OCR以下为可选下载
#sudo apt-get update
#sudo apt-get install python3-pil tesseract-ocr libtesseract-dev tesseract-ocr-eng tesseract-ocr-script-latn
#pip install unstructured[image] tesseract tesseract-ocr执行以下代码避免后续 documents loader.load() 可能的 LookupError 报错。
import nltknltk.download(punkt) # 下载 punkt 分词器
nltk.download(punkt_tab) # 下载 punkt_tab 分词器数据
nltk.download(averaged_perceptron_tagger) # 下载词性标注器
nltk.download(averaged_perceptron_tagger_eng) # 下载英文词性标注器RA
在实际实现中遵循的步骤大致如下
使用预训练的编码器模型将「文档」内容编码为向量表示embedding然后建立一个向量数据库。在检索阶段针对用户的「问题」同样使用编码器将其编码为向量然后在向量数据库中寻找与之相似的文档片段。
文档导入
以当前项目 Guide 文件夹下的指导文章为例建议修改 DOC_PATH替换成你想要处理的文件夹。
首先设置路径并使用 DirectoryLoader 加载文档
from langchain.document_loaders import DirectoryLoader# 定义文件所在的路径修改它或者克隆仓库
# git clone https://github.com/Hoper-J/AI-Guide-and-Demos-zh_CN cd Demos
DOC_PATH ../Guide# 使用 DirectoryLoader 从指定路径加载文件。*.md 表示加载所有 .md 格式的文件这里仅导入文章 10避免当前文章的演示内容对结果的影响注意如果修改成了自己的DOC_PATH务必修改 glob 进行匹配
loader DirectoryLoader(DOC_PATH, glob10*.md)# 加载目录中的指定的 .md 文件并将其转换为文档对象列表
documents loader.load()# 打印查看加载的文档内容
print(documents[0].page_content[:200])可以修改参数 glob 来指定想处理的文件也可以去除这个参数以处理所有文件。 输出
什么是 Top-K 和 Top-P 采样Temperature 如何影响生成结果在之前的文章中我们探讨了 Beam Search 和 Greedy Search。现在来聊聊 model.generate() 中常见的三个参数: top-k, top-p 和 temperature。代码文件下载目录采样方法概述Top-K 采样详解
...仔细观察输出格式想想有什么地方与原文本格式不同。 「Markdown 的结构化标记被去除了」 文本处理 或许你对 chunk 会有一点印象在 15. 用 API 实现 AI 视频摘要动手制作属于你的 AI 视频助手中我们使用了非常简单的分块方法直接截断。 LangChain 提供了多种文本分块方式例如 RecursiveCharacterTextSplitter、HTMLSectionSplitter、MarkdownTextSplitter 等可以根据需求选择。本文将演示 RecursiveCharacterTextSplitter。 不过在使用 split_documents() 处理文档之前我们先使用 split_text() 来看看它究竟是怎么进行分块的。摘取一段长隆万圣节的文本介绍
text 长隆广州世界嘉年华系列活动的长隆欢乐世界潮牌玩圣节隆重登场在揭幕的第一天就吸引了大批年轻人前往打卡。据悉这是长隆欢乐世界重金引进来自欧洲的12种巨型花车重磅出巡让人宛若进入五彩缤纷的巨人国全新的超级演艺广场每晚开启狂热的电音趴将整个狂欢氛围推向高点。记者在现场看到明日之城、异次元界、南瓜欢乐小镇、暗黑城、魔域五大风格迥异的“鬼”域在夜晚正式开启全新重磅升级的十大“鬼”屋恭候着各位的到来各式各样的“鬼”开始神出“鬼”没明日之城中丧尸成群出行寻找新鲜的“血肉”。异次元界异形生物游走美丽冷艳之下暗藏危机。暗黑城亡灵出没诅咒降临。魔域异“鬼”横行上演“血腥恐怖”。南瓜欢乐小镇小丑当家滑稽温馨带来欢笑。五大“鬼”域以灯光音效科技情景氛围营造360°沉浸式异域次元界探险模式为前来狂欢的“鬼”友们献上“惊奇、恐怖、搞怪、欢乐”的玩圣体验。持续23天的长隆欢乐玩圣节将挑战游客的认知极限让你大开眼界
据介绍今年长隆玩圣节与以往相比更为隆重沉浸式场景营造惊悚氛围两大新“鬼”王隆重登场盛大的“鬼”王出巡仪式、数十种集声光乐和高科技于一体的街头表演、死亡巴士酷跑、南瓜欢乐小镇欢乐电音、暗黑城黑暗朋克、魔术舞台双煞魔舞、异形魔幻等一系列精彩节目无不让人拍手称奇、惊叹不止的“玩圣”盛宴让 “鬼”友们身临其境过足“戏”瘾print(len(text))这段文本长度为 581。接下来看看结果如何
from langchain.text_splitter import RecursiveCharacterTextSplitter# 创建一个文本分割器。
text_splitter RecursiveCharacterTextSplitter(chunk_size100, # 每个文本块的最大长度chunk_overlap20 # 文本块之间的字符重叠数量
)# 将文本分割成多个块
texts text_splitter.split_text(text)# 打印分割后的文本块数量
print(len(texts))# 打印第一个文本块的长度
print(len(texts[0]))# 打印第一个文本块的最后 20 个字符
print(texts[0][80:])# 打印第二个文本块的前 20 个字符
print(texts[1][:20])输出
9
100
出巡让人宛若进入五彩缤纷的巨人国全新
出巡让人宛若进入五彩缤纷的巨人国全新很好text 被分为了 9 段而且可以看到第一段文本确实以 100 个字符进行分割而且 overlap 符合预期。 你可以通过下图来理解 overlap 到目前为止RecursiveCharacterTextSplitter 的表现就像是一个简单的文本截断没有什么特别之处。但是让我们观察 len(text) 和 len(texts)原文本长度为 581分割后的段落数为 9问题出现了。按照直接截断的假设前 8 段应为 100 个字符即便去除 overlap总长度仍应超过 600这与原始文本的长度不符。说明文本分割过程中一定执行了其他操作而不仅仅是直接截断。
实际上RecursiveCharacterTextSplitter() 的关键在于 RecursiveCharacter即递归地按照指定的分隔符默认为 [\n\n, \n, , ]进行文本拆分。也就是说在文本拆分的时候它会尝试使用较大的分隔符来拆分文本如果长度仍超过 chunk_size则逐步使用更小的分隔符直到长度满足或最终进行截断也就是出现第一次分块当中的结果。所以说第一次的分块实际上是一个“妥协”。
为了更好的进行理解现在将 chunk_overlap 设置为 0并打印输出
from langchain.text_splitter import RecursiveCharacterTextSplittertext_splitter RecursiveCharacterTextSplitter(chunk_size100,chunk_overlap0 # 不重叠
)
texts text_splitter.split_text(text)# 输出每个片段的长度和内容
for i, t in enumerate(texts):print(fChunk {i1} length: {len(t)})print(t)print(- * 50)输出
Chunk 1 length: 100
长隆广州世界嘉年华系列活动的长隆欢乐世界潮牌玩圣节隆重登场在揭幕的第一天就吸引了大批年轻人前往打卡。据悉这是长隆欢乐世界重金引进来自欧洲的12种巨型花车重磅出巡让人宛若进入五彩缤纷的巨人国全新
--------------------------------------------------
Chunk 2 length: 30
的超级演艺广场每晚开启狂热的电音趴将整个狂欢氛围推向高点。
--------------------------------------------------
Chunk 3 length: 99
记者在现场看到明日之城、异次元界、南瓜欢乐小镇、暗黑城、魔域五大风格迥异的“鬼”域在夜晚正式开启全新重磅升级的十大“鬼”屋恭候着各位的到来各式各样的“鬼”开始神出“鬼”没明日之城中丧尸成群
--------------------------------------------------
Chunk 4 length: 100
出行寻找新鲜的“血肉”。异次元界异形生物游走美丽冷艳之下暗藏危机。暗黑城亡灵出没诅咒降临。魔域异“鬼”横行上演“血腥恐怖”。南瓜欢乐小镇小丑当家滑稽温馨带来欢笑。五大“鬼”域以灯光音效科技情
--------------------------------------------------
...可以看到文本在 全新 和 的 之间被截断因为达到了 100 个字符的限制这符合直觉。然而接下来的 chunk 2 只有 30 个字符这是因为 RecursiveCharacterTextSplitter 并不是逐「段」分割而是逐「分隔符」分割。
递归拆分的过程
以下是 RecursiveCharacterTextSplitter 的递归拆分流程
尝试使用第一个分隔符 \n\n如果文本长度超过 chunk_size就以 \n\n 为分隔符拆分文本以当前文本为例Chunk 1 length: 130
长隆广州世界嘉年华...全新的超级演艺广场每晚开启狂热的电音趴将整个狂欢氛围推向高点。
--------------------------------------------------
Chunk 2 length: 451记者在现场看到...让你大开眼界
据介绍...惊叹不止的“玩圣”盛宴让 “鬼”友们身临其境过足“戏”瘾--------------------------------------------------注意如果两段被拆分的文本加起来不超过 chunk_size它们会被合并不然的话对于英文文本使用 空格分割就全拆成单词了。检查拆分后的子文本长度如果子文本长度仍然超过 chunk_size就对每个子文本递归地使用下一个分隔符 \n 进行拆分。Chunk 1 length: 130
...不变
--------------------------------------------------
Chunk 2 length: 285记者在现场看到...让你大开眼界
--------------------------------------------------
Chunk 3 length: 164Chunk 2 3 由之前的 Chunk 2 分割得来据介绍...惊叹不止的“玩圣”盛宴让 “鬼”友们身临其境过足“戏”瘾
--------------------------------------------------检查拆分后的子文本长度和之前一样如果子文本长度仍然超过 chunk_size就对每个子文本递归地使用下一个分隔符 空格 进行拆分注意到chunk 3 的“让”字后面有一个空格Chunk 1 length: 130
...不变
--------------------------------------------------
Chunk 2 length: 285...不变
--------------------------------------------------
Chunk 3 length: 146据介绍...惊叹不止的“玩圣”盛宴让
--------------------------------------------------
Chunk 4 length: 17Chunk 3 4 由之前的 Chunk 3 分割得来
“鬼”友们身临其境过足“戏”瘾
--------------------------------------------------还需要注意的是 14617163164这说明当前分隔符不被继承到新的 chunk 中。重复上述过程如果还有其他分隔符的话直到使用最小的分隔符 即逐字符进行拆分这一步将会直接截断Chunk 1 length: 100
长隆广州世界嘉年华...全新
--------------------------------------------------
Chunk 2 length: 30Chunk 1 2 由之前的 Chunk 1 截断得来
的超级...
--------------------------------------------------
Chunk 3 length: 99
记者在现场看到...
--------------------------------------------------
Chunk 4 length: 100
出行...科技情
--------------------------------------------------
Chunk 5 length: 85Chunk 2 3 4 由之前的 Chunk 2 截断得来
景氛围...让你大开眼界
--------------------------------------------------
Chunk 6 length: 99
据介绍...暗黑城黑
--------------------------------------------------
Chunk 7 length: 46Chunk 6 7 由之前的 Chunk 3 截断得来
暗朋克...惊叹不止的“玩圣”盛宴让
--------------------------------------------------
Chunk 8 length: 17Chunk 8 就是之前的 Chunk 4一段 good_split
“鬼”友们身临其境过足“戏”瘾
--------------------------------------------------关于拆分文本的 RecursiveCharacterTextSplitter._split_text() 的源码位于附录部分。 回看对于 \n\n 的处理chunk 均大于 100你可能会说“这是因为演示代码增加了 chunk_size”实际并不是如此\n\n 对应的代码如下 text_splitter RecursiveCharacterTextSplitter(chunk_size100,chunk_overlap0,separators[\n\n]
)
texts text_splitter.split_text(text)# 输出每个片段的长度和内容
for i, t in enumerate(texts):print(fChunk {i1} length: {len(t)})print(t)print(- * 50)思考一下为什么没有正确截断 修改为 separators[\n\n, ]再查看其输出。 中文的句号更偏向表达于一段叙述的结束所以我们可以试着增加这个符号来修改预期行为
text_splitter RecursiveCharacterTextSplitter(chunk_size100,chunk_overlap0,separators[\n\n, \n, , 。, ]
)
texts text_splitter.split_text(text)# 输出每个片段的长度和内容
for i, t in enumerate(texts):print(fChunk {i1} length: {len(t)})print(t)print(- * 50)输出
Chunk 1 length: 50
长隆广州世界嘉年华系列活动的长隆欢乐世界潮牌玩圣节隆重登场在揭幕的第一天就吸引了大批年轻人前往打卡
--------------------------------------------------
Chunk 2 length: 80
。据悉这是长隆欢乐世界重金引进来自欧洲的12种巨型花车重磅出巡让人宛若进入五彩缤纷的巨人国全新的超级演艺广场每晚开启狂热的电音趴将整个狂欢氛围推向高点。
--------------------------------------------------
...现在RecursiveCharacterTextSplitter() 还有最后一个参数没有进行讲解length_function这是在 split 时计算长度是否达标的重要参数
if self._length_function(s) self._chunk_size:_good_splits.append(s)所以一般指定为 len。
❌ length_function 错误示范
text_splitter RecursiveCharacterTextSplitter(chunk_size100,chunk_overlap0,length_functionlambda x: 1,
)
texts text_splitter.split_text(text)# 输出每个片段的长度和内容
for i, t in enumerate(texts):print(fChunk {i1} length: {len(t)})print(text_splitter._length_function(Hello))输出
Chunk 1 length: 580
1此时无论多长text_splitter._length_function() 都返回为1所以对任意文本来说都是一个 _good_splits导致直接返回不进行分割。
回归正题处理文档
from langchain.text_splitter import RecursiveCharacterTextSplittertext_splitter RecursiveCharacterTextSplitter(chunk_size500, # 尝试调整它chunk_overlap100, # 尝试调整它#length_functionlen, # 可以省略#separators[\n\n, \n, , 。, ] # 可以省略
)
docs text_splitter.split_documents(documents)
print(len(docs))加载编码模型
接下来使用 HuggingFaceEmbeddings 加载 Hugging Face 上的预训练模型
from langchain_huggingface import HuggingFaceEmbeddings# 指定要加载的预训练模型的名称参考排行榜https://huggingface.co/spaces/mteb/leaderboard
model_name chuxin-llm/Chuxin-Embedding# 创建 Hugging Face 的嵌入模型实例这个模型将用于将文本转换为向量表示embedding
embedding_model HuggingFaceEmbeddings(model_namemodel_name)# 打印嵌入模型的配置信息显示模型结构和其他相关参数
print(embedding_model)# embed_query() 方法会将文本转换为嵌入的向量
query_embedding embedding_model.embed_query(Hello)# 打印生成的嵌入向量的长度向量长度应与模型的输出维度一致这里是 1024你也可以选择打印向量看看
print(f嵌入向量的维度为: {len(query_embedding)})输出
clientSentenceTransformer((0): Transformer({max_seq_length: 8192, do_lower_case: False}) with Transformer model: XLMRobertaModel (1): Pooling({word_embedding_dimension: 1024, pooling_mode_cls_token: True, pooling_mode_mean_tokens: False, pooling_mode_max_tokens: False, pooling_mode_mean_sqrt_len_tokens: False, pooling_mode_weightedmean_tokens: False, pooling_mode_lasttoken: False, include_prompt: True})(2): Normalize()
) model_namechuxin-llm/Chuxin-Embedding cache_folderNone model_kwargs{} encode_kwargs{} multi_processFalse show_progressFalse
嵌入向量的维度为: 1024可以看到当前编码模型最终输出的维度是 1024。 注意输出结果显示的是模型的配置信息而不是具体的向量嵌入。向量嵌入将在后续步骤中生成。 建立向量数据库
现在使用预训练嵌入模型对文本片段生成实际的向量表示然后建立向量数据库来存储和检索这些向量。这里使用 FAISSFacebook AI Similarity Search
from langchain.vectorstores import FAISS# 使用预训练嵌入模型生成向量并创建向量数据库
vectorstore FAISS.from_documents(docs, embedding_model)FAISS.from_documents() 方法会调用 embedding_model 对 docs 中的每个文本片段生成相应的向量表示。
保存和加载向量数据库可选
为了避免每次运行程序都重新计算向量表示可以将向量数据库保存到本地以便下次直接加载
from langchain.vectorstores import FAISS# 保存向量数据库
vectorstore.save_local(faiss_index)# 加载向量数据库
# 注意参数 allow_dangerous_deserialization确保你完全信任需要加载的数据库当然自己生成的不需要考虑这一点
vectorstore FAISS.load_local(faiss_index, embedding_model, allow_dangerous_deserializationTrue)创建检索器
现在我们需要创建一个检索器用于在用户提出问题时从向量数据库中检索相关的文本片段。
retriever vectorstore.as_retriever(search_kwargs{k: 3})k3 表示每次检索返回最相似的 3 个文档片段k 的大小可以根据需要调整较大的 k 值会返回更多的文档片段但可能会包含较多无关信息也可以通过 score 的大小进行初筛。
试着检索一下
query Top-K 和 Top-P 的区别是什么# 检索与 query 相关的文档片段
retrieved_docs retriever.invoke(query)# 打印检索到的文档片段
for i, doc in enumerate(retrieved_docs):print(fDocument {i1}:)print(fContent: {doc.page_content}\n)输出省略部分内容
Document 1:
Content: 什么是 Top-K 和 Top-P 采样Temperature 如何影响生成结果...代码文件下载目录采样方法概述Top-K 采样详解
...Top-P 采样详解
...Document 2:
Content: 输出:...Top-P 采样又称 Nucleus Sampling是一种动态选择候选词汇的方法。与 Top-K 采样不同Top-P 采样不是固定选择 K 个词汇而是选择一组累计概率达到 P 的词汇集合即从高到低加起来的概率。这意味着 Top-P 采样可以根据当前的概率分布动态调整候选词汇的数量从而更好地平衡生成的多样性和质量。
...Document 3:
Content: top_p0.5: 在这 10 个词汇中从高到低选择累积概率达到 0.5 的词汇归一化后进行采样。temperature0.8: 控制生成的随机性较低的温度使模型更倾向于高概率词汇。...你需要注意到的是即便是在当前项目中进行简单的文档检索也会出现一个问题观察 Document 1由于文章在引言和目录部分一般会精炼总体的信息所以 retriever 非常有可能捕捉到它而这些部分通常无法有效回答具体技术细节。通过以下代码我们可以查看各部分的得分情况
# 使用 FAISS 数据库进行相似性搜索返回最相关的文档片段
retrieved_docs vectorstore.similarity_search_with_score(query, k3)# 现在的 retrieved_docs 包含 (Document, score)
for doc, score in retrieved_docs:print(fScore: {score})print(fContent: {doc.page_content}\n)输出
Document 1:
Score: 0.8947205543518066
Content: 什么是 Top-K 和 Top-P 采样Temperature 如何影响生成结果
...
目录
...
Document 2:
Score: 0.9108018279075623
Content: 输出:
...
Document 3:
Score: 0.9529485702514648
Content: top_p0.5: 在这 10 个词汇中从高到低选择累积概率达到 0.5 的词汇归一化后进行采样。需要注意的是在这里得分越低表示相似度越高参见源码 Returns:List of documents most similar to the query text and L2 distance in float for each. Lower score represents more similarity.返回一个包含与查询文本最相似的文档列表以及每个文档对应的 L2 距离浮点数。得分越低表示相似度越高。如果查询的问题都是关于具体细节的那么目录部分的得分可能没有实质意义。所以根据需求可以在文件预处理时简单地过滤掉目录内容。
G
通过 Transformers 以及 LangChain 的 HuggingFacePipeline完成文本生成任务。
加载文本生成模型
这里我们选择 19a 所使用的量化模型当然你可以替换它
from transformers import AutoTokenizer, AutoModelForCausalLM# 以下二选一也可以进行替换
# 本地
model_path ./Mistral-7B-Instruct-v0.3-GPTQ-4bit
# 远程
model_path neuralmagic/Mistral-7B-Instruct-v0.3-GPTQ-4bit# 加载
tokenizer AutoTokenizer.from_pretrained(model_path)
model AutoModelForCausalLM.from_pretrained(model_path,torch_dtypeauto, # 自动选择模型的权重数据类型device_mapauto, # 自动选择可用的设备CPU/GPU
)创建管道
使用 Transformers 的 pipeline 创建一个文本生成器
from transformers import pipelinegenerator pipeline(text-generation, # 指定任务类型为文本生成modelmodel,tokenizertokenizer,max_length4096, # 指定生成文本的最大长度pad_token_idtokenizer.eos_token_id
)pipeline() 的第一个参数 task 并不是可以随意自定义的名称而是特定任务的标识。例如“text-generation” 对应于构造一个 TextGenerationPipeline用于生成文本。 集成到 LangChain
使用 LangChain 的 HuggingFacePipeline 将生成器包装为 LLM 接口
from langchain_huggingface import HuggingFacePipelinellm HuggingFacePipeline(pipelinegenerator)定义提示词模版
from langchain.prompts import PromptTemplatecustom_prompt PromptTemplate(templateUse the following pieces of context to answer the question at the end. If you dont know the answer, just say that you dont know, dont try to make up an answer.{context}Question: {question}
Answer:,input_variables[context, question]
)
构建问答链
使用检索器和 LLM 创建问答链
from langchain.chains import RetrievalQAqa_chain RetrievalQA.from_chain_type(llmllm,chain_typestuff, # 直接堆叠所有检索到的文档retrieverretriever, # 使用先前定义的检索器来获取相关文档# chain_type_kwargs{prompt: custom_prompt} # 可以选择传入自定义提示模板传入的话记得取消注释如果不需要可以删除这个参数
)chain_type 参数说明 stuff 将所有检索到的文档片段直接与问题“堆叠”在一起传递给 LLM。这种方式简单直接但当文档数量较多时可能会超过模型的上下文长度限制。 map_reduce 对每个文档片段分别生成回答map 阶段然后将所有回答汇总为最终答案reduce 阶段。 refine 先对第一个文档片段生成初始回答然后依次读取后续文档对答案进行逐步细化和完善。 map_rerank 对每个文档片段分别生成回答并为每个回答打分最终选择得分最高的回答作为答案。 map_reduce 和 refine 在用 API 实现 AI 视频摘要一文中有简单的概念解释。 进行 QA
# 提出问题
query Top-K 和 Top-P 的区别是什么# 获取答案
answer qa_chain.run(query)
print(answer)输出
Use the following pieces of context to answer the question at the end. If you dont know the answer, just say that you dont know, dont try to make up an answer.什么是 Top-K 和 Top-P 采样Temperature 如何影响生成结果...Question: Top-K 和 Top-P 的区别是什么
Answer: Top-K 采样是固定选择 K 个词汇而 Top-P 采样是选择一组累计概率达到 P 的词汇集合。Top-P 采样可以根据当前的概率分布动态调整候选词汇的数量从而更好地平衡生成的多样性和质量。至此我们完成了一个简单的 RAG 流程。注意在实际应用中很多的参数都需要根据具体情况来调整。
实际上使用 LangChain 并非必需。你可以观察到代码对于模型的使用完全可以基于 Transformers文档的递归分割实际上也可以自己构造函数来实现使用 LangChain 只是为了将其引入我们的视野。
参考链接
LangChain - Docs
Understanding LangChain’s RecursiveCharacterTextSplitter
附录
完整代码
from langchain.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.prompts import PromptTemplate
from langchain.vectorstores import FAISS
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from langchain.chains import RetrievalQA
from langchain_huggingface import HuggingFaceEmbeddings, HuggingFacePipeline# 定义文件所在的路径
DOC_PATH ../Guide# 使用 DirectoryLoader 从指定路径加载文件。*.md 表示加载所有 .md 格式的文件这里仅导入文章 10避免文章 20 的演示内容对结果的影响
loader DirectoryLoader(DOC_PATH, glob10*.md)# 加载目录中的指定的 .md 文件并将其转换为文档对象列表
documents loader.load()# 文本处理
text_splitter RecursiveCharacterTextSplitter(chunk_size500, # 尝试调整它chunk_overlap100, # 尝试调整它#length_functionlen, # 可以省略#separators[\n\n, \n, , 。, ] # 可以省略
)
docs text_splitter.split_documents(documents)# 生成嵌入使用 Hugging Face 模型
# 指定要加载的预训练模型的名称参考排行榜https://huggingface.co/spaces/mteb/leaderboard
model_name chuxin-llm/Chuxin-Embedding# 创建 Hugging Face 的嵌入模型实例这个模型将用于将文本转换为向量表示embedding
embedding_model HuggingFaceEmbeddings(model_namemodel_name)# 建立向量数据库
vectorstore FAISS.from_documents(docs, embedding_model)# 保存向量数据库可选
#vectorstore.save_local(faiss_index)# 加载向量数据库可选
# 注意参数 allow_dangerous_deserialization确保你完全信任需要加载的数据库当然自己生成的不需要考虑这一点
#vectorstore FAISS.load_local(faiss_index, embedding_model, allow_dangerous_deserializationTrue)# 创建检索器
retriever vectorstore.as_retriever(search_kwargs{k: 3})# 加载文本生成模型
# 本地
model_path ./Mistral-7B-Instruct-v0.3-GPTQ-4bit
# 远程
#model_path neuralmagic/Mistral-7B-Instruct-v0.3-GPTQ-4bit# 加载
tokenizer AutoTokenizer.from_pretrained(model_path)
model AutoModelForCausalLM.from_pretrained(model_path,torch_dtypeauto, # 自动选择模型的权重数据类型device_mapauto, # 自动选择可用的设备CPU/GPU
)# 创建文本生成管道
generator pipeline(text-generation, # 指定任务类型为文本生成modelmodel,tokenizertokenizer,max_length4096, # 指定生成文本的最大长度pad_token_idtokenizer.eos_token_id
)# 包装为 LangChain 的 LLM 接口
llm HuggingFacePipeline(pipelinegenerator)custom_prompt PromptTemplate(templateUse the following pieces of context to answer the question at the end. If you dont know the answer, just say that you dont know, dont try to make up an answer.{context}Question: {question}
Answer:,input_variables[context, question]
)# 构建问答链
qa_chain RetrievalQA.from_chain_type(llmllm,chain_typestuff, # 直接堆叠所有检索到的文档retrieverretriever, # 使用先前定义的检索器来获取相关文档# chain_type_kwargs{prompt: custom_prompt} # 可以选择传入自定义提示模板传入的话记得取消注释如果不需要可以删除这个参数
)# 提出问题
query Top-K 和 Top-P 的区别是什么# 获取答案
answer qa_chain.run(query)
print(answer)RecursiveCharacterTextSplitter 源码 https://api.python.langchain.com/en/latest/_modules/langchain_text_splitters/character.html#RecursiveCharacterTextSplitter class RecursiveCharacterTextSplitter(TextSplitter):...def _split_text(self, text: str, separators: List[str]) - List[str]:Split incoming text and return chunks.final_chunks []# 获取要使用的分隔符separator separators[-1]new_separators []for i, _s in enumerate(separators):_separator _s if self._is_separator_regex else re.escape(_s)if _s :separator _sbreakif re.search(_separator, text):separator _snew_separators separators[i 1 :]break_separator separator if self._is_separator_regex else re.escape(separator)splits _split_text_with_regex(text, _separator, self._keep_separator)# 现在开始合并文本并递归拆分较长的文本片段_good_splits []_separator if self._keep_separator else separatorfor s in splits:if self._length_function(s) self._chunk_size:_good_splits.append(s)else:if _good_splits:merged_text self._merge_splits(_good_splits, _separator)final_chunks.extend(merged_text)_good_splits []if not new_separators:final_chunks.append(s)else:# **递归调用自身使用剩余的分隔符继续拆分**other_info self._split_text(s, new_separators)final_chunks.extend(other_info)if _good_splits:merged_text self._merge_splits(_good_splits, _separator)final_chunks.extend(merged_text)return final_chunks