来源:互联网 更新时间:2026-08-15 14:33
多少次你按照建议去优化机器学习模型,结果却发现这些方法根本不适合你的特定场景?答案想必你也清楚——大多数时候,甚至可以说一直都是。归根结底,关键就在于你的数据。你需要不断地测试、碰壁、再测试,直到找到那个最适合你实际情况的方案。这篇文章将深入探讨四种策略,教你如何利用私有数据和私有基础设施,来优化检索增强生成(RAG),从而提升AI解决方案的效果。

“我没有失败,我只是找到了10,000种行不通的方法。” ——
托马斯·爱迪生
在之前的文章中,我们聊过如何通过检索增强生成(RAG)策略,将私有知识注入到像LLaMA 3这样的公共模型中,同时不必担心敏感信息外泄。在自己可控的基础设施上,用RAG来处理私有数据,优势确实很明显。但要想落地,还得在几个关键环节上做足功课。
先快速回顾一下RAG的基本流程。它主要由两个过程构成:第一个是“
具体流程如下图所示:
RAG 过程。图片由作者提供
在上一篇文章中,我们用一家虚构公司ColdF的数据,搭建了“
首先,我们需要明确RAG过程的几个关键发力点:
确定好要优化的组件后,策略就变成了:对每个组件的不同配置进行对比测试,找出性能更优的那个版本。通过运行两个版本,依据预设的指标来衡量它们的表现。那么问题来了:用什么指标来衡量?为此,我们参考了论文《RAGAS:检索增强生成的自动评估》¹,它提出了三个核心指标:
论文还解释了如何通过提示大型语言模型(LLM)来全自动地测量这些指标。
在本次评估中,我们使用的库
简单来说:要评估前面提到的四个指标,我们需要准备问题、生成的答案、上下文以及实际答案。
我们将使用
ollama==0.2.1
chromadb==0.5.0
transformers==4.41.2
torch==2.3.1
langchain==0.2.0
ragas==0.1.9
以下是使用LangChain的代码片段:
# Import necessary libraries and modules
from langchain.embeddings.base import Embeddings
from transformers import BertModel, BertTokenizer, DPRQuestionEncoder, DPRQuestionEncoderTokenizer, RobertaModel, RobertaTokenizer
from langchain.prompts import ChatPromptTemplate
from langchain_text_splitters import MarkdownHeaderTextSplitter
import requests
from langchain_chroma import Chroma
from langchain import hub
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_models import ChatOllama
from operator import itemgetter
# Define a custom embedding class using the DPRQuestionEncoder
class DPRQuestionEncoderEmbeddings(Embeddings):
show_progress: bool = False
"""Whether to show a tqdm progress bar. Must ha ve `tqdm` installed."""
def __init__(self, model_name: str = 'facebook/dpr-question-encoder-single-nq-base'):
# Initialize the tokenizer and model with the specified model name
self.tokenizer = DPRQuestionEncoderTokenizer.from_pretrained(model_name)
self.model = DPRQuestionEncoder.from_pretrained(model_name)
def embed(self, texts):
# Ensure texts is a list
if isinstance(texts, str):
texts = [texts]
embeddings = []
if self.show_progress:
try:
from tqdm import tqdm
iter_ = tqdm(texts, desc="Embeddings")
except ImportError:
logger.warning(
"Unable to show progress bar because tqdm could not be imported. "
"Please install with `pip install tqdm`."
)
iter_ = texts
else:
iter_ = texts
for text in iter_:
# Tokenize the input text
inputs = self.tokenizer(text, return_tensors='pt')
# Generate embeddings using the model
outputs = self.model(**inputs)
# Extract the embedding and convert it to a list
embedding = outputs.pooler_output.detach().numpy()[0]
embeddings.append(embedding.tolist())
return embeddings
def embed_documents(self, documents):
return self.embed(documents)
def embed_query(self, query):
return self.embed([query])[0]
# Define a template for generating prompts
template = """
### CONTEXT
{context}
### QUESTION
Question: {question}
### INSTRUCTIONS
使用上方 CONTEXT markdown 文本回答用户问题。
提供简短的答案。
仅根据 CONTEXT 中的事实回答问题。
如果 CONTEXT 不包含回答问题的必要信息,则返回“NONE”。
"""
# 使用模板创建一个 ChatPromptTemplate 实例
prompt = ChatPromptTemplate.from_template(template)
# 从 URL 获取文本数据
url = "https://raw.githubusercontent.com/cgrodrigues/rag-intro/main/coldf_secret_experiments.txt"
response = requests.get(url)
if response.status_code == 200:
text = response.text
else:
raise Exception(f"Failed to fetch the file: {response.status_code}")
# 定义用于分割 markdown 文本的标题
headers_to_split_on = [
("#", "Header 1")
]
# 使用指定的标题创建一个 MarkdownHeaderTextSplitter 实例
markdown_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on, strip_headers=False
)
# 使用 markdown 分割器分割文本
docs_splits = markdown_splitter.split_text(text)
# 初始化一个聊天模型
llm = ChatOllama(model="llama3")
# 使用自定义嵌入从文档中创建一个 Chroma 向量存储
vectorstore = Chroma.from_documents(documents=docs_splits, embedding=DPRQuestionEncoderEmbeddings())
# 从向量存储中创建一个检索器
retriever = vectorstore.as_retriever()
# 定义一个格式化文档以供显示的函数
def format_docs(docs):
return "nn".join(doc.page_content for doc in docs)
# 创建一个检索增强生成 (RAG) 链
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| RunnablePassthrough.assign(context=itemgetter("context"))
| {"answer": prompt | llm | StrOutputParser(),
"context": itemgetter("context")}
)
# 使用问题调用 RAG 链
result = rag_chain.invoke("Who led the Experiment 1?")
print(result)
代码末尾定义了一个RAG链,可以用以下代码来评估各项指标:
# 导入必要的库和模块
import pandas as pd
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
context_precision,
faithfulness,
answer_relevancy,
context_recall
)
from langchain_community.chat_models import ChatOllama
def get_questions_answers_contexts(rag_chain):
""" 读取问题和答案列表并返回一个
用于评估的 ragas 数据集 """
# 文件的 URL
url = 'https://raw.githubusercontent.com/cgrodrigues/rag-intro/main/coldf_question_and_answer.psv'
# 从 URL 获取文件
response = requests.get(url)
data = response.text
# 将数据按行分割
lines = data.split('n')
# 通过管道符号分割每一行并创建元组
rag_dataset = []
for line in lines[1:10]: # 仅限前 10 个问题
if line.strip():# 确保该行不为空
question, reference_answer = line.split('|')
result = rag_chain.invoke(question)
generated_answer = result['answer']
contexts = result['context']
rag_dataset.append({
"question": question,
"answer": generated_answer,
"contexts": [contexts],
"ground_truth": reference_answer
})
rag_df = pd.DataFrame(rag_dataset)
rag_eval_datset = Dataset.from_pandas(rag_df)
# 返回 lragas 数据集
return rag_eval_datset
def get_metrics(rag_dataset):
""" 对于 RAG 数据集,计算指标的真实性、
答案相关性、上下文精度和上下文召回率 """
# 我们要评估的指标列表
metrics = [
faithfulness,
answer_relevancy,
context_precision,
context_recall
]
# 我们将使用带有 LLaMA 3 模型的本地 ollama
langchain_llm =ChatOllama(model="llama3")
langchain_embeddings = DPRQuestionEncoderEmbeddings('facebook/dpr-question_encoder-single-nq-base')
# 返回指标
results = evaluate(rag_dataset, metrics=metrics, llm=langchain_llm, embeddings=langchain_embeddings)
return results
# 获取 RAG 数据集
rag_dataset = get_questions_answers_contexts(rag_chain)
# 计算指标
results = get_metrics(rag_dataset)
print(results)
这段代码的输出示例如下:
{
'faithfulness': 0.8611,
'answer_relevancy': 0.8653,
'context_precision': 0.7778,
'context_recall': 0.8889
}
如前所述,前两个指标(真实性和答案相关性)与生成过程挂钩。想要改进它们,就得调整语言模型或修改提供给模型的提示词。而后两个指标(上下文精度和上下文召回率)则与检索过程相关,改进它们需要研究文档的存储、索引和选择方式。
分块方法的核心是确保数据被切割成最优的片段,方便后续检索。一般思路是尝试不同的块大小,在块太小(丢失上下文)和块太大(压垮检索系统)之间找到一个平衡点。在基线方案中,我们是按每个实验进行分块的;这样一来,实验的某些部分可能会被稀释,最终没能体现在嵌入向量中。解决这个问题的一个思路是使用父文档检索器。这个方法不仅能检索相关的文档片段或段落,还能顺带拉回它的父文档,从而保留相关片段周围的上下文语境。下面是测试这个方法的代码:
# Import necessary libraries and modules
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Create the parent document retriever
parent_document_retriever = ParentDocumentRetriever(
vectorstore = Chroma(collection_name="parents",
embedding_function=DPRQuestionEncoderEmbeddings('facebook/dpr-question_encoder-single-nq-base')),
docstore = InMemoryStore(),
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200),
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1500),
)
parent_document_retriever.add_documents(docs_splits)
# Create a retrieval-augmented generation (RAG) chain
rag_chain_pr = (
{"context": parent_document_retriever | format_docs, "question": RunnablePassthrough()}
| RunnablePassthrough.assign(context=itemgetter("context"))
| {"answer": prompt | llm | StrOutputParser(),
"context": itemgetter("context")}
)
# Get the RAG dataset
rag_dataset = get_questions_answers_contexts(rag_chain_pr)
# Calculate the metrics
results = get_metrics(rag_dataset)
print(results)
结果如下:
{
'faithfulness': 0.6667,
'answer_relevancy': 0.4867,
'context_precision': 0.7778,
'context_recall': 0.6574
}
结果显示,这种变化对提升性能并没有什么帮助。上下文召回率的下降说明检索过程没正常工作,上下文里缺少完整的信息。忠实度和答案相关性指标的变化,也是因为上下文质量不佳导致的。这种情况下,可以尝试评估其他分块和检索方法。
嵌入模型负责把文本块转换成稠密向量表示。不同的模型可能针对不同主题进行过训练,有时能带来更好的嵌入效果。选择嵌入方法时,需要在计算效率和嵌入质量之间做个权衡。
我们比较了几种不同的嵌入模型,比如密集段落检索("facebook/dpr-question_encoder-single-nq-base")、Sentence-BERT("paraphrase-MiniLM-L6-v2"),以及Chroma的默认模型("all-MiniLM-L6-v2")。每个模型都有自己的优势,在特定领域的数据上评估一下,就能找出哪个模型能提供最准确的语义表示。
要更换嵌入模型,需要定义一个名为 "SentenceBertEncoderEmbeddings" 的新类,实现Sentence-BERT模型。然后,用这个新类替换掉之前实现了密集段落检索模型的 "DPRQuestionEncoderEmbeddings"。下面是使用Sentence-BERT模型进行测试的代码:
# Import necessary libraries and modules
import pandas as pd
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
context_precision,
faithfulness,
answer_relevancy,
context_recall
)
from langchain_community.chat_models import ChatOllama
from sentence_transformers import SentenceTransformer
# Define a custom embedding class using the DPRQuestionEncoder
class SentenceBertEncoderEmbeddings(Embeddings):
show_progress: bool = False
"""Whether to show a tqdm progress bar. Must ha ve `tqdm` installed."""
def __init__(self, model_name: str = 'paraphrase-MiniLM-L6-v2'):
# Initialize the tokenizer and model with the specified model name
self.model = SentenceTransformer(model_name)
def embed(self, texts):
# Ensure texts is a list
if isinstance(texts, str):
texts = [texts]
embeddings = []
if self.show_progress:
try:
from tqdm import tqdm
iter_ = tqdm(texts, desc="Embeddings")
except ImportError:
logger.warning(
"Unable to show progress bar because tqdm could not be imported. "
"Please install with `pip install tqdm`."
)
iter_ = texts
else:
iter_ = texts
for text in iter_:
embeddings.append(self.model.encode(text).tolist())
return embeddings
def embed_documents(self, documents):
return self.embed(documents)
def embed_query(self, query):
return self.embed([query])[0]
# Create a Chroma vector store from the documents using the custom embeddings
vectorstore = Chroma.from_documents(documents=docs_splits, embedding=SentenceBertEncoderEmbeddings())
# Create a retriever from the vector store
retriever = vectorstore.as_retriever()
# Create a retrieval-augmented generation (RAG) chain
rag_chain_ce = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| RunnablePassthrough.assign(context=itemgetter("context"))
| {"answer": prompt | llm | StrOutputParser(),
"context": itemgetter("context")})
# Get the RAG dataset
rag_dataset = get_questions_answers_contexts(rag_chain_ce)
# Calculate the metrics
results = get_metrics(rag_dataset)
print(results)
结果如下:
{
'faithfulness': 0.5278,
'answer_relevancy': 0.5306,
'context_precision': 0.5556,
'context_recall': 0.7997
}
这种情况下,编码器的变化导致了指标性能下降。这并不意外,因为DPR的检索精度高于Sentence-BERT,在我们这个需要精确文档检索的场景下更合适。切换到Sentence-BERT后,'faithfulness' 和 'answer relevancy' 指标的显著下降,恰恰凸显了为高精度检索任务选择合适的嵌入模型是多么重要。
向量搜索方法根据相似度度量来检索最相关的块。常见的方法包括欧氏距离(L2)、余弦相似度等。调整搜索方法有望提升最终输出的质量。
代码如下:
# Import necessary libraries and modules
import pandas as pd
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
context_precision,
faithfulness,
answer_relevancy,
context_recall
)
from langchain_community.chat_models import ChatOllama
# Create a Chroma vector store from the documents
# using the custom embeddings and also changing to
# cosine similarity search
vectorstore = Chroma.from_documents(collection_name="dist",
documents=docs_splits,
embedding=DPRQuestionEncoderEmbeddings(),
collection_metadata={"hnsw:space": "cosine"})
# Create a retriever from the vector store
retriever = vectorstore.as_retriever()
# Create a retrieval-augmented generation (RAG) chain
rag_chain_dist = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| RunnablePassthrough.assign(context=itemgetter("context"))
| {"answer": prompt | llm | StrOutputParser(),
"context": itemgetter("context")})
# Get the RAG dataset
rag_dataset = get_questions_answers_contexts(rag_chain_dist)
# Calculate the metrics
results = get_metrics(rag_dataset)
print(results)
结果如下:
{
'faithfulness': 0.9444,
'answer_relevancy': 0.8504,
'context_precision': 0.6667,
'context_recall': 0.8889
}
'faithfulness' 的提升表明,尽管 'context precision' 有所下降,但使用余弦相似度进行向量搜索,确实增强了检索到的文档与查询之间的一致性。总体来看,较高的 'faithfulness' 和 'context recall' 说明,在当前场景下,余弦相似度是一种更有效的向量搜索方法,这也印证了选择合适的搜索方法对于优化检索性能的重要性。
最终提示的构建,本质上是把检索到的数据整合进模型查询中。提示词里的细微改动都可能对结果产生显著影响,这往往是个需要反复试验的过程。在提示词中给出一些示例,可以引导模型生成更准确、更相关的输出。
优化检索增强生成(RAG)管道是一个迭代过程,很大程度上取决于应用程序的具体数据和上下文。本文探讨了四种关键策略:改进分块方法、选择并微调嵌入模型、挑选有效的向量搜索方法,以及设计精确的提示词。每一个环节在提升RAG系统性能方面都扮演着重要角色。
结果也再次证明,
优化RAG管道的旅程,就是持续测试、从失败中学习、并进行明智调整的过程。通过拥抱这种迭代方法,你可以让自己的AI解决方案更有效地满足特定需求。记住,成功的关键在于深刻理解你的数据,大胆尝试不同策略,并不断完善你的流程。
[1]Es, S., James, J., Espinosa-Anke, L., & Schockaert, S. (2023). RAGAS: Automated Evaluation of Retrieval Augmented Generation. Exploding Gradients, CardiffNLP, Cardiff University, AMPLYFI.
腾讯ima怎么把微信内容一键导入知识库?
黄金价格不断创新高!黄金稳定币XAU、PAXG市值达11亿美元
CC币价格预测(2026-2035):Canton币今日价格走势+长期价格预测
新浪互联网热点小时报丨2026年07月26日16时_今日实时互联网热点速递
新浪机器学习热点小时报丨2026年07月25日18时_今日实时机器学习热点速递
2026鸣潮账号交易安全指南:五大交易平台对比与风险避坑分析
腾讯ima怎么创建共享知识库?
今日比特币暴涨分析:Metaplanet的比特币BTC投资推动股价上涨17%
蚂蚁庄园今日答案7月21日(今日已更新) 蚂蚁庄园今天正确答案是什么呢
新浪人工智能热点小时报丨2026年07月30日18时_今日实时人工智能热点速递
抖音怎么取消申请退货退款?抖音上取消退货怎么操作
比特币(BTC)核心周期指标复刻历史走势 价格或跌破5.8万美元关键支撑位
Windy卫星云图怎么看?云层变化识别技巧
9条破亿视频,新号涨粉百万,过去半年谁在制造AI爆款?
短剧《史上最强洪荒修为》剧情介绍
kimi提示词专家使用方法新手指南
Celestia价格预测2026-2032:TIA币能否引领山寨币上涨行情?历史价格回顾
如何修复Edge浏览器无法通过微软账号进行身份验证?
原神霜月三处月灵龛具体位置汇总
五菱星光L六座新能源SUV上市:三版可选,中配12.28
手机号码测吉凶
本站所有软件,都由网友上传,如有侵犯你的版权,请发邮件haolingcc@hotmail.com 联系删除。 版权所有 Copyright@2012-2013 haoling.cc