embedding和向量数据库(pinecone)
玩了这么久的gpt,大家多少都会发现使用过程中有一些尴尬的点:
LLM的训练数据是有ddl的,无法获取到最新的一些信息
LLM不知道答案,开始放飞自我,出现hallucination
想应用化(客服什么的),但某些数据是不通用的,需要自己投喂
如果是一个普通用户在使用,当然是关系不大。但是如果是想将其集成然后应用化,那么这些case总是不靠谱的。微调模型(fine-tuning)成本太大,效果不一定理想。
官方还提供了一种方式,简单来说就是:
我们自己有数据集(一些边缘case的答案和时效性都可维护),先存起来
当用户输入prompt之后,先去我们的数据集中查
如果找到其中一些相关的内容,就把内容和prompt重新组合成一个新的prompt,再丢给LLM进行交流
这样最大的好处就是省token,如果我们直接进行信息投喂,token很容易就爆了(中文占得还贼多)。然后的话这样也可以对LLM进行一个成本不大的微调
这种方式就是embedding
embedding
开头其实没怎么强调embedding的概念,因为个人感觉先知道为啥用和怎么用之后,后续理解概念可能会更简单。
embedding 中文翻译是“嵌入”,在⾃然语⾔处理和机器学习领域,"embeddings"是指将单词、短语或⽂本等离散变量转换成连续向量空间的过程。这个向量空间通常被称为嵌⼊空间(embedding space),⽽⽣成的向量则称为嵌⼊向量(embedding vector)或向量嵌⼊(vector embedding)。
刚开始看有点懵,其实我们上面的核心问题就是:怎么在我们的数据集中检索到和prompt相似的内容?
平常生活中我们是如何判断两个人像不像的呢,肯定会从很多维度去对比:
粗粒度一点:外貌、性格、性别、年龄等;
细粒度一点:头发长短、单双眼皮、做事是否认真等
同样的,如何对比两段内容是不是相近的呢?当然也是从两段内容的不同维度去对比
但机器不理解内容可以从什么维度去对比呀,所以就先需要把内容转换为多维空间中的连续向量,这样两段内容就可以进行相似性判断了:距离越近,相似程度越高,距离越远,相似程度越低:
我们可以调用openAI的api来生成内容对应的embedding vectors,好用且实惠:
python调用embedding的API也很简单:
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
openai.Embedding.create(
model="text-embedding-ada-002",
input="需要转为embedding vectors的内容"
)
向量数据库
那么这些向量存放在哪以及怎么对向量的管理和检索怎么进行呢?这就是向量数据库的职责,相比于关系型数据库,向量数据库更擅长进行相似性的搜索,以及在数据存储量上也存在优势。
向量数据库可能每个人用的都不一样,我使用的是pinecone,其他还有Milvus、Zilliz啥的,没用过就不介绍了(主要是pingcone免费版申请直接就秒过了)
pinecone官网:https://www.pinecone.io/
pinecone文档:Python Client
进去之后免费版需要申请,不过应该挺好过。付费版好像是70刀一个月
创建索引
索引名称:符合规范即可
特征维度:就是向量的数组长度,openAi将内容转成向量之后的长度是1536,所以我配置的是1536
度量距离指标:就是上面说的计算向量之间距离的方式,三种方式的差异如下:
创建索引完成之后等待一会就好了,然后就可以本地环境安装依赖,对着文档crud了:
pip3 install pinecone-client
术语
Index:是Pinecone中向量数据的最高层组织单位。它接受和存储向量,服务于对其包含的向量的查询,并对它的内容进行其他向量操作。每个索引至少运行在一个pod上
Pod:用于运行 Pinecone 服务的预配置硬件单元。每个索引在一个或多个 pod 上运行。通常,更多的 pod 意味着更多的存储容量、更低的延迟和更高的吞吐量。您还可以创建不同大小的 pod
Collections:集合是索引的静态副本。它是一组向量和元数据的不可查询表示。您可以从索引创建集合,也可以从集合创建新索引。这个新索引可以不同于原始源索引:新索引可以有不同数量的 pod、不同的 pod 类型或不同的相似性度量
Demo实现(python)
因为不管是pinecone还是后续要写的Langchain,python的支持程度目前都更好,所以demo是用python(不过语法和js都差不多,代码逻辑也很简单,一眼就懂)
import openai
import pinecone
openai.api_key = "Your key"
pinecone.init(api_key="Your key", environment="Your env")
# 提示词
prompt="2022年卡塔尔世界杯的冠军是?"
# 与LLM交流
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": prompt}
]
)
print("User: ", prompt)
print("AI: ", completion.choices[0].message.content)
AI肯定是不知道的:
然后我们在根目录新建 data/question_bank.txt作为我们测试的知识库,内容是:
已知2022年卡塔尔世界杯冠军是阿根廷
然后按照下面的逻辑:
初始化索引
获取txt文件中的数据
调用openAI的embedding接口生成embedding vectors
将embedding vectors保存到pinecone中,同时需要保存元数据
将propmt也调用openAI的embedding接口生成embedding vectors
去pinecone的索引中去检索,是否有相似的内容
将相似内容和prompt重新构造成一份新的prompt_final,丢给GPT
import openai
import pinecone
openai.api_key = "Your key"
pinecone.init(api_key="Your key",
environment="Your env")
# 提示词
prompt="2022卡塔尔世界杯的冠军是?"
# 初始化索引
active_indexes = pinecone.list_indexes()
index = pinecone.Index(active_indexes[0])
print("****************** 初始化索引:Done ******************")
# 获取知识库内容
file = open('data/question_bank.txt', 'r')
content = file.read()
file.close()
print("****************** 知识库获取:Done ******************")
# 生成知识库embedding vector
data_embedding_res = openai.Embedding.create(
model="text-embedding-ada-002",
input=content
)
print("****************** 生成知识库embedding vector:Done ******************")
# 更新知识库向量以及对应的元数据
upsertRes = index.upsert([
("q1", data_embedding_res['data'][0]['embedding'], { "data": content })
])
print("****************** 更新知识库向量以及对应的元数据:Done ******************")
# 生成问题embedding vector
promt_embedding_res = openai.Embedding.create(
model="text-embedding-ada-002",
input=prompt
)
print("****************** 生成问题embedding vector:Done ******************")
# 从知识库中检索相关内容
# 返回的数据格式如下:
# {
# 'matches': [{
# 'id': 'q1',
# 'metadata': {'data': '2022年卡塔尔世界杯的冠军是卡塔尔'},
# 'score': 0.952013373,
# 'values': []
# }],
# 'namespace': ''
# }
prompt_res = index.query(
promt_embedding_res['data'][0]['embedding'],
top_k=5,
include_metadata=True
)
print("****************** 从知识库中检索相关内容:Done ******************")
# 重新构造prompts
contexts = [item['metadata']['data'] for item in prompt_res['matches']]
prompt_final = "\n\n"+"\n\n---\n\n".join(contexts)+"\n\n-----\n\n"+prompt
print("****************** 重新构造prompts:Done ******************")
# 与LLM交流
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": prompt_final}
]
)
print("User: ", prompt)
print("AI: ", completion.choices[0].message.content)
由于GPT知道了你给他的信息,所以自然也就能正确地给出answer了:
总结
上述的demo中没涉及到的还有:
知识库大了需要切片上传
需要考虑token
…
不过这些模块都被Langchain很好地拆分并集成了,所以等Langchain了解的差不多,还需要重新完善一下,这是一个openai的embedding的demo,可以参考一下:
https://github.com/openai/openai-cookbook/blob/main/examples/Obtain_dataset.ipynb
该说不说最近接触的这些知识对于我来说,着实有点太新鲜,加上也暂时还没在生产环境中用上哈哈,目前琢磨的不算透彻的,效率也贼低,不过总算是打开了扇窗户吧
后续得尝试着用在项目中,不然闭门造车也着实没意义