微信扫码
添加专属顾问
我要投稿
项目开源地址
https://github.com/stay-leave/enhance_llm
检索增强生成(Retrieval Augmented Generation, RAG)是一种结合语言模型和信息检索的技术,用于生成更准确且与上下文相关的输出。
本项目即是搭建了一个高级RAG系统,实现本地知识库问答。
优势
即时性:通过检索外部信息源,RAG 能够即时更新模型的知识,让其对实时性、非公开或离线的数据也能提供有效回应。
准确性:注入的相关信息提升了模型输出的准确性,减少了幻觉问题。
数据安全:可以在内部构建知识库,从而确保敏感数据不会外泄。
工作流程
1.检索(Retrieve):
输入查询:用户通过输入查询或问题来开始这个流程。
相似性搜索:系统将用户查询通过嵌入模型转换为向量,并在外部知识源中的向量数据库中进行相似性搜索。
返回相关信息:搜索会返回与查询最接近的前 k 个数据对象(上下文信息),这些对象来自于知识库、数据库或其他数据源。
2. 增强(Augment):
填入模板:用户查询与检索到的上下文信息被填入到一个提示模板中。
构建完整提示:这个模板整合了查询和相关信息,构建出一个完整的提示词,用于指导模型生成。
3. 生成(Generate):
输入到 LLM:构建好的提示被输入到大型语言模型(LLM),比如 GPT 或 Qwen。
生成内容:模型根据提示词中的信息生成相关内容,包括回答、文本或其他输出。
系统架构
分为两个阶段:
1.indexing:数据读取、文档切分、向量嵌入、向量入库。
2.Retrieval and generation:检索、prompt增强、生成。
indexing阶段
1.数据读取
使用Langchain的文件读取类,将pdf、txt、image、html等解析为document。
from langchain_community.document_loaders.csv_loader import CSVLoader
from langchain_community.document_loaders import PyPDFLoader
# 读取csv,返回list
def load_csv(path):
# 每条记录为一个元素
loader = CSVLoader(
file_path="project_1/data/clean.csv",
encoding='utf-8' # 编码
)
data = loader.load()
return data
# 读取pdf,返回list
def load_pdf(path):
# 是以每页为一个元素的
loader = PyPDFLoader("project_1/data/1.pdf")
pages = loader.load_and_split()
return pages
读取结果如上图。可以看到是csv是默认按每条记录为一个document的。
2.文档切块
使用RecursiveCharacterTextSplitter,通过特定字符来分割,默认的字符列表是 [“\n\n”, “\n”, " ", “”],对于中文文段的分割不是很理想。
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, # 指定每个文本块的目标大小,这里设置为200个字符。
chunk_overlap=50, # 指定文本块之间的重叠字符数,这里设置为50个字符。
length_function=len, # 用于测量文本长度的函数,这里使用Python内置的`len`函数。
is_separator_regex=False, # 指定`separators`中的分隔符是否应被视为正则表达式,这里设置为False,表示分隔符是字面字符。
separators=["\n\n", "\n", " ", ".", ",", ",", "。", ] # 定义用于分割文本的分隔符列表。
)
pages = load_pdf("project_1/data/1.pdf")
texts = text_splitter.split_documents([pages[0].page_content])
3.向量化
针对特定领域,采取通用的向量模型会不适应当前数据集,影响其性能。因此进行了微调。
微调过程见http://t.csdnimg.cn/tLYek
from langchain.embeddings import HuggingFaceBgeEmbeddings
model_name = "project_2/bge-large-zh-v1.5"
model_kwargs = {'device': 'cuda'}
# # 当向量都被规范化(归一化)后,它们的范数都是1。
# 余弦相似度的计算只需要向量的点积运算,从而减少了计算的复杂度,加快了处理速度。
hf = HuggingFaceBgeEmbeddings(
encode_kwargs = {'normalize_embeddings': True}
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs,
query_instruction="为这个句子生成表示以用于检索相关文章:"
)
embedding = hf.embed_query("你好!")
len(embedding)
4.向量存储
向量数据库比较流行的有milvus、faiss、chroma等。
本项目使用chroma。
# 快速创建数据库
from langchain_chroma import Chroma
db = Chroma.from_documents(
documents = texts,
embedding = hf,
ids = None,
collection_name = 'test1',
collection_metadata = {"hnsw:space": "cosine"},
persist_directory = 'project_1/chroma_db'
)
# 相似度方法通过查询文本检索数据,会自动调用向量模型
query = "网络首发是什么"
docs = db.similarity_search(query)
print(docs[0].page_content)
Retrieval and generation阶段
经过indexing阶段,已经有了一个可供向量检索的数据库和本地存储的集合了。进入到真正的应用流程。
1.检索
可以直接将Chroma转换为检索器,会根据相关性分数返回。Langchain集成的Chroma默认采用的是余弦相似度。
由于用户的query不一定适合在数据库中成功检索,比如在数码领域,用户输入“平果”,意思是想询问有关“苹果品牌”的信息,但是直接拿来检索跟本意就差了十万八千里。因此要进行query改写,也是使用大模型解决。
# 创建一个检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 10})
query = "萧炎是谁?"
# Get relevant documents ordered by relevance score
docs = retriever.invoke(query)
可以看到检索返回也是一个list
对于向量召回的文本块,还需要重排,使得相关性强的文本块能排在前面。
重排也使用BGE的reranker模型,将query与召回的文本块挨个计算相似度,再按照得分倒排。
该模型基于交叉熵损失进行优化,相关性得分不受特定范围的限制。根据我的实验,小于0是一定不行的。
# 重排
tokenizer = AutoTokenizer.from_pretrained(self.config.rerank_model)
model = AutoModelForSequenceClassification.from_pretrained(self.config.rerank_model)
model.eval()
pairs = [[new_query, doc.page_content] for doc in docs]
with torch.no_grad():
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512)
scores = model(**inputs, return_dict=True).logits.view(-1, ).float()
2.生成
将检索返回的内容组成一个字符串,填充到提示词模版里,进行推理。
对于大模型也是有必要进行垂域的微调的,相关内容可见http://t.csdnimg.cn/kWA6S
# 实例化自定义模型
llm = Qwen(mode_name_or_path = "/root/autodl-tmp/Qwen1.5-7B-Chat")
prompt = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)
output_parser = StrOutputParser()
# 构建 chain
chain = prompt | llm | output_parser
res = chain.invoke(
{
"context": format_docs(reordered_docs),
"question": "小医仙是谁?"
}
)
print(res)
原始模型的回答:
sft后模型的回答:(太冷漠了)
dpo后模型的回答:(人类偏好优化就是强)
3.多轮对话——真正的qa机器人
手动构造了聊天记录缓存的功能,每次询问都会加上当前的聊天记录,模型同时使用聊天记录和向量召回的相关文本回答当前的query。
prompt = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)
chat_llm_chain = LLMChain(
llm=self.llm,
prompt=prompt,
verbose=True,
)
# 生成回复
res = chat_llm_chain.predict(
# 格式化聊天记录为JSON字符串
chat_history = "\n".join([f"{entry['role']}: {entry['content']}" for entry in self.memory]),
# chat_history = "\n".join(self.memory),
context=context,
question=query
)
logger.info(f"模型回复:{res}")
# 更新聊天历史
self.memory.append({'role': 'user', 'content': query})
self.memory.append({'role': 'assistant', 'content': res})
print(self.memory)
return res
看看效果,下面是我的query
sft后的模型效果,跟上面一样,表现太差,应该是训练轮次太少
dpo后的模型效果,非常好,这次dpo训了近十轮
原始的模型表现也不错
参考
https://huggingface.co/docs/transformers/main_classes/text_generation
https://zhuanlan.zhihu.com/p/688926320
https://api.python.langchain.com/en/latest/vectorstores/langchain_community.vectorstores.chroma.Chroma.html#langchain_community.vectorstores.chroma.Chroma.from_documents
https://www.cnblogs.com/AlwaysSui/p/18144181
https://github.com/datawhalechina/self-llm
https://python.langchain.com/docs/expression_language/get_started/
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-02-01
2025-01-01
2024-08-13
2025-02-04
2024-07-25
2024-04-25
2024-06-13
2024-09-23
2024-04-26
2024-08-21
2025-03-17
2025-03-17
2025-03-17
2025-03-17
2025-03-17
2025-03-17
2025-03-16
2025-03-16