-
Notifications
You must be signed in to change notification settings - Fork 2
2. 从这里开始:ResNet示例
对于分类任务,可以使用以下两种方法之一来训练模型:
- 用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部分最复杂、最重要,所以放在末尾介绍。
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。
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中即可。
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"来指定唯一的一张卡。
本仓库与其它仓库最大的区别就是Model的配置写法。一般的仓库中,模型的结构是预定义好的,配置文件主要用来调整部分超参数。
而在DL-Theory-Practice中,模型结构也可以在配置文件中定义,并且灵活性很大。但过大的灵活性也让配置文件的可读性变差,写法变得复杂。
这一部分将以前文中ResNet的示例为参考,说明一些基本概念。 如果确实觉得太过复杂,也不用担心,仓库中会预置常见网络的配置文件,拿来即用。
模型定义在Model关键字下,需要首先指定所使用的网络架构。网络架构只定义Block的组合模式。例如,在ResNet的示例中,采用的网络架构称为“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完整结构,对一些参数有疑惑的时候可以对照该图来理解。
-
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
momentum:0.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的结构如下:
由图中可知,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下只支持写入一个具体方法。