一、概述
1、背景
在使用大模型对垂直领域的数据进行RAG 强化搜索时,遇到在对无监督的情况下如何选择从向量数据中摘选有效信息。本文从返回的条数以及评价方式的角度进行实现。仅作为思考的方向之一。有其他的方法欢迎讨论。感谢!
2、代码实现
导入依赖
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.memory import ConversationBufferMemory
from langchain.chains import VectorDBQA, RetrievalQA,ConversationalRetrievalChain
from langchain.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.llms import HuggingFacePipeline
from langchain.docstore.document import Document
import transformers
import torch
from transformers import AutoTokenizer,BitsAndBytesConfig,AutoModelForCausalLM
from langchain.document_loaders import TextLoader
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
加载模型
#模型的文件位置
model_name='G:\hugging_fase_model\Llama-2-7b-chat-hf'
#加载模型config 文件
model_config = transformers.AutoConfig.from_pretrained(model_name)
#加载tokener
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
#这个设置的意思是指定填充标记(pad_token)使用结束标记(eos_token)。pad_token 是 tokenizer 中用于补足输入序列长度的填充标记,默认是 [PAD]。
tokenizer.pad_token = tokenizer.eos_token
#这行代码指定了填充(padding)应该发生在序列的哪一侧。在这里,"right" 表示填充将发生在序列的右侧。
tokenizer.padding_side = "right"
配置模型量化参数
use_4bit = True
bnb_4bit_compute_dtype = "float16"
use_nested_quant = False
"""
配置BitsAndBytes库的参数,其中load_in_4bit参数设置为True表示加载数据时使用4位量化,
bnb_4bit_quant_type参数设置为"nf4"表示使用非对称四位量化方式,
bnb_4bit_compute_dtype参数设置为torch.float16表示计算时数据类型为半精度浮点数,
bnb_4bit_use_double_quant参数设置为True表示使用双量化方法。
这些参数设置可以优化模型的性能和内存占用。
"""
#向量化设置
bnb_config = BitsAndBytesConfig(load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True
)
#加载模型 embedding
embedding = HuggingFaceEmbeddings(model_name="all-mpnet-base-v2")
#模型加载
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,#加载 量化
low_cpu_mem_usage=True, #这个参数用于指示模型在训练或推理时是否应该优化CPU内存使用。
)
构建管道
#文本生成管道
text_generation_pipeline = transformers.pipeline(
model=model,
tokenizer=tokenizer,
task="text-generation",#加载任务类型
repetition_penalty=1.1,#- 控制生成文本中重复内容的概率。大于 1,表示惩罚重复,会降低生成文本中重复内容的概率。
return_full_text=True,# 控制生成的文本是否包含完整的文本内容。设置为True表示生成的文本中包含完整的文本内容,即不会截断。
max_new_tokens=2048,# 这里有设置最大的token 数量
output_scores=True#控制是否输出生成文本时的得分值。设置为True表示生成的文本结果会包含每个 token 的得分信息,可以用于进一步分析和处理生成的文本。
)
#构建 huggingfase 的管道
llama_LLM = HuggingFacePipeline(pipeline=text_generation_pipeline)
加载数据与灌库
docs = []
# RecursiveCharacterTextSplitter 是用于处理文本的一种分词方法,特别是对较长的文本进行分割。这是一种递归的字符级别的文本分割方法,它可以根据输入文本的大小自动调整递归的深度和分词粒度。
splitter = RecursiveCharacterTextSplitter(chunk_size=1024)
#文档分割
for chunk in splitter.split_text(data[0]):
docs.append(Document(page_content=chunk, metadata={
"source": "biden-sotu-2023-planned-official"}))
vector_collection_fixed_size = Chroma.from_documents(documents=docs, persist_directory=persist_directory, embedding=embedding)
#灌库
vectordb = Chroma(persist_directory=persist_directory, embedding_function=embedding)
构建评价方式
这里思考采用采纳的文本与大模型的输出结果之间的余弦相似度。
推测如果随着条数的增加相似度在下降的时候说明大模型对灌入的信息已经出现边缘效应。
同时对最终的结果贡献已经很少。故在达到一定程度时需要停止。增加k的数量。
def seq_cosine_similarity(return_answer,response):
"""
输入:当前的回答与检索的内容
返回:余弦相似度
"""
# 初始化TF-IDF向量化器
vectorizer = TfidfVectorizer()
# 将文本转换为TF-IDF向量
tfidf_matrix = vectorizer.fit_transform([response['answer'], return_answer.page_content])
# 计算余弦相似度
cosine_sim = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])[0][0]
#print(f"Cosine similarity between the two texts: {cosine_sim:.4f}")
cosine_sim = cosine_sim
return cosine_sim
def asnwer_response(k,query,llama_LLM):
"""
输入:提取个数,提示,大模型
输出:模型返回
"""
memory = ConversationBufferMemory(return_messages=True,memory_key='chat_history')
retriever = vectordb.as_retriever(search_kwargs={
"k": k})
qa_chain = ConversationalRetrievalChain.from_llm(llm=llama_LLM,retriever = retriever,memory=memory)
response = qa_chain({
'question':query,'chat_history':[]})
return response
def answer_return(prompt,k):
"""
输入:次数与提示
输出:得分
"""
return_answers = vectordb.similarity_search(prompt,k=k)
response = asnwer_response(k,prompt,llama_LLM)
score_list = []
num=1
for return_answer in return_answers:
cosine_sim = seq_cosine_similarity(return_answer,response)
score_list.append([num,cosine_sim])
num+=1
print('{}个向量返回情况'.format(k))
print(score_list)
data_np = np.array(score_list, dtype=np.float64)
# 取第二列的均值
mean_value = np.mean(data_np[:, 1])
print("相似度均值", mean_value)
最终结果
prompt = "What were the main topics in the State of the Union in 2023? Summarize. Keep it under 200 words?"
for k in range(1,6):
answer_return(prompt,k)
返回:
1个向量返回情况
[[1, 0.49522410237874215]]
相似度均值 0.49522410237874215
2个向量返回情况
[[1, 0.34979362528952074], [2, 0.4053863496562705]]
相似度均值 0.3775899874728956
3个向量返回情况
[[1, 0.31158918183750806], [2, 0.38866371763962027], [3, 0.3899432350768045]]
相似度均值 0.3633987115179776
4个向量返回情况
[[1, 0.39929486683735915], [2, 0.2961141895186359], [3, 0.48989343613136793], [4, 0.49923408235936284]]
相似度均值 0.42113414371168145
5个向量返回情况
[[1, 0.3755519133897247], [2, 0.34776236994422227], [3, 0.4581126801804579], [4, 0.5110039230135537], [5, 0.4445057508165602]]
相似度均值 0.4273873274689038
结论
从上述的效果来看 当提问返回到第四条之后的时候均值的提升已经很有限,故而可以选择k等于4作为RAG向量返回的结果。以上仅个人的推断验证。主要是提供一种思路。附件已上传资源。