本文将了解到什么是向量数据库,以及如何与LLMs进行集成。通过LLMs和向量数据库的结合,可以节省微调带来的开销和时间。
通常,LLM会在各种各样的数据上进行训练,这使它们具有广泛的理解能力,但可能会导致在特定的知识领域存在差距。有时,它们甚至可能产生与目标无关或带有偏见的信息——这是从广阔但未经筛选的web学习的副产品。为了解决该问题,我们引入了向量数据库(Vector Database)的概念。这些数据库以一种称为"向量嵌入"的独特格式存储数据,可以让LLMs掌握和使用的信息更连贯和准确。
本文给出了如何使用向量数据库构建一个LLM,并改进LLM对该流程的使用。我们将会看到二者的结合是如何让LLMs更加准确可靠(特别针对特定主题)。
下面我们简单总结了向量数据库,解释向量嵌入的概念以及它在增加AI和机器学习应用方面的角色。之后我们会展示这些数据库和传统数据库的不同之处,以及为什么他们更适合AI任务,特别是与非结构数据(如文本、图片和复杂模式)打交道时。
之后,我们会探索使用该技术来构建一个封闭式QA机器人应用,该机器人使用了Falcon-7B模型和ChromaDB向量数据库,证明了LLMs与正确的工具和技术相结合时的成效。
最后,你将清楚认识到如何使用LLMs和向量数据库来创建新颖、上下文相关并且可靠的应用。不管你是AI爱好者还是经验丰富的开发者,本指南将帮助你轻松自信地探索这个激动人心的领域。
向量数据库概述
在深入了解向量数据库之前,需要了解向量嵌入(vector embedding)的概念。向量嵌入是机器学习中用于将原始数据转换为AI系统可以理解的数值格式必不可少的一种技术,涉及数据转换,如将文本或图片转换为一系列数字,这些数字在高维度空间称之为向量。高纬度数据指包括很多属性或特征的数据,每个数据代表一个不同的维度,这些维度可以帮助捕获该数据的细微特征。
创建向量嵌入的过程开始于数据输入,可以是语句中的任意内容或是图片的像素等。大语言模型和其他AI算法会分析这些数据并确定其关键特征。例如,在文本数据中,可能涉及理解单词的意义和它在语句中的上下文。嵌入模型会将这些特征转换为一个数值格式,向量中的每个数值代表数据的一个特征,通过将这些特征数值封装到一起,就可以作为机器可以处理的输入。
之所以说这些向量是高纬度的,是因为它们包含很多数值,每个数值对应数据中的某个(不同)特征。这种高维度使得向量能够捕捉复杂、详细的信息,使之成为强大的AI模型工具。模型使用这些嵌入向量来识别数据中的模式、关系和潜在结构。
向量数据库针对向量嵌入的特性提供了优化存储和查询的能力。它们擅长提供高效搜索、高性能、可扩展性,并通过比较和识别数据点之间的相似性,实现数据的快速检索。
这些数值代表了复杂的高纬度信息,使之有别于传统的主要使用文本和数字存储数据的系统。向量数据库的主要能力是管理和查询如图片、视频和文本格式的数据,当这些数据转换为向量格式后,特别适用于机器学习和AI应用。
在下面示例中,我们将一段文本转换为词向量,这一步是神经语言处理的基本步骤,可以让我们量化和分析语言关系。例如,"puppy"的向量表达应该更接近"dog"的向量空间,而不是"house",反映了它们的语义接近性。这种方式还可以拓展到类似的关系中。"man"和"woman"的向量距离和方向类似于"king"和"queen"之间的距离和方向。下面展示了单词向量不仅仅代表单词,还可以在多维度向量空间中对它们的语义关系进行有意义的比较。
LLMs崛起之前的向量数据库
向量数据库用于处理向量嵌入,已经有一些关键使用场景,特别是在机器学习和AI领域:
相似搜索:这是向量数据库的关键功能。它们可以在高维度空间内找出与给定请求相似的数据点。特别适用于图形和音频检索(希望找出和特定输入类似的内容),下面是一些业界使用场景:
- 电商:通过允许客户搜索与参考图像相似的产品来增强产品发现能力。
- 音乐流服务:找出并给用户推荐在音频特征上和喜欢的曲目类似的歌曲。
- 医疗成像:通过检索并对相似病理的医学图像(如X光或MRI)进行比较分析来帮助放射科医生。
推荐系统:向量数据库通过处理用户和商品嵌入来支持推荐系统。它可以将用户和他们感兴趣或过去有过互动的物品(如产品、电影或文章)关联起来。下面是一个业务使用场景:
- 流平台:个性化观看体验,通过收看的浏览历史来推荐电影和电视。
- 在线零售:根据购买者的浏览和购买历史来推荐产品,增强交叉销售和追加销售机会。
- 新闻聚合:通过匹配读者过去的参与模式和偏好来分发个性化新闻。
基于内容的检索:这里向量数据库用于基于实际内容而非传统元数据来检索内容。特别对于非结构化数据,如文本和图像,需要首先对内容本身进行分析。下面是一些业界使用场景:
- 数字资产管理: 通过方便地根据视觉或音频内容特征搜索和检索图像或视频,使公司能够管理庞大的数字媒体库。
- 法律和合规:通常查询大量文档来查找与法律案件或合规性调查上下文相关的信息和文档。
- 学术研究:帮助研究者查找与他们工作上下文相关的学术文档和研究论文(即使不指定关键字)
关于基于内容的检索的最后一点越来越重要,并促进了一种新的应用:
使用上下文理解来增强LLMs:通过存储和处理文本嵌入,向量数据库可以帮助LLMs实现更精细化以及上下文相关的检索工作。它们可以帮助理解大量文本的语义内容,在回答复杂搜索、维护对话上下文或生成相关内容等任务中起着关键作用。这种应用正迅速成为向量数据库的一个重要场景,展示了其在增强高级AI系统(如LLM)能力方面起到的作用。
向量数据库 vs 传统数据库
传统的SQL数据库在结构化数据管理方面表现出色,擅长处理精确匹配和明确定义的条件逻辑。它们维护数据的完整性,适合需要精确、结构化数据处理的应用程序。但这种刚性设计模式也使得它们不太适合处理非结构化数据的语义以及上下文之间的细微差别,而这正是LLM和生成式AI应用所需要的。
另一方面,相比传统的SQL系统,NoSQL数据库则更加灵活。它们可以处理半结构化和非结构化数据,如JSON文档,这使得它们更适用于AI和机器学习场景。但即便如此,相比用于LLMs和生成式的向量数据库来说,NoSQL数据库依然缺少某些方面的能力,如解析上下文、模式以及简单数据检索之外的语义内容。
向量数据库弥补了这一空白。它们以AI为中心,使用向量的方式处理数据,可以有效管理复杂的非结构化数据。当与LLMs协作时,向量数据库支持相似性查找和上下文理解,提供了超出传统SQL和NoSQL数据库的能力。它们擅长处理近似值和模式识别,使之特别适用于对数据的微妙理解比精确数据匹配更重要的AI应用。
提升向量数据库的性能
对于依赖速度和精确检索高纬数据的应用来说,向量数据库的性能优化至关重要。这里面涉及提升查询速度、保证高准确性以及有效处理不断增长的数据量和用户请求。其中最重要的一部分是围绕索引策略进行优化(一种更有效组织和查询向量数据的技术)。下面,我们将介绍这些索引策略,以及它们是如何提升向量数据库性能的。
索引策略
向量数据库的索引策略用于方便和准确检索与查询向量相似的向量。这些策略可以显著影响到查询操作的速度和准确性。
- 量化(Quantization):量化涉及将向量映射到向量空间中的一组有限的参考点,从而有效地压缩向量数据。这种策略通过将查询限制到有限点(而非整个数据库)的方式降低了存储需求并加快查询速度。量化的方式有很多种,如标量量化(Scalar Quantization,通过将大空间的向量转换成相对小空间的向量来减少整体的存储空间。 通常是将浮点数(比如f32)向量量化成整数(比如int8)向量(存储的优化比是4X))和向量量化(Vector Quantization,区别于 scalar quantization 是分别从每一个维度进行压缩,vector quantization 是通过将原始向量当做一个整体去看待,将一个向量映射到指定的参考向量里),每种量化方式都需要权衡查询速度和精度。
量化特别适用于管理大规模数据库的应用(存储和内存效率至关重要),这种方法在需要在查询速度和准确性之间取得平衡的环境中表现出色,这使得它非常适合对速度敏感且可以容忍一定精度损失的应用程序。但它不适合需要高度精确和最小化信息损失的场景,如精确科学研究。 - HNSW(Hierarchical Navigable Small World,分层可导航小世界)图:HNSW是一种构建分层图的索引策略,每层代表数据集中的一个不同粒度。查询从顶层开始,顶层具有较少、更远的点,然后向下移动到更详细的层级。这种方式可以快速遍历数据集,通过快速缩小相似向量的候选集合,大大减少了搜索时间。
HNSW图很好地均衡了查询速度和准确性,使其非常适于需要立即响应的实时查询应用和推荐系统。它们在中大型数据集中表现良好,提供可扩展的搜索能力。但对于超大型数据集来说,其内存需求可能会成为瓶颈,因此这种方式不适用于内存资源受限或数据集大小远超实际内存容量的场景。 - 倒排文件索引(Inverted File Index,IVF):IVF使用k-means这样的算法将向量空间分为预定义数量的几个聚类。每个向量分配给最近的聚类,在一个搜索中,只会涉及到最相关聚类中的向量。这种方式降低了搜索空间,提升了查询速度。通过将IVF和其他技术(如IVFADC-Inverted File Index with Asymmetric Distance Computation, 就结合了IVF和Quantization)相结合,可以通过进一步降低距离计算成本来提升性能。
IVF方法适合处理可扩展查询环境中的高纬度数据,可以通过对相似项划分聚类来有效降低搜索空间,特别适用于相对静态的数据库,且可以允许偶尔重新聚类的场景。但对于低纬数据(可能存在过分分割)或要求尽可能低延迟的应用(聚类过程和跨多个聚类查询可能会引入额外的查询时间)来说,IVF可能不是最佳的选择。
优化的其他注意事项
- 降低维度:在应用索引策略之前,可以尝试降低向量维度。像PCA或自编码器这样的技术可以在降低其复杂性的同时保持数据的关键特性,以此提升搜索操作的效率和索引速度。
- 并行处理:很多索引策略都可以通过多CPU核或GPU技术同步执行。这种并行处理能力可以同时处理多个请求,大大提升了吞吐量并降低了大型应用的响应时间。
- 动态索引:对于需要经常更新数据的数据库来说,动态索引策略可以在不需要大量重新组织索引的情况下有效插入或删除向量。这种方式确保数据库可以随着时间的推移保持响应并保持最小的性能下降。
通过这些索引策略和注意事项来提升向量数据库的性能需要深刻理解底层数据以及特定的应用需求。通过仔细选择并调节这些策略,开发者可以显著提高基于向量的应用的响应能力和可扩展性,保证其满足真实使用场景。
用向量数据库丰富LLM的上下文
像Facebook的 LLama2 或 TIIUAE的 Falcon大语言模型具有先进的人工智能和类人类的文本生成能力,但由于它们是基于广泛、通用的数据集进行训练的,因此缺乏对特定上下文的处理能力。
可以使用如下两种方式解决上下文限制的问题:
- 针对性训练:这种方式需要基于特定领域的数据集进行再训练或微调LLM。虽然这种方式可以显著增强模型在特定主题或行业的专业知识,但不适合大部分组织和个人。其原因包括与训练所需的高成本计算资源,以及有效再训练此类复杂模型所需的专业知识:有效再训练LLMs需要深刻理解机器学习、自然语言处理以及所讨论的模型的具体架构。
- 通过向量数据库来整合上下文:或者,LLM可以通过使用向量数据库来直接扩展上下文。此时,向量数据库保存了特定的信息,如向量嵌入,LLM可以检索并使用这些信息来增强其响应能力。通过这种方式可以纳入相关的专业知识,而无需进行大量再训练,特别适用于训练资源匮乏的组织或个人。它使用已有的模型,同时提供了目标明确的上下文洞察力。
第二种方式称为RAG,我们将在下一章中详细介绍。
使用Falcon-7B 和 ChromaDB构建一个封闭式问题机器人
本章中,我们将介绍如何使用向量数据库来构建一个LLM,是使用的模型是一个封闭式问题机器人(Closed Q&A bot),该机器人使用一组集成的技术组件来有效解答与科学相关的问题。
- databricks-dolly-15k HuggingFace Dataset:由Databricks员工生成的一个开源指令跟踪记录数据集。该数据集专门用于训练大型语言模型、合成数据生成和数据增强,其中包含各种类型的提示和响应,如头脑风暴、分类、封闭式QA、生成、信息抽取、开放式QA和总结等。
- 使用Chroma作为向量存储量:我们使用Chroma作为主要的向量存储,作为机器人的知识库。
- 语义搜索的句子转换器:我们使用了来自Sentence Transformers的multi-qa-MiniLM-L6-cos-v1模型,该模型特别适用于语义查询应用,负责生成嵌入向量,并将其存储在Chroma中。
- Falcon 7B Instruct 模型:作为我们的开源通用模型,Falcon 7B是一个由TII开发的一个仅包含70亿参数的解码器模型,Falcon 7B模型使用了一个庞大的名为RefinedWeb的1,500B tokens数据集进行训练,并补充了经过筛选的语料库。值得注意的是,Falcon 40B作为Falcon 7B的更大版本,在Hugging Face的Open LLM排行榜中名列前茅,是最顶尖的大型语言模型之一。
实现架构如下。使用multi-qa-MiniLM-L6-cos-v1作为嵌入模型,负责将数据集的数据导入数据库。
在用户提问时,会使用嵌入模型对提问文本进行编码,生成向量数据库可以理解的格式,并由向量数据库返回应答。Falcon 7B模型用于优化向量数据库的应答结果。
下图有点问题,看起来请求和应答是异步执行的,但代码中是串行的。
配置环境
为了实现本文的代码,需要安装如下库:
!pip install -qU \ transformers==4.30.2 \ torch==2.0.1+cu118 \ einops==0.6.1 \ accelerate==0.20.3 \ datasets==2.14.5 \ chromadb \ sentence-transformers==2.2.2
本代码运行在Qwak的Workspaces的一台gpu.a10.2xl 实例上。需要注意的是运行Falcon-7B-Instruct模型的代码可能会根据硬件配置而变化。
构建"知识库"
一开始,我们需要拉取Databricks-Dolly数据集,重点关注closed_qa
一类的数据集。这些数据条目通常以对精确信息的需求为特征,这种转一性给通用性大语言模型的训练带来了一定挑战。
在databricks/databricks-dolly-15k的dataset card中可以查看支持的
split
和过滤字段。
from datasets import load_dataset # Load only the training split of the dataset # 加载 train 类型的数据集 train_dataset = load_dataset("databricks/databricks-dolly-15k", split='train') # 通过过滤拉取 category为 closed_qa 的一组数据 closed_qa_dataset = train_dataset.filter(lambda example: example['category'] == 'closed_qa') print(closed_qa_dataset[0])
数据库通常可以分为三类:training、validating和evaluating。分别用于训练、校验和评估模型。
典型的数据集条目的格式如下:
{ "instruction": "When was Tomoaki Komorida born?", "context": "Komorida was born in Kumamoto Prefecture on July 10, 1981. After graduating from high school, he joined the J1 League club Avispa Fukuoka in 2000. His career involved various positions and clubs, from a midfielder at Avispa Fukuoka to a defensive midfielder and center back at clubs such as Oita Trinita, Montedio Yamagata, Vissel Kobe, and Rosso Kumamoto. He also played for Persela Lamongan in Indonesia before returning to Japan and joining Giravanz Kitakyushu, retiring in 2012.", "response": "Tomoaki Komorida was born on July 10, 1981.", "category": "closed_qa" }
下面,我们将重点为每组指令及其各自的上下文生成词嵌入,并将它们集成到向量数据库ChromaDB中。
Chroma DB是一个开源的向量数据库系统,擅长管理向量嵌入,专为语义查询引擎之类的应用量身定做,这种能力在自然语言处理和机器学习领域至关重要。Chroma DB作为一个内存型数据库,支持数据的快速访问和高速处理。其友好的Python设置增强了对我们项目的吸引力,简化了与我们工作流程的集成。更多参见Chroma DB 文档。
为了给回答生成嵌入向量,我们使用了multi-qa-MiniLM-L6-cos-v1 模型,该模型专为语义搜索场景而训练。给定一个问题/搜索请求,该模型可以找到相关的文本信息,这正是我们所需要的。
在下面例子中,解释了如何将嵌入向量存储在Chroma的内存集合中。
import chromadb from sentence_transformers import SentenceTransformer class VectorStore: def __init__(self, collection_name): # 初始化嵌入模型和向量数据库 self.embedding_model = SentenceTransformer('sentence-transformers/multi-qa-MiniLM-L6-cos-v1') self.chroma_client = chromadb.Client() self.collection = self.chroma_client.create_collection(name=collection_name) # 该方法用于将输入的数据集转换为向量,并保存到向量数据库中 def populate_vectors(self, dataset): for i, item in enumerate(dataset): combined_text = f"{item['instruction']}. {item['context']}" embeddings = self.embedding_model.encode(combined_text).tolist() self.collection.add(embeddings=[embeddings], documents=[item['context']], ids=[f"id_{i}"]) # 该方法直接从向量数据库中与查询相关的上下文 def search_context(self, query, n_results=1): query_embeddings = self.embedding_model.encode(query).tolist() return self.collection.query(query_embeddings=query_embeddings, n_results=n_results) # Example usage if __name__ == "__main__": # Initialize the handler with collection name vector_store = VectorStore("knowledge-base") # 输入上一步拉取的 closed_qa 数据集 vector_store.populate_vectors(closed_qa_dataset)
对于每个数据集条目,我们会生成一个结合了instruction
和context
字段的嵌入向量,在我们的LLM提示中,context
充当要检索的文档。
下面,我们使用Falcon-7b-instruct LLM来生成封闭式信息查询的应答(无需额外的上下文),并展示知识库的重要性。
生成基本的回答
为了实现文本生成任务,我们采用了来自Hugging Face的Falcon-7b-instruct模型。该模型是Falcon的革新系列,由Abu Dhabi的Technology Innovation Institute开发。
Falcon-7B-Instruct引人注目的一点是其有效平衡了高级功能和管理大小,用于理解复杂的文本和生成任务,其性能可以与更大的封闭源代码模型相媲美,但包更精简。这也是它成为我们理想选择的原因(可以深入语言理解,但不会带来跟更大模型一样的开销)。
如果你打算运行Falcon-7B-Instruct模型(无论是本地机器或远程服务器),需要注意硬件要求。在HuggingFace的文档中有提到,该模型最少需要16GB的内存来满足最佳的性能和更快的响应次数,最好使用GPU。
import transformers import torch from transformers import AutoTokenizer, AutoModelForCausalLM class Falcon7BInstructModel: def __init__(self): # 初始化模型 model_name = "tiiuae/falcon-7b-instruct" self.pipeline, self.tokenizer = self.initialize_model(model_name) def initialize_model(self, model_name): # 初始化 Tokenizer tokenizer = AutoTokenizer.from_pretrained(model_name) # Pipeline setup for text generation pipeline = transformers.pipeline( "text-generation", model=model_name, tokenizer=tokenizer, torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto", ) return pipeline, tokenizer def generate_answer(self, question, context=None): # Preparing the input prompt prompt = question if context is None else f"{context}\n\n{question}" # 生成应答 sequences = self.pipeline( prompt, max_length=500, do_sample=True, top_k=10, num_return_sequences=1, eos_token_id=self.tokenizer.eos_token_id, ) # Extracting and returning the generated text return sequences['generated_text']
可以参考Hugging Face的文档来编写上述代码:
- tokenizer是自然语言处理(NLP)中的一个重要组件,它的主要用于将输入文本转换为一个模型可以理解的格式。本质上讲,它将文本切分为更小的单元,称为tokens。根据
tokenizer
的设计,这些tokens可以是单词、子单词(subword)甚至是特征。在Falcon-7B-Instruct模型的上下文中,AutoTokenizer.from_pretrained(model)
调用会加载一个专为该模型设计的tokenizer
,保证能够遵循该模型训练的方式来将文本转化为token。 - pipeline是transformers库中的一个高级工具,它对处理数据和从模型获取预测所涉及的大部分复杂性进行了抽象。其内部分为多个步骤,如将输入文本划分为token、将token输入到模型以及将模型的输出处理为人类可读的格式。在上述脚本中,pipeline被设置为"text-generation",用于将一个提示(如用户问题)作为输入,然后根据提示生成一个连续的文本。
使用方式如下:
# 初始化 Falcon 模型类 falcon_model = Falcon7BInstructModel() user_question = "When was Tomoaki Komorida born?" # LLM使用LLM来为用户提问生成回答 answer = falcon_model.generate_answer(user_question) print(f"Result: {answer}")
你可能已经猜到模型的输出:
{ answer: “I don't have information about Tomoaki Komorida's birthdate.” }
使用Falcon-7B-Instruct
时,如果没有相关的上下文,就会返回一个否定响应。这说明需要进一步丰富背景知识,为非一般性问题提供更有针对性和有用的答案。
生成上下文相关的回答
下面我们通过向量存储来为模型提供相关上下文,从而提升模型能力。
有趣的是,我们使用相同的VectorStore类来同时生成嵌入向量和从用户提问中获取上下文。
# Assuming vector_store and falcon_model have already been initialized # 从 VectorStore 中获取上下文(假设数据集已经导入数据库) context_response = vector_store.search_context(user_question) # 从向量数据库的响应中抽取上下文,上下文应该是 context 关键字的第一个元素 context = "".join(context_response['context'][0]) # Falcon模型结合提取的上下文生成应答 enriched_answer = falcon_model.generate_answer(user_question, context=context) print(f"Result: {enriched_answer}")
最后可以得到准确的回答:
Tomoaki Komorida was born on July 10, 1981.
TIPs:
- 如何从开源查找适合自己的模型和数据集?
Hugging Face上的Models和Dataset都按照任务类型进行了划分,在找到感兴趣的task之后,还可以依据Trending、Most likes、Most downloads、Recently created和Recently Updated进行排序查找。 - 如何编写模型代码?
huggingface的各个模块都提供了对应的Example,参考编写即可。 - 向量数据库的读写当我们讨论到检索(retrieval)时,通常指一系列与查询最接近、且以某种格式保存在相同隐空间(Latent space,一个潜在的多维向量空间,其中每个向量表示一个潜在的特征或属性)中的向量。检索流程会使用最近邻居(Approximate Nearest Neighbour -ANN)方式进行搜索。查询可以是某种格式的对象,如查找相似的图片或检索后续传递给LLM的相关上下文。写/更新数据:
- 选择一个ML模型来生成向量嵌入
- 嵌入的信息类型:文本、图片、音频、表格等。嵌入模型的选择取决于数据的类型
- 通过运行嵌入模型来获得数据的向量。
- 为向量嵌入添加额外的元数据,后续查ANN搜索时可以使用元数据进行pre-filter 或 post-filter
- 向量数据库会分别为矢量嵌入和元数据建立索引。有多种方法可以创建向量索引,如: Random Projection, Product Quantization, Locality-sensitive Hashing等
- 向量数据会和嵌入对象的向量嵌入索引和元数据索引一起存储
- 读数据
- 针对向量数据库的查询通常分为两部分:
- ANN查询所需要的数据,如用于相似性查询的图片
- 元数据,用于排除包含某种已知特性的向量。例如你要查询公寓的相似图片,此时可以排除特定区域内的公寓
- 通过元数据索引来执行元数据查询,可以在ANN查询流程之前或之后
- 使用和数据写入相同的模型将请求数据嵌入到隐空间中
- 使用ANN搜索来检索到一组向量嵌入。ANN搜索使用的相似性测量方式有:Cosine Similarity, Euclidean Distance, Dot Product
从下面图可以看到,元数据索引可以减低检索的范围,在执行搜索时,会将请求数据一并嵌入隐空间中,然后围绕该向量嵌入找出和它最近的向量嵌入(数据)。