一、【一问一答】
Q:输入不同长度的文本,为什么通过embedding后维度是一致?
维度是一样的,虽然输入文字长短不一但是embedding是按照语义来最终输出维度一样的向量,维度一样是为了方便做计算
Q:bge模型最低需要什么配置才能跑通
模型文件文件大小是2.3GB,CPU也可以跑通,或者显存 >=4GB
Q:RAG怎么在Linux环境下部署?如:py脚本做的faiss
和windows差不多,同样需要先 pip install 所依赖的安装包,然后运行对应的xx.py。也可以在Linux中的jupyter运行xx.ipynb
Q:pip install gensim 时一直报错
可能和依赖的python其他安装包的版本相关
Q:Embedding之后是不是会有许多向量一样?向量一样是语义相似?
语义是否类似,是将输入内容通过Embedding转化为相同维度的向量,根据向量之间的相似度进行计算来判定语义相似
Q:公司资料库中有很多技术文档、标准等pdf,包含很多表格。大模型回答问题时需要精确数据,RAG效果是不是不太好?有什么解决方案吗?
用MinerU,可以将pdf中的表格、图片 转化为 markdown格式。MinerU 是一款开源的 PDF 结构化解析工具,由 OpenDataLab 开源社区主导。
MinerU核心功能:把 PDF(含扫描版)快速转换成 Markdown、JSON、数据库等可直接使用的结构化数据,保留文本、表格、公式、图片和排版信息。一句话:上传 PDF,秒得可编辑的结构化文档,支持在线即用、本地脚本、Docker 批量三种方式。
https://mineru.net/
Q:语音可以转到知识库吗
先将语音转化为文本=> 然后Embedding
Q:pip install faiss-cpu, 如何装gpu版本
conda install -c conda-forge faiss-gpu
Q: 客户需要语音识别为文字,文字转换成代码,最后执行代码,专家推荐SFT而不是RAG,这是出于什么考量啊?
一般的逻辑是:先RAG,RAG解决不了的再SFT
客户嫌弃COT太长(Chain-of-Thought(思维链/推理链))。比如:在客服工单、邮件或聊天里,系统把完整的内部推理步骤(COT)全部呈现给了客户。客户嫌内容冗长、阅读负担大,只想看“结论/解决方案”,不想看一步步推导。简言之:客户嫌“废话太多,直接告诉我结果”
Q:大模型应用开发3种模式,提示工程VS RAG VS 微调,什么时候使用?
通过海量知识训练出LLM大模型,当我们提问时,大模型LLM回答错误。(1)第1个环节:我们先审查一下,我们提问的问题是否清晰明了。若是问题本身没有提问清楚,可以修改一下提示词。(2)第二个环节:若是问题非常清晰,但是LLM大模型没有回答正确,尝试看一下LLM是不是对某个公司特定的产品不了解(不知道某个会议纪要,某个领导的讲话),将资料数据做成RAG给到LLM(3)第三环节:若是一、二环节都不能回答正确,根本性原因是LLM的能力不足,通过微调SFT让模型具备某些能力(中医药大模型LLM,需要对中医药系统性的有了解和学习)。对于业务人员而言最常见的是“提示词工程”,因为不需要写代码,直接问就行;对于具备一定开发能力的人员通常会使用“RAG知识库”,需要将一些背景资料(COT思维链)给到LLM
二、RAG简介
1、RAG是什么
RAG(Retrieval-Augmented Generation,检索增强生成)是一种先检索、后生成的端到端技术:系统收到问题后,立即从外部知识库或互联网实时拉取最相关、最新的文档片段,将其与原问题一起输入大模型作为动态上下文,模型再基于这些“新鲜证据”生成答案,从而把传统大模型“静态记忆”升级为“实时外挂硬盘”。
2、RAG的优势是什么
• 解决知识时效性问题:大模型的训练数据通常是静态的,无法涵盖最新信息,而RAG可以检索外部知识库实时更新信息
• 减少模型幻觉:通过引入外部知识,RAG能够减少模型生成虚假或不准确内容的可能性
• 提升专业领域回答质量:RAG能够结合垂直领域的专业知识库,生成更具专业深度的回答
3、RAG的核心原理与流程
第一步——数据预处理
• 知识库构建:收集并整理文档、网页、数据库等多源数据,构建外部知识库
• 文档分块:将文档切分为适当大小的片段(chunks),以便后续检索。分块策略需要在语义完整性与检索效率之间取得平衡
• 向量化处理:使用嵌入模型(如BGE、M3E、Chinese-Alpaca-2等)将文本块转换为向量,并存储在向量数据库中
第二步——检索阶段
• 查询处理:将用户输入的问题转换为向量,并在向量数据库中进行相似度检索,找到最相关的文本片段
• 重排序:对检索结果进行相关性排序,选择最相关的片段作为生成阶段的输入
第三步——生成阶段
• 上下文组装:将检索到的文本片段与用户问题结合,形成增强的上下文输入
• 生成回答:大语言模型基于增强的上下文生成最终回答
4、RAG 的“毛坯房”版NativeRAG
NativeRAG(也称 Naive RAG / Baseline RAG)是检索增强生成的“最原始形态”:先把用户问题向量化,去向量库里简单一搜,把最相似的文档片段直接塞进大模型,让它“看着答”
优点:实现简单、延迟低;缺点:检索粗、易幻觉、难处理复杂语境。一句话——RAG 的“毛坯房”版
NativeRAG的步骤:
Indexing (数据切分后向量化并存储)=> 如何更好地把知识存起来
Retrieval检索 (找到相似度高的向量)=> 如何在大量的知识中,找到一小部分有用的,给到模型参考
Generation产生 => 如何结合用户的提问和检索到的知识,让模型生成有用的答案
这三个步骤虽然看似简单,但在RAG 应用从构建到落地实施的整个过程中,涉及较多复杂的工作内容。RAG有点像推荐系统,推荐系统不一定是最准确的但是他一定是相关的
三、Embedding模型选择
选择不同的Embedding模型直接影响到我们对文档理解的质量
1、Embedding模型能力排名
https://huggingface.co/spaces/mteb/leaderboard 比较了1000多种语言中的100多种文本嵌入模型
2、有哪些常见的Embedding模型?
分类 | 模型名称 | 特点 | 适用场景 | 文件大小 |
通用文本嵌入模型 | BGE-M3(智源研究院) | 支持100+语言,输入长度达8192 tokens,融合密集、稀疏、多向量混合检索,适合跨语言长文档检索 | 跨语言长文档检索、高精度RAG应用 | 2.3 G |
text-embedding-3-large(OpenAI) | 向量维度3072,长文本语义捕捉能力强,英文表现优秀 | 英文内容优先的全球化应用 | — | |
Jina-embeddings-v2-small(Jina AI) | 参数量仅35 M,支持实时推理(RT<50 ms),适合轻量化部署 | 轻量级文本处理、实时推理任务 | — | |
中文嵌入模型 | xiaobu-embedding-v2 | 针对中文语义优化,语义理解能力强 | 中文文本分类、语义检索 | — |
M3E-Base | 针对中文优化的轻量模型,适合本地私有化部署 | 中文法律、医疗领域检索任务 | 0.4 G | |
stella-mrl-large-zh-v3.5-1792 | 处理大规模中文数据能力强,捕捉细微语义关系 | 中文文本高级语义分析、自然语言处理任务 | — | |
指令(具体任务)驱动与复杂任务模型 | gte-Qwen2-7B-instruct(阿里巴巴) | 基于Qwen大模型微调,支持代码与文本跨模态检索 | 复杂指令驱动任务、智能问答系统 | — |
E5-mistral-7B(Microsoft) | 基于Mistral架构,Zero-shot任务表现优异 | 动态调整语义密度的复杂系统 | — | |
企业级与复杂系统 | BGE-M3(智源研究院) | 适合企业级部署,支持混合检索 | 企业级语义检索、复杂RAG应用 | 2.3 G |
E5-mistral-7B(Microsoft) | 适合企业级部署,支持指令微调 | 需要动态调整语义密度的复杂系统 | — |
购买GPU云主机,完成初始化和部署:
https://console.compshare.cn/light-gpu/console/resources
https://passport.compshare.cn/register?referral_code=BwJtdGNa5q1FARKqawOE27
- 安装 pip(与系统 python3 匹配)
apt update
apt install python3-pip -y
- 验证
python3 -m pip --version
应看到类似输出:
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)
- 安装 modelscope
python3 -m pip install modelscope
如需国内镜像加速:
python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple modelscope
python3 -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple FlagEmbedding
完成后再次运行脚本
3、Embedding模型 bge-m3的实际使用
python3 bge-m3.py
bge-m3.py
#!/usr/bin/env python
# coding: utf-8
# In[1]:
# 第一步:下载BGE M3模型并放置到指定目录
# 使用modelscope库的snapshot_download函数下载模型
from modelscope import snapshot_download
# 下载模型到/root/autodl-tmp/models目录下并返回模型路径
model_dir = snapshot_download('BAAI/bge-m3', cache_dir='/root/autodl-tmp/models')
# In[1]:
# 第二步:导入并初始化BGE M3模型,向量维度是1024,不同的模型向量维度不同
from FlagEmbedding import BGEM3FlagModel
# 创建BGEM3FlagModel实例
# 参数1:模型路径
# 参数2:use_fp16=True 使用半精度浮点数计算,加快计算速度,性能略有下降
model = BGEM3FlagModel('/root/autodl-tmp/models/BAAI/bge-m3',
use_fp16=True)
# 第三步:准备需要编码的句子
# 第一组句子
sentences_1 = ["What is BGE M3?", "Defination of BM25"]
# 第二组句子
sentences_2 = ["BGE M3 is an embedding model supporting dense retrieval, lexical matching and multi-vector interaction.",
"BM25 is a bag-of-words retrieval function that ranks a set of documents based on the query terms appearing in each document"]
# 第四步:对句子进行编码得到嵌入向量
# 对第一组句子进行编码
# batch_size=12:批处理大小
# max_length=8192:最大序列长度,不需要这么长时可以设置更小的值来加快编码速度
embeddings_1 = model.encode(sentences_1,
batch_size=12,
max_length=8192,
)['dense_vecs'] # 获取密集向量
# 对第二组句子进行编码(使用默认参数)
embeddings_2 = model.encode(sentences_2)['dense_vecs']
# 第五步:计算相似度矩阵
# 使用矩阵乘法计算两组嵌入向量之间的余弦相似度
similarity = embeddings_1 @ embeddings_2.T
# 打印相似度矩阵
# 结果解释:矩阵中的每个元素[i,j]表示sentences_1[i]和sentences_2[j]之间的相似度
print(similarity)
# 预期输出示例:[[0.6265, 0.3477], [0.3499, 0.678 ]]
# 其中第一行表示"What is BGE M3?"与两个句子的相似度
# 第二行表示"Defination of BM25"与两个句子的相似度
运行成功
下一个案例:
Python 版本 3.11.13
python3 gte-qwen2-1.py
安装依赖:
python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com sentence_transformers
4、Embedding模型 gte-qwen2的实际使用
gte-qwen2-1.py
#!/usr/bin/env python3
# coding: utf-8
# --------------------------------------------------
# 0. 先打补丁(必须在 import transformers 之前)
# --------------------------------------------------
# 导入 transformers 库和 DynamicCache 类
import transformers
from transformers.cache_utils import DynamicCache
# 检查 DynamicCache 类是否缺少 get_usable_length 方法
# 这是为了兼容不同版本的 transformers 库
if not hasattr(DynamicCache, 'get_usable_length'):
# 定义一个兼容方法,接收 kv_seq_len 和 layer_idx 参数
# 但实际上只返回 get_seq_length() 的结果
def get_usable_length(self, kv_seq_len, layer_idx=0):
return self.get_seq_length()
# 动态添加方法到 DynamicCache 类
setattr(DynamicCache, 'get_usable_length', get_usable_length)
# --------------------------------------------------
# 1. 下载模型
# --------------------------------------------------
# 从 modelscope 导入 snapshot_download 函数用于下载模型
from modelscope import snapshot_download
# 下载 gte_Qwen2-1.5B-instruct 模型到指定的缓存目录
# gte (General Text Embedding) 是一种通用文本嵌入模型
# 基于 Qwen2-1.5B 模型架构,适合用于各种文本嵌入任务
model_dir = snapshot_download(
'iic/gte_Qwen2-1.5B-instruct', # 模型名称
cache_dir='/root/autodl-tmp/models' # 模型缓存目录
)
# --------------------------------------------------
# 2. 加载 tokenizer / model
# --------------------------------------------------
# 导入所需的库和类
from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn.functional as F
# 设置运行设备:优先使用 GPU (cuda),否则使用 CPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# 加载 tokenizer,用于文本处理和编码
# trust_remote_code=True 允许执行模型仓库中的自定义代码
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
# 设置 padding 方向为左侧,这对某些模型的性能有影响
# 特别是在处理对话或有上下文的文本时
# 如果 tokenizer 没有 pad_token,则使用 eos_token 作为 pad_token
tokenizer.padding_side = 'left'
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 加载模型并移至指定设备,设置为评估模式
# eval() 模式会关闭一些特定于训练的功能,如 dropout
model = AutoModel.from_pretrained(model_dir, trust_remote_code=True).to(device).eval()
# --------------------------------------------------
# 3. 工具函数
# --------------------------------------------------
def last_token_pool(hidden_states: torch.Tensor,
attention_mask: torch.Tensor) -> torch.Tensor:
"""
从模型的最后一层隐藏状态中提取最后一个有效 token 的表示
参数:
hidden_states: 模型输出的隐藏状态张量,形状为 [batch_size, seq_len, hidden_dim]
attention_mask: 注意力掩码张量,形状为 [batch_size, seq_len]
返回:
池化后的特征向量,形状为 [batch_size, hidden_dim]
"""
# 计算每个序列中有效 token 的数量,然后减去 1 得到最后一个有效 token 的位置
seq_len = attention_mask.sum(dim=1) - 1
# 获取批次大小
batch_size = hidden_states.size(0)
# 从每个序列中提取最后一个有效 token 的表示
return hidden_states[torch.arange(batch_size, device=hidden_states.device), seq_len]
# --------------------------------------------------
# 4. 数据
# --------------------------------------------------
# 定义任务描述,告诉模型我们要进行的是搜索查询与相关段落的匹配任务
task = 'Given a web search query, retrieve relevant passages that answer the query'
# 准备查询文本,每个查询包含任务指令和具体问题
# 这种格式有助于模型理解任务上下文,提高嵌入质量
queries = [
f'Instruct: {task}\nQuery: how much protein should a female eat', # 关于女性蛋白质摄入量的查询
f'Instruct: {task}\nQuery: summit define', # 关于 summit 定义的查询
]
# 准备文档文本,这些是可能与查询相关的文本段落
documents = [
"As a general guideline, the CDC's average requirement of protein for women ages 19 to 70 is 46 grams per day...",
"Definition of summit for English Language Learners..."
]
# 设置最大序列长度,适用于 gte-Qwen2 模型支持的较长上下文
MAX_LEN = 8192
# --------------------------------------------------
# 5. 逐条编码(避免 KV 长度错位)
# --------------------------------------------------
def embed(texts):
"""
将文本列表转换为嵌入向量列表
逐条处理文本以避免 KV 缓存长度不匹配的问题
参数:
texts: 文本字符串列表
返回:
文本的嵌入向量张量,形状为 [len(texts), embedding_dim]
"""
vecs = []
# 逐条处理文本,避免批处理可能导致的 KV 缓存问题
for txt in texts:
# 使用 tokenizer 处理文本
inputs = tokenizer(
txt,
return_tensors='pt', # 返回 PyTorch 张量
max_length=MAX_LEN, # 最大长度限制
truncation=True, # 超出长度时截断
padding=True # 不足长度时填充
).to(device) # 移至指定设备
# 关闭梯度计算以提高效率并节省内存
with torch.no_grad():
# 获取模型输出
outputs = model(**inputs)
# 提取最后一个有效 token 的表示
vec = last_token_pool(outputs.last_hidden_state, inputs['attention_mask'])
# 对向量进行 L2 归一化,使所有向量具有相同的模长
vecs.append(F.normalize(vec, p=2, dim=1))
# 将所有向量在批次维度上拼接
return torch.cat(vecs, dim=0)
# 获取查询和文档的嵌入向量
q_emb = embed(queries)
d_emb = embed(documents)
# --------------------------------------------------
# 6. 相似度 & 输出
# --------------------------------------------------
# 计算查询和文档之间的余弦相似度
# q_emb @ d_emb.T 计算所有查询与所有文档之间的点积
# 由于向量已经归一化,点积等于余弦相似度
# 乘以 100 是为了使结果更直观(范围从 -100 到 100)
scores = (q_emb @ d_emb.T) * 100
# 打印相似度分数
# 每行代表一个查询与所有文档的相似度
# 每列代表一个文档与所有查询的相似度
print(scores.tolist())
运行成功:
gte-qwen2-2.py
#!/usr/bin/env python3
# coding: utf-8
# --------------------------------------------------
# 0. 先打补丁(必须在 import transformers 之前)
# --------------------------------------------------
# 导入 transformers 库和 DynamicCache 类
import transformers
from transformers.cache_utils import DynamicCache
# 检查 DynamicCache 类是否缺少 get_usable_length 方法
# 这是为了兼容不同版本的 transformers 库
if not hasattr(DynamicCache, 'get_usable_length'):
# 定义一个兼容方法,接收 kv_seq_len 和 layer_idx 参数
# 但实际上只返回 get_seq_length() 的结果
def get_usable_length(self, kv_seq_len, layer_idx=0):
return self.get_seq_length()
# 动态添加方法到 DynamicCache 类
setattr(DynamicCache, 'get_usable_length', get_usable_length)
# --------------------------------------------------
# 1. 依赖
# --------------------------------------------------
# 导入 PyTorch 深度学习库
import torch
# 导入 PyTorch 函数式接口,包含各种神经网络函数
import torch.nn.functional as F
# 导入 Tensor 类型,用于类型提示
from torch import Tensor
# 从 transformers 导入自动分词器和模型加载器
from transformers import AutoTokenizer, AutoModel
# --------------------------------------------------
# 2. 工具函数
# --------------------------------------------------
def last_token_pool(hidden_states: torch.Tensor,
attention_mask: torch.Tensor) -> torch.Tensor:
"""
从模型的最后一层隐藏状态中提取最后一个有效 token 的表示
参数:
hidden_states: 模型输出的隐藏状态张量,形状为 [batch_size, seq_len, hidden_dim]
attention_mask: 注意力掩码张量,形状为 [batch_size, seq_len]
返回:
池化后的特征向量,形状为 [batch_size, hidden_dim]
"""
# 计算每个序列中有效 token 的数量,然后减去 1 得到最后一个有效 token 的位置
seq_len = attention_mask.sum(dim=1) - 1
# 获取批次大小
batch_size = hidden_states.size(0)
# 从每个序列中提取最后一个有效 token 的表示
return hidden_states[torch.arange(batch_size, device=hidden_states.device), seq_len]
def get_detailed_instruct(task_description: str, query: str) -> str:
"""
将任务描述和查询组合成模型所需的指令格式
参数:
task_description: 任务的详细描述
query: 具体的查询内容
返回:
格式化后的指令字符串
"""
return f'Instruct: {task_description}\nQuery: {query}'
# --------------------------------------------------
# 3. 数据
# --------------------------------------------------
# 定义任务描述,告诉模型我们要进行的是搜索查询与相关段落的匹配任务
task = 'Given a web search query, retrieve relevant passages that answer the query'
# 准备查询文本,使用 get_detailed_instruct 函数将任务描述和查询组合成特定格式
queries = [
get_detailed_instruct(task, 'how much protein should a female eat'), # 关于女性蛋白质摄入量的查询
get_detailed_instruct(task, 'summit define'), # 关于 summit 定义的查询
]
# 准备文档文本,这些是可能与查询相关的文本段落
documents = [
"As a general guideline, the CDC's average requirement of protein for women ages 19 to 70 is 46 grams per day...",
"Definition of summit for English Language Learners..."
]
# --------------------------------------------------
# 4. 加载 tokenizer / model
# --------------------------------------------------
# 模型目录路径,指向本地已下载的 gte_Qwen2-1.5B-instruct 模型
model_dir = "/root/autodl-tmp/models/iic/gte_Qwen2-1___5B-instruct"
# 加载 tokenizer,用于文本处理和编码
# trust_remote_code=True 允许执行模型仓库中的自定义代码
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
# 设置 padding 方向为左侧,这对某些模型的性能有影响
# 特别是在处理对话或有上下文的文本时
tokenizer.padding_side = 'left'
# 如果 tokenizer 没有 pad_token,则使用 eos_token 作为 pad_token
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 设置运行设备:优先使用 GPU (cuda),否则使用 CPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# 加载模型并移至指定设备,设置为评估模式
# eval() 模式会关闭一些特定于训练的功能,如 dropout
model = AutoModel.from_pretrained(model_dir, trust_remote_code=True).to(device).eval()
# 设置最大序列长度,适用于 gte-Qwen2 模型支持的较长上下文
MAX_LEN = 8192
# --------------------------------------------------
# 5. 逐条编码(避免 KV 长度错位)
# --------------------------------------------------
def embed(texts):
"""
将文本列表转换为嵌入向量列表
逐条处理文本以避免 KV 缓存长度不匹配的问题
参数:
texts: 文本字符串列表
返回:
文本的嵌入向量张量,形状为 [len(texts), embedding_dim]
"""
vecs = []
# 逐条处理文本,避免批处理可能导致的 KV 缓存问题
for txt in texts:
# 使用 tokenizer 处理文本
inputs = tokenizer(
txt,
return_tensors='pt', # 返回 PyTorch 张量
max_length=MAX_LEN, # 最大长度限制
truncation=True, # 超出长度时截断
padding=True # 不足长度时填充
).to(device) # 移至指定设备
# 关闭梯度计算以提高效率并节省内存
with torch.no_grad():
# 获取模型输出
outputs = model(**inputs)
# 提取最后一个有效 token 的表示
vec = last_token_pool(outputs.last_hidden_state, inputs['attention_mask'])
# 对向量进行 L2 归一化,使所有向量具有相同的模长
vecs.append(F.normalize(vec, p=2, dim=1))
# 将所有向量在批次维度上拼接
return torch.cat(vecs, dim=0)
# 获取查询和文档的嵌入向量
q_emb = embed(queries)
d_emb = embed(documents)
# --------------------------------------------------
# 6. 相似度 & 输出
# --------------------------------------------------
# 计算查询和文档之间的余弦相似度
# q_emb @ d_emb.T 计算所有查询与所有文档之间的点积
# 由于向量已经归一化,点积等于余弦相似度
# 乘以 100 是为了使结果更直观(范围从 -100 到 100)
scores = (q_emb @ d_emb.T) * 100
# 打印相似度分数
# 每行代表一个查询与所有文档的相似度
# 每列代表一个文档与所有查询的相似度
# 预期结果应该显示每个查询与对应的相关文档有较高的相似度
print(scores.tolist())
# 结果解释:
# [[70.00666809082031, 8.184867858886719], [14.62420654296875, 77.71405792236328]]
# 70.00: 第一个查询(蛋白质摄入)与第一个文档(蛋白质指南)的相似度(高相关)
# 8.18: 第一个查询(蛋白质摄入)与第二个文档(summit定义)的相似度(低相关)
# 14.62: 第二个查询(summit定义)与第一个文档(蛋白质指南)的相似度(低相关)
# 77.71: 第二个查询(summit定义)与第二个文档(summit定义)的相似度(高相关)
运行成功:
5、Embedding模型使用总结
项目 | 内容 |
模型名称 | gte-Qwen2-7B-instruct |
模型类型 | 基于 Qwen2 的指令优化型嵌入模型 |
指令优化 | 在大规模指令-响应对上精细训练,擅长解析复杂指令并给出准确、自然的回答 |
性能表现 | 文本生成、问答、分类、情感分析、命名实体识别、语义匹配等任务均表现优异 |
适用场景 | 复杂问答系统、多步推理、跨语言 NLP 任务,需要高语义理解能力的应用 |
核心优势 | - 指令理解与执行能力突出,支持复杂指令驱动 - 多语言覆盖,跨语种表现一致 - 在文本生成与语义理解方面领先 |
局限 | 模型体量大,对 GPU/内存资源要求高,推荐在资源充足的服务器或云端部署 |
四、DeepSeek + Faiss 搭建本地知识库检索
1、 项目的架构与技术选型是什么?
(1)RAG架构
检索(Retrieval):使用向量相似度搜索从PDF文档中检索相关内容
增强(Augmentation):将检索到的文档片段作为上下文
生成(Generation):基于上下文和用户问题生成答案
(2)技术栈选择
• 向量数据库:Faiss作为 高效的向量检索
• 嵌入模型:阿里云DashScope的text-embedding-v1
• 大语言模型:deepseek-v3
• 文档处理:PyPDF2用于PDF文本提取
2、程序的逻辑结构是什么?
Step1:文档预处理
PDF文件 → 文本提取 → 文本分割 → 页码映射
1) PDF文本提取
• 逐页提取文本内容
• 记录每行文本对应的页码信息
• 处理空页和异常情况
2) 文本分割策略
• 使用递归字符分割器
• 分割参数:chunk_size=1000, chunk_overlap=200
• 分割符优先级:段落 → 句子 → 空格 → 字符
3) 页码映射处理
• 基于字符位置计算每个文本块的页码
• 使用众数统计确定文本块的主要来源页码
• 建立文本块与页码的映射关系
Step2:知识库构建
文本块→ 嵌入向量 → Faiss索引 → 本地持久化
1) 向量数据库构建
• 使用DashScope嵌入模型生成向量
• 将向量存储到Faiss索引结构
2) 数据持久化
• 保存Faiss索引文件(.faiss)
• 保存元数据信息(.pkl)
• 保存页码映射关系(page_info.pkl)
Step3:问答查询
用户问题→ 向量检索 → 文档组合 → LLM生成 → 答案输出
1) 相似度检索
• 将用户问题转换为向量
• 在Faiss中搜索最相似的文档块,返回Top-K相关文档
2) 问答链处理
• 使用LangChain的load_qa_chain
• 采用 stuff 策略组合文档
• 将组合后的上下文和问题发送给LLM
3)程序运行结果展示
chatpdf-faiss.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
PDF文档问答系统
该脚本实现了一个基于FAISS向量数据库和大语言模型的PDF文档问答系统,
可以从PDF中提取文本,创建向量索引,并基于相似度搜索进行问答。
"""
# 导入必要的库
from PyPDF2 import PdfReader # 用于读取PDF文件
from langchain.chains.question_answering import load_qa_chain # 加载问答链
from langchain.text_splitter import RecursiveCharacterTextSplitter # 文本分割工具
from langchain_community.embeddings import DashScopeEmbeddings # 阿里云DashScope嵌入模型
from langchain_community.vectorstores import FAISS # FAISS向量数据库
from typing import List, Tuple # 类型提示
import os # 操作系统接口
import pickle # 序列化工具
# 从环境变量获取DashScope API密钥
DASHSCOPE_API_KEY = os.getenv('DASHSCOPE_API_KEY')
if not DASHSCOPE_API_KEY:
raise ValueError("请设置环境变量 DASHSCOPE_API_KEY")
def extract_text_with_page_numbers(pdf) -> Tuple[str, List[Tuple[str, int]]]:
"""
从PDF中提取文本并记录每个字符对应的页码
参数:
pdf: PDF文件对象,通过PdfReader创建
返回:
Tuple[str, List[int]]: 包含两个元素的元组
- 提取的完整文本内容
- 每个字符对应的页码列表
"""
text = "" # 存储提取的文本
char_page_mapping = [] # 存储每个字符对应的页码
# 遍历PDF的每一页
for page_number, page in enumerate(pdf.pages, start=1):
# 提取当前页的文本
extracted_text = page.extract_text()
if extracted_text:
text += extracted_text
# 为当前页面的每个字符记录页码
char_page_mapping.extend([page_number] * len(extracted_text))
else:
print(f"第 {page_number} 页未找到文本。")
return text, char_page_mapping
def process_text_with_splitter(text: str, char_page_mapping: List[int], save_path: str = None) -> FAISS:
"""
处理文本并创建向量存储
参数:
text: 提取的文本内容
char_page_mapping: 每个字符对应的页码列表
save_path: 可选,保存向量数据库的路径
返回:
FAISS: 基于FAISS的向量存储对象,包含文本嵌入和检索功能
"""
# 创建文本分割器,用于将长文本分割成小块
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", ".", " ", ""], # 分割符优先级
chunk_size=1000, # 每个文本块的大小
chunk_overlap=200, # 文本块之间的重叠部分大小
length_function=len, # 计算长度的函数
)
# 分割文本为多个文本块
chunks = text_splitter.split_text(text)
print(f"文本被分割成 {len(chunks)} 个块。")
# 创建DashScope嵌入模型,用于将文本转换为向量
embeddings = DashScopeEmbeddings(
model="text-embedding-v1", # 使用的嵌入模型
dashscope_api_key=DASHSCOPE_API_KEY, # API密钥
)
# 从文本块创建FAISS向量知识库
knowledgeBase = FAISS.from_texts(chunks, embeddings)
print("已从文本块创建知识库。")
# 为每个文本块找到对应的页码信息
page_info = {}
current_pos = 0 # 当前处理位置
for chunk in chunks:
chunk_start = current_pos
chunk_end = current_pos + len(chunk)
# 找到这个文本块中字符对应的所有页码
chunk_pages = char_page_mapping[chunk_start:chunk_end]
# 取页码的众数(出现最多的页码)作为该块的页码
if chunk_pages:
# 统计每个页码出现的次数
page_counts = {}
for page in chunk_pages:
page_counts[page] = page_counts.get(page, 0) + 1
# 找到出现次数最多的页码
most_common_page = max(page_counts, key=page_counts.get)
page_info[chunk] = most_common_page
else:
page_info[chunk] = 1 # 默认页码
current_pos = chunk_end
# 将页码信息存储在知识库对象中
knowledgeBase.page_info = page_info
print(f'页码映射完成,共 {len(page_info)} 个文本块')
# 如果提供了保存路径,则保存向量数据库和页码信息
if save_path:
# 确保目录存在
os.makedirs(save_path, exist_ok=True)
# 保存FAISS向量数据库
knowledgeBase.save_local(save_path)
print(f"向量数据库已保存到: {save_path}")
# 保存页码信息到同一目录
with open(os.path.join(save_path, "page_info.pkl"), "wb") as f:
pickle.dump(page_info, f)
print(f"页码信息已保存到: {os.path.join(save_path, 'page_info.pkl')}")
return knowledgeBase
def load_knowledge_base(load_path: str, embeddings = None) -> FAISS:
"""
从磁盘加载向量数据库和页码信息
参数:
load_path: 向量数据库的保存路径
embeddings: 可选,嵌入模型。如果为None,将创建一个新的DashScopeEmbeddings实例
返回:
FAISS: 加载的FAISS向量数据库对象
"""
# 如果没有提供嵌入模型,则创建一个新的
if embeddings is None:
embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key=DASHSCOPE_API_KEY,
)
# 加载FAISS向量数据库,添加allow_dangerous_deserialization=True参数以允许反序列化
knowledgeBase = FAISS.load_local(load_path, embeddings, allow_dangerous_deserialization=True)
print(f"向量数据库已从 {load_path} 加载。")
# 加载页码信息
page_info_path = os.path.join(load_path, "page_info.pkl")
if os.path.exists(page_info_path):
with open(page_info_path, "rb") as f:
page_info = pickle.load(f)
knowledgeBase.page_info = page_info
print("页码信息已加载。")
else:
print("警告: 未找到页码信息文件。")
return knowledgeBase
# 主程序开始
# 读取PDF文件
pdf_reader = PdfReader('./浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf')
# 提取文本和页码信息
text, char_page_mapping = extract_text_with_page_numbers(pdf_reader)
# 输出提取的文本长度信息
print(f"提取的文本长度: {len(text)} 个字符。")
# 处理文本并创建知识库,同时保存到磁盘
save_dir = "./vector_db"
knowledgeBase = process_text_with_splitter(text, char_page_mapping, save_path=save_dir)
# 示例:如何加载已保存的向量数据库
# 注释掉以下代码以避免在当前运行中重复加载
"""
# 创建嵌入模型
embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key=DASHSCOPE_API_KEY,
)
# 从磁盘加载向量数据库
loaded_knowledgeBase = load_knowledge_base("./vector_db", embeddings)
# 使用加载的知识库进行查询
docs = loaded_knowledgeBase.similarity_search("客户经理每年评聘申报时间是怎样的?")
# 直接使用FAISS.load_local方法加载(替代方法)
# loaded_knowledgeBase = FAISS.load_local("./vector_db", embeddings, allow_dangerous_deserialization=True)
# 注意:使用这种方法加载时,需要手动加载页码信息
"""
# 创建大语言模型实例,用于生成回答
from langchain_community.llms import Tongyi
llm = Tongyi(model_name="qwen-turbo", dashscope_api_key=DASHSCOPE_API_KEY) # 使用qwen-turbo模型
# 设置查询问题
query = "客户经理被投诉了,投诉一次扣多少分"
query = "客户经理每年评聘申报时间是怎样的?"
if query:
# 执行相似度搜索,找到与查询相关的文档
docs = knowledgeBase.similarity_search(query,k=10) # k=10表示返回最相关的10个文档
# 加载问答链,用于结合相关文档和问题生成回答
chain = load_qa_chain(llm, chain_type="stuff") # stuff模式表示将所有文档合并到上下文中
# 准备输入数据
input_data = {"input_documents": docs, "question": query}
# 执行问答链,生成回答
response = chain.invoke(input=input_data)
print(response["output_text"]) # 输出回答内容
print("来源:")
# 记录唯一的页码,避免重复显示
unique_pages = set()
# 显示每个文档块的来源页码
for doc in docs:
text_content = getattr(doc, "page_content", "") # 获取文档内容
# 从页码映射中获取该内容对应的页码
source_page = knowledgeBase.page_info.get(
text_content.strip(), "未知"
)
# 只显示唯一的页码
if source_page not in unique_pages:
unique_pages.add(source_page)
print(f"文本块页码: {source_page}")
运行成功
3、总结
(1)PDF文本提取与处理
使用PyPDF2库的PdfReader从PDF文件中提取文本在提取过程中记录每行文本对应的页码,便于后续溯源使用RecursiveCharacterTextSplitter将长文本分割成小块,便于向量化处理
(2)向量数据库构建
使用DashScopeEmbeddings将文本块转换为向量表示使用FAISS向量数据库存储文本向量,支持高效的相似度搜索为每个文本块保存对应的页码信息,实现查询结果溯源
(3)语义搜索与问答链
基于用户查询,使用similarity_search在向量数据库中检索相关文本块使用Qwen语言模型和load_qa_chain构建问答链将检索到的文档和用户问题作为输入,生成回答
(4)成本跟踪与结果展示
如果是openai模型,可以用get_openai_callback跟踪API调用成本展示问答结果和来源页码,方便用户验证信息
4、LangChain中的问答链
LangChain问答链中的4种chain_type:
项目 | chain_type | 一句话定义 | 处理流程示意 | 最适用场景 | 主要优点 | 主要缺点 |
1 | stuff(最暴力,也最简单) | 一次把所有文档塞进 prompt | [全部文档] → LLM → 答案 | 文档很短,远小于模型 token 上限 | 代码最少、调用最快、成本最低 | 容易超 token;长文噪声大 |
2 | map_reduce(先分治,再汇总) | 先对每个 chunk 单独问,再把所有中间结果合并 | [chunk₁→LLM] … [chunkₙ→LLM] → 汇总 LLM → 答案 | 长文档摘要 / 全局问答 | 可并行、支持超长文本 | 至少 2 次 LLM 调用;可能丢失跨 chunk 语境 |
3 | refine(滚动精修) | 滚动式精修:用前一个答案 + 下一个 chunk 继续优化 | chunk₁→LLM 得 ans₁ → (ans₁+chunk₂)→LLM 得 ans₂ … → 最终答案 | 需要逐步吸收信息,后文可能修正前文 | 答案连贯,token 利用率较高 | 串行调用,延迟随 chunk 数量线性增加 |
4 | map_rerank(打分挑最佳) | 对每个 chunk 让 LLM 打分,只返回得分最高 chunk 的答案 | [chunk→LLM+score] × n → 取最高 score 的答案 | 只需“最相关”单段即可回答的高精度场景 | 只返回最相关片段,token 最省 | 可能遗漏跨 chunk 信息;打分质量决定效果 |
(1) stuff(比较常用的策略)
适合文档拆分的比较小,一次获取文档比较少的情况调用LLM 的次数也比较少,能使用 stuff 的就使用这种方式
(2) map_reduce
将每个document 单独处理,可以并发进行调用。但是每个文档之间缺少上下文
(3) refine
Refine 这种方式能部分保留上下文,以及token 的使用能控制在一定范围
(4) map_rerank
会大量地调用LLM,每个document 之间是独立处理
5、 如果LLM可以处理无限上下文了,RAG还有意义吗?
效率与成本:LLM处理长上下文时计算资源消耗大,响应时间增加。RAG通过检索相关片段,减少输入长度
知识更新:LLM的知识截止于训练数据,无法实时更新。RAG可以连接外部知识库,增强时效性
可解释性:RAG的检索过程透明,用户可查看来源,增强信任。LLM的生成过程则较难追溯
定制化:RAG可针对特定领域定制检索系统,提供更精准的结果,而LLM的通用性可能无法满足特定需求
数据隐私:RAG允许在本地或私有数据源上检索,避免敏感数据上传云端,适合隐私要求高的场景
===> 结合LLM的生成能力和RAG的检索能力,可以提升整体性能,提供更全面、准确的回答
普通人如何抓住AI大模型的风口?
领取方式在文末
为什么要学习大模型?
目前AI大模型的技术岗位与能力培养随着人工智能技术的迅速发展和应用 , 大模型作为其中的重要组成部分 , 正逐渐成为推动人工智能发展的重要引擎 。大模型以其强大的数据处理和模式识别能力, 广泛应用于自然语言处理 、计算机视觉 、 智能推荐等领域 ,为各行各业带来了革命性的改变和机遇 。
目前,开源人工智能大模型已应用于医疗、政务、法律、汽车、娱乐、金融、互联网、教育、制造业、企业服务等多个场景,其中,应用于金融、企业服务、制造业和法律领域的大模型在本次调研中占比超过 30%。
随着AI大模型技术的迅速发展,相关岗位的需求也日益增加。大模型产业链催生了一批高薪新职业:
人工智能大潮已来,不加入就可能被淘汰。如果你是技术人,尤其是互联网从业者,现在就开始学习AI大模型技术,真的是给你的人生一个重要建议!
最后
只要你真心想学习AI大模型技术,这份精心整理的学习资料我愿意无偿分享给你,但是想学技术去乱搞的人别来找我!
在当前这个人工智能高速发展的时代,AI大模型正在深刻改变各行各业。我国对高水平AI人才的需求也日益增长,真正懂技术、能落地的人才依旧紧缺。我也希望通过这份资料,能够帮助更多有志于AI领域的朋友入门并深入学习。
真诚无偿分享!!!
vx扫描下方二维码即可
加上后会一个个给大家发
大模型全套学习资料展示
自我们与MoPaaS魔泊云合作以来,我们不断打磨课程体系与技术内容,在细节上精益求精,同时在技术层面也新增了许多前沿且实用的内容,力求为大家带来更系统、更实战、更落地的大模型学习体验。
希望这份系统、实用的大模型学习路径,能够帮助你从零入门,进阶到实战,真正掌握AI时代的核心技能!
01 教学内容
-
从零到精通完整闭环:【基础理论 →RAG开发 → Agent设计 → 模型微调与私有化部署调→热门技术】5大模块,内容比传统教材更贴近企业实战!
-
大量真实项目案例: 带你亲自上手搞数据清洗、模型调优这些硬核操作,把课本知识变成真本事!
02适学人群
应届毕业生: 无工作经验但想要系统学习AI大模型技术,期待通过实战项目掌握核心技术。
零基础转型: 非技术背景但关注AI应用场景,计划通过低代码工具实现“AI+行业”跨界。
业务赋能突破瓶颈: 传统开发者(Java/前端等)学习Transformer架构与LangChain框架,向AI全栈工程师转型。
vx扫描下方二维码即可
本教程比较珍贵,仅限大家自行学习,不要传播!更严禁商用!
03 入门到进阶学习路线图
大模型学习路线图,整体分为5个大的阶段:
04 视频和书籍PDF合集
从0到掌握主流大模型技术视频教程(涵盖模型训练、微调、RAG、LangChain、Agent开发等实战方向)
新手必备的大模型学习PDF书单来了!全是硬核知识,帮你少走弯路(不吹牛,真有用)
05 行业报告+白皮书合集
收集70+报告与白皮书,了解行业最新动态!
06 90+份面试题/经验
AI大模型岗位面试经验总结(谁学技术不是为了赚$呢,找个好的岗位很重要)
07 deepseek部署包+技巧大全
由于篇幅有限
只展示部分资料
并且还在持续更新中…
真诚无偿分享!!!
vx扫描下方二维码即可
加上后会一个个给大家发