受最近ChatGPT影响, RL的热潮不断兴起和发展,笔者也颇感兴趣,笔者认为它和DeepMind所开发的Gato的思想结合若能去长避短,那么未来人工智能的发展可能会更进一步。本篇主要针对自己最近的学习内容进行笔记总结,来介绍一下各界关注和跟进的另一个大模型:DeepMind所开发的Generalist-Agent(Gato)多模态智能体,原文链接如下:Gato。它的改进版有Digital Brain Lab所开发的DB1,有关DB1的细节内容请了解DB1,在本篇中笔者不介绍DB1,留到下一篇来介绍,本节只着重介绍有关Gato的关键技术和具体方法,方便感兴趣的读者进行阅读前的理解,欢迎感兴趣的读者进行一起讨论和学习,本篇内容仅为笔者个人的学习笔记,代表笔者个人观点,如有错误或疏漏之处欢迎各位进行批评指正,谢谢,图片来源于Gato原文和Vision-Transformer(ICLR-2021)。
在了解本篇前,你需要先了解或预备以下内容来进行本篇阅读可能会很轻松,如不了解也没有关系,笔者会尽量写的清楚,有关Gato更多的讲解,可以看机器之心的有关报道和原文链接。
一、Transformer和Multi-Attention的基本原理与经典NLP编码(SentencePiece)
二、有关RL决策的一些基本知识如:Statement,Action,Reward。
三、Sequence-To-Sequence模型中词token的定义。
四、经典的CV模型(ResNet),Vision-Transformer(ICLR-2021)。
0、Gato功能简介
0.1、什么是多模态?
我们都知道,一般而言,我们只会针对一种类型的数据进行建模,而非多种类型数据混合,多模态数据即:模型不仅要处理所谓的单一类型的数据,而是要对输入的文本,图片,音频,视频等数据进行混合处理的训练和预测任务。
0.2、Gato在做什么
一般的RL算法/模型是针对一类问题来进行解决的,比如一个Atria Game专家RL,它可以在游戏比赛中表现十分出色,再比如AlphaGo可以在围棋领域表现出色,它们都可以在各自的领域做到专家级别的效果。但是,如果你让AlphaGo去玩Atria Game,或者让一个Game专家去下围棋,这显然需要将原来的模型参数抛弃掉来进行重新设计和学习,因为它们是针对所处领域而设计的,并未学习其他领域的其他知识和内容。
那么一个很自然的想法就产生了:如果能够训练一个现实生活中的全能AI,一个既能下围棋,又能对话,又能在Game等领域表现优良的AI,这就满足了我们的需求,可否代替上述描述的两个AI?Gato即为一个实现了“全能”AI,注意这里的全能我标注了引号,代表它并不是真正的全能,而是可以接近“专家”模型的水平,但是它并不能在每个任务都达到“完美”的水平,它的优势在于可以处理多模态数据而非单一化类型数据。
1、Gato模型设计与架构
1.0、Gato框架-Transformer
受Transformer框架在处理序列问题中优秀的启发,Transformer原本是用于处理带有序关系的文本数据,用于提取特征/充当编码解码器/代替RNN框架的作用。Gato能通用于多种多模态任务如:
任务1:输入一张图片,给出一个有关该图片的描述(CV-Sequence)
任务2:玩Atria游戏(RL)
任务3:输入一段文字问题,给予该问题文字回答(Sequence-Sequence)
这些统统只需要Tansformer来处理,这很令人惊奇,巧妙之处在于Gato的编码设计,它将多模态数据转换成了具有位置信息的编码token向量。
1.1、多模态数据tokens设计(重要)
相比于单模态数据而言,多模态数据的难点在于如何良好的“处理”这些多模态数据,他可能是(图片-文本)混合数据,也可能是RL中的“状态数据”等,文本数据天生自带序列化信息,可以使用SentencePiece来进行文本数据的编码,但是对于图像数据,和一些其他的离散型变量,连续性变量而言,如何将其“序列化”?
Gato提出了如下设计方案:
1.文本token编码采用SentencePiece编码。
2.图像token数据采用“栅格化”处理,将一个图片按照16*16处理称为若干序列栅格。(见下图,将图片栅格化后按照顺序排列起来,采用和Vision Transformer一样的办法进行)(采用Resnet进行Embedding)。
3.离散数据(如RL中所take的action)等,本身就是一个序列信息,可以被直接进行token编码。
4.连续数据(如RL中所观测的状态)等,需要受先将其离散化处理,但是连续性数据可以任意的大,为了方便离散化,首先需要对连续数据进行mu-law转换编码转换到[-1,1]之间,然后将[-1,1]等分1024份,这样形成了一个1024个小区间到整数0~1024的一一映射。具体操作如下,
k
k
k为样例最终token编码:(原文将编码放在了[32000,33024]上,只需做个平移即可,方法是一致的)
μ
=
100
,
M
=
256
\mu=100,M=256
μ=100,M=256
F
(
x
)
=
s
i
g
n
(
x
)
l
o
g
(
μ
∣
x
∣
+
1
)
l
o
g
(
μ
M
+
1
)
,
F
(
x
)
∈
[
k
1024
,
k
+
2
1024
]
→
k
(
+
32000
)
F(x)=sign(x)\frac{log(\mu|x|+1)}{log(\mu M+1)},F(x) \in[\frac{k}{1024},\frac{k+2}{1024}]\rightarrow k(+32000)
F(x)=sign(x)log(μM+1)log(μ∣x∣+1),F(x)∈[1024k,1024k+2]→k(+32000)
1.2、多模态数据tokens序列编码(重要)
进行完成1.1中数据的tokens序列编码后,一个序列不止需要进行编码处理,更具有编码前后的顺序信息。 因此要将1.1所获取的token进行序列信息编码,同1.1一样,四种数据的编码方式略有不同。
针对一个时间步长为
L
L
L的RL任务,给出如下的序列编码:
1.文本序列编码采用与原文本输入相同的顺序进行编码,序列长短假设为
k
k
k,文本编码计作
(
y
1
,
y
2
⋅
⋅
⋅
⋅
⋅
y
k
)
(y_1,y_2·····y_k)
(y1,y2⋅⋅⋅⋅⋅yk)
2.图片序列编码采用序列栅格顺序进行编码,若栅格长短为
m
m
m,图片编码计作
(
x
1
,
x
2
⋅
⋅
⋅
⋅
⋅
x
m
)
(x_1,x_2·····x_m)
(x1,x2⋅⋅⋅⋅⋅xm)
3.其他离散数据/连续数据token使用正常的时间步按照行进行排列即可,若数据量为
n
n
n,其他数据编码计作
(
z
1
,
z
2
⋅
⋅
⋅
⋅
⋅
z
n
)
(z_1,z_2·····z_n)
(z1,z2⋅⋅⋅⋅⋅zn)
前三条为序列信息,即为RL中的“状态”,还需有针对动作的编码。
4.动作为离散数据/连续数据token使用正常的时间步按照行进行排列即可,若动作向量长度为
A
A
A,动作编码计作
(
a
1
,
a
2
⋅
⋅
⋅
⋅
⋅
a
A
)
(a_1,a_2·····a_A)
(a1,a2⋅⋅⋅⋅⋅aA)
设置在时间步长为
L
L
L的RL任务下则对应任务序列总长度为
L
(
k
+
m
+
A
)
L(k+m+A)
L(k+m+A),序列
S
S
S定义如下:
s
i
=
[
(
y
1
i
,
y
2
i
⋅
⋅
⋅
y
k
i
,
x
1
i
,
x
2
i
⋅
⋅
⋅
x
m
i
,
z
1
i
,
z
2
i
,
⋅
⋅
⋅
z
n
i
,
∣
∣
a
1
i
,
a
2
i
⋅
⋅
⋅
a
A
i
)
,
S
=
[
s
1
,
s
2
,
⋅
⋅
⋅
s
L
]
s_i=[(y_1^{i},y_2^{i}···y_k^{i},x_1^{i},x_2^{i}···x_m^{i},z_1^{i},z_2^{i},···z_n^{i},||a_1^{i},a_2^{i}···a_A^{i}),S=[s_1,s_2,···s_L]
si=[(y1i,y2i⋅⋅⋅yki,x1i,x2i⋅⋅⋅xmi,z1i,z2i,⋅⋅⋅zni,∣∣a1i,a2i⋅⋅⋅aAi),S=[s1,s2,⋅⋅⋅sL]
1.3、Tokens序列编码Embedding(重要)
进行完成1.2多模态数据tokens序列编码后,根据1.2所描述的那样,我们会得到一个tokens序列,沿用1.2的符号,序列编码后的token计作
S
S
S,它的长度为
L
L
L,
S
=
[
s
1
,
s
2
,
⋅
⋅
⋅
s
L
]
S=[s_1,s_2,···s_L]
S=[s1,s2,⋅⋅⋅sL]。
下面进入关键思想部分
下面我们需要将1.2完成的编码进行向量嵌入才能进入Transformer进行encoder。关键点在于,这里进行token序列编码后,图像数据的编码还需要进行一下小小的修改才能进行嵌入。
初始化三个表:①Item token——Embedding Table,②Position token——Embedding ③Local Position token——Embedding Table 其中①②分别针对非图像Item和图像Item设计。
Embedding的作用是将不同类型的token编码统一化为维度相同长度的向量,以便进行下一步的Transformer。
我们分别来看这三个Embedding Table的作用。
1.3.1 图像序列embedding
图像序列embedding分两个步骤来进行
①、ResNet With GELU 进行图像数据编码(Picture-Piece-Embedding)
具体操作是,按照
(
x
1
,
x
2
⋅
⋅
⋅
⋅
⋅
x
m
)
(x_1,x_2·····x_m)
(x1,x2⋅⋅⋅⋅⋅xm)顺序将已经栅格化后的小piece图片输入给一个使用GELU激活的ResNet进行编码,具体情况如下:
②、图像相对位置编码(Position-Piece-Embedding(Column and Row))
获得了图像编码,这是不够的,因为缺失了图像先后顺序的信息向量,即需要通过一个描述相对位置的向量嵌入来保证信息不被损失,针对图片而言,位置编码提供了当前piece在完整1图片中的相对位置信息,包括相对的列位置信息,行位置信息等内容。
这部分的描述如下:首先需要找到该piece在整体图片中的“相对位置”,该相对位置为一个[0,1]区间构成(行列都是),之后将该区间离散化称为128份(行列都是),在训练过程中,随机选取该128份的某一份作为位置标记(行列都是),根据Position token——Embedding Table中去索引该位置信息的Token-embedding作为图像相对位置编码,这样说起来很让人费解,接下来我用一个实例演示给各位,这样非常容易理解了,就用下图所示的实例来进行描述。
如上图所示,这是一个被分割为5*4个piece的图片,我们选取了红色小框内容的piece,现在要对其进行位置编码,首先不难观察到,它的归一化相对横坐标对应为[0,4,0.6],这是因为横向共有五块,归一化后每块横坐标长度为0.2。该红框处于第三,第四块,同理,它的归一化相对列坐标对应为[0,25,0.5]。
归一化后,我们需要对其进行离散化编码,而这里采用了将[0,1]区间128等分的编码方式,换而言之,每个小区间长度
l
=
1
128
l=\frac{1}{128}
l=1281,那么显然,横,纵坐标两者对应的token编码为:
0.4
=
k
1
128
→
k
1
=
51
。
0.6
=
k
2
128
→
k
2
=
77
0.4=\frac{k_1}{128} \rightarrow k_1=51。 0.6=\frac{k_2}{128} \rightarrow k_2=77
0.4=128k1→k1=51。0.6=128k2→k2=77
0.25
=
k
3
128
→
k
3
=
32
。
0
,
5
=
k
4
128
→
k
4
=
64
0.25=\frac{k_3}{128} \rightarrow k_3=32。 0,5=\frac{k_4}{128} \rightarrow k_4=64
0.25=128k3→k3=32。0,5=128k4→k4=64
即我们得到了横坐标token编码区间为[51,77],纵坐标token编码区间为[32,64]。
下面分为两种情况:
一、如果是Training,那么本次横坐标token编码从[51,77]随机整数采样,纵坐标token编码从[32,64]中随机整数采样。
二、如果是Testing,那么本次横坐标token编码采样为中间数64,纵坐标token编码采样为中间数48。
根据采样后的token编码去搜索Embedding Table中对应的Embedding 向量,行列各有一个Embedding 向量。之后进行上图所示的求和作为最终的图像整体Embedding。
1.3.2 非图像序列embedding
根据 Item token——Embedding Table进行每个token的索引查找对应的embedding向量
1.3.3 非图像局部位置信息embedding
由于一个sequence
S
=
[
s
1
,
s
2
,
⋅
⋅
⋅
s
L
]
S=[s_1,s_2,···s_L]
S=[s1,s2,⋅⋅⋅sL]中已经带有了位置信息
0
~
L
−
1
0~L-1
0~L−1,那么如何将其token化进而embedding也是一个关键问题,在这里,Gato采用了两种办法:
1.所有位置编码共用一个token表进行索引和embedding,而不区分任务类型
2.动作编码为固定的一个embedding向量
具体操作如下图所示,这图通俗易懂,笔者在这里做一点过程解释:
一、所有固定位置的Local-Position embedding向量固定,通过索引表进行embedding查找即可。
二、只针对非图像序列进行这样的操作,图像序列则不需要这样。
1.4、Gato训练(重点)
这里容易让人费解,我这里通过三种不同的实例解释对Gato在不同任务中训练进行详细的解释。以下结合笔者个人理解。给定一个训练序列
S
=
[
s
1
,
s
2
,
⋅
⋅
⋅
s
L
]
S=[s_1,s_2,···s_L]
S=[s1,s2,⋅⋅⋅sL]
首先是Gato的(Loss-function)定义如下,假设采样了一个
B
a
t
c
h
=
B
Batch=B
Batch=B的数据集作为训练样本,那么损失函数的定义为:
L
o
s
s
=
−
∑
b
=
1
B
∑
l
=
1
L
m
(
b
,
l
)
l
o
g
(
s
l
b
∣
s
1
b
,
s
2
b
⋅
⋅
⋅
⋅
⋅
⋅
s
l
−
1
b
)
Loss=-\sum_{b=1}^B\sum_{l=1}^Lm(b,l)log(s_l^b|s_1^b,s_2^b······s_{l-1}^b)
Loss=−b=1∑Bl=1∑Lm(b,l)log(slb∣s1b,s2b⋅⋅⋅⋅⋅⋅sl−1b)
其中,
m
(
b
,
l
)
=
1
m(b,l)=1
m(b,l)=1若token为文本或者动作token
反之,
m
(
b
,
l
)
=
0
m(b,l)=0
m(b,l)=0若token为图片或者观测token
下面我来分不同的情况来解释该损失函数
首先根据概率论基本知识,log概率求和的最大值实际上等同于联合概率分布最大值,那么显然的是,它的负值一定是正的,负值越小,联合概率分布越大,目标是去学习训练样本给予的分布。因此Gato是一种offline学习方式,并且是一种监督式,因为这里并不是让Agent去自我探索的寻求Reward。
1.4.1、输入文本——输出文本(聊天对话AI)
对于这种情况而言,显然这是一个经典Transformer或者Seq-to-Seq即可解决的问题,而对于Gato而言,现在我们的序列
S
S
S变成了
S
=
[
(
y
1
i
n
p
u
t
⋅
⋅
⋅
⋅
y
k
i
n
p
u
t
)
,
(
y
1
o
u
t
p
u
t
⋅
⋅
⋅
⋅
y
m
o
u
t
p
u
t
)
]
=
[
s
1
,
s
2
]
S=[(y_1^{input}····y_k^{input}),(y_1^{output}····y_m^{output})]=[s_1,s_2]
S=[(y1input⋅⋅⋅⋅ykinput),(y1output⋅⋅⋅⋅ymoutput)]=[s1,s2],显然这里是没有Action和“观测”的,那么将按照如下损失进行
L
o
s
s
=
−
∑
b
=
1
B
l
o
g
(
s
2
b
∣
s
1
b
)
=
−
∑
b
=
1
B
l
o
g
(
y
o
u
t
p
u
t
b
∣
y
i
n
p
u
t
b
)
Loss=-\sum_{b=1}^Blog(s_2^b|s_1^b)=-\sum_{b=1}^Blog(y_{output}^{b}|y_{input}^{b})
Loss=−b=1∑Blog(s2b∣s1b)=−b=1∑Blog(youtputb∣yinputb)
1.4.2、输入图片——输出文本(图片描述AI)
对于这种情况而言,现在我们的序列
S
S
S变成了
S
=
[
(
x
1
i
n
p
u
t
⋅
⋅
⋅
⋅
x
k
i
n
p
u
t
)
,
(
y
1
o
u
t
p
u
t
⋅
⋅
⋅
⋅
y
m
o
u
t
p
u
t
)
]
=
[
s
1
,
s
2
]
S=[(x_1^{input}····x_k^{input}),(y_1^{output}····y_m^{output})]=[s_1,s_2]
S=[(x1input⋅⋅⋅⋅xkinput),(y1output⋅⋅⋅⋅ymoutput)]=[s1,s2]。这里我们是没有必要去预测图片的,因此这里的计算Loss方式仍旧为
L
o
s
s
=
−
∑
b
=
1
B
l
o
g
(
s
2
b
∣
s
1
b
)
=
−
∑
b
=
1
B
l
o
g
(
y
o
u
t
p
u
t
b
∣
x
i
n
p
u
t
b
)
Loss=-\sum_{b=1}^Blog(s_2^b|s_1^b)=-\sum_{b=1}^Blog(y_{output}^{b}|x_{input}^{b})
Loss=−b=1∑Blog(s2b∣s1b)=−b=1∑Blog(youtputb∣xinputb)
1.4.3、连续性/离散型(智能机械臂不断转动完成任务AI,观测值为离散值)
对于这种情况而言,现在我们的序列
S
S
S变成了
S
=
[
(
z
1
i
n
p
u
t
⋅
⋅
⋅
⋅
z
k
i
n
p
u
t
∣
a
1
)
,
⋅
⋅
⋅
(
z
l
i
n
p
u
t
⋅
⋅
⋅
⋅
z
l
i
n
p
u
t
∣
a
k
)
]
=
[
s
1
,
s
2
⋅
⋅
⋅
s
k
]
S=[(z_1^{input}····z_k^{input}|a_1),···(z_l^{input}····z_l^{input}|a_k)]=[s_1,s_2···s_k]
S=[(z1input⋅⋅⋅⋅zkinput∣a1),⋅⋅⋅(zlinput⋅⋅⋅⋅zlinput∣ak)]=[s1,s2⋅⋅⋅sk]。那么显然从
z
i
z_i
zi转移到
z
i
+
1
z_{i+1}
zi+1的过程是通过动作
a
i
a_i
ai来转移的而不是一个预测过程,因此只需要对Action进行预测。
L
o
s
s
=
−
∑
b
=
1
B
∑
l
=
1
L
l
o
g
(
s
l
b
∣
s
1
b
,
s
2
b
⋅
⋅
⋅
s
l
−
1
b
)
=
−
∑
b
=
1
B
∑
l
=
1
L
l
o
g
(
a
l
b
∣
s
1
b
,
s
2
b
⋅
⋅
⋅
s
l
−
1
b
)
Loss=-\sum_{b=1}^B\sum_{l=1}^Llog(s_l^b|s_1^b,s_2^b···s_{l-1}^b)= -\sum_{b=1}^B\sum_{l=1}^Llog(a_l^b|s_1^b,s_2^b···s_{l-1}^b)
Loss=−b=1∑Bl=1∑Llog(slb∣s1b,s2b⋅⋅⋅sl−1b)=−b=1∑Bl=1∑Llog(alb∣s1b,s2b⋅⋅⋅sl−1b)
1.4.4、连续性/离散型(Atria游戏/Gaming决策/观测值为图像)
对于这种情况而言,现在我们的序列
S
S
S变成了
S
=
[
(
x
1
i
n
p
u
t
⋅
⋅
⋅
⋅
x
k
i
n
p
u
t
∣
a
1
)
,
⋅
⋅
⋅
(
x
l
i
n
p
u
t
⋅
⋅
⋅
⋅
x
l
i
n
p
u
t
∣
a
k
)
]
=
[
s
1
,
s
2
⋅
⋅
⋅
s
k
]
S=[(x_1^{input}····x_k^{input}|a_1),···(x_l^{input}····x_l^{input}|a_k)]=[s_1,s_2···s_k]
S=[(x1input⋅⋅⋅⋅xkinput∣a1),⋅⋅⋅(xlinput⋅⋅⋅⋅xlinput∣ak)]=[s1,s2⋅⋅⋅sk]。同1.4.3一样,我们的目标不是预测图像,而是预测动作。
L
o
s
s
=
−
∑
b
=
1
B
∑
l
=
1
L
l
o
g
(
s
l
b
∣
s
1
b
,
s
2
b
⋅
⋅
⋅
s
l
−
1
b
)
=
−
∑
b
=
1
B
∑
l
=
1
L
l
o
g
(
a
l
b
∣
s
1
b
,
s
2
b
⋅
⋅
⋅
s
l
−
1
b
)
Loss=-\sum_{b=1}^B\sum_{l=1}^Llog(s_l^b|s_1^b,s_2^b···s_{l-1}^b)= -\sum_{b=1}^B\sum_{l=1}^Llog(a_l^b|s_1^b,s_2^b···s_{l-1}^b)
Loss=−b=1∑Bl=1∑Llog(slb∣s1b,s2b⋅⋅⋅sl−1b)=−b=1∑Bl=1∑Llog(alb∣s1b,s2b⋅⋅⋅sl−1b)
1.4.5、输入文本/图像混合数据,输出为文本描述(辅助信息图像识别)
对于这种情况而言,现在我们的序列
S
S
S变成了
S
=
[
(
y
1
i
n
p
u
t
⋅
⋅
⋅
⋅
y
k
i
n
p
u
t
,
x
1
i
n
p
u
t
⋅
⋅
⋅
⋅
x
m
i
n
p
u
t
)
,
(
y
1
o
u
t
p
u
t
⋅
⋅
⋅
⋅
y
k
o
u
t
p
u
t
)
]
=
[
s
1
,
s
2
]
S=[(y_1^{input}····y_k^{input},x_1^{input}····x_m^{input}),(y_1^{output}····y_k^{output})]=[s_1,s_2]
S=[(y1input⋅⋅⋅⋅ykinput,x1input⋅⋅⋅⋅xminput),(y1output⋅⋅⋅⋅ykoutput)]=[s1,s2]。计算Loss方式为
L
o
s
s
=
−
∑
b
=
1
B
l
o
g
(
s
2
b
∣
s
1
b
)
=
−
∑
b
=
1
B
l
o
g
(
y
o
u
t
p
u
t
b
∣
(
y
1
i
n
p
u
t
⋅
⋅
⋅
⋅
y
k
i
n
p
u
t
,
x
1
i
n
p
u
t
⋅
⋅
⋅
⋅
x
m
i
n
p
u
t
)
b
)
Loss=-\sum_{b=1}^Blog(s_2^b|s_1^b)=-\sum_{b=1}^Blog(y_{output}^{b}|(y_1^{input}····y_k^{input},x_1^{input}····x_m^{input})^b)
Loss=−b=1∑Blog(s2b∣s1b)=−b=1∑Blog(youtputb∣(y1input⋅⋅⋅⋅ykinput,x1input⋅⋅⋅⋅xminput)b)
等其他大模型任务,总而言之,根据任务类型不同,Action和Text作为一个整体的输出需求被对比,让Gato去模仿学习已经得到的训练数据,更新Loss-Function,模型参数公用Transformer。
由于任务训练的多样性,那么这个Action和Text难免会出现,不同任务里面产生了相同的结果,如一只猫,给一张图片描述和文字描述结果几乎是相同的,那就需要给予模型一个**“提示”**来去区分这种有歧义的任务,即加入了一些Sequence进入到训练Batch里面,Sequence的一半是来自于该任务末端的轨迹(用于表示该任务的一些提示信息)另一半从Agent在训练过程中的产生进行采样获得,这样的Sequence称为提示序列(用于区分任务类型)
1.5、Gato预测部署/总结
Gato并不能直接进行输出预测,而是需要有一段前置的提示序列,然后将第一个观测值输入给Gato获得动作,然后进行状态转移,然后输出第二个动作等等直到序列结束,这样Gato会根据不同的任务提示,给予满足要求的不同类型输出,总之,Gato存在着很多不足之处,但也为我们多模态学习任务开辟了新的路径,思想值得我们学习。