欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

什么是GPT Embedding

最编程 2024-02-24 20:18:05
...

何为Embedding

对于自然语言他输入的就是一段文本,在中文中他就是一个字或者一个词,行业内把这个字或者词叫做token。如果要使用模型,拿到一段文本的第一件事就是把他token化。

token化的方法有很多,比如:

  1. 按字
  2. 按词
  3. 按Bi-Gram

那自然就有新的问题:我们应该选择什么样的token化方式?其实每种都有自己的优缺点,在大模型之前使用按词的方式的比较常见,但是有了大模型之后,基本都是按字来了。

token化后就是要怎么样表示这些token,我们知道计算机只能处理数字,所以要想办法将这些token转换为计算机认识的数字才行。

其实很简单很直观,把所有字作为一个字典,序号就代表自己

其实很简单很直观,把所有字作为一个字典,序号就代表它自己。我们还是以上面的句子为例,假设词表就包含上面那些字,那么词表就可以用一个txt文件存储,内容如下:

我 们 相 信 A I 可 以 让 世 界 变 得 更 美 好

一行一个字,每个字作为一个Token,此时,0=我,1=们,……,以此类推。拿中文来说,这个词表可能只要几千行,即使包含各种特殊符号、生僻字,也就2万个多点,我们假设词表大小为N。

接下来我们考虑如何用这些数字来表示一段文本。最简单的方法就是用它的ID直接串起来,这样也不是不行,但这种表示方法的特征是一维的,也就是说只能表示一个特征。这种方法不太符合实际情况,效果也不理想。所以,研究人员就想到另一种表示方法:One-Hot编码。其实,将文本变为数字表示的过程本质上就是一种编码过程。**One-Hot的意思是,对每一个字都有N(词表大小)个特征,除了该字的ID位置值为1,其余都为0。**我们依然用上面的例子来说明,把整个词表表示为下面的形式:

我 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

们 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0

相 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0

信 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0

此时,对每一个Token(字),它的表示就变成了一个一维向量,比如「我」:[1,0,...0],这个向量的长度就是词表的大小N,它被称为「我」这个字的One-Hot表示。

对于一段文本,我们一般会将每个Token的表示结合起来,结合方式可以采用求和或平均。这样,对于任意长度的任意文本,我们都能将其表示为固定大小的向量,非常方便进行各种矩阵或张量(三维以上的数组)计算,这对深度学习至关重要。

举个例子,比如有这么一句话:让世界更美好。现在我们使用刚刚的方法将其表示为一个向量,采用平均的方式。

首先,列出每个字的向量:

让 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0

世 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
界 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
更 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
美 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0
好 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
。 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

然后针对每一列取平均,结果为:0 0 0 0 0 0 0 1/7 1/7 1/7 0 0 1/7 1/7 1/7 1/7。不难发现,对任两句话,只要其中包含的字不完全一样,最终得到的向量表示也不会完全一样。

当然,在实际使用时,往往不会这么简单的用1/0来表示,因为每个字在句子中的作用是不一样的,所以一般会给不同的Token赋予不同的权重。最常见的方法是使用在句子中出现的频率,那些高频的(但不是「的」「更」这样的虚词)被认为是重要的。

这种方法不错,在深度学习之前很长一段时间里都是这样的,不过它有两个很大的问题:

  1. 数据维度太高:太高的维度会导致向量在空间中聚集在一个非常狭窄的角落,模型难以训练。
  2. 数据稀疏,向量之间缺乏语义上的交互(语义鸿沟):比如「我爱吃苹果」和「我爱用苹果」,前者是水果,后者是手机,怎么判断出来的呢?根据上下文。但由于现在这种表示方式,导致上下文之间是孤立的,所以模型学不到这个知识点。还有类似「我喜欢你」和「你喜欢我」这样会得到同样的表示,但其实是不同的意思。

终于轮到我们的主角Embedding登场了,它的主要思想是这样的:

  • 把特征固定在某一个维度D,比如256、300、768等等,这个不重要,总之不再是词表那么大的数字。这就避免了维度过高的问题。
  • 利用自然语言文本的上下文关系学习一个稠密表示。也就是说,每个Token的表示不再是预先算好的了,而是在过程中学习到的,元素也不再是很多个0,而是每个位置都有一个小数,这D个小数构成了一个Token表示。至于D个特征到底是什么,不知道,也不重要。我们只需要知道这D个小数就表示这个Token。

还是继续以前面的例子来说明,这时候词表的表示变成下面这样了:

我 0.xxx0, 0.yyy0, 0.zzz0, ... D个小数

们 0.xxx1, 0.yyy1, 0.zzz1, ... D个小数
相 0.xxx2, 0.yyy2, 0.zzz2, ... D个小数
信 0.xxx3, 0.yyy3, 0.zzz3, ... D个小数

在模型训练过程中,会根据不同的上下文不断地更新这个参数,最后模型训练完后得到的这个矩阵就是Token的表示。我们完全可以把它当成一个黑盒子,输入一个X,根据标签Y不断更新参数,最终就得到一组参数,这些参数的名字就叫「模型」。

这种表示方法在深度学习早期(2013-2015年左右)比较流行,不过由于这个矩阵训练好后就固定不变了,这在有些时候就不合适。比如「你好坏」这句话在不同的情况下可能完全是不同的意思。

我们知道,句子才是语义的最小单位,因此相比Token,我们其实更加关注和需要句子的表示,我们期望可以根据不同上下文动态地获得句子表示。这中间当然经历了比较多的探索,一直到如今的大模型时代,对模型输入任意一句话,它都能给我们返回一个非常不错的表示,而且依然是固定长度的向量。

总结

我们总结一下,Embedding本质就是一组稠密向量,用来表示一段文本(可以是字、词、句、段等),获取到这个表示后,我们就可以进一步做一些任务。大家不妨先思考一下,当给定任意句子并获得到它的固定长度的语义表示时,我们可以干什么?

相关API

LMAS Embedding API

import os
import openai
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
openai.api_key = OPENAI_API_KEY
text = "我喜欢你"
model = "text-embedding-ada-002"

emb_req = openai.Embedding.create(input=[text], model=model)
emb = emb_req.data[0].embedding

与Embedding息息相关的一个概念是「相似度」,准确来说是「语义相似度」。在自然语言处理领域,我们一般使用cosine相似度作为语义相似度的度量,评估两个向量在语义空间上的分布情况。

具体来说就是下面这个式子:

image.png

举个例子:

import numpy as np
a = [0.1, 0.2, 0.3]
b = [0.2, 0.3, 0.4]
cosine_ab = (0.1*0.2+0.2*0.3+0.3*0.4)/(np.sqrt(0.1**2+0.2**2+0.3**2) * np.sqrt(0.2**2+0.3**2+0.4**2))
cosine_ab
0.9925833339709301

OpenAI官方提供了一个集成接口,使用起来更加简单(但其实你也可以自己写一个):

from openai.embeddings_utils import get_embedding, cosine_similarity
# 注意它默认的模型是text-similarity-davinci-001,我们也可以换成text-embedding-ada-002
text1 = "我喜欢你"
text2 = "我钟意你"
text3 = "我不喜欢你"
emb1 = get_embedding(text1)
emb2 = get_embedding(text2)
emb3 = get_embedding(text3)

len(emb1), type(emb1)
(12288, list)

cosine_similarity(emb1, emb2)
0.9246855139297101

cosine_similarity(emb1, emb3)
0.8578009661644189

cosine_similarity(emb2, emb3)
0.8205299527695261
text1 = "我喜欢你"
text2 = "我钟意你"
text3 = "我不喜欢你"
emb1 = get_embedding(text1, "text-embedding-ada-002")
emb2 = get_embedding(text2, "text-embedding-ada-002")
emb3 = get_embedding(text3, "text-embedding-ada-002")

cosine_similarity(emb1, emb2)
0.8931105629213952

cosine_similarity(emb1, emb3)
0.9262074073566393

cosine_similarity(emb2, emb3)
0.845821877417193