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

news2024/12/30 2:59:57

大家好,我是学生大使 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 的用法也不只是这一种。得益于向量的可测距性,我们还可以将其用于聚类、分类、推荐,甚至是可视化等等,这些都是我们今天没有讨论的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/553110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第一章 初识Python

1.1 课前必读 课程大纲 1.2 Python介绍 Python特点: 主流语言;用途广泛,号称万能语言;上手简单; Python用途: 数据挖掘(爬虫)和数据分析自动化脚本编写(软件测试人员使用…

尚硅谷周阳老师 SpringCloud第二季学习笔记

前言:首先感谢尚硅谷周阳老师的讲解,让我对springcloud有了很好的理解,周阳老师的讲课风格真的很喜欢,内容充实也很幽默,随口一说就是一个段子,我也算是周阳老师的忠实粉丝啦。 先说说课程总体内容 以下是…

[学习笔记] [机器学习] 6. [上]决策树算法(熵Entropy、信息增益(率)、基尼值(指数)、CART剪枝、特征工程特征提取、回归决策树)

视频链接数据集下载地址:无需下载 学习目标: 掌握决策树实现过程知道信息熵的公式以及作用知道信息增益、信息增益率和基尼指数的作用知道id3、c4.5、cart算法的区别了解cart剪枝的作用知道特征提取的作用应用DecisionTreeClassifier实现决策树分类 1…

开放原子训练营(第三季)inBuilder低代码开发实验室,低代码到底该长什么样

目录 前言: 一、什么是inBuilder低代码开发实验室 二、技术特征 2.1开放性 2.2开发语言无关性 2.3云原生 2.4模型工程化 2.5全栈模型刻画 2.6运行态定制 2.7仓库介绍 三、快速入门 四、实操案例 4.1定义数据源 4.2 设计页面 4.3发布调试 五、总结 前言&#xf…

Activiti实战——Springboot整合Activiti

目录 一、Activiti数据库表名说明 二、Spring boot整合activiti 1. 创建springboot项目 2. 引入activiti依赖及项目依赖 3. 配置数据源 (1)创建数据源配置文件 (2)配置文件 4. 配置Acitviti引擎 5. 启动项目 三、Activiti…

【MySQL 数据库】1、MySQL 的 DDL、DML、DQL 语句

目录 一、MySQL 应该掌握哪些知识点 ?二、数据库相关概念三、主流关系型数据库管理系统四、关系型数据库五、SQL 语句的分类六、DDL(1) 数据库操作(2) 表操作(3) 字段的数据类型(4) 创建员工表(5) 修改表结构(6) 删除某一张表 七、DML八、DQL(1) 员工表(2) distinct…

经典神经网络(4)Nin-Net及其在Fashion-MNIST数据集上的应用

经典神经网络(4)Nin-Net及其在Fashion-MNIST数据集上的应用 1 Nin-Net的简述 1.1 Nin-Net的概述 LeNet、AlexNet和VGG都有⼀个共同的设计模式:通过⼀系列的卷积层与汇聚层来提取空间结构特征;然后通过全连接层对特征的表征进⾏处理。AlexNet和VGG对Le…

线程池的创建与使用

void execute(Runnable run)方法处理Runnbale任务 Future<> submit(Callable<> task)方法处理Callable任务 void shutdown()结束线程池 List<\Runnable> shutdownNow()立即结束线程池&#xff0c;不管任务是否执行完毕 //创建线程池的一种方式 ExecutorServi…

基于WebApi实现ModbusTCP数据服务

在上位机开发过程中&#xff0c;有时候会遇到需要提供数据接口给MES或者其他系统&#xff0c;今天跟大家分享一下&#xff0c;如何在Winform等桌面应用程序中&#xff0c;开发WebApi接口&#xff0c;提供对外数据服务。 为了更好地演示应用场景&#xff0c;本案例以读取Modbus…

Leetcode 209. 长度最小的子数组——go语言实现

文章目录 一、题目描述二、代码实现方法一&#xff1a;暴力法解题思路代码实现复杂度分析 方法二&#xff1a;滑动窗口 双指针解题思路代码实现复杂度分析 方法三&#xff1a;前缀和 二分查找解题思路代码实现复杂度分析 一、题目描述 给定一个含有 n 个正整数的数组和一个正…

STM32 10个工程篇:1.IAP远程升级(四)

在前三篇博客中主要介绍了IAP远程升级的应用背景、下位机的实现原理、以及基于STM32CubeMX对STM32F103串口DMA的基本配置&#xff0c;第四篇博客主要想介绍Labview端上位机和下位机端的报文定义和通信等。 当笔者工作上刚接触到STM32 IAP升级的时候&#xff0c;实事求是地说存在…

【科普】电压和接地真的存在吗?如何测试?

经常在实验室干活的&#xff0c;难免不被电击过&#xff0c;尤其是在干燥的北方&#xff0c;“被电”是常有的事情&#xff0c;我记得有一次拿着射频线往仪表上拧的时候&#xff0c;遇到过一次严重的电火花&#xff0c;瞬间将仪表的射频口边缘烧出了一个疤&#xff0c;实验室遭…

LeetCode83. 删除排序链表中的重复元素

写在前面&#xff1a; 题目链接&#xff1a;LeetCode83. 删除排序链表中的重复元素 编程语言&#xff1a;C 题目难度&#xff1a;简单 一、题目描述 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 …

Java中异常的处理及捕获

Java中异常的处理及捕获 一、异常的概述 &#xff08;1&#xff09;Java中异常的作用&#xff1a;增强程序的健壮性 &#xff08;2&#xff09;在Java中所有的Error&#xff08;错误&#xff09;和异常&#xff08;Exception&#xff09;都继承了同一个父类Throwable 二、异…

postgresql内核源码分析-删除表drop table流程

专栏内容&#xff1a;postgresql内核源码分析个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e; 目录 前言 调用关系 概要流程 详细流程 创建对象列表空间 删除多个指定的数据库…

【蓝桥杯国赛真题27】Scratch LED屏幕 少儿编程scratch图形化编程 蓝桥杯国赛真题讲解

目录 scratch LED屏幕 一、题目要求 编程实现 二、案例分析 1、角色分析

C#中使用git将项目代码上传到远程仓库的操作

一、远程仓库创建操作&#xff08;远程仓库使用的是gitHub&#xff09; 1、登录GitHub官网&#xff0c;注册登录账号后&#xff0c;点击创建仓库 2、仓库名称命名&#xff0c;如下所示&#xff1a; 3、创建成功如下所示&#xff1a;获得https协议&#xff08;https://github.c…

Android开发不可缺少的辅助工具

目录 jadxandroid_toolscrcpy-guiCode CraftsSQLite Expert Personal jadx jadx是一款apk反编译工具。 PS&#xff1a;部分版本安装&#xff0c;无法打开类文件&#xff0c;需换个版本。 开源地址&#xff1a;https://github.com/skylot/jadx android_tool android_tool可以通…

【瑞萨RA_FSP】SCL UART 串口通信

文章目录 一、串口通信协议简介1. 物理层2. 协议层 二、SCI 简介三、SCI的结构框图四、UART波特率计算 一、串口通信协议简介 串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式&#xff0c;因为它简单便捷&#xff0c;因此大部分电子设备都支持该通讯方式&a…

SNAT和DNAT策略

文章目录 1.SNAT策略及应用1.1 SNAT原理与应用1.2 SNAT策略的工作原理1.3 实验步骤 2.DNAT策略2.1 DNAT策略的概述2.1 DNAT原理与应用2.3 实验步骤 3.规则的导出、导入4. 总结 1.SNAT策略及应用 1.1 SNAT原理与应用 SNAT 应用环境&#xff1a;局域网主机共享单个公网IP地址接…