第三卷 第五章 在ImageNet上训练VGGNet
在本章中,我们将从头开始学习如何在 ImageNet 数据集上训练 VGG16 网络架构。
该网络的特点是简单,仅使用3*3 卷积层堆叠在彼此之上,深度逐渐增加。 减少体积的空间维度是通过使用最大池化来实现的。 两个完全连接的层,每个层有 4,096 个节点(以及中间的 dropout),然后是一个 softmax 分类器。
今天,VGG 经常用于迁移学习,因为该网络表现出高于平均水平的能力,可以将其泛化到未经训练的数据集(与其他网络类型如 GoogLeNet 和 ResNet 相比)。但是从头开始训练 VGG 是一种痛苦。 该网络的训练速度非常缓慢,并且网络架构本身的权重非常大(超过 500MB)。如果您没有至少四个 GPU,我建议您不要训练。由于网络的深度以及全连接层,反向传播阶段非常缓慢。在 8 个 GPU 上训练 VGG 需要 10 天——如果少于 4 个 GPU,从头开始训练 VGG 可能需要非常长的时间(除非你非常有耐心)。也就是说,作为深度学习从业者,重要的是 了解深度学习的历史,尤其是预训练的概念,以及我们后来如何通过优化初始化权重函数来避免这种昂贵的操作。
本章将重点介绍 PReLU 激活函数和 MSRA 初始化的正确使用。
1、实施VGGNet
无论何时训练 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
2、训练VGGNet
创建imagenet_vggnet_config.py文件。参考下面的链接,
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)
3、评估VGGNet
为了评估 VGGNet,我们将使用上一章中的 test_alexnet.py 。 那里没有对脚本进行任何更改,因为本章中的 test_*.py 脚本旨在成为可以应用和重新应用到任何在 ImageNet 上训练的 CNN 的模板。
4、VGGNet 实验
在这个实验中,我使用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,非常适合任何类型的资源受限深度学习。
5、小结
在本章中,我们使用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在内的更专业的微架构奠定基础。