llm-universe-第三节-词向量及向量知识库

发布于 2024-04-22  9 次阅读


llm-universe-第三节-词向量及向量知识库

一、词向量

1. 什么是词向量

Embeddding 在机器学习和自然语言处理(NLP)中,词向量(Embeddings)是一种将非结构化数据,如单词、句子或者整个文档,转化为实数向量的技术。这些实数向量可以被计算机更好地理解和处理。

嵌入背后的主要想法是,相似或相关的对象在嵌入空间中的距离应该很近。 similar 举个例子,我们可以使用词嵌入(word embeddings)来表示文本数据。在词嵌入中,每个单词被转换为一个向量,这个向量捕获了这个单词的语义信息。例如,"king" 和 "queen" 这两个单词在嵌入空间中的位置将会非常接近,因为它们的含义相似。而 "apple" 和 "orange" 也会很接近,因为它们都是水果。而 "king" 和 "apple" 这两个单词在嵌入空间中的距离就会比较远,因为它们的含义不同。

2. 词向量的优势

在RAG(Retrieval Augmented Generation,检索增强生成)方面词向量的优势主要有两点:

  • 词向量比文字更适合检索。当我们在数据库检索时,如果数据库存储的是文字,主要通过检索关键词(词法搜索)等方法找到相对匹配的数据,匹配的程度是取决于关键词的数量或者是否完全匹配查询句的;但是词向量中包含了原文本的语义信息,可以通过计算问题与数据库中数据的点积、余弦距离、欧几里得距离等指标,直接获取问题与数据在语义层面上的相似度;
  • 词向量比其它媒介的综合信息能力更强,当传统数据库存储文字、声音、图像、视频等多种媒介时,很难去将上述多种媒介构建起关联与跨模态的查询方法;但是词向量却可以通过多种向量模型将多种数据映射成统一的向量形式。

3. 一般构建词向量的方法

在搭建 RAG 系统时,我们往往可以通过使用嵌入模型来构建词向量,我们可以选择:

  • 使用各个公司的 Embedding API;
  • 在本地使用嵌入模型将数据构建为词向量。

二、向量数据库

1. 什么是向量数据库

向量数据库是用于高效计算和管理大量向量数据的解决方案。向量数据库是一种专门用于存储和检索向量数据(embedding)的数据库系统。它与传统的基于关系模型的数据库不同,它主要关注的是向量数据的特性和相似性。

在向量数据库中,数据被表示为向量形式,每个向量代表一个数据项。这些向量可以是数字、文本、图像或其他类型的数据。向量数据库使用高效的索引和查询算法来加速向量数据的存储和检索过程。

2. 向量数据库的原理及核心优势

向量数据库中的数据以向量作为基本单位,对向量进行存储、处理及检索。向量数据库通过计算与目标向量的余弦距离、点积等获取与目标向量的相似度。当处理大量甚至海量的向量数据时,向量数据库索引和查询算法的效率明显高于传统数据库。

3. 主流的向量数据库

  • Chroma:是一个轻量级向量数据库,拥有丰富的功能和简单的 API,具有简单、易用、轻量的优点,但功能相对简单且不支持GPU加速,适合初学者使用。
  • Weaviate:是一个开源向量数据库。除了支持相似度搜索和最大边际相关性(MMR,Maximal Marginal Relevance)搜索外还可以支持结合多种搜索算法(基于词法搜索、向量搜索)的混合搜索,从而搜索提高结果的相关性和准确性。
  • Qdrant:Qdrant使用 Rust 语言开发,有极高的检索效率和RPS(Requests Per Second),支持本地运行、部署在本地服务器及Qdrant云三种部署模式。且可以通过为页面内容和元数据制定不同的键来复用数据。

使用Embedding API

一、使用OpenAI API

GPT有封装好的接口,我们简单封装即可。目前GPT embedding mode有三种,性能如下所示: |模型 | 每美元页数 | MTEB得分 | MIRACL得分| | --- | --- | --- | --- | |text-embedding-3-large|9,615|54.9|64.6| |text-embedding-3-small|62,500|62.3|44.0| |text-embedding-ada-002|12,500|61.0|31.4|

  • MTEB得分为embedding model分类、聚类、配对等八个任务的平均得分。
  • MIRACL得分为embedding model在检索任务上的平均得分。

从以上三个embedding model我们可以看出text-embedding-3-large有最好的性能和最贵的价格,当我们搭建的应用需要更好的表现且成本充足的情况下可以使用;text-embedding-3-small有着较好的性能跟价格,当我们预算有限时可以选择该模型;而text-embedding-ada-002是OpenAI上一代的模型,无论在性能还是价格都不如及前两者,因此不推荐使用。

import os
from openai import OpenAI
from dotenv import load_dotenv, find_dotenv

os.environ["OPENAI_API_BASE"] = "https://oa.api2d.net/"
# 读取本地/项目的环境变量。
# find_dotenv()寻找并定位.env文件的路径
# load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中  
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())

# 如果你需要通过代理端口访问,你需要如下配置
# os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
# os.environ["HTTP_PROXY"] = 'http://127.0.0.1:7890'

def openai_embedding(text: str, model: str=None):
    # 获取环境变量 OPENAI_API_KEY
    api_key=os.environ['OPENAI_API_KEY']
    client = OpenAI(api_key=api_key)

    # embedding model:'text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002'
    if model == None:
        model="text-embedding-3-small"

    response = client.embeddings.create(
        input=text,
        model=model
    )
    return response

response = openai_embedding(text='要生成 embedding 的输入文本,字符串形式。')

image-20240426170838881

API返回的数据为json格式,除object向量类型外还有存放数据的data、embedding model 型号model以及本次 token 使用情况usage等数据,具体如下所示:

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [
        -0.006929283495992422,
        ... (省略)
        -4.547132266452536e-05,
      ],
    }
  ],
  "model": "text-embedding-3-small",
  "usage": {
    "prompt_tokens": 5,
    "total_tokens": 5
  }
}

我们可以调用response的object来获取embedding的类型。

print(f'返回的embedding类型为:{response.object}')
返回的embedding类型为:listCopy to clipboardErrorCopied

embedding存放在data中,我们可以查看embedding的长度及生成的embedding。

print(f'embedding长度为:{len(response.data[0].embedding)}')
print(f'embedding(前10)为:{response.data[0].embedding[:10]}')
embedding长度为:1536
embedding(前10)为:[0.03884002938866615, 0.013516489416360855, -0.0024250170681625605, -0.01655769906938076, 0.024130908772349358, -0.017382603138685226, 0.04206013306975365, 0.011498954147100449, -0.028245486319065094, -0.00674333656206727]

image-20240426170954119

我们也可以查看此次embedding的模型及token使用情况。

print(f'本次embedding model为:{response.model}')
print(f'本次token使用情况为:{response.usage}')Copy to clipboardErrorCopied
本次embedding model为:text-embedding-3-small
本次token使用情况为:Usage(prompt_tokens=12, total_tokens=12)

image-20240426171004519

二、使用文心千帆API

Embedding-V1是基于百度文心大模型技术的文本表示模型,Access token为调用接口的凭证,使用Embedding-V1时应先凭API Key、Secret Key获取Access token,再通过Access token调用接口来embedding text。同时千帆大模型平台还支持bge-large-zh等embedding model。

PS_1:调用需要用到Embedding-V1付费模型,需要在"计费管理"中开通该模型计费(作者写稿时,注册送20元代金券,可以直接抵用模型调用费用)

image-20240422224237574

PS_2:原版缺少了获取env配置添加到环境变量,以及依赖包,下面代码已补充!

import requests
import json
import os
from dotenv import load_dotenv, find_dotenv

# 读取本地/项目的环境变量。

# find_dotenv() 寻找并定位 .env 文件的路径
# load_dotenv() 读取该 .env 文件,并将其中的环境变量加载到当前的运行环境中  
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())
def wenxin_embedding(text: str):
    # 获取环境变量 wenxin_api_key、wenxin_secret_key
    api_key = os.environ['QIANFAN_AK']
    secret_key = os.environ['QIANFAN_SK']

    # 使用API Key、Secret Key向https://aip.baidubce.com/oauth/2.0/token 获取Access token
    url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={0}&client_secret={1}".format(api_key, secret_key)
    payload = json.dumps("")
    headers = {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
    response = requests.request("POST", url, headers=headers, data=payload)

    # 通过获取的Access token 来embedding text
    url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/embedding-v1?access_token=" + str(response.json().get("access_token"))
    input = []
    input.append(text)
    payload = json.dumps({
        "input": input
    })
    headers = {
        'Content-Type': 'application/json'
    }

    response = requests.request("POST", url, headers=headers, data=payload)

    return json.loads(response.text)
# text应为List(string)
text = "要生成 embedding 的输入文本,字符串形式。"
response = wenxin_embedding(text=text)

Embedding-V1每次embedding除了有单独的id外,还有时间戳记录embedding的时间。

print('本次embedding id为:{}'.format(response['id']))
print('本次embedding产生时间戳为:{}'.format(response['created']))Copy to clipboardErrorCopied
本次embedding id为:as-hvbgfuk29u
本次embedding产生时间戳为:1711435238Copy to clipboardErrorCopied

同样的我们也可以从response中获取embedding的类型和embedding。image-20240422224550921

搭建并使用向量数据库

本文对应源代码在此处,如需复现可下载运行源代码。

一、前序配置

本节重点为搭建并使用向量数据库,因此读取数据后我们省去数据处理的环节直入主题,数据清洗等步骤可以参考第三节

import os
from dotenv import load_dotenv, find_dotenv

# 读取本地/项目的环境变量。
# find_dotenv()寻找并定位.env文件的路径
# load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中  
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())

# 如果你需要通过代理端口访问,你需要如下配置
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'
os.environ["HTTP_PROXY"] = 'http://127.0.0.1:7890'

# 获取folder_path下所有文件路径,储存在file_paths里
file_paths = []
folder_path = '../../data_base/knowledge_db'
for root, dirs, files in os.walk(folder_path):
    for file in files:
        file_path = os.path.join(root, file)
        file_paths.append(file_path)
print(file_paths[:3])
['./data_base/knowledge_db/.DS_Store', './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', './data_base/knowledge_db/prompt_engineering/2. 提示原则 Guidelines.md']
from langchain.document_loaders.pdf import PyMuPDFLoader
from langchain.document_loaders.markdown import UnstructuredMarkdownLoader

# 遍历文件路径并把实例化的loader存放在loaders里
loaders = []

for file_path in file_paths:

    file_type = file_path.split('.')[-1]
    if file_type == 'pdf':
        loaders.append(PyMuPDFLoader(file_path))
    elif file_type == 'md':
        loaders.append(UnstructuredMarkdownLoader(file_path))
# 下载文件并存储到text
texts = []

for loader in loaders: texts.extend(loader.load())

载入后的变量类型为langchain_core.documents.base.Document, 文档变量类型同样包含两个属性

  • page_content 包含该文档的内容。
  • meta_data 为文档相关的描述性数据。
text = texts[1]
print(f"每一个元素的类型:{type(text)}.", 
    f"该文档的描述性数据:{text.metadata}", 
    f"查看该文档的内容:\n{text.page_content[0:]}", 
    sep="\n------\n")
每一个元素的类型:.
------
该文档的描述性数据:{'source': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'file_path': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'page': 1, 'total_pages': 196, 'format': 'PDF 1.5', 'title': '', 'author': '', 'subject': '', 'keywords': '', 'creator': 'LaTeX with hyperref', 'producer': 'xdvipdfmx (20200315)', 'creationDate': "D:20230303170709-00'00'", 'modDate': '', 'trapped': ''}
------
查看该文档的内容:
前言
“周志华老师的《机器学习》
(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读
者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推
导细节的读者来说可能“不太友好”
,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充
具体的推导细节。
”
读到这里,大家可能会疑问为啥前面这段话加了引号,因为这只是我们最初的遐想,后来我们了解到,周
老师之所以省去这些推导细节的真实原因是,他本尊认为“理工科数学基础扎实点的大二下学生应该对西瓜书
中的推导细节无困难吧,要点在书里都有了,略去的细节应能脑补或做练习”
。所以...... 本南瓜书只能算是我
等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二
下学生”
。
使用说明
• 南瓜书的所有内容都是以西瓜书的内容为前置知识进行表述的,所以南瓜书的最佳使用方法是以西瓜书
为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;
• 对于初学机器学习的小白,西瓜书第1 章和第2 章的公式强烈不建议深究,简单过一下即可,等你学得
有点飘的时候再回来啃都来得及;
• 每个公式的解析和推导我们都力(zhi) 争(neng) 以本科数学基础的视角进行讲解,所以超纲的数学知识
我们通常都会以附录和参考文献的形式给出,感兴趣的同学可以继续沿着我们给的资料进行深入学习;
• 若南瓜书里没有你想要查阅的公式,
或者你发现南瓜书哪个地方有错误,
请毫不犹豫地去我们GitHub 的
Issues(地址:https://github.com/datawhalechina/pumpkin-book/issues)进行反馈,在对应版块
提交你希望补充的公式编号或者勘误信息,我们通常会在24 小时以内给您回复,超过24 小时未回复的
话可以微信联系我们(微信号:at-Sm1les)
;
配套视频教程:https://www.bilibili.com/video/BV1Mh411e7VU
在线阅读地址:https://datawhalechina.github.io/pumpkin-book(仅供第1 版)
最新版PDF 获取地址:https://github.com/datawhalechina/pumpkin-book/releases
编委会
主编:Sm1les、archwalker、jbb0523
编委:juxiao、Majingmin、MrBigFan、shanry、Ye980226
封面设计:构思-Sm1les、创作-林王茂盛
致谢
特别感谢awyd234、
feijuan、
Ggmatch、
Heitao5200、
huaqing89、
LongJH、
LilRachel、
LeoLRH、
Nono17、
spareribs、sunchaothu、StevenLzq 在最早期的时候对南瓜书所做的贡献。
扫描下方二维码,然后回复关键词“南瓜书”
,即可加入“南瓜书读者交流群”
版权声明
本作品采用知识共享署名-非商业性使用-相同方式共享4.0 国际许可协议进行许可。
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 切分文档
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=50)

split_docs = text_splitter.split_documents(texts)

二、构建Chroma向量库

Langchain 集成了超过 30 个不同的向量存储库。我们选择 Chroma 是因为它轻量级且数据存储在内存中,这使得它非常容易启动和开始使用。

LangChain 可以直接使用 OpenAI 和百度千帆的 Embedding,同时,我们也可以针对其不支持的 Embedding API 进行自定义,例如,我们可以基于 LangChain 提供的接口,封装一个 zhupuai_embedding,来将智谱的 Embedding API 接入到 LangChain 中。在本章的附LangChain自定义Embedding封装讲解中,我们以智谱 Embedding API 为例,介绍了如何将其他 Embedding API 封装到 LangChain
中,欢迎感兴趣的读者阅读。

注:如果你使用智谱 API,你可以参考讲解内容实现封装代码,也可以直接使用我们已经封装好的代码zhipuai_embedding.py,将该代码同样下载到本 Notebook 的同级目录,就可以直接导入我们封装的函数。在下面的代码 Cell 中,我们默认使用了智谱的 Embedding,将其他两种 Embedding 使用代码以注释的方法呈现,如果你使用的是百度 API 或者 OpenAI API,可以根据情况来使用下方 Cell 中的代码。

# 使用 OpenAI Embedding
# from langchain.embeddings.openai import OpenAIEmbeddings
# 使用百度千帆 Embedding
# from langchain.embeddings.baidu_qianfan_endpoint import QianfanEmbeddingsEndpoint
# 使用我们自己封装的智谱 Embedding,需要将封装代码下载到本地使用
from zhipuai_embedding import ZhipuAIEmbeddings

# 定义 Embeddings
# embedding = OpenAIEmbeddings() 
embedding = ZhipuAIEmbeddings()
# embedding = QianfanEmbeddingsEndpoint()

# 定义持久化路径
persist_directory = '../../data_base/vector_db/chroma'
!rm -rf '../../data_base/vector_db/chroma'  # 删除旧的数据库文件(如果文件夹中有文件的话),windows电脑请手动删除
from langchain.vectorstores.chroma import Chroma

vectordb = Chroma.from_documents(
    documents=split_docs[:20], # 为了速度,只选择前 20 个切分的 doc 进行生成;使用千帆时因QPS限制,建议选择前 5 个doc
    embedding=embedding,
    persist_directory=persist_directory  # 允许我们将persist_directory目录保存到磁盘上
)

在此之后,我们要确保通过运行 vectordb.persist 来持久化向量数据库,以便我们在未来的课程中使用。

让我们保存它,以便以后使用!

vectordb.persist()
print(f"向量库中存储的数量:{vectordb._collection.count()}")
向量库中存储的数量:20

三、向量检索

3.1 相似度检索

Chroma的相似度搜索使用的是余弦距离,即:
$$
similarity = cos(A, B) = \frac{A \cdot B}{\parallel A \parallel \parallel B \parallel} = \frac{\sum_1^n a_i b_i}{\sqrt{\sum_1^n a_i^2}\sqrt{\sum_1^n b_i^2}}
$$
其中$a_i$、$b_i$分别是向量$A$、$B$的分量。

当你需要数据库返回严谨的按余弦相似度排序的结果时可以使用similarity_search函数。

question="什么是大语言模型"
sim_docs = vectordb.similarity_search(question,k=3)
print(f"检索到的内容数:{len(sim_docs)}")
检索到的内容数:3
for i, sim_doc in enumerate(sim_docs):
    print(f"检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
检索到的第0个内容: 
开发大模型相关应用时请务必铭记:

虚假知识:模型偶尔会生成一些看似真实实则编造的知识

在开发与应用语言模型时,需要注意它们可能生成虚假信息的风险。尽管模型经过大规模预训练,掌握了丰富知识,但它实际上并没有完全记住所见的信息,难以准确判断自己的知识边界,可能做出错误推断。若让语言模型描述一个不存在的产品,它可能会自行构造出似是而非的细节。这被称为“幻觉”(Hallucination),是语言模型
--------------
检索到的第1个内容: 
例如,在以下的样例中,我们先给了一个祖孙对话样例,然后要求模型用同样的隐喻风格回答关于“韧性”的问题。这就是一个少样本样例,它能帮助模型快速抓住我们要的语调和风格。

利用少样本样例,我们可以轻松“预热”语言模型,让它为新的任务做好准备。这是一个让模型快速上手新任务的有效策略。

```python
prompt = f"""
您的任务是以一致的风格回答问题。

<孩子>: 请教我何为耐心。

<
--------------
检索到的第2个内容: 
第二章 提示原则

如何去使用 Prompt,以充分发挥 LLM 的性能?首先我们需要知道设计 Prompt 的原则,它们是每一个开发者设计 Prompt 所必须知道的基础概念。本章讨论了设计高效 Prompt 的两个关键原则:编写清晰、具体的指令和给予模型充足思考时间。掌握这两点,对创建可靠的语言模型交互尤为重要。

首先,Prompt 需要清晰明确地表达需求,提供充足上下文,使语言模型准确理解
--------------</code></pre>
<h3>3.2 MMR检索</h3>
<p>如果只考虑检索出内容的相关性会导致内容过于单一,可能丢失重要信息。</p>
<p>最大边际相关性 (<code>MMR, Maximum marginal relevance</code>) 可以帮助我们在保持相关性的同时,增加内容的丰富度。</p>
<p>核心思想是在已经选择了一个相关性高的文档之后,再选择一个与已选文档相关性较低但是信息丰富的文档。这样可以在保持相关性的同时,增加内容的多样性,避免过于单一的结果。</p>
<pre><code class="language-python">mmr_docs = vectordb.max_marginal_relevance_search(question,k=3)</code></pre>
<pre><code class="language-python">for i, sim_doc in enumerate(mmr_docs):
    print(f"MMR 检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")</code></pre>
<pre><code>MMR 检索到的第0个内容: 
开发大模型相关应用时请务必铭记:

虚假知识:模型偶尔会生成一些看似真实实则编造的知识

在开发与应用语言模型时,需要注意它们可能生成虚假信息的风险。尽管模型经过大规模预训练,掌握了丰富知识,但它实际上并没有完全记住所见的信息,难以准确判断自己的知识边界,可能做出错误推断。若让语言模型描述一个不存在的产品,它可能会自行构造出似是而非的细节。这被称为“幻觉”(Hallucination),是语言模型
--------------
MMR 检索到的第1个内容: 
相反,我们应通过 Prompt 指引语言模型进行深入思考。可以要求其先列出对问题的各种看法,说明推理依据,然后再得出最终结论。在 Prompt 中添加逐步推理的要求,能让语言模型投入更多时间逻辑思维,输出结果也将更可靠准确。

综上所述,给予语言模型充足的推理时间,是 Prompt Engineering 中一个非常重要的设计原则。这将大大提高语言模型处理复杂问题的效果,也是构建高质量 Promp
--------------
MMR 检索到的第2个内容: 
```python
text_1 = f"""
Making a cup of tea is easy! First, you need to get some \ 
water boiling. While that's happening, \ 
grab a cup and put a tea bag in it. Once the water is \ 
hot enough, just 
--------------
是一名喜欢每天折腾的咸鱼! 也是一名半退役的算竞摸鱼选手,参与过icpc,天梯赛,蓝桥等比赛. --------------------------------------------------- 百度 飞桨领航团-团长 Datawhale -专业助教 上海人工智能实验室 书生·浦语实战营- 助教 --------------------------------------------------- 认证类: 华为 Harmony OS应用开发者高级认证, NISP 一级认证, H3C NE-RS网络工程师认证 --------------------------------------------------- 荣获奖项荣誉: 第十八届“挑战杯”全国大学生课外学术科技作品竞赛 “揭榜挂帅”专项赛-全国特等奖、 “美亚杯”第八届中国电子取证大赛 三等奖、 “蓝桥杯”国优、 中国高校计算机大赛-团体程序天梯赛 省高校一等奖、 “蓝桥杯”省一等奖、 H3C新华三杯 省三等奖、 中国移动“梧桐杯”大数据创新大赛 省三等奖、 百度 飞桨领航团 金牌团长
最后更新于 2024-04-26