多模态RL智能笔记(1): Generalist-Agent(Gato)大模型

受最近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)

扫描二维码关注公众号,回复: 15665810 查看本文章

这些统统只需要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=128k1k1=510.6=128k2k2=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=128k3k3=320,5=128k4k4=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 0L1,那么如何将其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=1Bl=1Lm(b,l)log(slbs1b,s2b⋅⋅⋅⋅⋅⋅sl1b)
其中, 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=1Blog(s2bs1b)=b=1Blog(youtputbyinputb)

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=1Blog(s2bs1b)=b=1Blog(youtputbxinputb)

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⋅⋅⋅⋅zkinputa1),⋅⋅⋅(zlinput⋅⋅⋅⋅zlinputak)]=[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=1Bl=1Llog(slbs1b,s2b⋅⋅⋅sl1b)=b=1Bl=1Llog(albs1b,s2b⋅⋅⋅sl1b)

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⋅⋅⋅⋅xkinputa1),⋅⋅⋅(xlinput⋅⋅⋅⋅xlinputak)]=[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=1Bl=1Llog(slbs1b,s2b⋅⋅⋅sl1b)=b=1Bl=1Llog(albs1b,s2b⋅⋅⋅sl1b)

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=1Blog(s2bs1b)=b=1Blog(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存在着很多不足之处,但也为我们多模态学习任务开辟了新的路径,思想值得我们学习。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lvoutongyi/article/details/129087954