微信扫码
与创始人交个朋友
我要投稿
简介
矢量数据库有什么特别之处?
我们如何将句子的含义映射到数字表示?
这对我们的 LLM 应用程序有何帮助?
为什么我们不能将我们拥有的所有数据都提供给 LLM?
实践教程——文本到嵌入和距离度量
1. 文本到嵌入
2. 使用 PCA 在 2 中绘制 384 个维度
3. 计算距离度量
走向向量存储
如何加速相似性搜索?
有哪些不同的向量存储可供选择?
动手教程——设置你的第一个矢量商店
1. 安装 chroma
2. 获取/创建 chroma 客户端和集合
3. 将一些文本文档添加到集合中
4. 从数据库提取所有条目到 excel 文件
5. 查询集合
摘要参考文献
矢量数据库是当前的热门话题。各大公司不断筹集资金来开发矢量数据库或为现有的 SQL 或 NoSQL 数据库添加矢量搜索功能。
向量数据库可以快速搜索和比较大量向量。这非常有趣,因为最新的嵌入模型能够很好地理解单词背后的语义/含义并将其转换为向量。这使我们能够有效地将句子相互比较。
好的,但是我们为什么要关心呢?
对于大多数大型语言模型 (LLM) 应用程序,我们依赖该功能,因为我们的 LLM 永远无法知晓一切。它只能看到一个冻结的世界,这取决于 LLM 所用的训练集。
因此,我们需要为模型提供额外的数据,这些信息是 LLM 自身不可能知道的。所有这些都需要在应用程序运行时进行。因此,我们必须有一个流程来尽快决定要为模型提供哪些额外的数据。
使用传统的关键字搜索,我们会遇到局限性,主要有两个问题:
语言很复杂。在大多数语言中,你可以用 20 种不同的方式提出差不多相同的问题。简单地在我们的数据中搜索关键词往往是不够的。我们需要一种方法来映射单词和句子背后的含义,以找到与问题相关的内容。
我们还需要确保搜索在几毫秒内完成,而不是几秒或几分钟。所以我们需要一个步骤,让我们尽可能高效地搜索 Vector Collection。
在搜索数据库之前,我们需要将文本内容转换为向量,以捕捉单词和句子的含义。OpenAI、Google、MetaAI 或开源社区的预训练嵌入模型可以帮助我们做到这一点。它们从大量文本中学习单词的正常用法和上下文。它们使用提取的知识将单词映射到多维向量空间中。向量空间中新数据点的位置告诉我们哪些单词彼此相关。
对于下面的简单示例,我们将各种水果和蔬菜的“含义”排列在一个简单的二维向量空间中。如果嵌入模型按预期工作,我们会认为苹果和梨比苹果和洋葱更接近(至少,这是我对语言和水果知识有限的期望)。相似性可能受到各种特征的影响。对于水果和蔬菜,这可能是:大小、颜色、味道、原产国等。这完全取决于您想要描述的对象。
嵌入模型通过观察单词在上下文中的使用方式进行学习,类似于人类学习语言的方式。在成长过程中,你会通过听对话和读书来学习单词的含义。
我们模型的训练过程并没有太大不同。经过训练,它了解到“梨和苹果”在句子中出现在一起的可能性比“苹果和洋葱”更大,因此它假设它们有一些共同之处。因此,当谈到食物时,最重要的可能是不同类型的食物是否一起食用。
物体、单词和句子具有许多无法用二维表示的特征。为了解决这个问题,现代嵌入模型将单词和句子转换为具有数百或数千个维度的向量。
通过将内容转换为向量,我们可以测量它们之间的距离。在下面的二维示例中,这很容易,但当向量有数百或数千个维度时,它会变得更加复杂,并且需要更多的计算能力。
这时矢量数据库就可以发挥作用了。
他们采用了多种技术,使我们能够高效地存储和搜索文本内容集合。一旦我们有了向量集合,我们想要做的就是将它们相互比较,并以某种方式量化它们之间的相似性。通常我们对 k-最近邻感兴趣,因此我们的向量空间中的数据点最接近我们的查询向量。在下面的例子中,我们的查询将是单词“apple”。但当然不是单词“apple”本身,我们使用从嵌入模型中返回的向量。
当LLM (LLM) 本身的知识达到极限时,我们在许多LLM(LLM) 申请中采用这种方法:
LLM 不了解的一些常识:
太新的数据— 有关时事、最新创新等的文章。只是收集 LLM 训练集后创建的任何新内容。
非公开数据——个人数据、公司内部数据、秘密数据等。
简短的回答:模型有一个限制,一个象征性的限制。
如果我们不想训练或微调模型,我们别无选择,只能在提示中向模型提供所有必要的信息。我们必须尊重模型的标记限制。
由于实际和技术原因, LLM 有 token 限制。OpenAI的最新模型的 token 限制约为 4,000-32,000 个 token,而开源 LLM LLama 有 2,048 个 token(如果不进行微调)。您可以通过微调来增加 token 的最大数量,但数据越多并不总是越好。32,000 个 token 限制使我们能够一次将大型文本打包到提示中。这是否合理是另一回事。(Lample,2023 年;OpenAI,2023 年)
数据的质量比数据的数量更重要,不相关的数据会对结果产生负面影响。
即使重新组织提示中的信息也会对 LLM 理解任务的准确性产生很大影响。斯坦福大学的研究人员发现,当重要信息放在提示的开头或结尾时,答案通常更准确。如果相同的信息位于提示的中间,准确性可能会显著下降。重要的是要仔细考虑我们为模型提供的数据以及我们如何构造提示。(Liu 等人,2023 年;Raschka,2023 年)
输入数据的质量是我们 LLM 申请成功的关键,因此我们必须实施一个流程,准确识别相关内容并避免添加过多不必要的数据。为了确保这一点,我们必须使用有效的搜索流程来突出显示最相关的信息。
矢量数据库由几个部分组成,可帮助我们快速找到所需内容。索引是最重要的部分,当我们将新数据插入数据集时,只需执行一次。此后,搜索速度会更快,从而节省我们的时间和精力。
要将数据输入到我们的向量数据库中,我们首先必须将所有内容转换为向量。如本文第一部分所述,我们可以使用所谓的嵌入模型来实现这一点。由于它更方便,我们经常使用 OpenAI、Google 和 Co. 提供的现成服务之一。
在下图中,您可以看到一些可以选择的嵌入模型。
要使用我们的示例,我们需要 Hugging Face API 和句子转换器模型“all-MiniLM-L6-v2”。要开始使用,只需转到https://huggingface.co/settings/tokens并获取您的令牌。
使用下面的代码片段,我们:
将文本片段转换为具有 384 维的向量(取决于您使用的嵌入模型)。这使我们能够在向量中捕获句子的含义。
然后我们可以通过计算句子之间的距离来识别数据点之间的相似性。
为了在简单的二维图中直观地展示这一点,我们使用主成分分析将 384 个维度减少到两个维度。这可能会导致大量信息丢失,但值得一试!
对于下面的例子,我生成了 10 个随机示例句子:
text_chunks = [
“天空是蓝色的。”,
“草是绿色的。”,
“太阳在闪耀。”,
“我喜欢巧克力。”,
“披萨很好吃。”,
“编码很有趣。”,
“玫瑰是红色的。”,
“紫罗兰是蓝色的。”,
“水是生命的必需品。”,
“月球绕地球运行。”,
]
如果您想亲自尝试本教程,我已将步骤分解为您可以独立运行的数据管道。
使用以下命令创建一个新的虚拟环境.venv:
安装“requirements.txt”中的所有模块:
pip 安装-r 要求.txt
anyio == 4.0 .0 backoff == 2.2 .1
bcrypt == 4.0 .1 certifi == 2023.7 . 22 charset - normalizer == 3.2 .0 chroma - hnswlib == 0.7 . 3 chromadb == 0.4 . 10 click == 8.1 . 7 colorama == 0.4 . 6 coloredlogs == 15.0 . 1 contourpy == 1.1 . 1 cycler == 0.11 . 0 exceptiongroup == 1.1 . 3 fastapi == 0.99 . 1 filelock == 3.12 . 4 flatbuffers == 23.5 . 26 fonttools == 4.42 . 1 fsspec == 2023.9 . 1 h11 == 0.14 . 0 httptools == 0.6 . 0 huggingface-hub == 0.16 . 4 humanfriendly == 10.0 idna == 3.4 importlib-resources == 6.0 . 1 joblib == 1.3 . 2 kiwisolver == 1.4 . 5 matplotlib == 3.8 . 0 monotonic == 1.6 mpmath == 1.3 . 0 numpy == 1.26 . 0 onnxruntime == 1.15 . 1 overrides == 7.4 . 0 packaging == 23.1 pandas == 2.1 . 0 Pillow == 10.0 . 1 posthog == 3.0 . 2 protobuf == 4.24 . 3 pulsar-client == 3.3 . 0
pydantic == 1.10 . 12
pyparsing == 3.1 . 1
PyPika == 0.48 . 9
pyreadline3 == 3.4 . 1
python-dateutil == 2.8 . 2
python-dotenv == 1.0 . 0
pytz == 2023.3 .post1
PyYAML == 6.0 . 1
request == 2.31 . 0
scikit-learn == 1.3 . 0
scipy == 1.11 . 2
six == 1.16 . 0
sniffio == 1.3 . 0
starlette == 0.27 . 0
sympy == 1.12
threadpoolctl == 3.2 . 0
tokenizers == 0.14 . 0
tqdm == 4.66 . 1
typing_extensions == 4.7 . 1
tzdata == 2023.3
urllib3 == 2.0 . 4
uvicorn == 0.23 . 2
watchfiles == 0.20 . 0
websockets == 11.0 . 3
如果您想跟着做,您可以简单地设置一个类似的文件夹结构,其中包含数据管道和数据的文件夹(或更改管道内 .csv 文件的路径)。
第一个管道“01_text_to_embeddings.py”使用嵌入模型来转换 10 个示例句子。结果保存在名为“embeddings_df”的数据框中,然后导出到名为“embeddings_df.csv”的 CSV 文件中。请确保用您自己的标记替换 HuggingFace 标记。
#############################################################################################################
'''
以下脚本将字符串列表“text_chunks”转换为向量嵌入,并将
包含“text_chunks”和“embeddings”的 DataFrame 保存在 csv 文件“embeddings_df.csv”中
'''
##########################################################################################################
import os
import request
import pandas as pd
import numpy as np
# hugging face token
os.environ[ 'hf_token' ] = os.environ.get( 'HF_TOKEN' )
# os.environ['hf_token'] = 'testtoken123'
# 我们要转换成向量嵌入的示例文本片段
text_chunks = [
“天空是蓝色的。”,
“草是绿色的。”,
“太阳在闪耀。”,
“我喜欢巧克力。”,
“披萨很好吃。”,
“编码很有趣。”,
“玫瑰是红色的。”,
“紫罗兰是蓝色的。”,
“水是生命的必需品。”,
“月球绕地球运行。” ,
]
def _get_embeddings ( text_chunk ):
'''
使用来自 hugging face 的嵌入模型来计算提供的文本片段的嵌入
参数:
- text_chunk(字符串):您想要翻译成嵌入的句子或文本片段
返回:
- embedding(list):包含所有嵌入维度的列表
'''
# 定义您要使用的嵌入模型
model_id = "sentence-transformers/all-MiniLM-L6-v2"
# 您可以在设置页面中找到 hugging face api 的令牌 https://huggingface.co/settings/tokens
hf_token = os.environ.get( 'hf_token' )
# 嵌入模型的 API 端点
api_url = f"https://api-inference.huggingface.co/pipeline/feature-extraction/ {model_id} "
headers = { "Authorization" : f“Bearer {hf_token} ” }
#调用API
响应=请求。post(api_url,headers=headers,json={ “inputs” :text_chunk,“options”:{ “wait_for_model”:True)
#将嵌入模型的响应加载为 json 格式
embedding = response.json()
return embedding
def from_text_to_embeddings ( text_chunks ):
'''
将句子翻译成向量嵌入
属性:
- text_chunks (list):示例字符串列表
返回:
- embeddings_df (DataFrame):具有“text_chunk”和“embeddings”列的数据框
'''
# 使用文本块列表创建新的数据框
embeddings_df = pd.DataFrame(text_chunks).rename(columns={ 0 : "text_chunk" })
# 使用 _get_embeddings 函数检索每个句子的嵌入
embeddings_df[ "embeddings" ] = embeddings_df[ "text_chunk" ].apply(_get_embeddings)
# 将嵌入列拆分为每个向量维度的单独列
embeddings_df = embeddings_df[ 'embeddings' ].apply(pd.Series)
embeddings_df[ "text_chunk" ] = text_chunks
return embeddings_df
# 获取每个文本块的嵌入
embeddings_df = from_text_to_embeddings(text_chunks)
# 将包含文本块和嵌入的数据框保存为 csv
embeddings_df.to_csv( '../02_Data/embeddings_df.csv' , index= False )
现在您应该在文件夹“02_Data”中找到 csv 文件“embeddings_df.csv”。它看起来应该像这样:
现在,您在文件夹“02_Data”中有了包含向量的 csv 文件“embeddings_df.csv”,让我们尝试将其可视化。我们可以使用主成分分析来提取 2 个最重要的“主成分”并将其可视化。我们无法在两个维度中捕获所有原始信息,但也许它可以让我们感受到将文本转换为向量时发生了什么。
为第二条管道创建一个新文件,我将其命名为:“02_create_PCA_analysis.py”
第二条管道包含:
从“embeddings_df.csv”加载嵌入
使用 scikit-learn 执行主成分分析,以创建并找到两个最重要的(主要)成分
创建散点图以在二维图中可视化结果
#######################################################################################################
# 使用嵌入 df 创建 PCA 图
#################################################################################################
import pandas as pd
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
def create_pca_plot ( embeddings_df ):
'''
该函数执行主成分分析以将维度减少到 2,以便我们可以将它们打印在图中
参数
- embeddings_df (DataFrame):包含“text_chunk”和“embeddings”列的数据框
返回
- df_reduced (DataFrame):包含 2 个最相关主成分的数据框
'''
# 使用 2 个成分执行 PCA
pca = PCA(n_components= 2 )
# 将主成分分析应用于嵌入表
df_reduced = pca.fit_transform(embeddings_df[embeddings_df.columns[:- 2 ]])
# 创建一个具有降低维度的新 DataFrame
df_reduced = pd.DataFrame(df_reduced, columns=[ 'PC1' , 'PC2' ])
##############################################################################################
# 创建散点图
##############################################################################################
def create_scatter_plot ( df_reduced ):
plt.scatter(df_reduced[ 'PC1' ], df_reduced[ 'PC2' ], label=df_reduced[ 'PC2' ])
# 添加标签和标题
plt.xlabel( 'X' )
plt.ylabel( 'Y' )
plt.title( '散点图' )
# 为每个点添加标签
for i, label in enumerate (embeddings_df.iloc[: , - 1 ].to_list()):
plt.text(df_reduced[ 'PC1' ][i], df_reduced[ 'PC2' ][i], label)
# 保存并显示图
plt.savefig( '../02_Data/principal_component_plot.png' ,format = 'png' )
# 创建并保存散点图
create_scatter_plot(df_reduced=df_reduced)
return df_reduced
# 将 embeddings_df.csv 加载到数据框中
embeddings_df = pd.read_csv( '../02_Data/embeddings_df.csv' )
# 使用函数 create_pca_plot 来
df_reduced = create_pca_plot(embeddings_df)
我不确定这个例子有多好,但你至少可以看到关于食物和饮料的句子往往位于图的右侧,描述天气的两个句子在左侧靠得很近,而关于花的两个句子在图的底部靠得很近。我只会把这当作一个“不太科学”的证据,表明该模型至少在映射单词/短语背后的语义方面取得了一定成功。
作为人类,当在简单的二维空间中绘制时,相对容易分辨出哪些点更接近。但是我们如何在具有数百和数千个维度的向量空间中量化这一点?——我们需要一个度量标准。一个描述相似性的度量标准。因此,我们计算空间中点之间的距离。
距离是如何定义的?
在统计学中,我们有几个指标来衡量数据点之间的距离。一个常用的指标是“余弦相似度”。我们将在下面的例子中使用这个指标。
我们希望使用矢量数据库来尽快找到相似的条目。但是矢量数据库如此特别的原因在于,为什么我们不能通过计算到每个数据点的距离度量来执行相似性搜索呢?
为了便于理解为什么我们需要一种替代方法,我创建了一个简单的流程来查找最近的数据点。它通过计算到每个数据点的距离,然后对它们进行排序以找到最近的邻居来实现这一点。
对于下面的例子,我们使用了本文前面已经转换成矢量格式的 10 条文本。
因此,我们的数据库的常见查询可能如下所示:
假设我们有一个新的句子(用户查询),这里:“百合是白色的”。
首先,我们获取新句子的嵌入。这个向量给出了嵌入空间中的一个位置,让我们可以将它们与集合中的其他文本块进行比较。
然后我们计算到每个数据点的距离(这里是余弦相似度)
通常,我们对 k 近邻感兴趣。我们只需过滤与新查询向量距离最小的那些
举个简单的例子,我们使用句子“百合是白色的。”并尝试通过计算它们之间的距离来描述与数据集中其他句子的相似性。
为此,我们创建了第三个管道,我称之为:“03_calculate_cosine_similarity.py”
####################################################################################################
# 计算查询向量与所有其他嵌入向量之间的余弦相似度
#################################################################################################
import numpy as np
from numpy.linalg import norm
import time
import pandas as pd
import os
import request
def _get_embeddings ( text_chunk ):
'''
使用来自 hugging face 的嵌入模型为提供的文本片段计算嵌入
参数:
- text_chunk(字符串):您想要翻译成嵌入的句子或文本片段
返回:
- embedding(list):包含所有嵌入维度的列表
'''
# 定义您要使用的嵌入模型
model_id = “sentence-transformers/all-MiniLM-L6-v2”
# 你可以在设置页面中找到 hugging face api 的令牌 https://huggingface.co/settings/tokens
hf_token = os.environ.get( 'HF_TOKEN' )
# 嵌入模型的 API 端点
api_url = f"https://api-inference.huggingface.co/pipeline/feature-extraction/ {model_id} "
headers = { "Authorization" : f"Bearer {hf_token} " }
# 调用 API
response = request.post(api_url, headers=headers, json={ "inputs" : text_chunk, "options" :{ "wait_for_model" : True }})
# 将嵌入模型的响应加载为 json 格式
embedding = response.json()
return embedding
def calculate_cosine_similarity ( text_chunk, embeddings_df ):
'''
计算查询句子和其他每个句子之间的余弦相似度
1. 获取文本块的嵌入
2. 计算文本块的嵌入与数据框中每个其他条目之间的余弦相似度
参数:
- text_chunk(字符串):我们要用来在数据库中查找类似条目的文本片段(embeddings_df)
- embeddings_df(DataFrame):包含“text_chunk”和“embeddings”列的数据框
返回:
-
'''
# 使用 _get_embeddings 函数检索文本块的嵌入
sentence_embedding = _get_embeddings(text_chunk)
# 将向量嵌入的所有维度组合成一个数组
embeddings_df[ 'embeddings_array' ] = embeddings_df.apply( lambda row: row.values[:- 1 ], axis= 1 )
# 启动计时器
start_time = time.time()
print (start_time)
# 创建一个列表来存储计算出的余弦相似度
cos_sim = []
for index, row in embeddings_df.iterrows():
A = row.embeddings_array
B = sentence_embedding
# 计算余弦相似度
cosine = np.dot(A,B)/(norm(A)*norm(B))
cos_sim.append(cosine)
embeddings_cosine_df = embeddings_df
embeddings_cosine_df[ "cos_sim" ] = cos_sim
embeddings_cosine_df.sort_values(by=[ "cos_sim" ], acceding= False )
# 停止计时器
end_time = time.time()
# 计算计算相似度所需的时间
elapsed_time = (end_time - start_time)
print ( "执行时间:" , elapsed_time, "seconds" )
return embeddings_cosine_df
# 将 embeddings_df.csv 加载到数据框中
embeddings_df = pd.read_csv( '../02_Data/embeddings_df.csv' )
# 测试查询句子
text_chunk = "Lilies are white."
# 计算余弦相似度
embeddings_cosine_df = calculate_cosine_similarity(text_chunk, embeddings_df)
# 将包含文本块和嵌入的数据框保存到 csv
embeddings_cosine_df.to_csv( '../02_Data/embeddings_cosine_df.csv' , index= False )
# 根据相似度进行排名
similarity_ranked_df = embeddings_cosine_df[[ "text_chunk" , "cos_sim" ]].sort_values(by=[ "cos_sim" ], accending= False )
如果我们看相似度分数,句子“紫罗兰是蓝色的。”和“玫瑰是红色的。”与我们的查询句子“百合是白色的。”比“编程很有趣。”更相似。
我想这是有道理的。
相似性搜索似乎有效。那么我们为什么需要另一种方法呢?
使用time.time()函数我停止计算我们的查询向量和其他 10 个向量之间的余弦相似度所需的时间。
据此,搜索我们的 10 个条目大约需要 0.005 秒。
将每个点与其他点进行比较称为“详尽搜索”,它需要线性时间。例如,如果比较 10 个点需要 0.005 秒,则比较 100 万个文本块需要几分钟。
我们需要找到一种有效的方法来加快相似性搜索过程。
近似最近邻算法用于查找最近的邻居,即使它们可能并不总是最接近的。这种以准确度换取速度的权衡通常对于 LLM 应用程序来说是可以接受的,因为速度更重要,而且通常在多个文本片段中都会找到相同的信息。(Trabelsi,2021 年)
我认为普通用户不需要深入了解索引技术。但我不想让你完全一无所知。为了让你至少对这些技术有一个基本的了解,这里有一个如何使用倒排文件索引的示例。这样,你就可以了解使用这些技术会在哪些地方损失准确性。
倒排文件索引 (IFV) 是一种常用的查找不同项目之间相似性的方法。它的工作原理是创建一个数据库索引,该索引存储内容并将其与表格或文档中的位置联系起来。我们将整个项目集合划分为分区及其质心。每个项目一次只能成为一个分区的一部分。当我们搜索相似性时,我们使用分区质心来快速找到我们要找的项目。
如果我们要寻找附近的点,通常只需在最接近我们点的质心内搜索。但如果有靠近邻近质心边缘的点,我们可能会错过它们。
为了避免这个问题,我们搜索多个分区,而不是一个。然而,潜在的问题仍然存在。我们损失了一些准确性。但这通常没问题,速度更重要。
Chroma 支持多种近似最近邻 (ANN) 算法,包括 HNSW、IVFADC 和 IVFPQ。(Yadav,2023 年)
分层可导航小世界 (HNSW): HNSW 是一种创建分层图形结构的算法,以最少的内存使用量快速存储和搜索高维向量。
带乘积量化的倒排文件(IVFPQ): IVFPQ 使用乘积量化在索引之前压缩向量,从而实现可处理海量数据集的高精度搜索。
如果您想详细了解不同的索引方法是如何工作的,我推荐James Briggs Youtube 频道。我还喜欢Peggy Chang的博客文章,内容涉及倒排文件索引 (IVF)、乘积量化 (PQ) 等。
什么对我们来说才是真正重要的?
我认为我们需要知道的是,通过索引步骤,我们将嵌入存储为一种形式,使我们能够快速找到“相似”向量,而无需每次计算到所有数据点的距离。通过这样做,我们用速度换取了一些准确性。
对于我们尝试用它完成的大部分任务而言,该任务的准确度应该足够好。无论如何,将语言翻译成嵌入并不是一门精确的科学。同一个词可能具有不同的含义,具体取决于您使用它的上下文或世界区域。因此,如果我们损失一些准确度点通常没关系,但更重要的是响应速度。
Google 的快速响应速度是它如此成功的原因。流程每一步的速度对于我们的申请来说更为重要,因为我们不仅要进行向量搜索,还要将问题和背景传递给我们的 LLM。
但是,这个过程比 Google 要长一点,我想这也是 Bing Chat(支持 LLM)尚未征服世界的原因。它只慢了几毫秒或几秒,但这种微小的差异足以让 Google 保持领先地位。
为了说明这些步骤——假设我们想创建一个像 Bing Chat 这样的聊天机器人
我们仍然有(传统的)搜索部分,用于查找最相关的内容和新闻。只有当我们找到一些共同的结果时,我们才会将它们提供给我们的 LLM,让它解释数据并制定一个听起来不错的答案。
我们的向量存储负责在加载数据时对其进行标记、嵌入和索引。一旦数据进入存储,我们就可以使用新数据点对其进行查询。
矢量数据库有不同的形式。我们区分为:
纯向量数据库
SQL、NoSQL 或文本搜索数据库中的扩展功能
简单的矢量库
文本搜索数据库可以在大量文本中搜索特定的单词或短语。最近,其中一些数据库已开始使用向量搜索来进一步提高其查找所需内容的能力。在上一届微软开发者大会上,Elasticsearch 解释了他们如何使用传统搜索和向量搜索来创建“混合评分”系统,为您提供最佳的搜索结果。
向量搜索也逐渐被越来越多的SQL 和 NoSQL 数据库(如 Redis、MongoDB 或 Postgres)采用。例如,Pgvector 是 Postgres 的开源向量相似性搜索。它支持(Github,2023 年):
精确和近似最近邻搜索
L2 距离、内积和余弦距离
对于较小的项目,矢量库是一个很好的选择,并且提供了所需的大部分功能。
2017 年,Facebook AI Research 发布了首批向量库之一 FAISS。FAISS是一个用于高效搜索和聚类密集向量的库,可以处理任何大小的向量集,即使是内存中无法容纳的向量集。它用 C++ 编写,并附带 Python 包装器,方便数据科学家将其集成到他们的代码中。(FAISS 文档,2023 年)
另一方面,Chroma、Pinecone 和 Weaviate 是纯矢量数据库,可以存储您的矢量数据并像任何其他数据库一样进行搜索。在本文中,我将教您如何使用 Chroma 设置矢量数据库以及如何用矢量数据填充它。如果您正在寻找快速解决方案,像 FAISS 这样的矢量库可以帮助您轻松开始使用所有必要的索引方法。
那么我们应该选择哪一个呢?
我想你们必须自己和你们的具体项目来回答这个问题,但不要让事情变得不必要地复杂化。Andre Karpathy 在 Twitter 上这样描述它:(Andrej Karpathy,2023 年):
“Np.array——如今人们追求更花哨的东西的速度太快了”——Andrew Karpathy
如果您只需要搜索 PDF 或文本文件的几页,您可以简单地使用 np.array 或 pandas 数据框来存储您的嵌入。当我们谈论数百、数千或数百万个向量时,向量数据库的功能变得有趣,我们希望定期搜索。对于本文,我使用的是 Chroma,但相同的原则适用于所有数据库。
为了将内容存储到向量中并提高相似性搜索的性能,我们需要设置自己的向量数据库。Chroma 是一个很好的开源选项,因为它可以免费使用并且拥有 Apache 2.0 许可证。还有其他替代方案,例如 FAISS、Weaviate 和 Pinecone。其中一些选项是开源的并且可以免费使用,而其他选项仅作为商业服务提供。
只需几个步骤,我们就可以启动并运行 chromadb。我们需要做的就是使用我们的包管理器pip下载chromadb库。下载完成后,我们就可以开始设置我们的第一个向量存储数据库并开始使用。
https://pypi.org/project/chromadb/
pip 安装 chromadb
集合是嵌入、文档和任何其他元数据的指定存储。如果要保存它,以便以后可以使用索引和集合,可以使用“persist_directory”
导入chromadb
从chromadb.config导入设置
导入pandas作为pd
# 向量存储设置
VECTOR_STORE_PATH = r'../02_Data/00_Vector_Store'
COLLECTION_NAME = 'my_collection'
# 将 embeddings_df.csv 加载到数据框中
embeddings_df = pd.read_csv( '../02_Data/embeddings_df.csv' )
def get_or_create_client_and_collection ( VECTOR_STORE_PATH, COLLECTION_NAME ):
# 获取/创建 chroma 客户端
chroma_client = chromadb.PersistentClient(path=VECTOR_STORE_PATH)
# 获取或创建集合
collection = chroma_client.get_or_create_collection(name=COLLECTION_NAME)
return collection
# 获取或创建集合
collection =获取或创建客户端和集合(VECTOR_STORE_PATH,COLLECTION_NAME)
Chroma 可让您轻松存储和组织文本文档。它会为您处理标记化、嵌入和索引过程,如果您已经创建了自己的嵌入,则可以将其直接加载到 Chroma 的矢量存储中。
我们希望将已经创建的数据框“embeddings_df”存储到我们的新数据存储中:
# 将 embeddings_df.csv 加载到数据框中
embeddings_df = pd.read_csv( '../02_Data/embeddings_df.csv' )
def add_to_collection ( embeddings_df ):
# 将样本条目添加到集合
# collection.add(
# documents=["This is a document", "This is another document"],
# metadata=[{"source": "my_source"}, {"source": "my_source"}],
# ids=["id1", "id2"]
# )
# 将向量嵌入的所有维度组合成一个数组
embeddings_df[ 'embeddings_array' ] = embeddings_df.apply( lambda row: row.values[:- 1 ], axis= 1 )
embeddings_df[ 'embeddings_array' ] = embeddings_df[ 'embeddings_array' ].apply( lambda x: x.tolist())
# 将数据框添加到集合
collection.add(
embeddings=embeddings_df.embeddings_array.to_list(),
documents=embeddings_df.text_chunk.to_list(),
# 创建一个字符串列表作为索引
ids= list ( map ( str , embeddings_df.index.tolist()))
)
# 将 embeddings_df 添加到我们的向量存储集合
add_to_collection(embeddings_df)
如果要导出向量存储中的所有条目,可以使用:
def get_all_entries ( collection ):
# 查询集合
existing_docs = pd.DataFrame(collection.get()).rename(columns={ 0 : "ids" , 1 : "embeddings" , 2 : "documents" , 3 : "metadatas" })
existing_docs.to_excel( r "..//02_Data//01_vector_stores_export.xlsx" )
return already_docs
# 提取向量存储集合中的所有条目
existing_docs = get_all_entries(collection)
Chroma 可以轻松找到与查询文本最相似的n 个结果:
def query_vector_database ( VECTOR_STORE_PATH, COLLECTION_NAME, query, n= 2 ):
# 查询集合
results = collection.query(
query_texts=query,
n_results=n
)
print ( f"相似性搜索:{n}最相似的条目:" )
print (results[ "documents" ])
返回结果
# 相似性搜索
similar_vector_entries = query_vector_database(VECTOR_STORE_PATH, COLLECTION_NAME, query=[ "Lilies are white." ])
对于这个例子,我们将返回向量存储中最相似的两个条目。
您可以在下面找到摘要
################################################################################################################
'''
包括一些用于创建新的向量存储集合、填充和查询它的函数
'''
#######################################################################################################
import chromadb
from chromadb.config import Settings
import pandas as pd
# 向量存储设置
VECTOR_STORE_PATH = r'../02_Data/00_Vector_Store'
COLLECTION_NAME = 'my_collection'
# 将 embeddings_df.csv 加载到数据框中
embeddings_df = pd.read_csv( '../02_Data/embeddings_df.csv' )
def get_or_create_client_and_collection ( VECTOR_STORE_PATH, COLLECTION_NAME ):
# 获取/创建 chroma 客户端
chroma_client = chromadb.PersistentClient(path=VECTOR_STORE_PATH)
# 获取或创建集合
collection = chroma_client.get_or_create_collection(name=COLLECTION_NAME)
return collection
# 获取或创建集合
collection = get_or_create_client_and_collection(VECTOR_STORE_PATH, COLLECTION_NAME)
def add_to_collection ( embeddings_df ):
# 将样本条目添加到集合
# collection.add(
# documents=["This is a document", "This is another document"],
# metadatas=[{"source": "my_source"}, {"source": "my_source"}],
# ids=["id1", "id2"]
# )
# 将向量嵌入的所有维度组合成一个数组
embeddings_df[ 'embeddings_array' ] = embeddings_df.apply( lambda row: row.values[:- 1 ], axis= 1 )
embeddings_df[ 'embeddings_array' ] = embeddings_df[ 'embeddings_array' ].apply( lambda x: x.tolist())
# 将数据框添加到集合
collection.add(
embeddings=embeddings_df.embeddings_array.to_list(),
documents=embeddings_df.text_chunk.to_list(),
# 创建一个字符串列表作为索引
ids= list ( map ( str , embeddings_df.index.tolist()))
)
# 将 embeddings_df 添加到我们的向量存储集合
add_to_collection(embeddings_df)
def get_all_entries ( collection ):
# 查询集合
目前存在的数据框(collection.get())。rename(columns={ 0:“ids”,1:“embeddings”,2:“documents”,3:“metadatas” })目前存在的数据
框(r“..//02_Data//01_vector_stores_export.xlsx”)
返回目前存在的数据框
#提取向量存储集合中的所有条目
目前存在的数据框(export.xlsx)
def query_vector_database(VECTOR_STORE_PATH,COLLECTION_NAME,query,n= 2):
#查询集合
results=collection.query(
query_texts=query,
n_results=n
)
print(f“相似性搜索:{n}最相似的条目:”)
print(results[ “documents” ])
返回结果
#相似性搜索
similar_vector_entries = query_vector_database(VECTOR_STORE_PATH, COLLECTION_NAME, query=[ "百合是白色的。" ])
然后,我们将使用这些 k-最近邻来输入我们的 LLM。围绕它构建一个简单的提示模板,将找到的文本块插入其中,然后您可以将其发送到 GPT、LLama 或您选择的任何其他 LLM。我在之前的一篇文章中描述了它的工作原理。
随着机器学习模型现在可以准确地将各种内容转换为向量,向量搜索变得越来越流行。不仅有越来越多的专用向量数据库,而且现有的 SQL、NoSQL 和文本搜索数据库也将向量搜索功能纳入其产品中。这要么是为了改进他们的搜索机制,要么是为了为那些专门寻找具有向量搜索功能的数据库的人提供产品。
人们对向量存储的兴趣与日俱增。得益于近年来 Transformer 模型的进步,我们现在可以放心地将文本模块转换为向量。这为处理文本带来了无限的数学可能性。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-12-23
大模型检索增强生成之向量数据库的问题
2024-12-21
SAC-KG:利用大型语言模型一键构建领域知识图谱 - 中科大&阿里
2024-12-19
北大Chatlaw - 基于知识图谱增强混合专家模型的多智能体法律助手
2024-12-18
Elasticsearch vs 向量数据库:寻找最佳混合检索方案
2024-12-16
轻量高效的知识图谱RAG系统:LightRAG
2024-12-16
5种方法,让文本信息瞬间变成结构化图谱!
2024-12-16
向量数据库到底算不算一种NoSQL数据库?
2024-12-14
大模型能自动创建高质量知识图谱吗?可行性及人机协同机制 - WhyHow.AI
2024-07-17
2024-07-11
2024-08-13
2024-07-13
2024-07-12
2024-06-24
2024-07-08
2024-06-10
2024-07-26
2024-08-27
2024-12-16
2024-12-10
2024-12-04
2024-12-01
2024-11-30
2024-11-22
2024-11-04
2024-10-10