适用于Transformer的图像编码/解码技术探索 ViT/VAE

12 阅读5分钟

在之前的文章中,我们花了一年左右的时间对Transformer技术本身进行了较全面的测试,包括:
1、逻辑运算
为何简单的乘法ChatGPT会算错?
大型语言模型(LLM)为什么连数数都做不好?
2、路径规划
基于Transformer的路径规划 - 目录
3、决策智能
基于Transformer的决策智能 目录+小结
基于模仿学习的决策智能:大语言模型LLM会还原三阶魔方吗?
4、自动泊车
基于模仿学习的自动泊车运动规划算法 目录
5、机器人智能控制
基于深度学习的机器人智能控制算法 目录/总结
6、OCR/机器视觉
多模态大模型入门:多行文本识别
CRNN进阶篇 多行文本识别/图像识别/对话模型
7、AIGC图片生成
字体图片生成:Rectified Flow控制条件

初步结论是:Transformer技术具有极强的通用性,在绝大部分单一任务中都能取得接近甚至超过人的水平(只要能通过低成本方式大量获取高质量的训练数据)

但Transformer只能处理一维序列(2D/3D/Graph Transformer不在本文讨论范围),当要处理图像、视频、三维模型等数据时,需要先将其转换成一维序列。在音频领域,虽然原始音频本身就是一维序列,但其序列过长,因此同样需要先进行编码以压缩序列长度。还有一种点云数据,比较特殊,是无序的,标准Transformer无法直接处理

在设计AI模型做各种形式数据(图像、视频、三维模型、语音、点云等)的理解/生成时,我感觉Transformer部分已经没太大的性能问题了,反而是编解码模块成了性能瓶颈,因此有必要将研究重点从Transformer转移到编解码模块

先以图像为例,最简单的方法是一个像素占一个token,但这种做法会导致序列长度过大,当图像尺寸大时对算力要求过高。只在图像尺寸低于32*32像素时推荐使用,参考代码如下:

nn.Sequential( 
    Rearrange("B C H W -> B (H W) C"),
    nn.Linear(nin, nout, bias=False),
)

本文希望探索出一种较通用的图像编码/解码技术,继承ViT简洁性的同时克服一些已知问题(比如局部信息丢失严重)。也不一定是探索,只是从琳琅满目的现有技术中挑出一种较通用的图像编解码技术

参考以下技术领域:
1、ViT(Vision Transformer)及其改进版本
2、BEV/Occupancy及其改进版本
3、Stable Diffusion/DiT/Flux/VQGAN/VAR/MAR等
4、多模态大模型

在Stable Diffusion中,会单独训练一个VAE模型来实现图像的编解码,也叫隐空间,这种方法在其它技术领域没有普及,需要进一步验证是否具备通用性

先从最基础的ViT开始,下面是我在《AIGC文生图技术入门:Rectified Flow字体生成》一文中用到的图像编解码代码:

# 输入张量尺寸为(B, 1, 64, 64)
# 图像编码
nn.Sequential(
    nn.PixelUnshuffle(4),
    Rearrange("B C H W -> B (H W) C"),
    nn.Linear(16, 512, bias=False),
)
# 图像解码
nn.Sequential( 
    nn.Linear(512, 16, bias=True),
    Rearrange("B (H W) C -> B C H W"),
    nn.PixelShuffle(4),
)

经过编码后,图像长宽变为原来的1/4,即将原图按4*4像素尺寸进行切块。通常认为这种方法在图像编码环节会丢失块内的局部信息。

分析图像编码环节,共三步:
1、nn.PixelUnshuffle(4)
2、Rearrange("B C H W -> B (H W) C")
3、nn.Linear(16, 512, bias=False)
其中第1、2步不会有任何信息丢失,但第1、2步改变了数据的组织形式,可能会因为接下来的Transformer学习能力不够导致信息丢失 第3步nn.Linear应该是最可能导致局部信息丢失的了,在后面的文章中我们会设计实验进行验证

如果不用nn.Linear,改用卷积可以解决这个问题吗?下面给出一种卷积版本的图像编码代码:

# 参考https://github.com/madebyollin/taesd/blob/main/taesd.py
def Conv(n_in, n_out, kernel_size=3, padding=None, stride=1, bias=True, **kwargs):
    if padding is None: padding = kernel_size // 2
    return nn.Conv2d(n_in, n_out, 
        kernel_size=kernel_size, padding=padding, 
        stride=stride, bias=bias,
        **kwargs)

class Block(nn.Module):
    def __init__(self, n_in, n_out):
        super().__init__()
        self.conv = nn.Sequential(
            Conv(n_in, n_out), nn.ReLU(), 
            Conv(n_out, n_out), nn.ReLU(),
            Conv(n_out, n_out))
        self.skip = nn.Conv2d(n_in, n_out, 1, bias=False) if n_in != n_out else nn.Identity()
        self.fuse = nn.ReLU()

    def forward(self, x):
        return self.fuse(self.conv(x) + self.skip(x))

def Encoder(latent_channels=512, n_in=1):
    C = 64
    return nn.Sequential(
        Conv(n_in, C), Block(C, C),
        Conv(C, C, stride=2, bias=False), Block(C, C), Block(C, C), Block(C, C),
        Conv(C, C, stride=2, bias=False), Block(C, C), Block(C, C), Block(C, C),
        Conv(C, latent_channels, kernel_size=1),
        Rearrange("B C H W -> B (H W) C"),
    )

《基于模仿学习的自动泊车运动规划算法 模型优化》一文中,我们还给出过一种基于YOLOv8的图像编码代码,也可以将Backbone换成VGG、ResNet等

可以看到:在图片生成方向,图像编码环节没有使用BatchNorm,LayerNorm等规范化层。可能规范化层会丢失一些对图片生成比较关键的信息,比如亮度、对比度

除此之外,我觉得还可以尝试使用Transformer来实现图像编码,真正逼近“Attention Is All You Need”。具体做法就是对每个切块(在ViT中叫patch)也使用Transformer而不是nn.Linear来生成特征,避免局部信息的丢失

接下来就是漫长的测试了

测试是一项超级耗时的工程,因为现阶段模型越来越大,训练一个模型经常需要几天到几周。如果是大公司则可凭借优势的人力/硬件资源做并行测试,否则就只能等训练完一个模型再训练下一个模型。而想要验证一套技术方案的通用性又需要大量的对比实验。

另外就是理解本质,或者叫第一性原理。从第一性原理出发,可以降低试错成本。

下一篇:适用于Transformer的图像编码/解码技术探索 浅探本质