Skip to content

2. 从这里开始:ResNet示例

civ2018 edited this page Nov 4, 2023 · 9 revisions

2.1 一个完整的Config示例

对于分类任务,可以使用以下两种方法之一来训练模型:

  • 用IDE去运行trainer_classification.py,仅需在代码中指定config_file即可;
  • 使用如下命令来指定一个config_file来启动训练:

python trainer_classification.py --config_file your_config_file.yaml

因此对于用户来说,只需要设置好对应的config file即可。下面以经典的ResNet为例,我们首先给出它的完整配置文件,然后再对配置文件中的每部分详细解释。

Dataset:
  name: "CIFAR10"
  root_path: "../workspace/Datasets"
  batch_size: 128
  h: 32
  w: 32
  num_workers: 16
  pin_memory: True
Argumentation:
  RandomHorizontalFlip:
    p: 0.5
  RandomCrop:
    size: [32, 32]
    padding: 4
  mean: [0.485, 0.456, 0.406]
  std: [0.229, 0.224, 0.225]
Model:
  PlainOANet:
    in_channels    : 3
    hidden_channels: 16
    n_blocks_list  : [1, 3, 3, 3]
    stride_list    : [1, 1, 2, 2]
    stride_factor  : 2
    num_classes    : 10
    last_act       : "IdentityAct"
    use_pool       : False
    conv1          :
      ConvNormAct:
        norm: "BatchNorm"
        act: "ReLU"
        conv:
          Conv2d:
            kernel_size: 3
            bias: False
            padding: 1
    conv2:
      ResBlock:
        act: "ReLU"
        conv1:
          ConvNormAct:
            norm: "BatchNorm"
            act: "ReLU"
            conv:
              Conv2d:
                kernel_size: 3
                bias: False
                padding: 1
        conv2:
          ConvNormAct:
            norm: "BatchNorm"
            act: "IdentityAct"
            conv:
              Conv2d:
                kernel_size: 3
                bias: False
                padding: 1
        conv_sc:
          ConvNormAct:
            norm: "BatchNorm"
            act: "IdentityAct"
            conv:
              Conv2d:
                kernel_size: 1
                bias: False
                padding: 0
  OPT:
    SGD:
      lr: 0.1
      momentum: 0.9
      weight_decay: 1e-4
  Scheduler:
    MultiStepLR:
      gamma: 0.1
      milestones: [32000, 48000]
  Init:
    kaiming_normal_:
      mode: "fan_in"
      nonlinearity: "relu"

Train:
  iterations: 64000
  save_freq: 500
  output: "../workspace/Output/ResNet/ResNet_20-Layers_CIFAR10"
  keep_gradients: False
  device: [0]  # set to [0, 1, ...] for multiple GPUs; set "cpu" for cpu

整个配置文件一共分成四部分:

  • Dataset:与数据集加载相关的配置
  • Argumentation:与数据增强相关的配置
  • Model:模型结构
  • Train:与训练相关的配置

下面对上述四部分做详细解释。因为Model部分最复杂、最重要,所以放在末尾介绍。

2.2 Dataset

Dataset是与数据相关的配置参数。有两种方法来指定数据集:

  • 通过关键字name来指定PyTorch内置的数据集。并通过关键字root_path来指定数据集存放的路径。当数据集不存在时,会自动下载到该目录下。目前只支持CIFAR10。关键字name和root_path需要同时指定。上述ResNet示例采用的是这种方式。
  • 通过关键字trn_path和tst_path来分别指定训练集和测试集所在的根目录。在这种设置下,数据需要按照如下结构准备:

trn_path下包含多个文件夹,每一个文件夹表示不同类别。属于同一类别的图像存放在对应的文件夹下。

例如,假设有一个区分猫狗的数据集,训练集在“cat_dog_trn/”目录下,测试集在“cat_dog_tst”目录下。在“cat_dog_trn/”目录下中有两个子文件夹:“cat”文件夹存放所有用于训练的猫的图像;"dog"文件夹中存放所有用于训练的狗的图像。“cat_dog_tst/”目录下也需要包含这两个文件夹,且名字需要和在“cat_dog_trn/”目录下保持一致。在这种情况下,指定数据集的config如下:

Dataset:
  trn_path: "cat_dog_trn/"
  tst_path: "cat_dog_tst/"

batch_size:指定数据批大小。

h、w:分别指定输入图像的高和宽。输入图像会首先缩放至尺寸(h, w)再送进网络。

num_workers:读取数据的进程数。根据数据集大小和机器配置而定,通常取值1、4、8、16。

pin_memory:根据PyTorch官方文档,pin_memory=True可以极大加快从CPU到GPU的数据传输。所以只要数据集规模不是特别小,且需要用到GPU,可以将pin_memory设置为True。

2.3 Argumentation

Argumentation是与数据增强相关的配置参数。 Argumentation下可以写入任何PyTorch支持的增强方法

注:目前只支持torchvision0.16中torchvision.transforms中的增强方法。torchvision.transforms.v2暂不支持。

写法也很简单,只需首先写入增强方法的名称(如RandomHorizontalFlip),然后再在该关键字下写入参数(如p: 0.5)。 需要注意,方法名和参数名需要与PyTorch严格一致。 多个增强方法需要注意它们的顺序,由上至下执行。 to_tensor()不用写在此处,会自动加入。

在ResNet的示例中,一共使用了两个增强方法:

  RandomHorizontalFlip:
    p: 0.5
  RandomCrop:
    size: [32, 32]
    padding: 4

注意它们的顺序!!! 代码中会顺序执行指定的增强方法。mean、std的顺序不重要,只要写在Argumentation中即可。

2.4 Train

Train是与训练相关的一些配置。

iterations:总的训练迭代次数。

save_freq:存储checkpoint的频率,也是训练、测试信息的打印频率。示例中的save_freq=500表示每训练500次iter,存储一次。

output:checkpoint以及其它相关信息的输出目录。

keep_gradients:是否记录梯度信息。设置为True,在存储的checkpoint中可用key "gradients"取出每一层的梯度的L2值。

device:设置cpu或gpu。如果使用cpu,设置为“cpu”。如果使用GPU,可使用一个列表来指定GPU编码,如[0, 1]。列表中的第一个设备是主设备,占用的存储会略高。所以[0,1]和[1,0]的含义不一样。也可以直接使用"gpu"来指定唯一的一张卡。

2.5 Model

本仓库与其它仓库最大的区别就是Model的配置写法。一般的仓库中,模型的结构是预定义好的,配置文件主要用来调整部分超参数。

而在DL-Theory-Practice中,模型结构也可以在配置文件中定义,并且灵活性很大。但过大的灵活性也让配置文件的可读性变差,写法变得复杂。

这一部分将以前文中ResNet的示例为参考,说明一些基本概念。 如果确实觉得太过复杂,也不用担心,仓库中会预置常见网络的配置文件,拿来即用。

模型定义在Model关键字下,需要首先指定所使用的网络架构网络架构只定义Block的组合模式。例如,在ResNet的示例中,采用的网络架构称为“PlainOANet”。PlainOANet的网络结构如下图所示:

PlainOANet

上图表示PlainOANet由两个Block构成,一个称为Conv1,另一个称为Conv2(先不管Conv1和Conv2的具体形式,只需要知道它们是标准的building block即可)。PlainOANet的构成如下:

  • 网络的第一部分由N1个Conv1构成,如图中第一个蓝色方块表示。我们把这种重复堆叠而形成的部分称为Part。所以也可以说:PlainOANet由k个Part构成,其中第一个Part由N1个Conv1构成。
  • 剩下的k-1个Part均由Conv2构成,每个Part堆叠的Conv2个数由N2, N3,...,N_k。

然后来看PlainOANet的参数部分。为方便理解,下图绘制了由上述配置文件定义的ResNet完整结构,对一些参数有疑惑的时候可以对照该图来理解。

ResNet_Example

  • in_channels:输入图像的通道数,示例中为3,表示BGR的彩色图像。

  • hidden_channels:表示第一个Part的输出通道数量,示例中为16。第一个Part又由多个Conv1构成,所以有一个问题是:Part1中由哪个具体的Conv1将通道数由3变成了16?本仓库遵循的原则是:每个Part中的第一个网络层负责通道变换。 例如,假设Part1由三个网络层构成,Conv11,Conv12和Conv13。那么Conv11首先会把通道由3变为16,Conv12和Conv13的输入、输出通道则都为16。

  • n_blocks_list:最重要的参数之一。这个列表定义了:1)网络由几个Part构成;2)每个Part中堆叠的block个数。列表的长度就是Part数量,而列表中的每个数值对应的是每个Part中block的个数。例如,示例中n_blocks_list=[1, 3, 3],表示该网络一共由3个Part构成,其中第一个Part由1个block构成;第2个和第3个Part均由3个block构成。在ResNet示例中,因为第一个Part的building block是Conv1,所以Part1实际上由1个Conv1构成;而Part2、Part3则各由3个Conv2堆叠而成,如图例中Part 1、Part2、Part3三部分所示。

  • stride_list:最重要的参数之一。这个列表定义了每一个Part中block使用的步长(stride)。该列表长度需要与n_blocks_list保持一致。注意,每个Part中只有第一个block会使用该步长,余下block步长都固定为1。这意味着,如果某个Part想对feature map使用步长2进行下采样,那么该Part中只有第一个block会对feature map进行下采样,余下的block会保持feature map的尺寸(后文会再详细解释)。

  • stride_factor:该参数定义了当下采样(或上采样)发生时,feature map的通道数增长(减少)的倍数。例如,ResNet示例中stride_factor=2,且由stride_list=[1, 1, 2]可知前两个Part的步长都为1,所以它们不会对通道进行变换(除了Part1中的第一个block,它的输出通道由hidden_channels决定)。因此第3个Part的输入通道数仍为16。而第3个Part的步长为2,所以它其中的第一个block会使用步长2将feature map尺寸减半,且让通道数变为原来2倍(2倍是因为stride_factor=2)。stride_factor也可以小于1,如0.5,这通常用于转置卷积的场景。

  • num_classes:类别数量。当num_classes>0时,网络会自动在最后一个block之后添加global average pooling层和一个全连层,来输出与num_classes数量一致的logits。当num_classes<=0时,最后一个block的输出就是网络的输出。这通常用于让网络作为某些下游任务主干网络的情况。在目标检测的场景中,会经常遇到num_classes<=0的设置情况。在ResNet示例中,num_classes=10,因此图例最后了FC-layer,包含Global average pooling和FC(不包含图例中的IdentityAct,它由下一个参数决定)。

  • last_act:在最后一个block之后使用的激活函数。正常情况下此处不使用任何激活函数,如ResNet示例中last_act="IdentityAct"。IdentityAct表示输入与输出相同的激活函数。

  • use_pool:第一个Part之后是否使用pool层。设置为True时,会固定添加一个大小为3,步长为2,padding为1的MaxPool层。这主要为实现早期的一些网络结构,它们经常在第一个卷积后使用MaxPool来快速将图像尺寸缩小,以加快训练速度,减少显存占用。

至此,关于网络结构的基础参数设置完毕。可以看到,虽然Conv1和Conv2的具体形式不清楚,但网络的整体轮廓已经比较清晰了。下面介绍最重要的Block参数的写法。

前文提到,PlainOANet网络需要指定两个Block,分别由参数Conv1和参数Conv2指定。这种Block参数的指定形式是所有网络都遵循的。也就是说,如果一个网络需要有三个Block,那么它对应的参数一定是Conv1、Conv2和Conv3。唯一的例外是只有一个Block的情况,这时通常参数名为Conv,而不是Conv1。

先来看Conv1。Conv1选择的具体Block是ConvNormAct。从名字能看出,这个Block是一个标准的conv-norm-act的形式。它有三个基本的参数,分别用来指定conv、norm和act的具体形式。其中Norm被指定为“BatchNorm”,Act被指定为“ReLU”。Norm和Act有两种指定方法。第一种如示例中所示,使用字符串来指定其名字,并使用默认参数。第二种是使用参数形式,如下所示:

  norm:
    BatchNorm2d:
      eps: 1e-5
      momentum0.1
  Act:
    LeakyReLU:
      negative_slope: 0.01

需要注意,名称和参数的名字写法必须与Pytorch一致。仓库中目前只适配了BatchNorm2d、ReLU、LeakyReLU。适配起来很简单,但仓库后续的实现会一直遵循“最小可用原则”,对当前目标无用的实现均不加入。后续随着支持的网络、任务越来越多,也会适配更多。如果大家有特殊需求,也可提issue或知乎私信。

因ConvNormAct是N-Block,所以它需要指定conv的具体形式。示例中,它选择的是标准的Conv2d,并且指定其参数。注意,在此处,Conv2d的参数无需指定in_channels,out_channels和stride。这些都由网络结构决定,并会在构建过程中自动计算出来。 除此,Pytorch支持的其它参数均可在Conv2d中指定。Conv2d是M-Block,所以它不能再嵌套其它Block了。

再看Conv2。Conv2采用的Block是ResBlock。ResBlock的结构如下:

ResNet_Architecture

由图中可知,ResBlock由conv1、conv2、conv_sc和Act构成。conv1和conv2指定ResBlock中两个连续的Block的形式。conv_sc指定跳跃连接中使用的Block形式。与ResNet论文一样,conv_sc只有在ResBlock需要下采样而导致输入和输出尺寸不匹配,导致主分支和跳跃连接无法相加时才会使用(仍需设置,是否使用由网络自行决定)。Act指定相加后使用的激活函数。

conv1、conv2、conv_sc使用的都是ConvNormAct,不再重复介绍。大家可仔细对照它们的参数来理解为何这样设置。

最后,在Model参数下还需要指定优化器OPT、学习率调整方法Scheduler和参数初始化方法Init。这三者都支持以参数形式*(参考Norm和Act处)写入Pytorch支持的方法。其中,Scheduler参数允许写入多个方法,在训练时会一起执行。而OPT和Init下只支持写入一个具体方法。