1. 前言&RAG
1.1 絮叨几句
之所以大模型要经历RAG这么一遭,不妨想想看现在的模型相对成熟的模型(比如GPT3.5)有什么特点:
- 知识局限性:没有检索机制的大模型主要依赖于训练时接触到的数据集。这些数据集虽然可能非常庞大,但仍然无法覆盖目前earth上所有的知识和信息。因此,这些模型在生成文本时可能会受到其训练数据的限制,并无法准确回答或生成超出其训练数据范围的内容。
- “Fake News”: 由于模型无法实时检索和验证外部信息,它们可能会生成不准确或错误的事实性内容。特别是在处理需要最新信息或具体事实的场景时,这种缺点尤为明显。
于是呢,为了解决这个问题,RAG这个东西就这么诞生了。
1.2 RAG简介
RAG(Retrieval-Augmented Generation),中文大名检索增强生成,是一种结合了检索和生成两种自然语言处理(NLP)技术的前沿技术。该技术主要用于提高生成式模型在处理信息和生成响应时的质量和准确性,适用于问答系统、文本摘要、对话系统等任务。
1.3 RAG工作原理
可以简单将RAG分成三个步骤:
- 检索:首先,模型会使用检索组件在一个大型的文档集合中寻找与输入查询相关的信息或文档。这一步骤的目的是从广泛的知识源中快速提取出相关信息,这些信息随后将作为上下文或证据,辅助生成组件产生回答或输出。
- 利用:检索到的信息会被传递到生成模型,如基于Transformer的语言模型(如GPT系列、BART等)。生成模型会利用这些检索到的内容和原始查询共同生成回答或输出文本。
- 生成:生成模型在理解检索到的内容后,会生成连贯、相关且信息丰富的文本作为最终输出。
2. RAG实战
2.1 向量模型
首先选择一个适合的文本嵌入模型,这里我选择了BERT(我这里图方便,直接使用了魔搭平台提供的模型,其他开源模型以后再试试)。
from modelscope import snapshot_download
model_dir = snapshot_download("AI-ModelScope/bge-small-zh-v1.5", cache_dir='.')
2.2 数据准备&构建索引
这里我就省略了,直接用原先的LLM模型。
class EmbeddingModel:
"""
class for EmbeddingModel
"""
def __init__(self, path: str) -> None:
self.tokenizer = AutoTokenizer.from_pretrained(path)
self.model = AutoModel.from_pretrained(path).cuda()
print(f'Loading EmbeddingModel from {path}.')
def get_embeddings(self, texts: List) -> List[float]:
"""
calculate embedding for text list
"""
encoded_input = self.tokenizer(texts, padding=True, truncation=True, return_tensors='pt')
encoded_input = {k: v.cuda() for k, v in encoded_input.items()}
with torch.no_grad():
model_output = self.model(**encoded_input)
sentence_embeddings = model_output[0][:, 0]
sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
return sentence_embeddings.tolist()
这里再进行一下索引优化,实现向量检索。
class VectorStoreIndex:
"""
class for VectorStoreIndex
"""
def __init__(self, doecment_path: str, embed_model: EmbeddingModel) -> None:
self.documents = []
for line in open(doecment_path, 'r', encoding='utf-8'):
line = line.strip()
self.documents.append(line)
self.embed_model = embed_model
self.vectors = self.embed_model.get_embeddings(self.documents)
print(f'Loading {len(self.documents)} documents for {doecment_path}.')
def get_similarity(self, vector1: List[float], vector2: List[float]) -> float:
"""
calculate cosine similarity between two vectors
"""
dot_product = np.dot(vector1, vector2)
magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2)
if not magnitude:
return 0
return dot_product / magnitude
def query(self, question: str, k: int = 1) -> List[str]:
question_vector = self.embed_model.get_embeddings([question])[0]
result = np.array([self.get_similarity(question_vector, vector) for vector in self.vectors])
return np.array(self.documents)[result.argsort()[-k:][::-1]].tolist()
2.3 测试生成
再定义大模型LLM
class LLM:
"""
class for Yuan2.0 LLM
"""
def __init__(self, model_path: str) -> None:
print("Creat tokenizer...")
self.tokenizer = AutoTokenizer.from_pretrained(model_path, add_eos_token=False, add_bos_token=False, eos_token='<eod>')
self.tokenizer.add_tokens(['<sep>', '<pad>', '<mask>', '<predict>', '<FIM_SUFFIX>', '<FIM_PREFIX>', '<FIM_MIDDLE>','<commit_before>','<commit_msg>','<commit_after>','<jupyter_start>','<jupyter_text>','<jupyter_code>','<jupyter_output>','<empty_output>'], special_tokens=True)
print("Creat model...")
self.model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.bfloat16, trust_remote_code=True).cuda()
print(f'Loading Yuan2.0 model from {model_path}.')
def generate(self, question: str, context: List):
if context:
prompt = f'背景:{context}\n问题:{question}\n请基于背景,回答问题。'
else:
prompt = question
prompt += "<sep>"
inputs = self.tokenizer(prompt, return_tensors="pt")["input_ids"].cuda()
outputs = self.model.generate(inputs, do_sample=False, max_length=1024)
output = self.tokenizer.decode(outputs[0])
print(output.split("<sep>")[-1])
print("> Create Yuan2.0 LLM...")
model_path = './Yuan2-2B-Mars-hf'
'''这里放置自己定义模型的路径'''
llm = LLM(model_path)
print('> Without RAG:')
llm.generate(question, [])
print('> With RAG:')
llm.generate(question, context)