来 Azure 学习 OpenAI 四 - 用 Embedding 赋能 GPT

2023-10-29

大家好,我是学生大使 Jambo。在我们前一篇文章中,我们介绍了 OpenAI 模型的调用。今天,我将为大家介绍 Embedding 的使用。

嵌入是什么

嵌入(Embedding )是一种将高维数据映射到低维空间的方法。嵌入可以将高维数据可视化,也可以用于聚类、分类等任务。嵌入可以是线性的,也可以是非线性的。在深度学习中,我们通常使用非线性嵌入。非线性嵌入通常使用神经网络实现。

上面这句话对于没接触过 NLP(自然语言处理)的同学来说可能有点抽象。你可以理解为通过嵌入,可以将文字信息压缩、编码成向量(或者不准确的称之为数组),而这个向量包含了这段文字的语义。我们可以将这个技术用于搜索引擎、推荐系统等等。

调用 Embedding 模型

与前篇一样,我们需要先部署模型。这里我们使用 text-embedding-ada-002

1

然后安装 openai 包。用以下命令安装,会将 numpy、pandas 等库一并安装。

pip install openai[datalib]

接下来导入 openai,并做一些初始化工作。

import openai

openai.api_key = "REPLACE_WITH_YOUR_API_KEY_HERE"    # Azure 的密钥
openai.api_base = "REPLACE_WITH_YOUR_ENDPOINT_HERE"  # Azure 的终结点
openai.api_type = "azure" 
openai.api_version = "2023-03-15-preview" # API 版本,未来可能会变
model = "text-embedding-ada-002"  # 模型的部署名
embedding = openai.Embedding.create(
    input="苹果", engine="text-embedding-ada-002"
)
print(embedding1)
{
  "data": [
    {
      "embedding": [
        0.011903401464223862,
        -0.023080304265022278,
        -0.0015027695335447788,
        ...
    ],
      "index": 0,
      "object": "embedding"
    }
  ],
  "model": "ada",
  "object": "list",
  "usage": {
    "prompt_tokens": 3,
    "total_tokens": 3
  }
}

其中 embedding 就是 “苹果” 所对应的向量。

计算向量相似度

在我们将文字转换成向量之后,我们讨论两句话的相似度,其实就是问它们所对应向量的相似度。通常我们使用余弦相似度来衡量两个向量的相似度。

余弦相似度是计算两个向量夹角角度的 cos ⁡ \cos cos 值,取值范围在 -1 和 1 之间。如果两个向量的方向完全一致,那么它们的余弦相似度为 1;如果两个向量的方向完全相反,那么它们的余弦相似度为 -1;如果两向量是垂直(正交)的,那么它们的余弦相似度为 0。其公式如下:

cos ⁡ ( θ ) = A ⃗ ⋅ B ⃗ ∥ A ⃗ ∥ ∥ B ⃗ ∥ \cos(\theta) = \frac{\vec A \cdot \vec B}{\|\vec A\| \|\vec B\|} cos(θ)=A ∥∥B A B

A ⃗ \vec A A B ⃗ \vec B B 分别是两个向量, θ \theta θ 是两个向量的夹角。而 ∥ A ⃗ ∥ \|\vec A\| A ∥ B ⃗ ∥ \|\vec B\| B 分别是向量 A ⃗ \vec A A B ⃗ \vec B B 的长度(模长)。因为 OpenAI 的 Embedding 模型返回的是单位向量,即向量的长度为 1,所以我们不需要计算模长,而它们的夹角就是两个向量的点积。

cos ⁡ ( θ ) = A ⃗ ⋅ B ⃗ 1 ⋅ 1 = A ⃗ ⋅ B ⃗ \cos(\theta) = \frac{\vec A \cdot \vec B}{1 \cdot 1} = \vec A \cdot \vec B cos(θ)=11A B =A B

有的人可能会疑惑为什么不用欧式距离来计算。在这种向量长度都为 1 的情况下,欧式距离和余弦相似度其实是等价的,它们之间是可以互相转换的。

在 Python 中,我们可以使用 numpy 库来计算两个数列的余弦相似度:

import numpy as np

# 计算两个向量的余弦相似度
def cosine_similarity(a, b):
    return np.dot(a, b)  # 计算点积

下面是个简单的例子:

embedding1 = openai.Embedding.create(
    input="苹果", engine="text-embedding-ada-002"
)["data"][0]["embedding"]
embedding2 = openai.Embedding.create(
    input="apple", engine="text-embedding-ada-002"
)["data"][0]["embedding"]
embedding3 = openai.Embedding.create(
    input="鞋子", engine="text-embedding-ada-002"
)["data"][0]["embedding"]

print(cosine_similarity(embedding1, embedding2))
print(cosine_similarity(embedding1, embedding3))
print(cosine_similarity(embedding2, embedding3))
0.8823086919469535
0.8256366789720779
0.7738048005367909

用 Embedding 加强 GPT 的能力

GPT模型非常强大,它能够根据训练的数据回答问题。但是,它只能回答那些在训练时获取到的知识,对于训练时获取不到的知识,它一无所知。所以对于类似“明天天气如何”,或者企业内部的一些专业知识,GPT模型就无能为力了。

天气等及时性较强的内容,我们可以通过搜索引擎获得相关的信息。而像新闻报道或是文档这类内容,通常篇幅较长,可 GPT 模型能处理的文字有限,因此我们需要将其分割成多个段落,然后找到其中最相关的段落,再将其与问题一起传入 GPT 模型中。而如何找到最相关的段落呢?这就需要用到 Embedding 模型了。将分割后的段落传入 Embedding 模型,得到每个段落的向量,然后计算与问题的相似度,最后找到最相似的段落。特别是本地文档,我们可以事先将其转换成向量,然后保存下来,这样就可以快速地找到最相关的段落。

下面我用 Codon 的文档作为资料来源,并让 GPT 可以根据文档里的内容进行回答。

预处理文档

我用下面的代码将文档分割成多个段落,并且保证每段字数不超过 2048:

import os
import pandas

MAX_LEN = 2048


def split_text(text, max_length=2048):
    paragraphs = text.split("\n")
    result = []
    current_paragraph = ""
    for paragraph in paragraphs:
        if len(current_paragraph) + len(paragraph) > max_length:
            result.append(current_paragraph)
            current_paragraph = paragraph
        else:
            current_paragraph += "\n" + paragraph
    if current_paragraph:
        result.append(current_paragraph)
    return result


def find_md_files(directory):
    result = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(".md"):
                result.append(os.path.join(root, file))
    return result


if __name__ == "__main__":
    df = pandas.DataFrame(columns=["file", "content"])
    for file in find_md_files("."):
        with open(file) as f:
            text = f.read()
        for c in split_text(text, MAX_LEN):
            df.loc[len(df)] = [file, c]

    df.to_csv("output.csv", index=False)

然后将这些段落传入 Embedding 模型,得到每个段落的向量。这里我没有使用异步,这是为了避免触发 API 的速率限制。为了演示方便,我只是将数据保存在 csv 文件中,实际使用时,我们可以将数据保存到 Pinecone,Milvus 等向量数据库中。

import openai
import pandas

openai.api_key = ""
openai.api_base = ""
openai.api_type = "azure"
openai.api_version = "2023-03-15-preview"
model = "text-embedding-ada-002"


def get_embedding(text):
    response = openai.Embedding.create(input=text, engine="text-embedding-ada-002")
    embedding = response["data"][0]["embedding"]
    assert len(embedding) == 1536
    return embedding


def main():
    df = pandas.read_csv("output.csv")
    embeddings = [get_embedding(text) for text in df["content"]]
    df["embedding"] = embeddings
    df.to_csv("docs.csv", index=False)


if __name__ == "__main__":
    import time

    star = time.time()
    main()
    print(f"Time taken: {time.time() - star}")

制作 Prompt

为了让 GPT 只回答文档里的内容,我们首先要将这件事告诉 GPT,并且我们还需要传入与问题相关的段落。

prompt_prefix = """
你是一个客服,回答用户关于文档的问题。
仅使用以下资料提供的事实进行回答。 如果下面没有足够的信息,就说你不知道。不要生成不使用以下资料的答案。

资料:
{sources}
"""

有时我们提问的问题可能会与先前的对话相关,因此为了更好的匹配文档段落,我们将对话历史和新的问题告诉 GPT,并让它帮我们生成一个查询语句。

summary_prompt_template = """
以上是到目前为止的对话记录,下面我将提出一个新问题,需要通过在知识库中搜索相关的条目来回答问题。根据以上的对话记录和下面的新问题,生成一个英文的查询语句,用于在知识库中搜索相关的条目。你只需要回答查询的语句,不用加其他任何内容。

新问题:
{question}
"""

生成查询语句

我们首先先定义一些帮助函数:

def cos_sim(a, b):
    return np.dot(a, b)


def get_chat_answer(messages: dict, max_token=1024):
    return openai.ChatCompletion.create(
        engine=chat_model,
        messages=messages,
        temperature=0.7,
        max_tokens=max_token,
    )["choices"][0]["message"]


def get_embedding(text):
    return openai.Embedding.create(
        engine=embed_model,
        input=text,
    )["data"][
        0
    ]["embedding"]


docs = pd.read_csv("docs.csv", converters={"embedding": eval})
pd.set_option("display.max_colwidth", None) # 显示完整的文本
history = []

history 是用来储存对话历史。在下面的代码中如果 history 为空,那么我们就直接使用用户的输入作为查询语句,否则我们就使用 GPT 生成的查询语句。要注意的是,我是把历史记录和生成查询的请求拼在一起输入给模型的,没有把请求放到 history 中。

user_input = ""

if len(history) == 0:
    query = user_input
else:
    query = get_chat_answer(
        history
        + [
            {
                "role": "user",
                "content": summary_prompt_template.format(question=user_input),
            }
        ],
        max_token=32,
    )["content"]

print(f"Searching: {query}")

搜索最相关的段落

我用 pandas 将先前保存好的段落和对应向量读取出来,然后计算查询语句和每个段落的相似度,最后拿到最相似的段落。当然,如果相似度不够高,我们就告诉 GPT “no information”。

docs = pd.read_csv("data.csv", converters={"embedding": eval})

query_embedding = get_embedding(query)
dot_products = np.dot(np.stack(docs["embedding"].values), query_embedding)
top_index = np.argsort(dot_products)[-1:]
top_content = (
    docs.iloc[top_index]["content"].to_string(index=False)
    if dot_products[top_index] > 0.8
    else "no information"
)

生成回答

现在我们获取到了相关的信息,接下来我们将相关的信息和问题一起传入 GPT,让它生成回答。这里因为我用的是 GPT-3,他对 system 的内容没有那么看重,所以我用了 user 的身份来传入最开始我们设定的 prompt,并手动编写了一个回答来强化 GPT 对于我们的提示的理解。这句话和上面生成查询语句的请求一样,并没有放到 history 中。但我们有将 GPT 的回答放进去。

history.append({"role": "user", "content": user_input})

massage = [
    {"role": "user", "content": prompt_prefix.format(sources=top_content)},
    {
        "role": "assistant",
        "content": "好的,我只会根据以上提供的资料提供的内容回答问题,我不会回答不使用资源的内容。",
    },
] + history
res = get_chat_answer(massage)
print(res["content"])
history.append(res)
print("-" * 50, end="\n\n")

接下来我们可以来尝试一下,我先输入一个问题:“Python Codon 是什么?”

[Searching: Python Codon 是什么?]
Codon 是一个高性能的 Python 编译器,它可以将 Python 代码编译成本地机器代码,而不需要任何运行时开销。Codon 的性能通常与 C/C++ 相当,而且还支持本地多线程,可以实现更高的加速比。此外,Codon 还可以通过插件基础架构进行扩展,可以轻松地集成新的库、编译器优化和关键字等。
--------------------------------------------------

2

作为对比,我们来看看 ChatGPT 的回答:

3

可见在 ChatGPT 的训练集中,并没有 Codon 相关的信息,因此他无法给出我们想要的答案。而我们通过 Embedding 的方式,找到 Codon 相关的资料,然后将其传入 GPT,让 GPT 生成答案,这样就可以得到我们想要的答案了。

当然,在实际的应用中,代码绝对不会这么简单,我们还需要考虑很多问题,比如如何储存和更新知识库,如何处理对话的输入和输出,如何管理对话的历史等等。但是,这些都不是我们今天要讨论的问题,我们今天只是想要讨论一下 Embedding 与 GPT 的结合,以及如何将文字转换为 Embedding。

而 Embedding 的用法也不只是这一种。得益于向量的可测距性,我们还可以将其用于聚类、分类、推荐,甚至是可视化等等,这些都是我们今天没有讨论的内容。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

来 Azure 学习 OpenAI 四 - 用 Embedding 赋能 GPT 的相关文章

  • 使用 SqlBulkCopy 和 Azure 并行批量插入

    我在云上有一个带有 sql azure 数据库的 azure 应用程序 我有一个辅助角色 需要对文件 最多约 3000 万行 进行解析 处理 因此我无法直接使用 BCP 或 SSIS 我目前正在使用 SqlBulkCopy 但这似乎太慢了
  • 根据字符串列表查找第一个可用的序列号?

    给定一个字符串列表 例如 apple01 apple02 and apple04 banana02 cherry01 你会如何想出first可用序列号each类型 也就是说 apple03如果我问apple or banana01如果我问b
  • Azure:用户登录失败

    我做了什么 我在 Azure 中添加了资源组 添加了带有先前资源组的 Web 服务 添加了带有先前资源组的 SQL Server 数据库 从此数据库检索生成的连接字符串并在此处提供用户名和密码 将此连接字符串添加到Web服务中的应用程序设置
  • Azure:Powershell:Set-AzureRmWebApp:如何设置“alwaysOn”属性

    我正在运行 Powershell 5 并尝试使用操作我的 Azure WebApp 对象设置 AzureRmWebApp 而不是 Set AzureResource 设置 Web 应用程序的 Always On 属性 我的基本代码片段从一个
  • DocumentDb GUID 索引精度

    假设我们的文档中有一个非唯一的 GUID UUID 值 id 123456 Key 117dfd49 a71d 413b a9b1 841e88db06e8 Name Kaapstad 我们只想通过平等来查询这一点 不需要范围或 order
  • Kusto 无法将值投影到用户定义的函数中

    我在我们的域中有一个查询 但无法使其正常工作 我使用数据表来模拟我的问题 我正在尝试在用户定义的函数中使用投影值 this works let f a int datatable b string d int 2015 12 31 1 20
  • Azure - 查询 2 亿个实体

    我需要查询 Windows Azure 中包含 2 亿个实体的存储 理想情况下 我希望使用表服务而不是 SQL Azure 来完成此任务 用例是这样的 包含新实体的 POST 将从面向 Web 的 API 传入 我们必须查询大约 2 亿个实
  • 如何获取Azure帐户租户ID?

    我的问题是 是否可以在不使用 powershell 命令的情况下获取 azure Active Directory 租户 id 我找到了这两篇博客 在这个帮助下 我已经能够从 powershell 获取租户 ID 和订阅 ID 这是找回租客
  • 两个应用程序的相同表、通知中心和服务器

    我们被微软放弃 Silverlight 并专注于 UWP 所困扰 这最终导致我们不得不开发两个双重应用程序 UWP 和 Silverlight 这使得我们有两个无法捆绑 打包在一起的应用程序 因此 我们决定将这些应用程序作为配套应用程序 这
  • “Connect-MsolService”未被识别为 cmdlet 的名称

    PSCommand commandToRun new PSCommand commandToRun AddCommand Connect MsolService commandToRun AddParameter Credential ne
  • Azure AD 注销 URL 未重定向

    我正在构建以下 URL https login microsoftonline com
  • 使用天蓝色错误“找不到资源”进行情绪分析

    我创建了一个 python 程序 它接受字符串作为输入并对其执行情感分析 我已经按照文档中所述创建了环境变量 并重新启动了 cmd 和 Visual Studio 但仍然出现以下错误 遇到异常 操作返回无效状态代码 未找到资源 python
  • 直接从浏览器将文件上传到 Azure Blob 存储?

    是否可以创建一个 html 表单以允许 Web 用户直接将文件上传到 azure blob 存储 而无需使用其他服务器作为中介 S3 和 GAW blobstore 都允许这样做 但我找不到任何对 azure blob 存储的支持 编辑 2
  • 在控制台应用程序中使用 SendGrid

    是否可以在 C 的控制台应用程序中使用发送网格 我的代码不起作用 我真的不知道为什么 你可以帮帮我吗 using System using System Net using System Net Mail using SendGrid na
  • asp.net core / kestrel中的线程管理

    我正在解决我们已迁移到 asp net core 2 0 的 asp net 应用程序的性能 可扩展性问题 我们的应用程序作为应用程序服务托管在 azure 上 并且在任何中等流量的情况下都很容易崩溃 让我困惑的一件事是如何处理多个并发请求
  • 从另一台设备访问 Azure 模拟器

    我有两个不同的项目 Windows Phone 8 应用程序 我正在真实的物理开发设备上运行 Azure 云服务 其中包含一个简单的 WebRole 端点 其中包含 ASP NET MVC WebAPI 我的目标很简单 使用从真实设备运行的
  • 无法在 Azure AD 的 access_token 中获取电子邮件声明

    我们已在 Azure 中为 SPA 配置了应用程序注册 用于身份验证代码流程 We have added email under optional claims as per below 清单文件配置如下 id
  • 无法将 TXT 记录设置为 Freenom 提供商中的域

    我想为分配给 Azure 中 WordPress 的域启用 SSL 我的域名是在 Freenom 中创建的 要完成该过程 我需要从 Azure 手动验证域 Azure 域验证 https i stack imgur com 4park jp
  • 使用服务总线触发器在 Azure 函数中参考配置中的主题和订阅名称

    我有一个带有服务总线主题触发器的 Azure 服务总线 我的功能看起来像这样 FunctionName SbListener public static async Task Run ServiceBusTrigger test topic
  • 如何在本地运行 Microsoft Azure DocumentDB?

    我使用 Mac 主要从事 Node js 项目 我想尝试 DocumentDB 将其与 MongoDB 进行比较 有没有办法运行 Azure DocumentDB 的本地实例 而无需经历设置真实帐户的麻烦 Thanks 自2016年11月1

随机推荐

  • Quartus中的unused pin设置

    在逻辑可编程器件中并不是每个引脚都会用到 对于没有用到的引脚 我们也可以通过设置Quartus II software gt assignments gt settings gt devices gt unused pins把它们设置为需要
  • GLSL中Uniform块

    Uniform块 当着色器程序变复杂时 用到的uniform变量数量也会上升 通常会在多个着色器程序中用到同一个uniform变量 由于uniform变量的位置是着色器链接的时候产生的 也就是glLinkProgram 的时候 因此它在应用
  • SQL新用户留存分析

    表 用户登录日期区间表 dws app user act rng 2020 09 04 前一天 其中9999 12 31代表计算当天也登录过 用户日活表 dws app trf agr user 2020 09 05 新一天 需求 求出用户
  • 【2022最新Java面试宝典】—— ZooKeeper面试题(28道含答案)

    目录 1 ZooKeeper 是什么 2 ZooKeeper 提供了什么 3 Zookeeper 文件系统 4 ZAB 协议 5 四种类型的数据节点 Znode 6 Zookeeper Watcher 机制 数据变更通知 7 客户端注册 W
  • sublime搭建C/C++编译环境(超完美的配置并配上内容详细讲解!!)

    sublime搭建C C 编译环境 超完美的配置并配上内容详细讲解
  • 证书服务 笔记

    1 www verisign com www ssl com www godaddy com www wosign com 2 转载于 https www cnblogs com emmagikyo p 5292820 html
  • Unexpected token o in JSON at position 1 报错原因及解决方法

    Unexpected token o in JSON at position 1 报错原因及解决方 问题描述 在做登录页面 实现登录功能 返回token值时 提示 Unexpected token o in JSON at position
  • Burst(突发)信号详解

    突发信号是一个根据字面意思非常难以理解的信号 为此头疼了好久 终于理解了什么是突发信号 突发是指在同一行中相邻的存储单元连续进行数据传输的方式 连续传输所涉及到存储单元 列 的数量就是突发长度 SDRAM 简称BL burst length
  • EQ均衡器原理

    做音乐最离不开的效果器是什么 相信大多数朋友都会回答 是EQ 不错 正是有了这个所谓 均衡 的效果器 我们的音乐才不会过载 乐器音色才会如此丰富 然而知道1加1等于2更要知道1加1为什么等于2 今天我把这个效果器扒光 从根本上来分析它的工作
  • my-innodb-heavy-4G.cnf配置文件详解

    client 客户端 port 3306 客户端连接端口 socket tmp mysql sock 客户端套接字文件的路径 mysqld port 3306 服务器监听端口 socket tmp mysql sock 服务器套接字文件的路
  • 做自动化测试可选择的工具有哪些?

    如今 作为一名软件测试工程师 几乎所有人都需要具备自动化测试相关的知识 并且懂得如何去利用工具 来为企业减少时间成本和错误成本 这是为什么呢 在以前 会测试人员一般都只需要扮演终端用户 会做手动测试用例并记录观察结果就足够了 但如今 一旦你
  • QT中connect函数中的lambda表达式关于局部变量的按值拷贝与按引用拷贝,以及mutable关键字

    1 当lambda表达式中使用 时 为按值拷贝作用范围的局部变量 默认不可修改按值传进来的拷贝 如图 不加mutable时报错 a不可改变 加mutable后可以改变a值 但是时修改的拷贝对象 connect上边的a值未修改 include
  • Spring Boot 3.0 (十四): Spring Boot 整合 Shiro安全框架

    官方暂时还没有适配 Spring Boot 3 X 这篇文章我们来学习如何使用 Spring Boot 集成 Apache Shiro 安全应该是互联网公司的一道生命线 几乎任何的公司都会涉及到这方面的需求 在 Java 领域一般有 Spr
  • wxc-progress使用

  • docker 安装gitlab

    https www cnblogs com zuxing articles 9329152 html
  • C语言中输出string方法c_str()用法

    今天突然用printf s str str 是一个string 的时候突然出现乱码 就查了一下为什么 任何时候只有自己敲代码的时候才能发现 可以用cout lt
  • 智慧节能照明系统的八大应用场景

    节能 作为社会经济发展的主旋律 在生产生活的方面都要积极实践 照明是首当其冲需要进行节能升级的 智慧节能照明系统解决方案 基于电力网络 以物联网 自动化控制 无线通讯等技术 结合智能化物联网设备 实现对照明系统的集中 定时 模式等多样化控制
  • xcode编译代码的时候 ld: library not found for -xxx 解决方法

    有时候在Xcode下面我们需要引入第三方类库 或者自己写的静态库 如果配置不正确 就会出现如下错误 ld library not found for lpcre clang error linker command failed with
  • Redis:实现全局唯一ID

    Redis 实现全局唯一ID 一 概述 二 实现 1 获取初始时间戳 2 生成全局ID 三 测试 为什么可以实现全局唯一 其他唯一ID策略 补充 countDownLatch 一 概述 全局ID生成器 是一种在 分布式系统下 用来生成全局唯
  • 来 Azure 学习 OpenAI 四 - 用 Embedding 赋能 GPT

    大家好 我是学生大使 Jambo 在我们前一篇文章中 我们介绍了 OpenAI 模型的调用 今天 我将为大家介绍 Embedding 的使用 嵌入是什么 嵌入 Embedding 是一种将高维数据映射到低维空间的方法 嵌入可以将高维数据可视