电脑基础 · 2023年3月31日

MAE实现及预训练可视化 (CIFAR-Pytorch)

MAE实现及预训练可视化 (CIFAR-Pytorch)

文章目录

  • MAE实现及预训练可视化 (CIFAR-Pytorch)
    • 灵感来源
    • 自监督学习
    • 自监督的发展
    • MAE (Masked Autoencoders)
      • 方法介绍
      • MAE流程图
    • 搭建 MAE 模型
      • MAE 组网
      • MAE 预训练(pretrain)
        • Encoder
        • Decoder
        • 总结
        • 测试MAE
      • MAE 微调(finetune)
    • MAE 预训练
    • MAE 微调
    • Result 结果
    • 总结
    • 感谢和体会

灵感来源

监督学习是训练机器学习模型的传统方法,它在训练时每一个观察到的数据都需要有标注好的标签。如果我们有一种训练机器学习模型的方法不需要收集标签,会怎么样?如果我们从收集的相同数据中提取标签呢?这种类型的学习算法被称为自监督学习。这种方法在自然语言处理中工作得很好。一个例子是BERT,谷歌自2019年以来一直在其搜索引擎中使用BERT¹。不幸的是,对于计算机视觉来说,情况并非如此。

Facebook AI的kaiming大神等人提出了一种带掩码自编码器(MAE),它基于(ViT) 架构。他们的方法在ImageNet上的表现要好于从零开始训练的VIT。

自去年 11 月份恺明大神提出 MAE 来,大家都被 MAE 简单的实现、极高的效率和惊艳的性能所吸引。近几个月,大家也纷纷 follow 恺明的工作,在 MAE 进行改进(如将 MAE 用到层次 Transformer 结构)或将 MAE 应用于图片之外的数据(如视频、多模态)。

这是何凯明大佬的又一力作=,CV 圈子基本都晓得,当时火爆了整个圈子,所以今天尝试在cifar数据集上进行搭建。

MAE实现及预训练可视化 (CIFAR-Pytorch)

别人的工作是提升了多少点,kaiming 的工作是 best、best、best

自监督学习

在深度学习模型中,数据通常会通过 Backbone 来提取特征,常见的 Backbone 包括 ResNet、ResNeXt 和 Transformer 等。Backbone 之所有能够提取出对任务有用的特征,是因为它通常已经在带标签的大数据集(如 ImageNet)中已经进行训练。然而,人工进行标注数据是昂贵和费时的,如何在没有标注数据的情况下获得一个 strong 的 Backbone 是一个非常重要的问题。

自监督学习(Self-supervised learning)可以解决这个问题,它的目标是基于无标注的数据,设计辅助任务来将数据本身的某一部分的信息作为监督信号,从而基于这个监督信号来训练模型。基于这些无标签的数据,可以学习到一个模型,这个过程可以称为预训练(pre-train)

由于这个预训练之后的模型已经具备一定的知识,因此在进行具体的下游任务时,可以将它作为 Backbone 的初始化,进行下游任务的训练,这个过程成为微调(fine-tuning)。由于模型在预训练阶段已经学习到了一定的知识,因此就可以大大减少微调阶段所需的数据集和训练时间。由于预训练阶段的数据是无需标注的,因此也就大大减少的标注数据的成本。

根据自监督训练阶段的辅助任务不同,可以大致将目前的自监督学习工作分为三类:Data Centric, PredictionContrastive(如下所示)。由于本文介绍的 MAE 的辅助任务为预测 Mask 部分的图像内容,因此属于Prediction 这一类别。

MAE实现及预训练可视化 (CIFAR-Pytorch)

自监督的发展

在详细解读 MAE 之前我们先了解一下视觉自监督发展的背景,在 BEiT 之前视觉自监督一直是对比学习(Contrastive Learning)为主导,如 SimCLR、MoCo v3 等。对比学习说简单点就是让模型学习一种能力,去分辨相同的类型和不同的类型。

MAE实现及预训练可视化 (CIFAR-Pytorch)

拉近相同图片(Aug),疏远不同图片

如上图所示,我们要让模型去拉近 origin image 和经过 Aug 的图片,同时分开和 origin image 不同的图片,这样通过拉近原图和其 Aug 之后的图片,疏远不同的图片达到了对比学习的效果,这样模型就可以学会自己区分相同类型的图片

尽管对比学习在一些 benchmark 上超过了有监督的方法,但是其局限也很明显,过度依赖 data augmentation(数据扩增),不可避免陷入不变性和一致性的矛盾,但是对比学习确实吊打了之前自监督方法(预测旋转上色拼图等)

kaiming(没错又是他)的 MoCo v3 大概算是后对比学习时代的优秀工作之一了。在这个时期微软提出了 BEiT,通过 Masked Image 的方式来做自监督,以此来复制 NLP 领域 Masked Language 的成功,结果确实很成功,ImageNet1k 下Top-1 acc 达到了惊人的 88.6 %,就这样自监督研究风向开始偏向了生成式自监督

MAE实现及预训练可视化 (CIFAR-Pytorch)

BEiT 是一个生成式自监督范式

基于 BEiT 产生了很多优秀的工作,除了本文的 MAE 之外还有 PeCo、SimMIM、MaskedFeat 等生成式自监督算法,也可以说是因为视觉 Transformer 的发展带动了生成式自监督算法发展。

MAE (Masked Autoencoders)

MAE实现及预训练可视化 (CIFAR-Pytorch)

**论文标题:**Masked Autoencoders Are Scalable Vision Learners

论文地址:https://arxiv.org/abs/2111.06377

代码地址:https://github.com/facebookresearch/mae

论文动机:

MAE实现及预训练可视化 (CIFAR-Pytorch)

随着 BERT 的出现,Mask Language Modeling(MLM)的自监督学习方法逐渐进入人们的视野,这一方法在 NLP 领域中得到了广泛的应用。受到 MLM 的启发,一些工作也尝试在图像上进行 Mask Modeling(即,mask 图片的部分区域,然后对区域的内容进行重建),并且也取得了不错的效果。但目前的方法通常都采用对称的 encoder 和 decoder 结构,在 encoder 中,mask token 也需要消耗大量的计算,因此作者提出了一个非对称 encoder-decoder 的结构——masked autoencoders(MAE)。

MAE 方法很简单:mask 输入图像的随机 patch,并重建缺失的像素(上图展示了不同 mask 比率的重建结果)。它基于两个核心设计。首先,作者开发了一种非对称编码器-解码器结构,其中的编码器仅对可见的 patch 子集(不带 mask token)进行操作,而轻量级解码器则从潜在表示和 mask token 重建原始图像。

MAE基于两个核心进行设计的

  • 第一,首先MAE是一个非对称的编码—解码结构,这种不对称是因为encoder只作用在可见的patches,也就没有mask的patches,同时也还有一个轻量级的解码器来重构原始图像。
  • 第二,作者发现,mask比较高的比例,比如说mask75%的patches,这样就会产生一个有意义的自监督任务。这两者结合起来,加速了训练次数,因为原来需要整个图像,当我们mask掉75%的patches以后,我们只剩下了25%的像素,所以训练速度提高了3倍或更多,并且提高了准确性。在论文中,作者利用ImageNet-1K的数据集进行训练,一个普通的v-huge的模型获得了最好的准确率87.8%。在一些目标检测、分类、分割的任务中,效果超过了一些有监督学习预训练的效果,显示了良好的可扩展性。

方法介绍

MAE实现及预训练可视化 (CIFAR-Pytorch)

大道至简的 MAE

MAE 的结构如上图所示,与所有自动编码器一样,MAE 有一个编码器,将观察到的信号映射到潜在表示,还有一个解码器,从潜在表示重建原始信号。与经典的自动编码器不同,作者采用了一种非对称设计,允许编码器仅对部分观察信号(无mask token)进行操作,并采用一种轻量级解码器,从潜在表示和 mask token 重建完整信号。

具体来说,作者首先将图像划分为规则的非重叠 patch。然后,对一个子集的 patch 进行采样,并移除其余的 patch。然后将这些剩余的 patch 送入到编码器中,编码器是一个标准的 ViT 结构,由于编码器只处理很少一部分的 patch,因此可以用很少的计算和显存来训练非常大的编码器。编码器输出 token 后,作者在 mask 的位置加入了可学习的向量,组成完整的全套 token。

此外,作者向这个完整集合中的所有 token 添加位置嵌入;如果没有这一点,mask token 将没有关于其在图像中位置的信息。MAE 解码器仅在预训练期间用于执行图像重建任务(仅编码器用于生成用于识别的图像表示)。因此,可以以独立于编码器设计的方式灵活地设计解码器架构。作者用比编码器更窄、更浅的解码器进行实验。使用这种非对称设计,全套 token 仅由轻量级解码器处理,这大大减少了预训练时间。

MAE流程图

其实很简单,从左到右,将图片 patch 化然后 mask 掉一部分,未 mask 的部分进入 encoder,得到的输出再加上之前 mask 的部分一起进入 decoder 复原图像,目标是复原的图像尽可能接近原图,更详细的东西我们搭建模型时候慢慢讲解

为了方便大家理解,我借鉴了一个流程图带大家实现一个简单的 MAE

MAE实现及预训练可视化 (CIFAR-Pytorch)

MAE 流程图

MAE,可以认为这是一个BERT 的一个 CV 的版本,它基于 ViT ,把整个训练 拓展到没有标号的数据上面,通过完型填空来获取图片的一个理解,它不是第一个将 BERT 拓展到 CV 上,但MAE 很有可能 未来影响最大,BERT 加速了 Transformer 架构 在 NLP 的应用,MAE 加速 Transformer 在 CV 上的应用。

原论文在 ImageNet1k 下使用了 8 机 8 卡跑实验,在21k用了两个集群的TPU,这里我们采用 Cifar10 来作为 MAE 的数据集,这样我们仅需单卡 V100-32g 就可以实现一个简单的 MAE。

搭建 MAE 模型

首先是搭建模型,如上图所示我们先搭建 pretrain 和 finetune 模型,分别是

  1. MAE finetune model

  2. MAE pretrain model

🎯 FAQ:pretrain 和 finetune 都是在干啥?

答:pretrain 用来让模型学习 “复原能力”,即把原图 mask 掉一部分,让模型去学习复原它,在学习复原过程中模型学到了数据内在的表示。finetune 则是将 pretrain 之后的encoder 权重提取出来,利用学习好的权重在 down stream 做微调

🎯 FAQ:encoder 和 decoder 有什么区别?

答:在 pretrain 阶段,encoder 主要用来学习数据内在表征,decoder 主要用来复原图像。encoder 模型大一些,decoder 模型小一些。它们都是 ViT 的架构

MAE 组网

因为 encoder 和 decoder 都是 ViT 的架构,需要先搭建 ViT 需要的模块,如果想详细了解ViT的话,可以看我另一篇博客。Pytorch CIFAR10图像分类 Vision Transformer(ViT) 篇

首先我们可以充分利用当前timm中的各个模型架构,不过我们也可以自己进行定义,我这里不进行讲解。给出部分代码

from timm.models.vision_transformer import Block

MAE 预训练(pretrain)

Encoder

记住最重要的一点,Encoder 仅处理可见(unmasked)的 patches。Encoder 本身可以是 ViT 或 ResNet(其它 backbone 也 ok,不过paper中是ViT,我们也用ViT),至于如何将图像划分成 patch 嘛,使用 ViT 时的套路是这样的:

先将图像从 (B,C,H,W) reshape 成 (B, N, PxPxC),其中 N 和 P 分别为 patch 数量 和 patch 大小 (
N
=
H
P
×
W
P
N = \frac{H}{P} \times \frac{W}{P}
N=PH×PW
),也就是将3通道的图像转换成 N 个 维度大小为 PxPxC 的向量;然后,通过线性映射(linear projection,可以是全连接层)将其嵌入(embed)到指定的维度空间大小,记为 ‘dim’(从 PxPxC project 到 dim),转换成为 token(B,N,dim);最后再加上位置嵌入(position embedding),从而为各个 patch 添加位置信息。位置嵌入是所有图像共享的、可学习的,shape 与 每张图的 token 相对应,即:(N,dim)。

由于 unmasked 的 patches 所有 patches 的少数,因此可以训练很大的 Encoder,因为计算和空间要求都减少了。

接着我们就可以构建我们的MAE Encoder了

class MAE_Encoder(torch.nn.Module):
    def __init__(self,
                 image_size=32,
                 patch_size=2,
                 emb_dim=192,
                 num_layer=12,
                 num_head=3,
                 mask_ratio=0.75,
                 ) -> None:
        super().__init__()
        self.cls_token = torch.nn.Parameter(torch.zeros(1, 1, emb_dim))
        self.pos_embedding = torch.nn.Parameter(torch.zeros((image_size // patch_size) ** 2, 1, emb_dim))
        # 对patch进行shuffle 和 mask
        self.shuffle = PatchShuffle(mask_ratio)
        # 这里得到一个 (3, dim, patch, patch)
        self.patchify = torch.nn.Conv2d(3, emb_dim, patch_size, patch_size)
        self.transformer = torch.nn.Sequential(*[Block(emb_dim, num_head) for _ in range(num_layer)])
        # ViT的laynorm
        self.layer_norm = torch.nn.LayerNorm(emb_dim)
        self.init_weight()
    # 初始化类别编码和向量编码
    def init_weight(self):
        trunc_normal_(self.cls_token, std=.02)
        trunc_normal_(self.pos_embedding, std=.02)
    def forward(self, img):
        patches = self.patchify(img)
        patches = rearrange(patches, 'b c h w -> (h w) b c')
        patches = patches + self.pos_embedding
        patches, forward_indexes, backward_indexes = self.shuffle(patches)
        patches = torch.cat([self.cls_token.expand(-1, patches.shape[1], -1), patches], dim=0)
        patches = rearrange(patches, 't b c -> b t c')
        features = self.layer_norm(self.transformer(patches))
        features = rearrange(features, 'b t c -> t b c')
        return features, backward_indexes

Decoder

Decoder它不仅需要处理经过 Encoder 编码的 unmasked 的 tokens,还需要处理mask tokens。但请注意,mask token 并非由之前 mask 掉的 patch 经过 embedding 转换而来,而是可学习的、所有 masked patch 都共享的1个向量,对,仅仅就是1个!

那么你会问:这样如何区分各个 maked patch 所对应的 token 呢?

别忘了,我们还有 position embedding 嘛!如同在 Encoder 中的套路一样,这里对于 mask token 也需要加入位置信息。position emebdding 是每个 masked patch 对应1个,shape 是 (N’,dim),其中 N’ 是 masked patch 的数量。但 mask token 只有1个怎么办是不是?简单粗暴——“复制”多份即可,使得每个 masked patch 都对应1个 mask token,这样就可以和 position embedding 进行相加了。

另外,Decoder 仅仅是在预训练任务为了重建图像而存在,而我们的下游任务形式多样,因此实际应用时很可能没 Decoder 什么事了(和它 say byebye 咯~)。

所以,Decoder 的设计和 Encoder 是解耦的,Decoder 可以设计得简单、轻量一些(比 Encoder 更窄、更浅。窄:对应通道数;浅:对应深度),毕竟真正能学习到潜在特征表示的是 Encoder

这样,尽管 Decoder 要处理的 token 数很多(全量token,而 Encoder 仅处理 unmasked 的部分),但其本身轻量,所以还是能够高效计算。再结合 Encoder 虽然本身结构重载(相对 Decoder 来说),但其处理的 token 较少,这样,整体架构就十分 efficient 了,漂亮~!

class MAE_Decoder(torch.nn.Module):
    def __init__(self,
                 image_size=32,
                 patch_size=2,
                 emb_dim=192,
                 num_layer=4,
                 num_head=3,
                 ) -> None:
        super().__init__()
        self.mask_token = torch.nn.Parameter(torch.zeros(1, 1, emb_dim))
        self.pos_embedding = torch.nn.Parameter(torch.zeros((image_size // patch_size) ** 2 + 1, 1, emb_dim))
        self.transformer = torch.nn.Sequential(*[Block(emb_dim, num_head) for _ in range(num_layer)])
        self.head = torch.nn.Linear(emb_dim, 3 * patch_size ** 2)
        self.patch2img = Rearrange('(h w) b (c p1 p2) -> b c (h p1) (w p2)', p1=patch_size, p2=patch_size, h=image_size//patch_size)
        self.init_weight()
    def init_weight(self):
        trunc_normal_(self.mask_token, std=.02)
        trunc_normal_(self.pos_embedding, std=.02)
    def forward(self, features, backward_indexes):
        T = features.shape[0]
        backward_indexes = torch.cat([torch.zeros(1, backward_indexes.shape[1]).to(backward_indexes), backward_indexes + 1], dim=0)
        features = torch.cat([features, self.mask_token.expand(backward_indexes.shape[0] - features.shape[0], features.shape[1], -1)], dim=0)
        features = take_indexes(features, backward_indexes)
        features = features + self.pos_embedding # 加上了位置编码的信息
        features = rearrange(features, 't b c -> b t c')
        features = self.transformer(features)
        features = rearrange(features, 'b t c -> t b c')
        features = features[1:] # remove global feature 去掉全局信息,得到图像信息
        patches = self.head(features) # 用head得到patchs
        mask = torch.zeros_like(patches)
        mask[T:] = 1  # mask其他的像素全部设为 1
        mask = take_indexes(mask, backward_indexes[1:] - 1)
        img = self.patch2img(patches) # 得到 重构之后的 img
        mask = self.patch2img(mask)
        return img, mask

总结

最后可以总结这整一个流程,然后构建模型

  1. 将图像划分成 patches:(B,C,H,W)->(B,N,PxPxC);
  2. 对各个 patch 进行 embedding(实质是通过全连接层),生成 token,并加入位置信息(position embeddings):(B,N,PxPxC)->(B,N,dim);
  3. 根据预设的掩码比例(paper 中提倡的是 75%),使用服从均匀分布的随机采样策略采样一部分 token 送给 Encoder,另一部分“扔掉”(mask 掉);
  4. 将 Encoder 编码后的 token 与 加入位置信息后的 mask token 按照原先在 patch 形态时对应的次序拼在一起,然后喂给 Decoder 玩(如果 Encoder 编码后的 token 的维度与 Decoder 要求的输入维度不一致,则需要先经过 linear projection 将维度映射到符合 Decoder 的要求);
  5. Decoder 解码后取出 mask tokens 对应的部分送入到全连接层,对 masked patches 的像素值进行预测,最后将预测结果与 masked patches 进行比较,计算 MSE loss
class MAE_ViT(torch.nn.Module):
    def __init__(self,
                 image_size=32,
                 patch_size=2,
                 emb_dim=192,
                 encoder_layer=12,
                 encoder_head=3,
                 decoder_layer=4,
                 decoder_head=3,
                 mask_ratio=0.75,
                 ) -> None:
        super().__init__()
        self.encoder = MAE_Encoder(image_size, patch_size, emb_dim, encoder_layer, encoder_head, mask_ratio)
        self.decoder = MAE_Decoder(image_size, patch_size, emb_dim, decoder_layer, decoder_head)
    def forward(self, img):
        features, backward_indexes = self.encoder(img)
        predicted_img, mask = self.decoder(features,  backward_indexes)
        return predicted_img, mask

测试MAE

最后测试一下,是否代码正确

shuffle = PatchShuffle(0.75)
a = torch.rand(16, 2, 10)
b, forward_indexes, backward_indexes = shuffle(a)
print(b.shape)
img = torch.rand(2, 3, 32, 32)
encoder = MAE_Encoder()
decoder = MAE_Decoder()
features, backward_indexes = encoder(img)
print(forward_indexes.shape)
predicted_img, mask = decoder(features, backward_indexes)
print(predicted_img.shape)
loss = torch.mean((predicted_img - img) ** 2 * mask / 0.75)
torch.Size([4, 2, 10])
torch.Size([16, 2])
torch.Size([2, 3, 32, 32])

MAE 微调(finetune)

MAE finetune 模型和 ViT 模型是一样的,不同之处是后续处理部分,ViT 是提取 cls token 做分类,MAE finetune 模型则是将 patches token(除了 cls token 之外) 做 mean 然后分类

所以很简单,我就定义了一个分类器,其实就是接受encoder的输出,接着输入分类器即可,并且,我们已经设置了我们的类别,因为cifar10的类别是十个。

class ViT_Classifier(torch.nn.Module):
    def __init__(self, encoder : MAE_Encoder, num_classes=10) -> None:
        super().__init__()
        self.cls_token = encoder.cls_token
        self.pos_embedding = encoder.pos_embedding
        self.patchify = encoder.patchify
        self.transformer = encoder.transformer
        self.layer_norm = encoder.layer_norm
        self.head = torch.nn.Linear(self.pos_embedding.shape[-1], num_classes)
    def forward(self, img):
        patches = self.patchify(img)
        patches = rearrange(patches, 'b c h w -> (h w) b c')
        patches = patches + self.pos_embedding
        patches = torch.cat([self.cls_token.expand(-1, patches.shape[1], -1), patches], dim=0)
        patches = rearrange(patches, 't b c -> b t c')
        features = self.layer_norm(self.transformer(patches))
        features = rearrange(features, 'b t c -> t b c')
        logits = self.head(features[0])
        return logits

MAE 预训练

接着我们就可以开始准备Cifar10 数据集

现在我们用搭建好的模型来试一下 Cifar10 数据集把

在我们训练的过程中,我们的目的是重构图片,在这之后进行学习,我利用了tensorboard可视化,我们可以看一看结果

在我们的epoch为225时,我们可以看到,模型已经能够基本构建一个轮廓信息了,但是可能还是不够好

MAE实现及预训练可视化 (CIFAR-Pytorch)

接着我们看到达到2000次的时候,模型已经能够较好的重构出图像,说明学习到了很多信息了

MAE实现及预训练可视化 (CIFAR-Pytorch)

我们可以看到,经过 pretrain 之后的 mae 可以大致复原出原图像轮廓,令人吃惊的是这仅仅只用了原图像的 25% 像素,正如 mae 论文所说的,“与 language 不同,image 具有很高的冗余性”

如果你问论文为什么选mask ratio 0.75?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uD5jSBt4-1663202645416)(https://ai-studio-static-online.cdn.bcebos.com/3f49ce2f0a7b4154ba94318513fa2c79fd5bdd8d9556438a9e9ebb612b6f32ca)]

ratio=0.75 性能更好,不论是训练整个模型的 fine-truning ,还是冻结权重只微调最后分类头的 linear probing,mask ratio 0.75 都取得了良好的性能

除此之外,我一共迭代了2000次,也有损失曲线,也就是我们重构损失。下面是损失函数曲线,越到后面,我们会发现,模型基本收敛了,下降的不多了。(提一下,训练了2天,也可以减少预训练的次数,也能达到不错的结果)。

MAE实现及预训练可视化 (CIFAR-Pytorch)

MAE 微调

MAE 微调有两种,一个是对整个模型进行 finetune,加载的权重参与更新,一个是 linear prob,加载的权重不参与更新,只更新最后的分类头部分

首先进行了自监督的预训练,这时候是不需要标记的,然后会将encoder用在ViT的图像分类中,做一些下游的任务,也就是利用训练好的encoder作为特征提取的部分,去掉编码器,在ViT中进行分类

这里我们用 cifar10 分类做 finetune 微调,训练 epoch 为100,我们可以自己进行调参以获得更好的性能,也可以尝试 linear prob。

为了比较MAE分类的性能,用一样的训练数据,这里我们做了两个训练

  • 第一个是将分类器进行单独训练,也就是从0开始训练

MAE实现及预训练可视化 (CIFAR-Pytorch)

  • 第二个就是将MAE对图片进行处理,不更新他的权重,将其的encoder的输出作为输入,进行训练。

    MAE实现及预训练可视化 (CIFAR-Pytorch)

  • 我们可以一起比较一下,我们会发现,首先MAE的架构会更快的收敛,并且最后的准确率也是远远大于我们从0开始训练的,除此之外,他还使得模型有更好的泛化性,在迭代次数加深的时候,模型的损失不会一直上升,有很好的泛化性。

    MAE实现及预训练可视化 (CIFAR-Pytorch)

Result 结果

最后我们可以看到结果,确实MAE得到了很不错的结果,是一个非常好的思路,同时得到很好的结果。

Model Validation Acc
ViT-T pretrain (scratch 从0开始训练) 74.13
ViT-T pretrain 89.77

总结

本项目简单实现了MAE 在 cifar10 数据集上的训练,MAE 表现了令人惊讶的重建能力,进一步说明图像相比语言具有更冗余的信息,作者认为像素信息具有连续性。

其实仔细研究一下模型,会发现 mae 在降低计算量上面是很优雅的,encoder 部分计算的 token 数是经过 masked 的 token,即原来的四分之一(mask ratio 0.75),这大大降低了计算复杂度,同时用于重建的 decoder 模型深度很浅,尽管进入 decoder 的 token 数几乎是原 token 数,但是其带来的计算复杂度在可接受的范围,是非常成功的模型。

感谢和体会

这次实验也让我看到了一个新的方向,从有监督学习到自监督学习的转变,也有可能之后会还往无监督学习中进行发展。也看到了一个从原始的CNN,到利用Transformer来得到更好的结果,但是可能不好的是,虽然说Transformer的精度比较高,不过有时候需要很大很大的数据集,并且速度可能一般,如果Transformer在保持精度的情况下能够达到的试试的效果可能会统治整个CV界,不过已经看到了Transformer的各种在计算机视觉中的各种应用了,还是非常好的。

除此之外,在后面的论文中,基于MAE重构像素,展开了很多的自监督学习的展开,比如MAE出现的一个月后,北大Chen Wei发现,重构图像的HOG,也就是方向梯度直方图比重构像素能得到更好的结果,并且因此在众多下游任务中,得到了12个SOTA,称为MaskFeat,简而言之,MaskFeat的ViT-B在ImageNet 1K上的准确率达到了84.0%,MViT-L在Kinetics-400上的准确率达到了86.7%,成功地超越了MAE,BEiT和SimMIM等方法。

MAE实现及预训练可视化 (CIFAR-Pytorch)

有人评价说,视觉自监督领域做了这么些年,从最早的生成式学习出发,绕了一圈,又回到生成式学习。到头来,我们发现像素级特征跟各种手工特征、tokenizer、甚至离线预训练网络得到的特征,在作为判断生成图像质量方面,没有本质区别。也就是说,自监督也许只是把模型和参数调得更适合下游任务,但在「新知识从哪里来」这个问题上,并没有任何实质进展。这也可能是这样的,不过具体可能得不断的调节,比如说,如何在CNN设计Mask,虽然说何凯明也在论文中说,CNN是很难进行mask的,因为卷积是一个滑动窗口,CNN 在一张图片上,使用一个卷积窗口、不断地平滑,来汇聚一些像素上面的信息 + 模式识别,而卷积窗口扫过来、扫过去时,无法区分边界,无法保持 mask 的特殊性,无法拎出来 mask,最后从掩码信息很难还原出来。不过自监督学习能得到这样好的效果,说明这是一种趋势,如果考虑如何从一个更前沿的方法来得到更好的结果,可能就是之后的目标,融合多种特征,结合多个tricks。

在这次实验,我尝试着往比较前沿的方法去学习和发展,而没有拘泥于下游任务的图像分类、目标检测、图像分割等,在这里面也学到了更多东西,知道了一些没想过的,我觉得这也是符合人的,在我们没认识到一些东西的时候,我们是被mask的,当我们或者一些信息的时候,我们也会进行一些重构,得到一些东西,我们要做的就是需要,让我们的自监督学习学习学的更好。