python 类型注释 # type

Type Comments[类型注解]

注释是在Python 3中引入的,并且它们没有被反向移植到Python 2.这意味着如果您正在编写需要支持旧版Python的代码,则无法使用注释。

要向函数添加类型注释,您可以执行以下操作:

import math 
def circumference(radius):    
# type: (float) -> float    
   return 2 * math.pi * radius

类型注释只是注释,所以它们可以用在任何版本的Python中。

类型注释由类型检查器直接处理,所以不存在__annotations__字典对象中:

>>> circumference.__annotations__{}

类型注释必须以type: 字面量开头,并与函数定义位于同一行或下一行。如果您想用几个参数来注释一个函数,您可以用逗号分隔每个类型:

def headline(text, width=80, fill_char="-"):  
  # type: (str, int, str) -> str    
   return f" {text.title()} ".center(width, fill_char) 

print(headline("type comments work", width=40))

您还可以使用自己的注释在单独的行上编写每个参数:

# headlines.py
 
  def headline(
      text,           # type: str
      width=80,       # type: int
      fill_char="-",  # type: str
  ):                  # type: (...) -> str
      return f" {text.title()} ".center(width, fill_char)
 
 print(headline("type comments work", width=40))

通过Python和Mypy运行示例:

$  python headlines.py
---------- Type Comments Work ---------- 
$ mypy headline.py
$

如果传入一个字符串width=”full”,再次运行mypy会出现一下错误。

$ mypy headline.py
headline.py:10: error: Argument "width" to "headline" has incompatible
                       type "str"; expected "int"

您还可以向变量添加类型注释。这与您向参数添加类型注释的方式类似:

pi = 3.142  # type: float

上面的例子可以检测出pi是float类型。

A ConvNet for the 2020s

paper:https://arxiv.org/abs/2201.03545 CVPR 2022.Facebook AI Research

github:https://github.com/facebookresearch/ConvNeXt

作者提出了ConvNeXt,一个完全由标准 ConvNet 模块构建的纯 ConvNet 模型。ConvNeXt 准确、高效、可扩展且设计非常简单。

2020年以来,ViT一直是研究热点。ViT在图片分类上的性能超过卷积网络的性能,后续发展而来的各种变体将ViT发扬光大(如Swin-T,CSwin-T等),值得一提的是Swin-T中的滑窗操作类似于卷积操作,降低了运算复杂度,使得ViT可以被用做其他视觉任务的骨干网络,ViT变得更火了。本文探究卷积网络到底输在了哪里,卷积网络的极限在哪里。在本文中,作者逐渐向ResNet中增加结构(或使用trick)来提升卷积模型性能,最终将ImageNet top-1刷到了87.8%。作者认为本文所提出的网络结构是新一代(2020年代)的卷积网络(ConvNeXt),因此将文章命名为“2020年代的卷积网络”。

作者的出发点时Resnet-50模型。首先使用用于训练视觉变压器的类似训练技术训练它,与原始Resnet-50相比,获得了较大的改进效果。这将是我们的基线。然后,我们研究了一系列设计决策,总结为1)宏观设计,2)Resnext,3)inverted bottleneck,4)使用大的核,以及5)各种层的微型设计。

1、训练技巧:

作者认为,除了网络结构,训练技巧也会影响最终的效果,除了vision Transformers的结构 ,vision Transformers的一些训练技巧也给作者带来 一些启发。作者使用swin transformer的训练技巧,应用在基线模型中:

1、增加 epoch到300

2、使用AdamW优化器

3、数据增强技术(cutmix、mixup等等)

结果分类准确率由76.1%上升到78.8%。具体训练config如下:

2、宏观设计

作者借鉴了Swin-T的两个设计:

  1. 每阶段的计算量(调整每个阶段block数量)
  2. 对输入图片下采样方法

对于第一点类似Swin-T四个阶段1:1:9:1的计算量,作者将ResNet-50每个阶段block数调整为3,3,9,3(原来为3,4,6,3),增加第三阶段计算量,准确率由78.8%提升至79.4%。

这个每阶段计算量的设计:感觉很多模型都是在中间部分的计算量最多 ,两头的计算量最小,这种设计的效果最好。

对于第二点Swin-T融合压缩2×2的区域,作者则使用4×4步长为4的卷积对输入图片进行下采样,这样每次卷积操作的感受野不重叠,准确率由79.4%提升至79.5%。

3、类ResNeXt设计

depthwise conv中的逐channel卷积操作和self-attention中的加权求和很类似,因此作者采用depthwise conv替换普通卷积。参照ResNeXt,作者将通道数增加到96,准确率提升至80.5%,FLOPs相应增大到了5.3G。相比之下原始的ResNet-50 FLOPs为4G,运算量增大很多。

4、Inverted Bottleneck

在depthwise conv的基础上借鉴MobileNet的inverted bottleneck设计(维度先扩增4倍在缩减),将block由下图(a)变为(b)。因为depthwise不会使channel之间的信息交互,因此一般depthwise conv之后都会接1 × 1 × C的pointwise conv。这一顿操作下来准确率只涨了0.1%到80.6%。在后文说明的大模型上涨点多一点。

Moving up depthwise conv layer

首先,考虑到卷积核太大会导致计算复杂度上升,不方便作者去寻找大卷积核。因此作者借鉴transformer里面MSA block 放在了1*1卷积之前,把7*7的 depthwise conv layer 放在1*1卷积之前,这样, depthwise conv layer 的通道数下降,相应计算量也下降,但性能下降到了79.9%

增大卷积kernel

作者认为更大的感受野是ViT性能更好的可能原因之一,作者尝试增大卷积的kernel,使模型获得更大的感受野。首先在pointwise conv的使用上,作者为了获得更大的感受野,将depthwise conv提前到1 × 1 conv之前,之后用384个1 × 1 × 96的conv将模型宽度提升4倍,在用96个1 × 1 × 96的conv恢复模型宽度。反映在上图中就是由(b)变为(c)。由于3×3的conv数量减少,模型FLOPs由5.3G减少到4G,相应地性能暂时下降到79.9%。

然后作者尝试增大depthwise conv的卷积核大小,证明7×7大小的卷积核效果达到最佳

其他乱七八糟的尝试

借鉴最初的Transformer设计,作者将ReLU替换为GELU;ViT的K/Q/V计算中都没有用到激活函数和归一化层,于是作者也删除了大量的激活函数和归一化层,仅在1 × 1卷积之间使用激活函数,仅在7 × 7卷积和1 × 1 卷积之间使用归一化层,同时将BN升级为LN。最终block结构确定如下:

顺便复习一下各种归一化方法:

最后仿照Swin-T,作者将下采样层单独分离出来,单独使用2 × 2卷积层进行下采样。为保证收敛,在下采样后加上Layer Norm归一化。最终加强版ResNet-50准确率82.0%(FLOPs 4.5G)。

总的来说ResNet-50、本文模型和Swin-T结构差别如下:

实验结果

作者在ResNet-50加强版的基础上又提出了多个变体(ConvNeXt-T/B/L/XL),从实验结果上看ResNet-50加强版性能收益较为突出,越是大模型性能收益越低。

在检测、分割下游任务中ConvNeXt也获得了与Swin-T相似或更好的结果。结果就不细说了。

消融实验

每一部分具体涨点效果如下:

总的来说本文实验做的比较充分,总结一下,卷积网络涨点可以尝试:

  1. 对输入下采样时尝试无重叠小一点的卷积层,例如4 × 4,stride=4的卷积;
  2. block中采用大卷积核,例如7 × 7;
  3. depthwise conv + inverted bottleneck + moving up depthwise layer的block结构;
  4. 减少激活/归一化层,ReLU换成GELU,BN换成LN;
  5. 使用2×2 conv + LN下采样。

Vision MLP –Pay Attention to MLPs

MLP-Mixer的增强版,带gating的MLP。有两个版本,分别是gMLP和aMLP。Pay-Attention-to-MLPs是gMLP版本,同时也提出了gMLP的增强版aMLP。

paper: https://arxiv.org/abs/2105.08050

github: https://github.com/antonyvigouret/Pay-Attention-to-MLPs

此文和最近刊出MLP文章相同,旨在探究self-attention对于Transformer来说是否至关重要。并在CV和NLP上的相关任务进行实验。

Transformer结构具有可并行化汇聚所有token间的空间信息的优点。众所周知self-attention是通过计算输入间的空间关系动态的引入归纳偏置,同时被静态参数化的MLP能表达任意的函数,所以self-attention对于Transformer在CV和NLP等领域的成功是否是至关重要的呢?

  • 此文提出了一个基于MLP的没有self-attention结构名为gMLP,仅仅存在静态参数化的通道映射(channel projections)和空间映射(spatial projections)。同时作者通过实验发现当对空间映射的线性结果进行门机制乘法得到的效果最好
  • 此文使用gMLP做图片分类并在ImageNet上取得了与DeiT、ViT等Transformer模型相当的效果。与先前的MLP模型MLP-Mixer相比,gMLP做到了参数更少(参数减少66%)效果更强(效果提升3%)。
  • 此文使用gMLP做masked language modeling,gMLP采用和Bert一样的设置最小化perplexity取得了和Transformer模型预训练一样好的效果。通过pretraining和finetuning实验发现随着模型容量的增加,gMLP比Transformer提升更大,表明模型相较于self-attention可能对于模型容量的大小更为敏感。
  • 对于需要跨句对齐的微调任务MNLI,gMLP与Transformer相比逊色一筹。对此作者发现加上一个128特征大小的单头注意力足以使得gMLP在任何NLP任务上取得比Transformer更好的效果。

gMLP由L个如下图所示的模块堆叠而成

设每个模块的输入 \(X \in \mathbb{R}^{n \times d}\), n为序列长度, d为特征维度。每个模块表达如下:
\(Z=\sigma(X U), \quad \tilde{Z}=s(Z), \quad Y=\tilde{Z} V\)
\(\sigma\) 是GELU等激活函数, U 和 V 和Transformer中的FFN类似都是线性映射。为了简洁表达上式中 省略了shortcuts, normalizations 和 biases。
上式中最重要的是能捕捉空间交互的 \(s(\cdot)\) 。如果上式去掉 \(s(\cdot)\) 那么将不再能进行空间交互和FFN 并无区别。文中作者选择名为 Spatial Gating Unit (SGU) 的模块作为 \(s(\cdot)\) 捕捉空间依赖。另外,gMLP在NLP、CV任务中遵循与BERT、ViT一样的输入输出规则。

Spatial Gating Unit:

为了能有跨token的交互, \(s(\cdot)\) 操作须在空间维度。可以简单的使用线性映射表示:
\(f_{W, b}(Z)=W Z+b\)
其中 \(W \in \mathbb{R}^{n \times n}\) 表示空间交互的映射参数。在self-attention中 W 是通过 Z 动态计算得到的。 此文对上式使用gating操作以便更好的训练,如下所示:
\(s(Z)=Z \odot f_{W, b}(Z)\)
为了训练更稳定,作者将 W 和 b 分别初始化为接近 0 与 1 来保证在开始训练时 \(f_{W, b} \approx 1\) 、 \(s(Z z) \approx Z\) 使得在开始阶段gMLP近似于FFN并在训练中逐渐学习到跨token的空间信息。
作者进一步发现将 Z 从通道维度分割成两部分 \(\left(Z_1, Z_2\right)\) 进行gating操作更有用,如下所示:
\(
s(Z)=Z_1 \odot f_{W, b}\left(Z_2\right)
\)
另外函数 \(f_{W, b}\)的输入通常需要normalizel以此提升模型的稳定性。

一些思考:这里的SpatialGatingUnit里面用到了一个通道split,然后再将分割后的两部分做乘法,让我想到了NAFnet中的simplegate,这个的作用一是减少计算量(相比于GELU)、另外引入门控机制,在通道维度进行通道交织,对于模型的效果表现很好。

作者进一步分析了SGU与现有的一些操作的相似之处:首先是Gated Linear Units (GLU) 与 SGU的区别在于SGU对spatial dimension而GLU对channel dimension; 其次SGU和
Squeeze-and-Excite (SE) 一样使用hadamard-product,只是SGU并没有跨通道的映射来保 证排列不变性;SGU的空间映射可以看作depthwise convolution不过SGU只学习跨通道只是, 并没有跨通道过滤器;SGU学习的是二阶空间交互 \(z_i z_j\) , self-attention学习的是三阶交互 \(q_i k_j v_k\) , SGU的复杂度为 \(n^2 e / 2\) 而self-attention的复杂度为 \(2 n^2 d_{\text {。 }}\)

实验:

1、Image Classification

此文首先将gMLP应用于图片分类,使用ImageNet数据集而且不使用额外数据。下表首先展示了gMLP用于图片分类的参数,gMLP和ViT/B16一样使用 16×16 个patch,同时采用和DeiT相似的正则化方法防止过拟合。

下表中gMLP与baselines在ImageNet上的结果表示gMLP取得了与视觉Transformer相当的结果,同时与其它MLP视觉模型相比,gMLP取得了准确率、速度权衡下最好的结果。

Masked Language Modeling with BERT:

此文同时将gMLP应用于masked language modeling(MLM)任务,对于预训练和微调任务,模型的输入输出规则都保持与BERT一致。

作者观察到在MLM任务最后学习到的空间映射矩阵总是Toeplitz-like matrics,如下图所示。所以作者认为gMLP是能从数据中学习到平移不变性的概念的,这使得gMLP实质起到了卷积核是整个序列长度的1-d卷积的作用。在接下来的MLM实验中,作者初始 W 为Toeplitz matrix。

Ablation: The Importance of Gating in gMLP for BERT’s Pretraining:下表展示了gMLP的各种变体与Transoformer模型、MLP-Mixer的比较,可以看到gMLP在与Transformer相同模型大小的情况下能达到与Transformer相当的效果。同时gating操作对于空间映射十分有用。同时下图还可视化了模型学习到的空间映射参数。

Case Study: The Behavior of gMLP as Model Size Increases:下表与下图展示了gMLP随着模型增大逐渐能有与Transformer相当的效果,可见Transformer的效果应该主要是依赖于模型尺寸而非self-attention。

  • Ablation: The Usefulness of Tiny Attention in BERT’s Finetuning:从上面的Case Study可以发现gMLP对于需要跨句子连接的finetuing任务可能不及Transformer,所以作者提出了gMLP的增强版aMLP。aMLP相较于gMLP仅增加了一个单头64的self-attention如下图所示:

从下图结果可以发现aMLP相较于gMLP极大提升了效果并在所有task超过了Transformer。

Vision MLP –ResMLP

Feedforward networks for image classification with data-efficient training

我们提出了ResMLP,一种完全基于多层感知机(MLP)进行图像分类的体系结构。 它是一个简单的残差网络,它交替(i)线性层,其中图像 patches在通道之间独立且相同地交互;以及(ii)两层前馈网络,其中通道中的每个 patch独立地相互作用。

CODE:

import torch
import numpy as np
from resmlp import ResMLP

img = torch.ones([1, 3, 224, 224])

model = ResMLP(in_channels=3, image_size=224, patch_size=16, num_classes=1000,
                 dim=384, depth=12, mlp_dim=384*4)

parameters = filter(lambda p: p.requires_grad, model.parameters())
parameters = sum([np.prod(p.size()) for p in parameters]) / 1_000_000
print('Trainable Parameters: %.3fM' % parameters)

out_img = model(img)

print("Shape of out :", out_img.shape)  # [B, in_channels, image_size, image_size]

本文作者提出了一种基于全连接层的图像分类网络。网络结构与MLP-Mixer相似,即先将输入图像拆分成若干patch,对每个patch通过全连接层转换为特征嵌入矩阵,该矩阵的两个维度分别表示channel维度(每个局部位置的特征维度)和patch维度(表示局部位置的维度)。首先将该矩阵转置后沿patch维度进行全连接层运算,实现不同patch之间的交互;再沿channel维度进行全连接运算,实现不同channel之间的交互。最后使用池化层和输出层获得分类结果。本文与MLP-Mixer的不同之处在于采用了更强的数据增强方法和蒸馏策略。

当采用现代的训练策略进行训练时,使用大量的数据增广和可选的蒸馏方法,可以在ImageNet上获得令人惊讶的良好精度/复杂度折衷。

Affine仿射变换:

函数名称:diag(x)
函数功能:构建一个n维的方阵,它的主对角线元素值取自向量x,其余元素都为0

Vision MLP系列–RepMLP

RepMLPNet: Hierarchical Vision MLP with Re-parameterized Locality (https://arxiv.org/abs/2112.11081)

CVPR 2022

RepMLP Block

Github source: https://github.com/DingXiaoH/RepMLP

最近公开了一系列视觉MLP论文,包括RepMLP、MLP-Mixer、ResMLP、gMLP等。在这个时间点出现关于MLP的一系列讨论是很合理的:

1) Transformer大火,很多研究者在拆解Transformer的过程中多多少少地对self-attention的必要性产生了疑问。去掉了self-attention,自然就剩MLP了。

2) 科学总是螺旋式上升的,“复兴”老方法(比如说另一篇“复兴”VGG的工作,RepVGG)总是喜闻乐见的。

这些论文引发了热烈的讨论,比如:

1) 这些模型到底是不是MLP?

2) 卷积和全连接(FC)的区别和联系是什么?FC是不是卷积,卷积是不是FC?

3) 真正的纯MLP为什么不行?

4) 所以MLP is all you need?

《RepMLP: Re-parameterizing Convolutions into Fully-connected Layers for Image Recognition》。这篇文章讲了一个全连接层找到一份陌生的工作(直接进行feature map的变换),为了与那些已经为这份工作所特化的同胞(卷积层)们竞争,开始“内卷”的故事。

关键贡献在于,RepMLP用卷积去增强FC,既利用其全局性又赋予其局部性,并通过结构重参数化,将卷积融合到FC中去,从而在推理时去除卷积。

论文:RepMLP: Re-parameterizing Convolutions into Fully-connected Layers for Image Recognition

代码:DingXiaoH/RepMLP

1. 为什么真正的纯MLP不太行?

我们一般认为多层感知机(MLP)是至少两层全连接层(FC)堆叠得到的模型,而且一般把同时含有卷积和MLP的模型(或模型中的一个模块)称为CNN。尽管目前大家对什么叫MLP的问题尚有争议(下图),我们不妨先定义一个任何人都会称之为MLP的100%纯MLP:

这个MLP在ImageNet上的输入是(3, 112, 112),第一层将其变为(32, 56, 56),第二层将其变为(64, 28, 28),然后global average pool,然后经过FC映射为1000类。这样总共只有三个FC,毫无疑问是MLP。这三层的参数为:

第一层:3x112x112x32x56x56 = 3.77G 参数

第二层:32x56x56x64x28x28 = 5.03G 参数

第三层:64×1000 = 64k 参数,忽略不计

看起来有点吓人,但这确实是一个处于A1位置的纯MLP应有的体量,虽然它只有两层,而且通道数只有32和64。除了减小通道数量,任何试图减小参数量的改动都将使其不再属于A1位置。比如说:

1) 先切块。把112×112的输入切成56×56的四块,每一块经过第一层变成28×28,再拼起来,这样第一层的参数量变成了3x56x56x32x28x28=236M,看起来好多了。但是,这破坏了全局性,因为分属于两块中的两点之间不再有联系了!换句话说,我们引入了一种局部性:一张图切成四块之后,每块中的任一像素只跟同块中的其他像素有联系。ViT,RepMLP和其他几篇MLP都用了这种操作或某种类似的操作。

2) 分组FC。正如卷积有分组卷积一样,FC也可以分组。由于torch里没有现成的算子,分组FC可以用分组1×1卷积实现。组数为g,参数量和计算量就会变成1/g。可惜,这也引入了局部性。RepMLP用了这种操作。

3) 把一个FC拆分成两次操作,第一次操作对channel维度线性重组,spatial共享参数(等价于1×1卷积);第二次操作对spatial维度线性重组,channel共享参数(等价于先转置后1×1卷积)。这思想可以类比于depthwise conv + 1×1 conv。MLP-Mixer使用这种操作,用两个各自都不具有全局性的操作实现了整体的全局性(而RepMLP使用另一种不同的机制,对不同的分块做pooling再连接,实现了这种全局性)。

所以,真正的100%纯MLP不太行,大家都在用各种花式操作做“伪MLP”的原因之一,就是体量太大。

这篇文章介绍的RepMLP属于B2的位置,不追求纯MLP。称其为“MLP”的原因是想强调卷积和FC的区别:RepMLP将卷积看成一种特殊的FC,显式地用卷积去强化FC(把FC变得具有局部性又不失全局性),指出了这样的FC强在哪里(如ResNet-50中,用一半通道数量的RepMLP替换3×3卷积就可以实现同等精度和55%加速),并用这种强化过的FC(及一些其他技巧)构造一种通用的CNN基本组件,提升多重任务性能。论文中说明了这里MLP的意思是推理时结构“不包含大于1×1的卷积”。

2. RepMLP:FC“内卷”,卷出性能

真正的100%纯MLP不太行的原因之二,是不具有局部先验。

在一张图片中,一个像素点跟它周围的像素点的关系往往比远在天边的另一个像素点更密切,这称为局部性。人类在识别图片的时候潜意识地利用这一点,称为局部先验。卷积网络符合局部先验,因为卷积核通过滑动窗口在图片上“一块一块地”寻找某种特征。

那么FC层呢?FC能自动学到这一点吗?在有限的数据量(ImageNet)和有限的计算资源前提(GPU)下,很难。

实验验证:下面我们假设FC层的输入是64x10x10的feature map直接 “展平”成的6400维向量。输出也是6400维向量,然后reshape成64x10x10的feature map。下图展示了FC学得的kernel中的一个切片的权值大小。简单地讲(详见论文),展示的这一部分表示在输出的第0个channel中随便找的一个采样点(6,6)(也就是图中黄框标出来的点)作用于第0个输入channel上的10×10个像素点的权值。颜色越深,表示权值越大。比如说,如果图中的(5,5)点颜色深,就表示这个FC层认为输出中的(0,6,6)点与输入中的(0,5,5)点关系紧密。

结果很明显,(6,6)周围的权值并没有颜色更深,也就是说FC并不认为这个点和周围点的联系更紧密。相反,似乎这个FC层认为(6,6)点与右上和右下部分关系更密切。实验也证明,不具有局部性的FC效果较差。

既然图像的局部性很强,FC把握不住,那怎么办呢?RepMLP提出,用卷积去增强FC(如下图所示,输入既被展平成向量并输入FC,又用不同大小的卷积核进行卷积,各自过BN后相加),并通过结构重参数化,将卷积融合到FC中去,从而在推理时去除卷积。

我们将卷积和FC之间建立联系,是因为卷积可以看成一个稀疏且存在重复参数的FC。如下图代码所示,给定输入X和卷积核conv_K,其卷积的结果等于X(直接展平成向量)和fc_K的矩阵乘,fc_K称为conv_K的等效FC核。尽管我们都相信这样的fc_K一定存在,但根据conv_K的值直接构造出fc_K的方法(下图中的convert_K函数)似乎不太简单。

本文提出了一种简洁优美的做法(见后文)。我们用这种方法构造出fc_K并打印出来,可以看出它是一个稀疏且有很多元素相同的矩阵(Toeplitz矩阵)。如下图的代码和结果所示。

RepMLP把卷积的输出和FC的输出相加,这样做的好处是:

1) 降低FLOPs,提高速度。用我们提出的方法把卷积全都转换为等效FC kernel后,由于矩阵乘法的可加性(AX + BX = (A+B)X),一个稀疏且共享参数的FC(Toeplitz矩阵)加一个不稀疏不共享参数的FC(全自由度的矩阵),可以等价转换为一个FC(其参数是这两个矩阵之和)。这样我们就可以将这些卷积等效地去掉。这一思路也属于结构重参数化(通过参数的等价转换实现结构的等价转换,如RepVGG)。

2) 在同等参数量的情况下,FC的FLOPs远低于卷积。

3) 相比于纯FC,这样做产生了局部性。注意这种局部性是我们“赋予”FC的,而不是让FC学到的。

4) 相比于卷积层,这样做使得相距遥远的两个点直接相连,具备了全局性。

这样做看起来像是让FC的“内部”含有卷积,所以也可以称为“内卷”。事实证明,跟人类相似,FC的“内卷”也可以提高性能。

只剩下一个问题了:我们相信存在一个FC kernel等价于卷积的卷积核,但是给定一个训练好的卷积核,怎么构造出FC kernel(Toeplitz矩阵)呢?

其实也很简单:FC kernel等于在单位矩阵reshape成的feature map上用卷积核做卷积的结果。这一做法是高效、可微、与具体的卷积算法和平台无关的。推导过程也很简洁(详见论文)。

现在,整个流程就很清晰了:

1) 训练时,既有FC又有卷积,输出相加。

2) 训练完成后,先把BN的参数“吸”到卷积核或FC中去(跟RepVGG一样),然后把每一个卷积转换成FC,把所有FC加到一起。从此以后,不再有卷积,只有FC。

3) 保存并部署转换后的模型。

现在我们再看一下用卷积增强后转换得到的FC kernel,可以看出采样点周围的权值变大了,现在(6,6)点更关注它旁边的输入点了。有趣的是,这里用到的最大卷积是7×7,但是7×7的范围(蓝色框)外还有一些值(红色框)比蓝框内的值大,这说明全局性也没有被局部性“淹没”。

一些其他设计

RepMLP中也用了一些其他设计,包括:

1) 用groupwise conv实现groupwise FC,减少参数和计算量。

2) 将输入分块(最近大家都会用的常见操作),进一步减少参数和计算量。如下图所示,H和W是feature map的分辨率,h和w是每一块的分辨率。

3) 用两个FC在不同分块之间建立联系,确保全局性。如下图所示。

实验结果

用RepMLP替换Res50中的部分结构,在ImageNet上有性能提升。将ImageNet pretrained模型迁移到语义分割和人脸上,也都有性能提升。

在ImageNet上的实验是在Res50中做的。考虑到Res50的主干通道较多(256、512、1024、2048),为了将RepMLP用到Res50中取得合理的trade-off,我们做了以下设计:

1)RepMLP Bottleneck Block:在RepMLP之前用1×1和3×3降维,RepMLP之后用3×3和1×1升维。这一结构类似于旷视在工程中探索并申请的专利GLFP(202010422194.X, Visual task processing method and device and electronic system,下图)。

2)RepMLP Light Block:在RepMLP之前用1×1大幅降维,之后用1×1大幅升维。降维/升维的幅度(8x)比Res50(4x)更大。

一些有趣的发现:

1) RepMLP中具有局部先验的成分(融合进FC的卷积),所以对于具有平移不变性的任务(ImageNet,Cityscapes语义分割)有效。

2) RepMLP中也具有不具有平移不变性的成分(大FC kernel),所以对于具有某种位置模式(例如人脸图像中,眼睛总是在鼻子上面)的任务也有效。

3) 由于FC和卷积的差别,RepMLP可以大幅增加参数而不降低速度(参数增加47%,ImageNet精度提升0.31%,速度仅降低2.2%)。

一些常见问题

RepMLP和ResMLP是什么关系?

相当于旺旺碎冰冰和王冰冰的关系。只是名字有点像。RepMLP中用卷积增强FC的思路也可以用在其他MLP架构中,应该也会有提升。另外,ResMLP、RepMLP和ResRep(去年做的一篇用重参数化做剪枝的论文)也没有关系。

把卷积融合进FC里,那FC不就是卷积了吗?

卷了,但不是完全卷,而且比卷积更强。上面可视化的图显示,转换后的kernel可以关注到卷积核的感受野以外的信息,因而表征能力更强。论文中报告的实验表明,这样的操作可以以一半的channel量达到与纯CNN相当的性能,速度更快,FLOPs更低。本文的关键也在于把卷积看成一种特殊的FC,然后考虑如何利用这种特殊性

所以MLP is all you need?

目前看来,还差得远。目前的方法多多少少都用到了切块等操作,都需要用某种方式降低参数量和引入局部性。真正的纯MLP(A1位置)依然还没有希望。真正纯MLP的一个大麻烦是总的参数量和输入分辨率耦合,因而改变输入分辨率会很困难。MLP-Mixer的一个缺点是不方便改变输入分辨率,所以它在ImageNet分类上的性能不容易迁移到其他任务上去。

ps–进程查看器

写这个的原因:服务器多人共享使用,有些时候,有些进程可能不知道是哪位大哥用户,而某些进程可能对于服务器来说需要kill掉,这时候就需要查看进程的详细信息(比如,跑 GPU,有些用户虽然程序停止了,但去后台发现其实还在占用显存……,这时候就需要去查看当前进程的用户 是哪个,然后kill)

查看PID对应的用户的方法:ps aux | grep [your_PID]

Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令/htop命令。

要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而 ps 命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。

ps 为我们提供了进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程时间监控,应该用 top linux下的任务管理器 工具。

注:kill 命令用于杀死进程。

linux上进程有5种状态:

  1. 运行(正在运行或在运行队列中等待)
  2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)
  3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)
  4. 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)
  5. 停止(进程收到SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU信号后停止运行运行)

ps工具标识进程的5种状态码:

  • D 不可中断 uninterruptible sleep (usually IO)
  • R 运行 runnable (on run queue)
  • S 中断 sleeping
  • T 停止 traced or stopped
  • Z 僵死 a defunct (”zombie”) process

命令参数:

  • a 显示所有进程
  • -a 显示同一终端下的所有程序
  • -A 显示所有进程
  • c 显示进程的真实名称
  • -N 反向选择
  • -e 等于“-A”
  • e 显示环境变量
  • f 显示程序间的关系
  • -H 显示树状结构
  • r 显示当前终端的进程
  • T 显示当前终端的所有程序
  • u 指定用户的所有进程
  • -au 显示较详细的资讯
  • -aux 显示所有包含其他使用者的行程
  • -C<命令> 列出指定命令的状况
  • –lines<行数> 每页显示的行数
  • –width<字符数> 每页显示的字符数
  • –help 显示帮助信息
  • –version 显示版本显示

执行ps -aux 的输出:-aux 显示所有包含其他使用者的行程

  • F 代表这个程序的旗标 (flag), 4 代表使用者为 super user
  • S 代表这个程序的状态 (STAT),关于各 STAT 的意义将在内文介绍
  • UID 程序被该 UID 所拥有
  • PID 进程的ID
  • PPID 则是其上级父程序的ID
  • C CPU 使用的资源百分比
  • PRI 这个是 Priority (优先执行序) 的缩写,详细后面介绍
  • NI 这个是 Nice 值,在下一小节我们会持续介绍
  • ADDR 这个是 kernel function,指出该程序在内存的那个部分。如果是个 running的程序,一般就是 “-“
  • SZ 使用掉的内存大小
  • WCHAN 目前这个程序是否正在运作当中,若为 – 表示正在运作
  • TTY 登入者的终端机位置
  • TIME 使用掉的 CPU 时间。
  • CMD 所下达的指令为何

 ps 与grep 组合使用,查找特定进程

显示指定用户信息:

PyTorch医学图像分割开源库

github: https://github.com/MontaEllis/Pytorch-Medical-Segmentation

基于PyTorch的专注于医学图像分割的开源库,其支持模型丰富,方便易用。其可算为torchio的一个实例,作者将其综合起来,包含众多经典算法,实用性比较强。

该库特点:

  1. 支持2D和3D医学图像分割,可以修改hparam.py文件来确定是2D分割还是3D分割以及是否可以进行多分类。
  2. 支持绝大数主流分割模型,几乎提供了所有的2D和3D分割的算法。
  3. 兼容几乎所有的医学数据格式(例如 nii.gz, nii, mhd, nrrd, …),修改hparam.py的fold\_arch即可。

作者提供了训练和测试推断的代码,简单配置后训练和推断都仅需要一行命令。

已包含的分割模型:

AI部署系列:你知道模型权重的小秘密吗???

今天简单聊聊模型权重,也就是我们俗称的weight

深度学习中,我们一直在训练模型,通过反向传播求导更新模型的权重,最终得到一个泛化能力比较强的模型。同样,如果我们不训练,仅仅随机初始化权重,同样能够得到一个同样大小的模型。虽然两者大小一样,不过两者其中的权重信息分布相差会很大,一个脑子装满了知识、一个脑子都是水,差不多就这个意思。

所谓的AI模型部署阶段,说白了就是将训练好的权重挪到另一个地方去跑。一般来说,权重信息以及权重分布基本不会变(可能会改变精度、也可能会合并一些权重)。

不过执行模型操作(卷积、全连接、反卷积)的算子会变化,可能从Pytorch->TensorRT或者TensorFlow->TFLITE,也就是实现算子的方式变了,同一个卷积操作,在Pytorch框架中是一种实现,在TensorRT又是另一种时间,两者的基本原理是一样的,但是精度和速度不一样,TensorRT可以借助Pytorch训练好的卷积的权重,实现与Pytorch中一样的操作,不过可能更快些。

权重/Weight/CheckPoint

那么权重都有哪些呢?他们长什么样?

这还真不好描述…其实就是一堆数据。对的,我们千辛万苦不断调优训练出来的权重,就是一堆数据而已。也就是这个神奇的数据,搭配各种神经网络的算子,就可以实现各种检测、分类、识别的任务。

例如上图,我们用Netron这个工具去查看某个ONNX模型的第一个卷积权重。很显然这个卷积只有一个W权重,没有偏置b。而这个卷积的权重值的维度是[64,3,7,7],也就是输入通道3、输出通道64、卷积核大小7x7

再仔细看,其实这个权重的数值范围相差还是很大,最大的也就0.1的级别。但是最小的呢,肉眼看了下(其实应该统计一波),最小的竟然有1e-10级别。

一般我们训练的时候,输入权重都是0-1,当然也有0-255的情况,但不论是0-1还是0-255,只要不溢出精度上限和下限,就没啥问题。对于FP32来说,1e-10是小case,但是对于FP16来说就不一定了。

我们知道FP16的普遍精度是~5.96e−8 (6.10e−5) … 65504,具体的精度细节先不说,但是可以很明显的看到,上述的1e-10的精度,已经溢出了FP16的精度下限。如果一个模型中的权重分布大部分都处在溢出边缘的话,那么模型转换完FP16精度的模型指标可能会大大下降。

除了FP16,当然还有很多其他精度(TF32、BF16、IN8),这里暂且不谈,不过有篇讨论各种精度的文章可以先了解下。

话说回来,我们该如何统计该层的权重信息呢?利用Pytorch中原生的代码就可以实现:

# 假设v是某一层conv的权重,我们可以简单通过以下命令查看到该权重的分布
v.max()
tensor(0.8559)
v.min()
tensor(-0.9568)
v.abs()
tensor([[0.0314, 0.0045, 0.0182,  ..., 0.0309, 0.0204, 0.0345],
        [0.0295, 0.0486, 0.0746,  ..., 0.0363, 0.0262, 0.0108],
        [0.0328, 0.0582, 0.0149,  ..., 0.0932, 0.0444, 0.0221],
        ...,
        [0.0337, 0.0518, 0.0280,  ..., 0.0174, 0.0078, 0.0010],
        [0.0022, 0.0297, 0.0167,  ..., 0.0472, 0.0006, 0.0128],
        [0.0631, 0.0144, 0.0232,  ..., 0.0072, 0.0704, 0.0479]])
v.abs().min() # 可以看到权重绝对值的最小值是1e-10级别
tensor(2.0123e-10)
v.abs().max()
tensor(0.9568)
torch.histc(v.abs()) # 这里统计权重的分布,分为100份,最小最大分别是[-0.9558,0.8559]
tensor([3.3473e+06, 3.2437e+06, 3.0395e+06, 2.7606e+06, 2.4251e+06, 2.0610e+06,
        1.6921e+06, 1.3480e+06, 1.0352e+06, 7.7072e+05, 5.5376e+05, 3.8780e+05,
        2.6351e+05, 1.7617e+05, 1.1414e+05, 7.3327e+04, 4.7053e+04, 3.0016e+04,
        1.9576e+04, 1.3106e+04, 9.1220e+03, 6.4780e+03, 4.6940e+03, 3.5140e+03,
        2.8330e+03, 2.2040e+03, 1.7220e+03, 1.4020e+03, 1.1130e+03, 1.0200e+03,
        8.2400e+02, 7.0600e+02, 5.7900e+02, 4.6400e+02, 4.1600e+02, 3.3400e+02,
        3.0700e+02, 2.4100e+02, 2.3200e+02, 1.9000e+02, 1.5600e+02, 1.1900e+02,
        1.0800e+02, 9.9000e+01, 6.9000e+01, 5.2000e+01, 4.9000e+01, 2.2000e+01,
        1.8000e+01, 2.8000e+01, 1.2000e+01, 1.3000e+01, 8.0000e+00, 3.0000e+00,
        4.0000e+00, 3.0000e+00, 1.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00,
        1.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
        1.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 2.0000e+00,
        0.0000e+00, 2.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00,
        2.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
        0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00,
        0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
        0.0000e+00, 0.0000e+00, 0.0000e+00, 1.0000e+00])

这样看如果觉着不是很直观,那么也可以自己画图或者通过Tensorboard来时候看。

那么看权重分布有什么用呢?

肯定是有用处的,训练和部署的时候权重分布可以作为模型是否正常,精度是否保持的一个重要信息。不过这里先不展开说了。

有权重,所以重点关照

在模型训练过程中,有很多需要通过反向传播更新的权重,常见的有:

  • 卷积层
  • 全连接层
  • 批处理化层(BN层、或者各种其他LN、IN、GN)
  • transformer-encoder层
  • DCN层

这些层一般都是神经网络的核心部分,当然都是有参数的,一定会参与模型的反向传播更新,是我们在训练模型时候需要注意的重要参数。

# Pytorch中conv层的部分代码,可以看到参数的维度等信息
self._reversed_padding_repeated_twice = _reverse_repeat_tuple(self.padding, 2)
if transposed:
    self.weight = Parameter(torch.Tensor(
        in_channels, out_channels // groups, *kernel_size))
else:
    self.weight = Parameter(torch.Tensor(
        out_channels, in_channels // groups, *kernel_size))
if bias:
    self.bias = Parameter(torch.Tensor(out_channels))

也有不参与反向传播,但也会随着训练一起更新的参数。比较常见的就是BN层中的running_meanrunning_std

# 截取了Pytorch中BN层的部分代码
def __init__(
    self,
    num_features: int,
    eps: float = 1e-5,
    momentum: float = 0.1,
    affine: bool = True,
    track_running_stats: bool = True
) -> None:
    super(_NormBase, self).__init__()
    self.num_features = num_features
    self.eps = eps
    self.momentum = momentum
    self.affine = affine
    self.track_running_stats = track_running_stats
    if self.affine:
        self.weight = Parameter(torch.Tensor(num_features))
        self.bias = Parameter(torch.Tensor(num_features))
    else:
        self.register_parameter('weight', None)
        self.register_parameter('bias', None)
    if self.track_running_stats:
        # 可以看到在使用track_running_stats时,BN层会更新这三个参数
        self.register_buffer('running_mean', torch.zeros(num_features))
        self.register_buffer('running_var', torch.ones(num_features))
        self.register_buffer('num_batches_tracked', torch.tensor(0, dtype=torch.long))
    else:
        self.register_parameter('running_mean', None)
        self.register_parameter('running_var', None)
        self.register_parameter('num_batches_tracked', None)
    self.reset_parameters()

可以看到上述代码的注册区别,对于BN层中的权重和偏置使用的是register_parameter,而对于running_meanrunning_var则使用register_buffer,那么这两者有什么区别呢,那就是注册为buffer的参数往往不会参与反向传播的计算,但仍然会在模型训练的时候更新,所以也需要认真对待。

关于BN层,转换模型和训练模型的时候会有暗坑,需要注意一下。

刚才描述的这些层都是有参数的,那么还有一些没有参数的层有哪些呢?当然有,我们的网络中其实有很多op,仅仅是做一些维度变换、索引取值或者上/下采样的操作,例如:

  • Reshape
  • Squeeze
  • Unsqueeze
  • Split
  • Transpose
  • Gather

等等等等,这些操作没有参数仅仅是对上一层传递过来的张量进行维度变换,用于实现一些”炫技“的操作。至于这些炫技吗,有些很有用有些就有些无聊了。

上图这一堆乱七八槽的op,如果单独拆出来都认识,但是如果都连起来(像上图这样),估计连它爸都不认识了。

开个玩笑,其实有时候在通过Pytorch转换为ONNX的时候,偶尔会发生一些转换诡异的情况。比如一个简单的reshape会四分五裂为gather+slip+concat,这种操作相当于复杂化了,不过一般来说这种情况可以使用ONNX-SIMPLIFY去优化掉,当然遇到较为复杂的就需要自行优化了。

哦对了,对于这些变形类的操作算子,其实有些是有参数的,例如下图的reshap:

像这种的op,怎么说呢,有时候会比较棘手。如果我们想要将这个ONNX模型转换为TensorRT,那么100%会遇到问题,因为TensorRT的解释器在解析ONNX的时候,不支持reshape层的shape是输入TensorRT,而是把这个shape当成attribute来处理,而ONNX的推理框架Inference则是支持的。

不过这些都是小问题,大部分情况我们可以通过改模型或者换结构解决,而且成本也不高。但是还会有一些其他复杂的问题,可能就需要我们重点研究下了。

提取权重

想要将训练好的模型从这个平台部署至另一个平台,那么首要的就是转移权重。不过实际中大部分的转换器都帮我们做好了(比如onnx-TensorRT),不用我们自己操心!

不过如果想要对模型权重的有个整体认知的话,还是建议自己亲手试一试。

Caffe2Pytorch

先简单说下Caffe和Pytorch之间的权重转换。这里推荐一个开源仓库Caffe-python,已经帮我们写好了提取Caffemodel权重和根据prototxt构建对应Pytorch模型结构的过程,不需要我们重复造轮子。

我们都知道Caffe的权重使用Caffemodel表示,而相应的结构是prototxt。如上图,左面是prototxt右面是caffemodel,而caffemodel使用的是protobuf这个数据结构表示的。我们当然也要先读出来:

model = caffe_pb2.NetParameter()
print('Loading caffemodel: ' + caffemodel)
with open(caffemodel, 'rb') as fp:
    model.ParseFromString(fp.read())

caffe_pb2就是caffemodel格式的protobuf结构,具体的可以看上方老潘提供的库,总之就是定义了一些Caffe模型的结构。

而提取到模型权重后,通过prototxt中的模型信息,挨个从caffemodel的protobuf权重中找,然后复制权重到Pytorch端,仔细看这句caffe_weight = torch.from_numpy(caffe_weight).view_as(self.models[lname].weight),其中self.models[lname]就是已经搭建好的对应Pytorch的卷积层,这里取weight之后通过self.models[lname].weight.data.copy_(caffe_weight)将caffe的权重放到Pytorch中。

很简单吧。

if ltype in ['Convolution', 'Deconvolution']:
    print('load weights %s' % lname)
    convolution_param = layer['convolution_param']
    bias = True
    if 'bias_term' in convolution_param and convolution_param['bias_term'] == 'false':
        bias = False
    # weight_blob = lmap[lname].blobs[0]
    # print('caffe weight shape', weight_blob.num, weight_blob.channels, weight_blob.height, weight_blob.width)
    caffe_weight = np.array(lmap[lname].blobs[0].data)
    caffe_weight = torch.from_numpy(caffe_weight).view_as(self.models[lname].weight)
    # print("caffe_weight", caffe_weight.view(1,-1)[0][0:10])
    self.models[lname].weight.data.copy_(caffe_weight)
    if bias and len(lmap[lname].blobs) > 1:
        self.models[lname].bias.data.copy_(torch.from_numpy(np.array(lmap[lname].blobs[1].data)))
        print("convlution %s has bias" % lname)

Pytorch2TensorRT

先举个简单的例子,一般我们使用Pytorch模型进行训练。训练得到的权重,我们一般都会使用torch.save()保存为.pth的格式。

PTH是Pytorch使用python中内置模块pickle来保存和读取,我们使用netron看一下pth长什么样。。

可以看到只有模型中有参数权重的表示,并不包含模型结构。不过我们可以通过.py的模型结构一一加载.pth的权重到我们模型中即可。

看一下我们读取.pth后,state_dictkey。这些key也就对应着我们在构建模型时候注册每一层的权重名称和权重信息(也包括维度和类型等)。

当然这个pth也可以包含其他字符段{'epoch': 190, 'state_dict': OrderedDict([('conv1.weight', tensor([[...,比如训练到多少个epoch,学习率啥的。

对于pth,我们可以通过以下代码将其提取出来,存放为TensorRT的权重格式。

def extract_weight(args):
    # Load model
    state_dict = torch.load(args.weight)
    with open(args.save_path, "w") as f:
        f.write("{}\n".format(len(state_dict.keys())))
        for k, v in state_dict.items():
            vr = v.reshape(-1).cpu().numpy()
            f.write("{} {} ".format(k, len(vr)))
            for vv in vr:
                f.write(" ")
                f.write(struct.pack(">f", float(vv)).hex())
            f.write("\n")

需要注意,这里的TensorRT权重格式指的是在build之前的权重,TensorRT仅仅是拿来去构建整个网络,将每个解析到的层的权重传递进去,然后通过TensorRT的network去build好engine

// Load weights from files shared with TensorRT samples.
// TensorRT weight files have a simple space delimited format:
// [type] [size] <data x size in hex>
std::map<std::string, Weights> loadWeights(const std::string file)
{
    std::cout << "Loading weights: " << file << std::endl;
    std::map<std::string, Weights> weightMap;

    // Open weights file
    std::ifstream input(file);
    assert(input.is_open() && "Unable to load weight file.");

    // Read number of weight blobs
    int32_t count;
    input >> count;
    assert(count > 0 && "Invalid weight map file.");

    while (count--)
    {
        Weights wt{DataType::kFLOAT, nullptr, 0};
        uint32_t size;

        // Read name and type of blob
        std::string name;
        input >> name >> std::dec >> size;
        wt.type = DataType::kFLOAT;

        // Load blob
        uint32_t *val = reinterpret_cast<uint32_t *>(malloc(sizeof(val) * size));
        for (uint32_t x = 0, y = size; x < y; ++x)
        {
            input >> std::hex >> val[x];
        }
        wt.values = val;
        wt.count = size;
        weightMap[name] = wt;
    }
    std::cout << "Finished Load weights: " << file << std::endl;
    return weightMap;
}

那么被TensorRT优化后?模型又长什么样子呢?我们的权重放哪儿了呢?

肯定在build好后的engine里头,不过这些权重因为TensorRT的优化,可能已经被合并/移除/merge了。

模型参数的学问还是很多,近期也有很多相关的研究,比如参数重参化,是相当solid的工作,在很多训练和部署场景中经常会用到。

超越YOLOv7 | YOLOv6论文

各种Tricks大放异彩!!

论文: https://arxiv.org/abs/2209.02976

github:https://github.com/meituan/YOLOv6

多年来,YOLO 系列一直是高效目标检测的行业标准。YOLO 社区蓬勃发展,丰富了其在众多硬件平台和丰富场景中的使用。在这份技术报告力求将其极限推向新的高度,以坚定不移的行业应用心态向前迈进。

考虑到真实环境中对速度和准确性的不同要求,作者广泛研究了来自工业界或学术界的最新目标检测进展。具体来说,从最近的网络设计、训练策略、测试技术、量化和优化方法中大量吸收了一些想法。最重要的是,整合思想和实践,构建了一套不同规模的部署网络,以适应多样化的用例。

在 YOLO 作者的慷慨许可下,作者将其命名为 YOLOv6。作者也热烈欢迎用户和贡献者进一步增强。YOLOv6-N 在 NVIDIA Tesla T4 GPU 上以 1234 FPS 的吞吐量在 COCO 数据集上达到 35.9% 的 AP。YOLOv6-S 以 495 FPS 的速度达到 43.5% 的 AP,优于同规模的其他主流检测器(YOLOv5-SYOLOX-S 和 PPYOLOE-S)。

YOLOv6-S 量化版本甚至带来了 869 FPS 的最新 43.3% AP。此外,与具有相似推理速度的其他检测器相比,YOLOv6-M/L 还实现了更好的准确度性能(即 49.5%/52.3%)。

1、简介

YOLO 系列因其在速度和准确性之间的出色平衡而成为工业应用中最流行的检测框架。YOLO 系列的开创性作品是YOLOv1-3,随着后期的实质性改进,开创了单阶段检测器的新路。YOLOv4 将检测框架重组为几个独立的部分(backboneneck 和 head),并验证了当时的 bag-of-freebies 和 bag-of-specials,设计了一个适合在单 GPU 上训练的框架。目前,YOLOv5YOLOXPPYOLOE 和 YOLOv7 都是高效检测器部署的竞争候选者。不同大小的模型通常通过缩放技术获得。

在本报告中,作者凭经验观察了几个促使更新 YOLO 框架的重要因素:

  1. RepVGG 的重参化是一种高级技术,尚未在检测中得到很好的利用。作者还注意到 RepVGG Block 的简单模型缩放变得不切实际,为此作者认为小型和大型网络之间的网络设计的优雅一致性是不必要的。普通的单路径架构对于小型网络是更好的选择,但对于较大的模型,参数的指数增长和单路径架构的计算成本使其不可行;
  2. 基于重参化的检测器的量化也需要细致处理,否则由于其在训练和推理过程中的异构配置,将难以处理性能下降。
  3. 以前的工作往往不太关注部署,其延迟通常在 V100 等高成本机器上进行比较。在实际服务环境方面存在硬件差距。通常,像 Tesla T4 这样的低功耗 GPU 成本更低,并且提供相当好的推理性能。
  4. 考虑到架构差异,标签分配和损失函数设计等高级特定领域策略需要进一步验证;
  5. 对于部署,可以容忍训练策略的调整,提高准确度性能但不增加推理成本,例如知识蒸馏。

考虑到上述观察,带来了 YOLOv6 的诞生,它在准确性和速度方面实现了迄今为止最好的权衡。在图 1 中展示了 YOLOv6 与其他类似规模的同行的比较。为了在不大幅降低性能的情况下提高推理速度,研究了包括训练后量化 (PTQ) 和量化感知训练 (QAT) 在内的尖端量化方法,并将它们纳入 YOLOv6 以实现部署就绪网络的目标。

将 YOLOv6 的主要方面总结如下:

  • 针对不同场景中的工业应用重新设计了一系列不同规模的网络。不同规模的架构各不相同,以实现最佳的速度和准确性权衡,其中小型模型具有简单的单路径主干,大型模型建立在高效的多分支块上。
  • 为 YOLOv6 注入了一种self-distillation策略,在分类任务和回归任务上都执行。同时,动态调整来自教师和标签的知识,以帮助学生模型在所有训练阶段更有效地学习知识。
  • 广泛验证标签分配、损失函数和数据增强技术的先进检测技术,并有选择地采用它们以进一步提高性能。
  • 在 RepOptimizer 和通道蒸馏的帮助下改进了检测的量化方案,这带来了具有 43.3% 的 COCO AP 和 869 FPS 的吞吐量的快速准确的检测器,批量大小为 32。

2、YOLOv6方法全解

YOLOv6 的改造设计包括以下组件,网络设计、标签分配、损失函数、数据增强、行业便利改进以及量化和部署:

网络设计:

  • Backbone:与其他主流架构相比, RepVGG 主干在相似的推理速度下在小型网络中具有更强的特征表示能力,但由于参数和计算成本的爆炸式增长,它很难扩展以获得更大的模型。在这方面,将 RepBlock 作为小型网络的构建块。对于大型模型,修改了一个更高效的 CSP Block,名为 CSPStackRep 块。
  • Neck:YOLOv6 的 Neck 采用 YOLOv4 和 YOLOv5 之后的 PAN。使用 RepBlocks 或 CSPStackRep Blocks 增强 Neck 以获得 Rep-PAN
  • Head:简化了 Decoupled Head,使其更高效,称为Efficient Decoupled Head

标签分配:

通过大量实验评估了 YOLOv6 上标签分配策略的最新进展,结果表明 TAL 更有效且对训练更友好。

损失函数:

主流的Anchor-Free检测器的损失函数包括分类损失、框回归损失和目标损失。对于每个损失,用所有可用的技术系统地对其进行试验,最后选择 VariFocal Loss 分类损失,SIoU/GIoU 损失作为回归损失。

行业便利的改进:

引入了额外的常见实践和技巧来提高性能,包括self-distillation和更多的训练时期。对于self-distillation,分类和框回归分别由教师模型监督。多亏了 DFL使得框回归的蒸馏成为可能。此外,来自Soft Label和Hard Label的信息比例通过余弦衰减动态下降,这有助于学生在训练过程中的不同阶段选择性地获取知识。此外,作者遇到了性能受损的问题,而在评估时没有添加额外的灰色边框,为此提供了一些补救措施。

量化和部署:

为了解决量化基于重参化模型的性能下降问题,使用 RepOptimizer 训练 YOLOv6 以获得对 PTQ 友好的权重。进一步采用 QAT 和通道蒸馏和图优化来追求极致性能。量化 YOLOv6-S 达到了最新的技术水平,AP 为 42.3%,吞吐量为 869 FPS(batch size=32)。

2.1、网络设计

单阶段目标检测器一般由以下部分组成:BackboneNeckHeadBackbone主要决定特征表示能力,同时,它的设计对推理效率有至关重要的影响,因为它承载了很大一部分计算成本。Neck用于将低层次的物理特征与高层次的语义特征进行聚合,然后构建各个层次的金字塔特征图。Head由几个卷积层组成,它根据Neck融合的多级特征预测最终检测结果。从结构的角度来看,它可以分为Anchor-BaseAnchor-Free,或者更确切地说是参数耦合Head和参数解耦Head

在 YOLOv6 中,基于硬件友好的网络设计原则,提出了两个可缩放的可重参数BackboneNeck以适应不同大小的模型,以及一个具有混合通道策略的高效解耦HeadYOLOv6 的整体架构如图 2 所示。

1、Backbone

如上所述,Backbone网络的设计对检测模型的有效性和效率有很大的影响。以前,已经表明多分支网络通常可以比单路径网络实现更好的分类性能,但它通常伴随着并行度的降低并导致推理延迟的增加。相反,像 VGG 这样的普通单路径网络具有高并行性和更少内存占用的优势,从而带来更高的推理效率。最近在 RepVGG 中,提出了一种结构重参化方法,将训练时多分支拓扑与推理时普通架构解耦,以实现更好的速度-准确度权衡。

受上述工作的启发,设计了一个高效的可重参化Backbone,表示为 EfficientRep。对于小型模型,Backbone的主要组成部分是训练阶段的 RepBlock,如图 3(a)所示。并且每个 RepBlock 在推理阶段被转换为具有 ReLU 激活函数的 3×3 卷积层(表示为 RepConv)的堆栈,如图 3(b)所示。通常,3×3 卷积在主流 GPU 和 CPU 上进行了高度优化,并且具有更高的计算密度。因此,EfficientRep Backbone 充分利用了硬件的计算能力,在显着降低推理延迟的同时增强了表示能力。

然而,作者注意到随着模型容量的进一步扩大,单路径普通网络中的计算成本和参数数量呈指数增长。为了在计算负担和准确性之间取得更好的平衡,修改了一个 CSPStackRep Block 来构建中型和大型网络的Backbone。如图 3(c) 所示,CSPStackRep Block 由3个 1×1 卷积层和一堆子块组成,该子块由两个 RepVGG Block 或 RepConv(分别在训练或推理时)和一个残差连接组成。此外,采用跨级部分(CSP)连接来提高性能,而不会产生过多的计算成本。与 CSPRepResStage 相比,它的外观更加简洁,并考虑了准确性和速度之间的平衡。

2、Neck

在实践中,多尺度的特征集成已被证明是目标检测的关键和有效部分。采用来自 YOLOv4 和 YOLOv5 的修改后的 PAN 拓扑作为检测Neck的基础。此外,将 YOLOv5 中使用的 CSPBlock 替换为 RepBlock(适用于小型模型)或 CSPStackRep Block(适用于大型模型),并相应调整宽度和深度。YOLOv6 的Neck表示为 Rep-PAN

3、Head

Efficient decoupled head

YOLOv5 的检测头是一个耦合Head,在分类和定位分支之间共享参数,而 FCOS 和 YOLOX 中的检测头将两个分支解耦,并且在每个分支中引入了额外的两个 3×3 卷积层以提高性能。

在 YOLOv6 中采用混合通道策略来构建更高效的解耦Head。具体来说,将中间 3×3 卷积层的数量减少到只有一个。头部的宽度由BackboneNeck的宽度乘数共同缩放。这些修改进一步降低了计算成本,以实现更低的推理延迟。

Anchor-free

Anchor-free检测器因其更好的泛化能力和解码预测结果的简单性而脱颖而出。其后处理的时间成本大大降低。有两种类型的Anchor-free检测器:基于Anchor和基于关键点。在 YOLOv6 中,我们采用了基于Anchor点的范式,其框回归分支实际上预测了Anchor点到边界框4个边的距离。

2.2、Label Assignment

标签分配负责在训练阶段为预定义的Anchor分配标签。以前的工作已经提出了各种标签分配策略,从简单的基于 IoU 的策略和内部真实方法到其他更复杂的方案。

SimOTA OTA 将目标检测中的标签分配视为最佳传输问题。它从全局角度为每个真实对象定义了正/负训练样本。SimOTA 是 OTA 的简化版本,它减少了额外的超参数并保持了性能。在 YOLOv6 的早期版本中使用 SimOTA 作为标签分配方法。然而,在实践中,作者发现引入 SimOTA 会减慢训练过程。而且陷入不稳定训练的情况并不少见。因此,希望更换 SimOTA

任务对齐学习任务对齐学习(Task Alignment Learning,TAL)最早是在TOOD中提出的,其中设计了一个分类分数和预测框质量的统一度量。IoU 被这个指标替换以分配对象标签。在一定程度上缓解了任务错位(分类和框回归)的问题。TOOD 的另一个主要贡献是关于任务对齐的头部(T-head)。T-head 堆叠卷积层以构建交互式特征,在其之上使用任务对齐预测器 (TAP)。PP-YOLOE 改进了 T-head,将 T-head 中的 layer attention 替换为轻量级的 ESE attention,形成 ET-head。然而,我们发现 ET-head 会降低模型中的推理速度,并且没有准确度增益。因此,保留了高效解耦头的设计。

此外,作者观察到 TAL 可以带来比 SimOTA 更多的性能提升并稳定训练。因此,采用 TAL 作为 YOLOv6 中的默认标签分配策略。

2.3、损失函数

1、Classification Loss

提高分类器的性能是优化检测器的关键部分。Focal Loss 修改了传统的交叉熵损失,以解决正负样本之间或难易样本之间的类别不平衡问题。为了解决训练和推理之间质量估计和分类的不一致使用,Quality Focal LossQFL)进一步扩展了Focal Loss,联合表示分类分数和分类监督的定位质量。而 VariFocal Loss (VFL) 源于 Focal Loss,但它不对称地对待正样本和负样本。通过考虑不同重要性的正负样本,它平衡了来自两个样本的学习信号。Poly Loss 将常用的分类损失分解为一系列加权多项式基。它在不同的任务和数据集上调整多项式系数,通过实验证明比交叉熵损失和Focal Loss损失更好。

在 YOLOv6 上评估所有这些高级分类损失,最终采用 VFL

2、Box Regression Loss

框回归损失提供了精确定位边界框的重要学习信号。L1 Loss 是早期作品中的原始框回归损失。逐渐地,各种精心设计的框回归损失如雨后春笋般涌现,例如 IoU-series 损失和概率损失。

IoU-series Loss IoU loss 将预测框的四个边界作为一个整体进行回归。它已被证明是有效的,因为它与评估指标的一致性。IoU的变种有很多,如GIoUDIoUCIoUα-IoUSIoU等,形成了相关的损失函数。我们在这项工作中对 GIoUCIoU 和 SIoU 进行了实验。并且SIoU应用于YOLOv6-NYOLOv6-T,而其他的则使用GIoU

Probability Loss Distribution Focal Loss (DFL) 将框位置的基本连续分布简化为离散化的概率分布。它在不引入任何其他强先验的情况下考虑了数据中的模糊性和不确定性,这有助于提高框定位精度,尤其是在ground-truth框的边界模糊时。在 DFL 上,DFLv2 开发了一个轻量级的子网络,以利用分布统计数据与真实定位质量之间的密切相关性,进一步提高了检测性能。然而,DFL 输出的回归值通常比一般框回归多 17 倍,从而导致大量开销。额外的计算成本阻碍了小型模型的训练。而 DFLv2 由于额外的子网络,进一步增加了计算负担。在实验中,DFLv2 在模型上为 DFL 带来了类似的性能提升。因此,只在 YOLOv6-M/L 中采用 DFL

3、Object Loss

Object loss 最早是在 FCOS 中提出的,用于降低低质量边界框的得分,以便在后处理中将其过滤掉。它还被用于 YOLOX 以加速收敛并提高网络精度。作为像 FCOS 和 YOLOX 这样的Anchor-free框架,在 YOLOv6 中尝试过 object loss。不幸的是,它并没有带来很多积极的影响。

2.4、行业便利的改进

1、More training epochs

经验结果表明,检测器的性能随着训练时间的增加而不断进步。作者将训练持续时间从 300 个 epoch 延长到 400 个 epoch,以达到更好的收敛性。

2、Self-distillation

为了在不引入太多额外计算成本的情况下进一步提高模型精度,应用了经典的知识蒸馏技术,最小化了教师和学生预测之间的 KL-divergence。将老师限制为学生本身,但经过预训练,因此称之为自我蒸馏。

请注意,KL-divergence通常用于衡量数据分布之间的差异。然而,目标检测中有两个子任务,其中只有分类任务可以直接利用基于 KL-divergence的知识蒸馏。由于 DFL 损失,也可以在框回归上执行它。知识蒸馏损失可以表示为:

其中Ldet  是使用预测和标签计算的检测损失。引入超参数α来平衡两个损失。在训练的早期阶段,来自老师的软标签更容易学习。随着训练的继续,学生的表现将与老师相匹配,因此硬标签将更多地帮助学生。在此基础上,将余弦权重衰减应用于 α,以动态调整来自硬标签和来自教师的软标签的信息。

3、Gray border of images

作者注意到在 YOLOv5 和 YOLOv7 的实现中评估模型性能时,每个图像周围都有一个半步长的灰色边框。虽然没有添加有用的信息,但它有助于检测图像边缘附近的对象。这个技巧也适用于 YOLOv6

然而,额外的灰色像素明显降低了推理速度。没有灰色边框,YOLOv6 的性能会变差。假设该问题与马赛克增强中的灰色边框填充有关。进行了在最后一个时期关闭马赛克增强的实验(也称为淡入淡出策略)以进行验证。对此,改变了灰色边框的区域,将带有灰色边框的图像直接调整为目标图像大小。结合这两种策略,模型可以在不降低推理速度的情况下保持甚至提高性能。

2.5、量化与部署

对于工业部署,通常的做法是采用量化来进一步加快运行时间而不会对性能造成太大影响。训练后量化(PTQ)直接量化模型,只需要一个小的校准集。而量化感知训练(QAT)通过访问训练集进一步提高了性能,这通常与蒸馏结合使用。然而,由于在 YOLOv6 中大量使用了重参化块,以前的 PTQ 技术无法产生高性能,而在训练和推理期间在匹配假量化器时很难结合 QAT

1、Reparameterizing Optimizer

RepOptimizer 在每个优化步骤提出梯度重参化。该技术也很好地解决了基于重参化模型的量化问题。因此,以这种方式重建 YOLOv6 的重参化块,并使用 RepOptimizer 对其进行训练以获得对 PTQ 友好的权重。特征图的分布在很大程度上变窄了,这极大地有利于量化过程。

2、Sensitivity Analysis

通过将量化敏感操作部分转换为浮点计算来进一步提高 PTQ 性能。为了获得灵敏度分布,通常使用几个指标,均方误差 (MSE)、信噪比 (SNR) 和余弦相似度。通常为了比较,可以选择输出特征图(在激活某个层之后)来计算这些带有和不带量化的指标。作为替代方案,通过打开和关闭特定层的量化来计算验证 AP 也是可行的。

在使用 RepOptimizer 训练的 YOLOv6-S 模型上计算所有这些指标,并选择前 6 个敏感层以浮动运行。

3、使用 Channel-wise Distillation 进行量化感知训练

如果 PTQ 不足,建议使用量化感知训练 (QAT) 来提高量化性能。为了解决训练和推理过程中假量化器不一致的问题,有必要在 RepOptimizer 上构建 QAT。此外,通道蒸馏(后来称为 CW Distill)适用于 YOLOv6 框架,如图 5 所示。这也是一种自我蒸馏方法,其中教师网络是 FP32 精度的学生本身。

实验

消融实验

1、label assignment

2、损失函数

3、自蒸馏

4、Gray border of images

5、PTQ

6、QAT

SOTA对比

TensorRT部署实践对比

T4 GPU

V100 GPU

Diffusion Model 综述

Diffusion Models: A Comprehensive Survey of Methods and Applications来自加州大学&Google Research的Ming-Hsuan Yang、北京大学崔斌实验室以及CMU、UCLA、蒙特利尔Mila研究院等众研究团队,首次对现有的扩散生成模型(diffusion model)进行了全面的总结分析,从diffusion model算法细化分类、和其他五大生成模型的关联以及在七大领域中的应用等方面展开,最后提出了diffusion model的现有limitation和未来的发展方向

论文:https://arxiv.org/abs/2209.00796

摘自:AI科技评论

github链接:https://github.com/YangLing0818/Diffusion-Models-Papers-Survey-Taxonomy(扩散模型论文汇总)

介绍

扩散模型(diffusion models)是深度生成模型中新的SOTA。扩散模型在图片生成任务中超越了原SOTA:GAN,并且在诸多应用领域都有出色的表现,如计算机视觉,NLP、波形信号处理、多模态建模、分子图建模、时间序列建模、对抗性净化等。此外,扩散模型与其他研究领域有着密切的联系,如稳健学习、表示学习、强化学习。然而,原始的扩散模型也有缺点,它的采样速度慢,通常需要数千个评估步骤才能抽取一个样本;它的最大似然估计无法和基于似然的模型相比;它泛化到各种数据类型的能力较差。如今很多研究已经从实际应用的角度解决上述限制做出了许多努力,或从理论角度对模型能力进行了分析。然而,现在缺乏对扩散模型从算法到应用的最新进展的系统回顾。为了反映这一快速发展领域的进展,我们对扩散模型进行了首个全面综述。我们设想我们的工作将阐明扩散模型的设计考虑和先进方法,展示其在不同领域的应用,并指出未来的研究方向。此综述的概要如下图所示:

尽管diffusion model在各类任务中都有着优秀的表现,它仍还有自己的缺点,并有诸多研究对diffusion model进行了改善。为了系统地阐明diffusion model的研究进展,我们总结了原始扩散模型的三个主要缺点,采样速度慢,最大化似然差、数据泛化能力弱,并提出将的diffusion models改进研究分为对应的三类:采样速度提升、最大似然增强和数据泛化增强。我们首先说明改善的动机,再根据方法的特性将每个改进方向的研究进一步细化分类,从而清楚的展现方法之间的联系与区别。在此我们仅选取部分重要方法为例, 我们的工作中对每类方法都做了详细的介绍,内容如图所示:

在分析完三类扩散模型后,我们将介绍其他的五种生成模型GAN,VAE,Autoregressive model, Normalizing flow, Energy-based model。考虑到扩散模型的优良性质,研究者们已经根据其特性将diffusion model与其他生成模型结合,所以为了进一步展现diffusion model 的特点和改进工作,我们详细地介绍了diffusion model和其他生成模型的结合的工作并阐明了在原始生成模型上的改进之处。Diffusion model在诸多领域都有着优异的表现,并且考虑到不同领域的应用中diffusion model产生了不同的变形,我们系统地介绍了diffusion model的应用研究,其中包含如下领域:计算机视觉,NLP、波形信号处理、多模态建模、分子图建模、时间序列建模、对抗性净化。对于每个任务,我们定义了该任务并介绍利用扩散模型处理任务的工作,我们将本项工作的主要贡献总结如下:

  • 新的分类方法:我们对扩散模型和其应用提出了一种新的、系统的分类法。具体的我们将模型分为三类:采样速度增强、最大似然估计增强、数据泛化增强。进一步地,我们将扩散模型的应用分为七类:计算机视觉,NLP、波形信号处理、多模态建模、分子图建模、时间序列建模、对抗性净化。
  • 全面的回顾:我们首次全面地概述了现代扩散模型及其应用。我们展示了每种扩散模型的主要改进,和原始模型进行了必要的比较,并总结了相应的论文。对于扩散模型的每种类型的应用,我们展示了扩散模型要解决的主要问题,并说明它们如何解决这些问题。
  • 未来研究方向:我们对未来研究提出了开放型问题,并对扩散模型在算法和应用方面的未来发展提供了一些建议。

扩散模型基础

生成式建模的一个核心问题是模型的灵活性和可计算性之间的权衡。扩散模型的基本思想是正向扩散过程来系统地扰动数据中的分布,然后通过学习反向扩散过程恢复数据的分布,这样就了产生一个高度灵活且易于计算的生成模型。

1.Denoising Diffusion Probabilistic Models(DDPM)

一个DDPM由两个参数化马尔可夫链组成,并使用变分推断以在有限时间后生成与原始数据分布一致的样本。前向链的作用是扰动数据,它根据预先设计的噪声进度向数据逐渐加入高斯噪声,直到数据的分布趋于先验分布,即标准高斯分布。反向链从给定的先验开始并使用参数化的高斯转换核,学习逐步恢复原数据分布。用表示原始数据及其分布,则前向链的分布是可由下式表达:

这说明前向链是马尔可夫过程,是加入t步噪音后的样本,是事先给定的控制噪声进度的参数。当 趋于1时,可以近似认为服从标准高斯分布。当很小时,逆向过程的转移核可以近似认为也是高斯的:

我们可以将变分下界作为损失函数进行学习:

2.Score-Based Generative Models(SGM)

上述DDPM可以视作SGM的离散形式。SGM构造一个随机微分方程(SDE)来平滑的扰乱数据分布,将原始数据分布转化到已知的先验分布:

和一个相应的逆向SDE,来将先验分布变换回原始数据分布:

因此,要逆转扩散过程并生成数据,我们需要的唯一信息就是在每个时间点的分数函数。利用score-matching的技巧我们可以通过如下损失函数来学习分数函数:

对两种方法的进一步介绍和两者关系的介绍请参见我们的文章。原始扩散模型的三个主要缺点,采样速度慢,最大化似然差、数据泛化能力弱。最近许多研究都在解决这些缺点,因此我们将改进的扩散模型分为三类:采样速度提升、最大似然增强和数据泛化增强。在接下来的三、四、五节我们将对这三类模型进行详细的介绍。

采样加速方法

在应用时,为了让新样本的质量达到最佳,扩散模型往往需要进行成千上万步计算来获取一个新样本。这限制了diffusion model的实际应用价值,因为在实际应用时,我们往往需要产生大量的新样本,来为下一步处理提供材料。研究者们在提高diffusion model采样速度上进行了大量的研究。我们对这些研究进行了详细的阐述。我们将其细化分类为三种方法:Discretization Optimization,Non-Markovian Process,Partial Sampling。

1.Discretization Optimization

方法优化求解diffusion SDE的方法。因为现实中求解复杂SDE只能使用离散解来逼近真正的解,所以该类方法试图优化SDE的离散化方法,在保证样本质量的同时减少离散步数。SGM 提出了一个通用的方法来求解逆向过程,即对前向和后向过程采取相同的离散方法。如果给定了前向SDE的离散方式:

那么我们就可以以相同的方式离散化逆向SDE:

这种方法比朴素DDPM效果略好一点。进一步,SGM向SDE求解器中加入了一个矫正器,从而让每一步生成的样本都有正确的分布。在求解的每一步,求解器给出一个样本后,矫正器都使用马尔可夫链蒙特卡罗方法来矫正刚生成的样本的分布。实验表明向求解器中加入矫正器比直接增加求解器的步数效率更高。

2.Non-Markovian Process方法突破了原有Markovian Process的限制,其逆过程的每一步可以依赖更多以往的样本来进行预测新样本,所以在步长较大时也能做出较好的预测,从而加速采样过程。其中主要的工作DDIM,不再假设前向过程是马尔可夫过程,而是服从如下分布:

DDIM的采样过程可以视为离散化的神经常微分方程,其采样过程更高效,并且支持样本的内插。进一步的研究发现DDIM可以视作流形上扩散模型PNDM的特例。3.Partial Sampling方法通过在generation process中忽略一部分的时间节点,而只使用剩下的时间节点来生成样本,直接减少了采样时间。例如,Progressive Distillation从训练好的扩散模型中蒸馏出效率更高的扩散模型。对于训练好的一个扩散模型,Progressive Distillation会从新训练一个扩散模型,使新的扩散模型的一步对应于训练好的扩散模型的两步,这样新模型就可以省去老模型一半的采样过程。具体算法如下:

不断循环这个蒸馏过程就能让采样步骤指数级下降。

最大似然估计加强

扩散模型在最大似然估计的表现差于基于似然函数的生成模型,但最大化似然估计在诸多应用场景都有重要意义,比如图片压缩, 半监督学习, 对抗性净化。由于对数似然难以直接计算,研究主要集中在优化和分析变分下界(VLB)。我们对提高扩散模型最大似然估计的模型进行了详细的阐述。我们将其细化分类为三类方法:Objectives Designing,Noise Schedule Optimization,Learnable Reverse Variance。

1.Objectives Designing方法利用扩散 SDE推倒出生成数据的对数似然与分数函数匹配的损失函数的关系。这样通过适当设计损失函数,就可以最大化 VLB 和对数似然。Song et al. 证明了可以设计损失函数的权重函数,使得plug-in reverse SDE 生成样本的似然函数值小于等于损失函数值,即损失函数是似然函数的上界。分数函数拟合的损失函数如下:

我们只需将权重函数设为扩散系数g(t)即可让损失函数成为似然函数的VLB,即:

2.Noise Schedule Optimization通过设计或学习前向过程的噪声进度来增大VLB。VDM证明了当离散步数接近无穷时,损失函数完全由信噪比函数SNR(t)的端点决定:

那么在离散步数接近无穷时,可以通过学习信噪比函数SNR(t)的端点最优化VLB,而通过学习信噪比函数中间部分的函数值来实现模型其他方面的改进。

3.Learnable Reverse Variance方法学习反向过程的方差,从而较少拟合误差,可以有效地最大化VLB。Analytic-DPM证明,在DDPM和DDIM中存在反向过程中的最优期望和方差:

使用上述公式和训练好的分数函数,在给定前向过程的条件下,最优的VLB可以近似达到。

数据泛化增强

扩散模型假设数据存在于欧几里得空间,即具有平面几何形状的流形,并添加高斯噪声将不可避免地将数据转换为连续状态空间,所以扩散模型最初只能处理图片等连续性数据,直接应用离散数据或其他数据类型的效果较差。这限制了扩散模型的应用场景。数个研究工作将扩散模型推广到适用于其他数据类型的模型,我们对这些方法进行了详细地阐释。我们将其细化分类为两类方法:Feature Space Unification,Data-Dependent Transition Kernels。1.Feature Space Unification方法将数据转化到统一形式的latent space,然后再latent space上进行扩散。LSGM提出将数据通过VAE框架先转换到连续的latent space 上后再在其上进行扩散。这个方法的难点在于如何同时训练VAE和扩散模型。LSGM表明由于潜在先验是intractable的,分数匹配损失不再适用。LSGM直接使用VAE中传统的损失函数ELBO作为损失函数,并导出了ELBO和分数匹配的关系:

该式在忽略常数的意义下成立。通过参数化扩散过程中样本的分数函数,LSGM可以高效的学习和优化ELBO。

2.Data-Dependent Transition Kernels方法根据数据类型的特点设计diffusion process 中的transition kernels,使扩散模型可以直接应用于特定的数据类型。D3PM为离散型数据设计了transition kernel,可以设为lazy random-walk,absorbing state等。GEODIFF为3D分子图数据设计了平移-旋转不变的图神经网络,并且证明了具有不变性的初分布和transition kernel可以导出具有不变性的边缘分布。假设是一个平移-旋转变换,如:

那么生成的样本分布也有平移-旋转不变性:

和其他生成模型的联系

在下面的每个小节中,我们首先介绍其他五类重要的生成模型,并分析它们的优势和局限性。然后我们介绍了扩散模型是如何与它们联系起来的,并说明通过结合扩散模型来改进这些生成模型。VAE,GAN,Autoregressive model, Normalizing flow, Energy-based model和扩散模型的联系如下图所示:

  1. DDPM可以视作层次马尔可夫VAE(hierarchical Markovian VAE)。但DDPM和一般的VAE也有区别。DDPM作为VAE,它的encoder和decoder都服从高斯分布、有马尔科夫行;其隐变量的维数和数据维数相同;decoder的所有层都共用一个神经网络。
  2. DDPM可以帮助GAN解决训练不稳定的问题。因为数据是在高维空间中的低维流形中,所以GAN生成数据的分布和真实数据的分布重合度低,导致训练不稳定。扩散模型提供了一个系统地增加噪音的过程,通过扩散模型向生成的数据和真实数据添加噪音,然后将加入噪音的数据送入判别器,这样可以高效地解决GAN无法训练、训练不稳定的问题。
  3. Normalizing flow通过双射函数将数据转换到先验分布,这样的作法限制了Normalizing flow的表达能力,导致应用效果较差。类比扩散模型向encoder中加入噪声,可以增加Normalizing flow的表达能力,而从另一个视角看,这样的做法是将扩散模型推广到前向过程也可学习的模型。
  4. Autoregressive model在需要保证数据有一定的结构,这导致设计和参数化自回归模型非常困难。扩散模型的训练启发了自回归模型的训练,通过特定的训练方式避免了设计的困难。
  5. Energy-based model直接对原始数据的分布建模,但直接建模导致学习和采样都比较困难。通过使用扩散恢复似然,模型可以先对样本加入微小的噪声,再从有略微噪声的样本分布来推断原始样本的分布,使的学习和采样过程更简单和稳定。

扩散模型的应用

在本节中,我们分别介绍了扩散模型在计算机视觉、自然语言处理、波形信号处理、多模态学习、分子图生成、时间序列以及对抗学习等七大应用方向中的应用,并对每类应用中的方法进行了细分并解析。例如在计算机视觉中可以用diffusion model进行图像补全修复(RePaint):

在多模态任务中可以用diffusion model进行文本到图像的生成(GLIDE):

还可以在分子图生成中用diffusion model进行药物分子和蛋白质分子的生成(GeoDiff):

应用分类汇总见表:

未来研究方向

  1. 应用假设再检验。我们需要检查我们在应用中普遍接受的假设。例如,实践中普遍认为扩散模型的前向过程会将数据转换为标准高斯分布,但事实并非如此,更多的前向扩散步骤会使最终的样本分布与标准高斯分布更接近,与采样过程一致;但更多的前向扩散步骤也会使估计分数函数更加困难。理论的条件很难获得,因此在实践中操作中会导致理论和实践的不匹配。我们应该意识到这种情况并设计适当的扩散模型。
  2. 从离散时间到连续时间。由于扩散模型的灵活性,许多经验方法可以通过进一步分析得到加强。通过将离散时间的模型转化到对应的连续时间模型,然后再设计更多、更好的离散方法,这样的研究思路有前景。
  3. 新的生成过程。扩散模型通过两种主要方法生成样本:一是离散化反向扩散 SDE,然后通过离散的反向 SDE 生成样本;另一个是使用逆过程中马尔可夫性质对样本逐步去噪。然而,对于一些任务,在实践中很难应用这些方法来生成样本。因此,需要进一步研究新的生成过程和视角。
  4. 泛化到更复杂的场景和更多的研究领域。虽然目前diffusion model已经应用到多个场景中,但是大多数局限于单输入单输出的场景,将来可以考虑将其应用到更复杂的场景,比如text-to-audiovisual speech synthesis。也可以考虑和更多的研究领域相结合。

part2:【扩散模型笔记整理】从DDPM到Imagen

扩散模型(Diffusion Model)

1. 概述

  • 如图所示,扩散模型分两个过程:扩散(diffusion, 从x0到xT的过程逐步加入噪声)和去噪(denoise, 从xT到x0逐步去噪)。训练的时候,需要利用扩散加噪来生成训练样本;推理的时候,输入一个噪音,逐步去噪输出原始信号(比如图像、语音)。

参考文献:Denoising Diffusion Probabilistic Models

2. 扩散和去噪(Diffusion&Denoise)

  • 首先介绍一下高斯分布的表达,记作X∽N(μ,σ2):

扩散过程每一步都加入一个方差为βt∈(0,1)的高斯噪声可以用马尔科夫链来表示:

  • 这里的βt是一个0到1的等比序列(β0=0),此时表示原始图像;第T步的时候,βt=1,表示标准高斯噪声N∽(0,I)。因而实际上扩散过程是一个从原始图像变为标准高斯分布的过程。加噪和高斯采样等价,无非就是改变了一下高斯采样的均值中心点。
  • 实际训练的时候,我们可以直接用下面的公式一次性算出某一步的加噪图片作为训练素材,无需逐步迭代。
  • 去噪过程和扩散过程反过来:从一张随机采样的高斯噪声图片逐步去噪得到我们想要生成的图像。表达式:
  • 去噪过程,需要用模型预测加入的高斯噪声,得到原始的无噪声的图像。上式表示,利用模型算出原始第n步的未加噪图像,实质上只要算出均值和方差,再做一个采样得到原始图像。而为了算出μ,我们需要预测出噪声ϵ,反推出原始图像的均值中心,方差项可以由网络预测也可以取常数(前者效果好)。下节将介绍模型的训练和推理过程。

3. 训练和采样(Training&Sampling)

  • 训练其实就是扩散过程,而采样其实就是去噪过程。
  • 算法如上图所示,训练training的过程实际上是随机采第t步的加噪图像,输入带噪图片以及步数t,模型预测噪声ϵ,模型训练目标:预测噪声与实际加入噪声的误差越小越好。
  • 采样sampling的过程(生成过程)为:将有噪声的图像(第一张图像为随机采样的高斯分布噪声)减去模型预测的噪声(噪声前面的其它参数可以由上面加噪的过程反向推导出来)不断把噪声去掉以恢复出原始的图像。
  • 方差项σ也可以由模型来预测。

参考文献: Improved Denoising Diffusion Probabilistic Models

引导扩散模型(Guided Diffusion)

前文已经讲述扩散模型的原理,然而我们随机输入一张高斯噪声显然不能按照人的意愿生成我们想要的内容,因而需要额外的引导guidance以得到我们需要的图像。一种想法是使用外部模型(分类器or广义的判别器)的输出作为引导条件来指导扩散模型的去噪过程,从而得到我们想要的输出;还有一种则比较直观一些:我们直接把我们想要的引导条件condition也作为模型输入的一部分,从而让扩散模型见到这个条件后就可以直接生成我们想要的内容。

下文将讲解classifier guidance和semantic guidance diffusion model(后者包括前者,前者是比较简单的一个应用),除此之外,由于额外的判别器会拖慢推理速度,因此后来有人提出了 classifier-free guidance diffusion model来替代前面的那种方案,也即把条件作为模型的输入,直接生成我们需要的图像。

1. Classifier Guidance Diffusion Model

  • 这种方法不用额外训练扩散模型,直接在原有训练好的扩散模型上,通过外部的分类器来引导生成期望的图像。唯一需要改动的地方其实只有sampling过程中的高斯采样的均值,也即采样过程中,期望噪声图像的采样中心越靠近判别器引导的条件越好。
  • 上图总结了采样算法。Algorithm 1和 Algorithm 2其实是等价的(1是直接预测均值和方差,2是预测噪声的误差)。直接看Algorithm 1可知,实质上改变的只有高斯分布的均值中心,将扩散方向“引导”成我们想要的内容。具体而言,用分类模型pϕ对生成的图片进行分类,得到预测分数与目标类别的交叉熵,将其对带噪图像求梯度用梯度引导下一步的生成采样。(实际使用的时候,需要把这个分类器也在带噪数据额外训练一下)
  • 因为我们实际使用的模型预测的是噪音,实际计算为Algorithm 2,可以由1推导而来。(具体推导过程可以参考文献)

参考文献:Diffusion Models Beat GANs on Image Synthesis

2. Semantic Guidance Diffusion

  • 介绍完前面的 classifier guidance后,显然我们可以把分类器替换成其它任意的判别器,也即更换引导条件,从而实现利用不同的语义信息来指导扩散模型的去噪过程。比如说,我们可以实现text-guidance和image-guidance等。
  • 实质上就是把classifier guidance的条件推广,表达为:
  • Fϕ表示就是新的引导条件,这里展示的是分类的,其实也可以换成相似度之类的分数指标。具体可以有以下的例子:图像引导、文本引导、图像+文本引导。


参考文献:More Control for Free! Image Synthesis with Semantic Diffusion Guidance

3. Classifier-Free Guidance Diffusion

  • 正如前文提到的,额外引入一个网络来指导,推理的时候比较复杂(扩散模型需要反复迭代,每次迭代都需要额外算一个分数)。然而,直接将引导条件作为模型的输入,直到Classifier-Free Diffusion Guidance被提出前似乎效果也一般般。Classifier-Free Diffusion Guidance这篇文章的贡献就是提出了一个等价的结构替换掉了外部的判别器,从而可以直接用一个扩散模型来做条件生成任务。
  • 实际做法只是改变了模型输入的内容,有conditional(除了随机高斯噪声输入外,把引导信息的embedding也加进来)和unconditional 的 sample输入。两种输入都会被送到同一个diffusion model从而让其能够具有无条件和有条件生成的能力。得到这两种输入的输出后,就可以用来引导扩散模型进行训练。
  • 回忆一下前面的 classifier guidance的噪音更新方式:
  • 实质上,这个classifier-free用另一个近似的等价结构替换掉了后面那一项:
  • 其中,ϵθ(xt,y) 表示conditional的输入,而ϵθ(xt)则表示unconditional输入,用这两项之差乘以一个系数来替换掉原来的那项。至于为什么可以这么直接替换,其实可以用贝叶斯公式推导而来:
  • 因而,实际上这个过程就训练了一个 implicit classifier,从而移除外部的分类器。

参考文献:Classifier-Free Diffusion Guidance

GLIDE

  • 这篇文章主要就是用到了前面所说的classifier-free扩散模型,只不过把输入的condition换成了文本信息,从而实现文本生成图像,此外还利用diffusion model实现了超分辨率。一些效果展示如下,可以看到,其实已经可以生成一些比较逼真的图片了。
  • 具体可以表达为:
  • 这里无非就是把原来的label y换成了 caption,实际上就是运用了足够量的image-text pair从而可以把caption当作是某种程度上的label。(随机替换为空序列以实现unconditional的训练方式)
动图封面
  • 由于此时的生成图像质量一般般,文章也提供了图像编辑的方式(具体操作为:将选中区域mask掉,将图像也作为一个condition连同文本输入到模型中去):

DALL·E 2

概况

  • 第一版DALL·E用的是GAN+CLIP重排序的结构。
  • DALL·E 2可以把diffusion model和CLIP结合在一起,生成效果十分惊艳,可以直接去官网浏览一下。DALL·E 2 (openai.com)
  • 包括prior网络用于将caption转换为CLIP image embedding,一个decoder把image embedding作为condition来生成图像。prior有两种:一种是autoregressive model、一种是diffusion model(后者效果更好一些);decoder就是diffusion model。总之,这里相比前面的变化主要在于加入了prior,以及把condition换成了CLIP的embedding。

Decoder

  • 具体而言,把CLIP image embedding作为condition输入到diffusion model中,同时把CLIP image embedding映射成4个额外的tokens接到GLIDE text encoder的输出。
  • 除了用于生成图像的diffusion model,这部分还有2个额外用于超分辨率的diffusion model,生成高清图像。

Prior

这部分的内容是为了将caption y转换为 CLIP image embedding,以用于后面decoder的图像生成。

  • 一种是auto-regressive model,将image embedding转换为一串离散的编码,并且基于condition caption y自回归地预测。(这里不一定要condition on caption(GLIDE的方法——额外用一个Transformer处理caption),也可以condition on CLIP text embedding)。此外,这里还用到了PCA来降维,降低运算复杂度。
  • 一种是diffusion model。这是一个decoder-only Transformer,输入是encoded text+CLIP text embedding+noised CLIP image embedding+额外token(类似class embedding)输入,其输出一个unnoised CLIP image embedding(取那个额外的embedding)。

Variations

  • 这部分是为了给一张图,生成相似的图像。做法很简单:用CLIP把图像编码,把这个CLIP image embedding作为condition引导decoder生成图像。除此之外,还可以对2张图像的CLIP embedding进行插值,以实现风格迁移。( spherical interpolation 几何球面线性插值)。这里证明了CLIP语义空间的可解释性

量化结果

  • 本文方法又称unCLIP(其实本质上就是把CLIP生成的embedding进行decode),相比GLIDE有小幅的提高。

Paper List

  1. (DDPM) Denoising Diffusion Probabilistic Models. NIPS 20. (Diffusion and deep-learning-based 图像生成开山之作)
  2. More Control for Free! Image Synthesis with Semantic Diffusion Guidance. arXiv 21. (对DDIM进行了推广,引入了一般形式的判别器引导)
  3. Denoising Diffusion Implicit Models. ICLR 21. (提出了一种新的sampling的方法,可以通过改变eta来skip一些step,进而达到加速sampling的目的)
  4. Improved denoising diffusion probabilistic models. ICML 21.
  5. Classifier-Free Diffusion Guidance. NIPSW 21. (引入了等价结构替代了分类器引导)
  6. GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models. ICML 22.
  7. Hierarchical Text-Conditional Image Generation with CLIP Latents. NIPS 22 在投. (DALL-E 2)
  8. Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding. NIPS 22 在投. (Imagen, SOTA)
  9. High-Resolution Image Synthesis with Latent Diffusion Models. CVPR 22. (隐空间LDM)