• 日常学习之:如何基于 OpenAI 构建自己的向量数据库


    原理

    在这里插入图片描述

    • 将数据集通过 OpenAI 的模型转换成 embedding 的向量
    • 将这些向量存储到向量数据库 pinecone
    • 当构建一个应用的时候,给出一个查询的句子 query,依然通过 OpenAI 的模型进行 embedding 得到查询向量
    • query 向量被拿来与 pinecone 中的每一个向量进行相似度匹配,最终返回相似度最高的 topk

    前期准备

    依赖安装

    # 如果你要基于 pinecone 来构建向量数据库就安装这些库
    pip install -qU pinecone-client openai datasets
    
    • 1
    • 2

    Pinecone 数据库注册

    https://www.pinecone.io/
    在这里插入图片描述

    Index 创建(相当于传统数据库中的创建 table)

    在这里插入图片描述

    基于 pinecone 数据库的代码实现

    尝试用 OpenAI 的 API 构建 embedding

    """
     @file: embedding_database.py
     @Time    : 2023/9/25
     @Author  : Peinuan qin
     """
    import openai
    import os
    
    
    openai.api_key = "[去 OPENAI 官网复制粘贴你自己的 API KEY]"
    # get API key from top-right dropdown on OpenAI website
    
    MODEL = "text-embedding-ada-002"
    
    # 基于 text-embedding-ada-002 模型尝试一下将两个句子进行 embedding,默认会构建每个向量的维度是 1536,每个句子都会单独构建一个 embedding 向量
    res = openai.Embedding.create(
        input=[
            "Sample document text goes here",
            "there will be several phrases in each batch"
        ], engine=MODEL
    )
    
    print(f"vector 0: {len(res['data'][0]['embedding'])}\nvector 1: {len(res['data'][1]['embedding'])}")
    
    embeds = [record['embedding'] for record in res['data']]
    print(len(embeds))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    import pinecone
    
    index_name = 'paper-semantic-search'
    
    # initialize connection to pinecone (get API key at app.pinecone.io)
    pinecone.init(
        api_key="[去 Pinecone 注册一个账号,并且在你自己的账号下手动创建一个 index,然后将这个 index 的 API Key 拷贝到这里",
        environment="[这个也是系统默认的,当你复制 API_KEY 的时候就能看见,在同一个页面上]"  # find next to api key in console
    )
    
    # check if 'openai' index already exists (only create index if not)
    if index_name not in pinecone.list_indexes():
        pinecone.create_index(index_name, dimension=len(embeds[0]))
    # connect to index
    index = pinecone.Index(index_name)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    将示例的数据 embedding 后写入你的 pinecode (构建向量数据库)

    参考

    from tqdm.auto import tqdm
    
    from datasets import load_dataset
    
    # load the first 1K rows of the TREC dataset
    trec = load_dataset('trec', split='train[:1000]')
    count = 0  # we'll use the count to create unique IDs
    
    # 设定 batch = 32
    batch_size = 32  # process everything in batches of 32
    for i in tqdm(range(0, len(trec['text']), batch_size)):
        # set end position of batch
        i_end = min(i+batch_size, len(trec['text']))
        # get batch of lines and IDs
        # 按照 trec 数据集中的数据的存放方式将每个句子的原文本提取出来, lines_batch 就是个字符串列表,每个列表中有 32 个字符串
        lines_batch = trec['text'][i: i+batch_size]
        # 给一个 batch 中的每个句子标号,例如这是第五个 batch,那么对应的编号应该是 160-192
        ids_batch = [str(n) for n in range(i, i_end)]
    	# 为 batch 中的每个句子创建 embeddings, 使用的模型是 "text-embedding-ada-002"
        res = openai.Embedding.create(input=lines_batch, engine=MODEL)
    	
    	# OpenAI 返回的结果中不只是包含 embedding 的值,而是为每个 sentence 都创建了一个 json 的形式,因此,利用列表表达式提取出这些 embedding 的值
        embeds = [record['embedding'] for record in res['data']]
        
        # 保留所有的 text 的内容当做元数据(metadata),保留元数据的目的是为了当你在 query pinecone 数据库得到最相似的向量之后,我们可以直接拿到他的文本数据,而不用将这一个最匹配的 embedding 再用 openai 的 api 解码。
        # 每个 embedding 都对应了一个 json 结构体可以存放他们的元数据,可以存放很多字段
        meta = [{'text': line} for line in lines_batch]
    	
    	# 最终将他们的 id,embedding 数据和 metadata 打包,放到 pinecode 数据库中存储
        to_upsert = zip(ids_batch, embeds, meta)
        # upsert to Pinecone
        index.upsert(vectors=list(to_upsert))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    构建查询 query

    # 查询语句
    query = "What caused the 1929 Great Depression?"
    
    # 将查询语句进行 embedding
    xq = openai.Embedding.create(input=query, engine=MODEL)['data'][0]['embedding']
    
    # 使用查询语句的 embedding 从数据库中索引出 cosine 相似度最高的 5 个结果,同时返回这些 embedding 的 metadata
    res = index.query([xq], top_k=5, include_metadata=True)
    
    
    # 将这些结果循环打印出来
    for match in res['matches']:
        print(f"{match['score']:.2f}: {match['metadata']['text']}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    删除 Index (慎用)

    pinecone.delete_index(index_name)
    
    • 1

    基于 chroma 数据库的代码实现

    原理介绍

    下图来源于博客
    在这里插入图片描述

    依赖安装

    pip install langchain
    pip install tiktoken
    pip install chromadb 
    pip install unstructured
    pip install "unstructured[md]"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    代码

    """
     @file: retrival.py
     @Time    : 2023/9/25
     @Author  : Peinuan qin
     """
    import os
    import openai
    from langchain.document_loaders import TextLoader, DirectoryLoader
    from langchain.indexes import VectorstoreIndexCreator
    
    os.environ['OPENAI_API_KEY'] = "[去 OPENAI 官网复制粘贴你自己的 API KEY]"
    query = "[给一个你自己想要的 query]"
    
    # 构建 loader 从某个文本文件中建立 index
    loader = TextLoader("./data/test.txt")
    
    # 也可以直接从目录中构建
    # loader = DirectoryLoader("./papers", glob='*.md') # 从一个目录文件夹中将所有扩展名为 md 的文件进行构建 index,但是这需要安装单独的依赖 pip install "unstructure[md]"
    
    # 构建基于 chromadb 的索引
    index = VectorstoreIndexCreator().from_loaders([loader])
    
    
    llm = ChatOpenAI()
    llm.model_name = 'gpt-4'
    while True:
        query = input(">")
    
    	# 这里的 llm 参数可以不给,当给了 llm 参数之后,系统不仅会在你构建的数据库中进行索引,还会根据 llm 模型对你的答案进行进一步的扩展	
        print(index.query(query, llm=llm))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 当然还涉及到一些 chromadb 持久化的操作,或者使用原生的 chromadb 进行存储而不是使用 langchain 封装的 chromadb 的方式,可以参考
  • 相关阅读:
    总结数据结构-1
    论文解读(SEP)《Structural Entropy Guided Graph Hierarchical Pooling》
    python对异常的处理
    读书笔记:Effective C++ 2.0 版,条款35(public继承==是一个)、条款36(接口继承和实现继承)
    C++笔记之临时变量与临时对象与匿名对象
    java进阶-Netty
    selenium 知网爬虫之根据【关键词】获取文献信息
    深度学习入门(三十九)计算性能——分布式训练、参数服务器(TBC)
    版本管理工具 SVN和git
    【flask进阶】Flask实现自定义分页(python web通用)
  • 原文地址:https://blog.csdn.net/qq_42902997/article/details/133300702