在本章中,我们将从头开始学习如何在 ImageNet 数据集上训练 VGG16 网络架构。
该网络的特点是简单,仅使用3*3 卷积层堆叠在彼此之上,深度逐渐增加。 减少体积的空间维度是通过使用最大池化来实现的。 两个完全连接的层,每个层有 4,096 个节点(以及中间的 dropout),然后是一个 softmax 分类器。
今天,VGG 经常用于迁移学习,因为该网络表现出高于平均水平的能力,可以将其泛化到未经训练的数据集(与其他网络类型如 GoogLeNet 和 ResNet 相比)。但是从头开始训练 VGG 是一种痛苦。 该网络的训练速度非常缓慢,并且网络架构本身的权重非常大(超过 500MB)。如果您没有至少四个 GPU,我建议您不要训练。由于网络的深度以及全连接层,反向传播阶段非常缓慢。在 8 个 GPU 上训练 VGG 需要 10 天——如果少于 4 个 GPU,从头开始训练 VGG 可能需要非常长的时间(除非你非常有耐心)。也就是说,作为深度学习从业者,重要的是 了解深度学习的历史,尤其是预训练的概念,以及我们后来如何通过优化初始化权重函数来避免这种昂贵的操作。
本章将重点介绍 PReLU 激活函数和 MSRA 初始化的正确使用。
无论何时训练 VGG16 或 VGG19,请确保:
1. 将所有 ReLU 换成 PReLU。
2. 使用 MSRA(也称为“He”)来初始化网络中的权重层。
创建mxvggnet.py文件。
# import the necessary packages import mxnet as mx class MxVGGNet: @staticmethod def build(classes): # data input data = mx.sym.Variable("data") # Block #1: (CONV => RELU) * 2 => POOL conv1_1 = mx.sym.Convolution(data=data, kernel=(3, 3), pad = (1, 1), num_filter = 64, name = "conv1_1") act1_1 = mx.sym.LeakyReLU(data=conv1_1, act_type="prelu", name = "act1_1") bn1_1 = mx.sym.BatchNorm(data=act1_1, name="bn1_1") conv1_2 = mx.sym.Convolution(data=bn1_1, kernel=(3, 3), pad = (1, 1), num_filter = 64, name = "conv1_2") act1_2 = mx.sym.LeakyReLU(data=conv1_2, act_type="prelu", name = "act1_2") bn1_2 = mx.sym.BatchNorm(data=act1_2, name="bn1_2") pool1 = mx.sym.Pooling(data=bn1_2, pool_type="max", kernel = (2, 2), stride = (2, 2), name = "pool1") do1 = mx.sym.Dropout(data=pool1, p=0.25) # Block #2: (CONV => RELU) * 2 => POOL conv2_1 = mx.sym.Convolution(data=do1, kernel=(3, 3), pad = (1, 1), num_filter = 128, name = "conv2_1") act2_1 = mx.sym.LeakyReLU(data=conv2_1, act_type="prelu", name = "act2_1") bn2_1 = mx.sym.BatchNorm(data=act2_1, name="bn2_1") conv2_2 = mx.sym.Convolution(data=bn2_1, kernel=(3, 3), pad = (1, 1), num_filter = 128, name = "conv2_2") act2_2 = mx.sym.LeakyReLU(data=conv2_2, act_type="prelu", name = "act2_2") bn2_2 = mx.sym.BatchNorm(data=act2_2, name="bn2_2") pool2 = mx.sym.Pooling(data=bn2_2, pool_type="max", kernel = (2, 2), stride = (2, 2), name = "pool2") do2 = mx.sym.Dropout(data=pool2, p=0.25) # Block #3: (CONV => RELU) * 3 => POOL conv3_1 = mx.sym.Convolution(data=do2, kernel=(3, 3), pad = (1, 1), num_filter = 256, name = "conv3_1") act3_1 = mx.sym.LeakyReLU(data=conv3_1, act_type="prelu", name = "act3_1") bn3_1 = mx.sym.BatchNorm(data=act3_1, name="bn3_1") conv3_2 = mx.sym.Convolution(data=bn3_1, kernel=(3, 3), pad = (1, 1), num_filter = 256, name = "conv3_2") act3_2 = mx.sym.LeakyReLU(data=conv3_2, act_type="prelu", name = "act3_2") bn3_2 = mx.sym.BatchNorm(data=act3_2, name="bn3_2") conv3_3 = mx.sym.Convolution(data=bn3_2, kernel=(3, 3), pad = (1, 1), num_filter = 256, name = "conv3_3") act3_3 = mx.sym.LeakyReLU(data=conv3_3, act_type="prelu", name = "act3_3") bn3_3 = mx.sym.BatchNorm(data=act3_3, name="bn3_3") pool3 = mx.sym.Pooling(data=bn3_3, pool_type="max", kernel = (2, 2), stride = (2, 2), name = "pool3") do3 = mx.sym.Dropout(data=pool3, p=0.25) # Block #4: (CONV => RELU) * 3 => POOL conv4_1 = mx.sym.Convolution(data=do3, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv4_1") act4_1 = mx.sym.LeakyReLU(data=conv4_1, act_type="prelu", name="act4_1") bn4_1 = mx.sym.BatchNorm(data=act4_1, name="bn4_1") conv4_2 = mx.sym.Convolution(data=bn4_1, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv4_2") act4_2 = mx.sym.LeakyReLU(data=conv4_2, act_type="prelu", name="act4_2") bn4_2 = mx.sym.BatchNorm(data=act4_2, name="bn4_2") conv4_3 = mx.sym.Convolution(data=bn4_2, kernel=(3, 3), pad=(1, 1), num_filter=512, name="conv4_3") act4_3 = mx.sym.LeakyReLU(data=conv4_3, act_type="prelu", name="act4_3") bn4_3 = mx.sym.BatchNorm(data=act4_3, name="bn4_3") pool4 = mx.sym.Pooling(data=bn4_3, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool3") do4 = mx.sym.Dropout(data=pool4, p=0.25) # Block #5: (CONV => RELU) * 3 => POOL conv5_1 = mx.sym.Convolution(data=do4, kernel=(3, 3), pad = (1, 1), num_filter = 512, name = "conv5_1") act5_1 = mx.sym.LeakyReLU(data=conv5_1, act_type="prelu", name = "act5_1") bn5_1 = mx.sym.BatchNorm(data=act5_1, name="bn5_1") conv5_2 = mx.sym.Convolution(data=bn5_1, kernel=(3, 3), pad = (1, 1), num_filter = 512, name = "conv5_2") act5_2 = mx.sym.LeakyReLU(data=conv5_2, act_type="prelu", name = "act5_2") bn5_2 = mx.sym.BatchNorm(data=act5_2, name="bn5_2") conv5_3 = mx.sym.Convolution(data=bn5_2, kernel=(3, 3), pad = (1, 1), num_filter = 512, name = "conv5_3") act5_3 = mx.sym.LeakyReLU(data=conv5_3, act_type="prelu", name = "act5_3") bn5_3 = mx.sym.BatchNorm(data=act5_3, name="bn5_3") pool5 = mx.sym.Pooling(data=bn5_3, pool_type="max", kernel = (2, 2), stride = (2, 2), name = "pool5") do5 = mx.sym.Dropout(data=pool5, p=0.25) # Block #6: FC => RELU layers flatten = mx.sym.Flatten(data=do5, name="flatten") fc1 = mx.sym.FullyConnected(data=flatten, num_hidden=4096, name="fc1") act6_1 = mx.sym.LeakyReLU(data=fc1, act_type="prelu", name="act6_1") bn6_1 = mx.sym.BatchNorm(data=act6_1, name="bn6_1") do6 = mx.sym.Dropout(data=bn6_1, p=0.5) # Block #7: FC => RELU layers fc2 = mx.sym.FullyConnected(data=do6, num_hidden=4096, name="fc2") act7_1 = mx.sym.LeakyReLU(data=fc2, act_type="prelu", name="act7_1") bn7_1 = mx.sym.BatchNorm(data=act7_1, name="bn7_1") do7 = mx.sym.Dropout(data=bn7_1, p=0.5) # softmax classifier fc3 = mx.sym.FullyConnected(data=do7, num_hidden=classes, name = "fc3") model = mx.sym.SoftmaxOutput(data=fc3, name="softmax") # return the network architecture return model
创建imagenet_vggnet_config.py文件。参考下面的链接,
读书笔记《Deep Learning for Computer Vision with Python》- 第三卷 第3章 准备ImageNet(2)_bashendixie5的博客-CSDN博客第三卷 第三章 准备ImageNet(2)1、构建 ImageNet 数据集构建 ImageNet 数据集的总体目标是让我们可以从头开始训练卷积神经网络。 因此,我们将在为 CNN 做准备的背景下回顾构建 ImageNet 数据集。 为此,我们将首先定义一个配置文件,该文件存储所有相关的图像路径、纯文本路径以及我们希望包含的任何其他设置。我们将定义一个名为 ImageNetHelper 的 Python 类,使我们能够快速轻松地构建...https://blog.csdn.net/bashendixie5/article/details/122149332 只修改
BATCH_SIZE = 32
NUM_DEVICES = 8
创建train_vggnet.py文件。
# import the necessary packages import imagenet_vggnet_config as config from mxvggnet import MxVGGNet import mxnet as mx import argparse import logging import json import os # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-c", "--checkpoints", required=True, help="path to output checkpoint directory") ap.add_argument("-p", "--prefix", required=True, help="name of model prefix") ap.add_argument("-s", "--start-epoch", type=int, default=0, help="epoch to restart training at") args = vars(ap.parse_args()) # set the logging level and output file logging.basicConfig(level=logging.DEBUG, filename="training_{}.log".format(args["start_epoch"]), filemode="w") # load the RGB means for the training set, then determine the batch # size means = json.loads(open(config.DATASET_MEAN).read()) batchSize = config.BATCH_SIZE * config.NUM_DEVICES # construct the training image iterator trainIter = mx.io.ImageRecordIter( path_imgrec=config.TRAIN_MX_REC, data_shape=(3, 224, 224), batch_size=batchSize, rand_crop=True, rand_mirror=True, rotate=15, max_shear_ratio=0.1, mean_r=means["R"], mean_g=means["G"], mean_b=means["B"], preprocess_threads=config.NUM_DEVICES * 2) # construct the validation image iterator valIter = mx.io.ImageRecordIter( path_imgrec=config.VAL_MX_REC, data_shape=(3, 224, 224), batch_size=batchSize, mean_r=means["R"], mean_g=means["G"], mean_b=means["B"]) # initialize the optimizer opt = mx.optimizer.SGD(learning_rate=1e-2, momentum=0.9, wd=0.0005, rescale_grad=1.0 / batchSize) # construct the checkpoints path, initialize the model argument and # auxiliary parameters checkpointsPath = os.path.sep.join([args["checkpoints"], args["prefix"]]) argParams = None auxParams = None # if there is no specific model starting epoch supplied, then # initialize the network if args["start_epoch"] <= 0: # build the LeNet architecture print("[INFO] building network...") model = MxVGGNet.build(config.NUM_CLASSES) # otherwise, a specific checkpoint was supplied else: # load the checkpoint from disk print("[INFO] loading epoch {}...".format(args["start_epoch"])) model = mx.model.FeedForward.load(checkpointsPath, args["start_epoch"]) # update the model and parameters argParams = model.arg_params auxParams = model.aux_params model = model.symbol # compile the model model = mx.model.FeedForward( ctx=[mx.gpu(i) for i in range(0, config.NUM_DEVICES)], symbol=model, initializer=mx.initializer.MSRAPrelu(), arg_params=argParams, aux_params=auxParams, optimizer=opt, num_epoch=80, begin_epoch=args["start_epoch"]) # initialize the callbacks and evaluation metrics batchEndCBs = [mx.callback.Speedometer(batchSize, 250)] epochEndCBs = [mx.callback.do_checkpoint(checkpointsPath)] metrics = [mx.metric.Accuracy(), mx.metric.TopKAccuracy(top_k=5), mx.metric.CrossEntropy()] # train the network print("[INFO] training network...") model.fit( X=trainIter, eval_data=valIter, eval_metric=metrics, batch_end_callback=batchEndCBs, epoch_end_callback=epochEndCBs)
为了评估 VGGNet,我们将使用上一章中的 test_alexnet.py 。 那里没有对脚本进行任何更改,因为本章中的 test_*.py 脚本旨在成为可以应用和重新应用到任何在 ImageNet 上训练的 CNN 的模板。
在这个实验中,我使用SGD优化器来训练VGG16,初始学习率为1e-2,动量项为0.9,L2权重正则化为 0.0005。为了加快训练速度,我使用了一个带有8个GPU的AmazonEC2实例。除非您非常有耐心,否则我不建议您尝试在GPU少于四个的机器上训练VGG。
我使用以下命令开始了 VGG 训练过程:
我让网络训练到第 50 期,此时训练和验证准确度似乎都停滞不前(图 7.2,左上角)。 然后我从 train_vggnet.py 脚本中按 ctrl + c 并将学习率从 1e-2 降低到 1e-3:
opt = mx.optimizer.SGD(learning_rate=1e-3, momentum=0.9, wd=0.0005, rescale_grad=1.0 / batchSize)
然后使用以下命令恢复训练:
在下图(右上)中,您可以看到在 20 个时期内降低学习率的结果。 您可以立即看到训练和验证准确度的大幅提升。
在第 70 时期之后,我再次注意到验证损失/准确率停滞,而训练损失继续下降——该指标表明过度拟合开始发生。
为了尽可能地从 VGG 中榨取最后一点性能(不会过度拟合太可怕),我再次将学习率从 1e3 降低到 1e4,并在第 70 期重新开始训练:
然后我让网络继续训练另外10个时期,直到第80个时期,在那里我应用了早期停止标准(图7.2,底部)。在第80个时期结束时,VGG16获得了68.77%的rank-1和88.78%的rank-5验证准确率。然后我使用以下命令在测试集上评估了第80个时期:
正如我的输出所示,VGG16 达到了 71.42% rank-1 和 90.03% rank-5 准确率,这与 Simonyan 和 Zisserman 的原始 VGGNet 论文几乎相同。 为完整起见,我在下表中包含了我的学习率计划,供希望复制这些结果的读者使用。
VGG16 的最大缺点(除了训练需要多长时间)是由此产生的模型大小,重量超过 533MB。
幸运的是,我们将在本包中讨论的所有剩余模型都比VGGNet小得多。我们高度准确的ResNet模型的权重为102MB。GoogLeNet更小,只有28MB。并且超小、高效的SqueezeNet模型大小仅为4.9MB,非常适合任何类型的资源受限深度学习。
在本章中,我们使用mxnet库实现了VGG16架构,并在ImageNet数据集上从头开始训练。我们没有使用繁琐、耗时的预训练过程来训练较小版本的网络架构,然后使用这些预训练的权重作为我们更深层次架构的初始化,而是跳过了这一步,依赖于 He 等人的工作 阿尔。 和米什金等人:
1. 我们用 PReLU 替换了标准的 ReLU 激活。
2. 我们为 MSRA/He 等人交换了 Glorot/Xavier 权重初始化。
这个过程使我们能够在一个实验中复制 Simonyan 和 Zisserman 的工作。无论何时从头开始训练类似 VGG 的架构,一定要考虑尽可能使用 PReLUs 和 MSRA 初始化。 在某些网络架构中,使用 PReLU + MSRA 时您不会注意到对性能的影响,但使用 VGG 时,影响是巨大的。
总的来说,我们的VGG16版本在ImageNet测试集上获得了71.42% rank-1 和 90.03% rank-5 的准确率,这是我们迄今为止在这个包中看到的最高准确率。此外,VGG 架构已经证明自己非常适合泛化任务。在下一章中,我们将更详细地探索微架构,包括GoogLeNet 模型,它将为包括ResNet和SqueezeNet在内的更专业的微架构奠定基础。