6.ImageNet上训练AlexNet
在前一章中,我们详细讨论了ImageNet数据集;具体来说,数据集的目录结构和所使用的支持元文件为每个图像提供了类标签。在这里,我们定义了两组文件:
- 这个配置文件允许我们在ImageNet上训练卷积神经网络时轻松创建新的实验。
- 一组实用程序脚本,用于准备数据集,以便将驻留在磁盘上的原始图像转换为有效打包的mxnet记录文件。
使用mxnet提供的im2rec二进制文件,以及我们使用实用程序脚本创建的.lst文件,我们能够为每个培训、测试和验证集生成记录文件。这种方法的美妙之处在于.rec文件只需要生成一次——我们可以在任何ImageNet分类实验中重用这些记录文件。
其次,配置文件本身也是可重用的。当我们建立我们的第一个配置文件记住AlexNet,现实情况是,我们将使用相同的配置文件VGGNet, GoogLeNet, ResNet,以及SqueezeNet——唯一的方面需要更改配置文件,在培训一个新的网络ImageNet是:
- 网络架构的名称(嵌入在配置文件名中)。
- 批量大小;
- 用于培训网络的gpu数量(如果适用)。
在本章中,我们将首先使用mxnet库来实现AlexNet架构。在实践者包的第10章中,我们已经使用Keras实现了AlexNet。正如您将看到的,mxnet和Keras之间有许多相似之处,这使得在这两个库之间移植实现非常简单。然后,我将演示如何在ImageNet数据集上训练AlexNet。
本章和本包中演示如何在ImageNet数据集上训练给定网络架构的所有其他章节都被视为案例研究和实验室期刊的结合。对于本书的每一章,我都进行了数十到数百次的实验来收集相应的结果。我想和你分享我的思维过程,在具有挑战性的ImageNet数据集上训练深度的,最先进的神经网络,这样你可以通过观察我获得次优的结果-然后调整一些参数来提高我的准确性,以复制最先进的性能来获得经验。分享网络是如何训练的“故事”,而不仅仅是最终的结果,将有助于您自己的深度学习实验。观察他人,然后根据经验学习,是快速掌握成功使用大型图像数据集和深度学习所需技术的最佳方法。
6.1 实现AlexNet架构
在ImageNet上培训AlexNet的第一步是使用mxnet库实现AlexNet架构。我们已经在实践者包的第10章中回顾了Krizhevsky等人的[8]创意架构,在那里我们对AlexNet进行Kaggle Dogs vs. Cats挑战的训练,所以这个网络应该不会感到新鲜和陌生。也就是说,我已经包含了表6.1,表6.1表示了网络结构的完整性。
现在我们将使用Python和mxnet实现这个体系结构。作为个人偏好,我喜欢将我的mxnet CNN实现与Keras CNN实现分开。因此,我在pyimagessearch的nn模块中创建了一个名为mxconv的子模块:
所有使用mxnet实现的网络架构都在这个子模块中(因此模块名称以文本mx开头)。在mxconv中创建一个名为MxAlexNet .py的新文件来存储我们对MxAlexNet类的实现:
第2行导入我们唯一需要的库,为方便起见,将mxnet别名为mx。对于类似于OpenCV和cv2库的读者来说,所有的功能都被整洁地组织起来并包含在一个导入中——这样做的好处是保持我们的导入代码块整洁干净,但也要求体系结构代码稍微冗长一些。
然后我们在第6行定义MxAlexNet的构建方法,这是我们在本书中一直开发的一个标准。构建方法负责构建网络体系结构并将其返回给调用函数。
但是,检查结构时,您会注意到我们只需要一个参数class,即数据集中类标签的总数。参数的数量与在Keras中实现cnn相反,在Keras中我们还需要提供空间维度,包括宽度、高度和深度。为什么我们不需要向mxnet库提供这些值呢?我们会发现,mxnet明确不需要空间维度的原因是因为ImageRecordIter类(类负责阅读图片从我们的记录文件)自动推断我们的图像的空间维度——不需要显式地通过空间维度的类。
第8行定义了一个名为data的mxnet变量——这是一个非常重要的变量,因为它表示到神经网络的输入数据。没有这个变量,我们的网络将无法接收输入,因此我们将无法训练或评估它。
接下来,我们实现第一组CONV => RELU =>的POOL层:
//截止到P58页
//2022.2.27日下午12:36开始阅读笔记
正如您所看到的,mxnet API类似于Keras的API。函数名略有变化,但总的来说,很容易看到mxnet函数名与Keras函数名的映射(例如,Keras中的Conv2D只是mxnet中的Convolution)。mxnet中的每一层都要求你传入一个数据参数——这个数据参数是该层的输入。使用Keras和Sequential模型,输入将被自动推断出来。相反,mxnet为您提供了轻松构建图结构的灵活性,其中一层的输入不一定是前一层的输出。当我们开始定义更奇特的架构(如GoogLeNet和ResNet)时,这种灵活性尤其方便。
第11和12行定义了我们的第一个CONV层。这一层将我们的输入数据作为一个输入,然后应用11 × 11像素的内核大小,使用4 × 4的步幅,并学习num_filter=96个过滤器。
AlexNet最初的实现使用了标准的ReLU层;然而,我经常发现elu执行得更好,特别是在ImageNet数据集上;因此,我们将在第13行应用ELU。ELU激活是在LeakyReLU类中实现的,该类接受我们的第一个CONV层conv1_1作为输入。然后我们提供act_type="elu"来表示我们希望使用Leaky ReLU族的elu变体。在应用激活之后,我们希望通过BatchNorm类在第14行执行批处理规范化。请注意,我们不需要供应用于规范化激活的通道轴-通道轴由mxnet自动确定。
为了减少输入的空间维度,我们可以在第15行和第16行应用Pooling类。Pooling类接受bn1_1的输出作为它的输入,然后应用max Pooling,内核大小为3 × 3,步长为2 × 2。Dropout应用于第17行,帮助减少过拟合。
鉴于这是您第一次接触到在mxnet中实现层集,我建议您回头再读一两次这一节。在mxnet和Keras中的命名约定之间有明显的相似之处,但请确保您现在已经理解了它们,特别是数据参数,以及我们必须如何显式地将当前层的输入定义为前一层的输出。
AlexNet实现的其余部分,我将详细解释如下:
- 你已经在实践包中实现过一次AlexNet。
- 代码本身是相当不言自明的,解释每一行代码会变得令人作呕的乏味。
我们的下一层集合由另一个CONV => RELU =>池层组成:
这里我们的CONV层学习了256个过滤器,每个大小为5 × 5。然而,与Keras可以自动推断所需填充的数量(即填充="same")不同,我们需要显式地提供填充值,如上表所示。提供pad=(2, 2)确保输入和输出空间尺寸相同。
在CONV层之后,应用另一个ELU激活,然后是一个BN。对BN的输出采用Max pooling,将空间输入大小减小到13 × 13像素。同样,dropout被用来减少过拟合。
为了学习更深入、更丰富的特性,我们将把多个CONV => RELU层叠加在一起:
我们的第一个CONV层学习384个3 × 3滤波器,使用填充大小(1,1)来确保输入空间尺寸与输出空间尺寸匹配。在卷积之后立即应用激活,然后是批量归一化。我们的第二层CONV层也学习了384个3 × 3滤波器,然后再次进行激活和批量归一化。最后一层的CONV将学到的滤波器数量减少到256个;但是,保持相同的文件大小为3 × 3。
最终CONV的输出然后通过激活和批处理规范化传递。POOL操作再次用于减少卷的空间维度。Dropout跟随POOL来帮助减少过拟合。
按照上面的表6.1,实现AlexNet的下一步是定义两个FC层集:
每个FC层包括4096个隐藏单元,每个单元后面都有一个激活、批量归一化和更积极的50%的dropout。通常在FC层中使用40-50%的dropout,因为这是CNN连接最密集和最可能发生过拟合的地方。
最后,我们使用提供的类数量应用我们的softmax分类器:
在检查了这个实现之后,您可能会惊讶于mxnet库与Keras的相似之处。虽然不完全相同,但函数名和参数很容易匹配在一起。也许唯一的不便之处是我们现在必须显式地计算填充,而不是依赖于Keras的自动填充推理。否则,用mxnet实现卷积神经网络就像Keras一样简单。
6.2 训练AlexNet模型
现在我们已经在mxnet中实现了AlexNet架构,我们需要定义一个驱动程序脚本,负责实际培训网络。与Keras类似,使用mxnet训练一个网络是相当简单的,尽管有两个关键的区别:
- mxnet训练代码稍微有些冗长,因为我们希望利用多个gpu(如果可能的话)。
- mxnet库没有提供一种方便的方法来绘制随时间变化的损失/准确性,而是将训练进度记录到终端。
因此,我们需要使用Python的日志包来捕获该输出并将其保存到磁盘。然后,我们手动检查培训进度的输出日志,并编写Python实用程序脚本来解析日志并绘制我们的培训/损失。它比使用Keras稍微乏味一些;然而,由于(1)mxnet是一个带有Python绑定的编译c++库,(2)多个gpu,这样的代价是值得的,因此可以大大提高网络训练速度。
我在下面包含了一个mxnet培训日志的例子:
在这里,您可以看到我使用8个GPU来训练AlexNet(一个GPU就足够了,但是为了更快地收集结果,我使用了8个GPU)。在每一组批次之后(我们将在后面定义),培训损失、rank-1和rank-5的准确性被记录到文件中。一旦epoch完成,将重置训练数据迭代器,创建检查点文件并序列化模型权重,显示验证丢失、秩1和秩5的准确性。我们可以看到,在第一epoch之后,AlexNet在训练数据上得到≈3%的rank-1准确率,在验证数据上得到≈6%的rank-1准确率。
6.2.1 训练情节
训练日志很容易阅读和解释;然而,扫描一个纯文本文件并不弥补缺乏可视化——实际上可视化情节的损失和准确性随着时间的推移,可以使我们更好地,更明智的决定关于我们是否需要调整学习速率,适用更正规化,等等。
mxnet库(不幸的是)没有提供现成的工具来解析日志和构造训练图,因此我创建了一个单独的Python工具来为我们完成这项任务。我决定在PyImageSearch博客上直接讨论这个主题,而不是用实用程序代码(简单地使用基本的编程和正则表达式来解析日志)来进一步充实本章,你可以在这里了解更多关于plot_log.py脚本的信息:
但是,目前只需要理解这个脚本用于解析mxnet培训日志,并绘制我们各自的培训和验证损失和准确性。
6.2.2 实现训练脚本
现在我们已经定义了AlexNet架构,我们需要创建一个Python脚本来实际训练ImageNet数据集上的网络。首先,打开一个新文件,命名为train_alexnet.py,并插入以下代码:
第2-8行导入所需的Python包。注意我们是如何导入imagenet_ alexnet_config(别名为config)的,这样我们就可以访问特定于imagenet的训练配置了。然后,我们将在第3行mxnet中导入AlexNet架构的实现。正如我在本章前面提到的,mxnet将培训进度记录到文件中;因此,我们需要第6行上的日志包来捕获mxnet的输出,并将其直接保存到一个文件中,以便稍后解析。
从这里,让我们解析我们的命令行参数:
我们的train_alexnet.py脚本需要两个开关,然后是第三个可选开关。检查点开关控制到输出目录的路径,我们的模型权重将在每个epoch之后被序列化。与Keras需要显式定义何时将模型序列化到磁盘不同,mxnet在每个epoch之后都会自动这样做。
prefix命令行参数是您正在训练的体系结构的名称。在我们的例子中,我们将使用alexnet的前缀。前缀名将包含在每个序列化权重文件的文件名中。
最后,我们还可以提供一个“start-epoch”。当在ImageNet上训练AlexNet时,我们不可避免地会注意到训练停滞或过度拟合的迹象。在本例中,我们将使用ctrl +c 跳出脚本,调整学习速度,继续训练。通过提供-start-epoch,我们可以从已经序列化到磁盘的特定前一个epoch继续训练。
当我们从头开始训练我们的网络时,我们将使用以下命令来开始训练过程:
然而,如果我们想从一个特定的epoch(在本例中是epoch 50)继续训练,我们会提供以下命令:
当你在ImageNet数据集上训练AlexNet时,请记住这个过程。
如前所述,mxnet使用日志显示训练进度。我们不应该将培训日志显示到stdout,而是应该捕获该日志并将其保存到磁盘,这样我们就可以解析它并在以后查看它:
第21和22行根据——start-epoch的值创建了一个名为training_{epoch}.log的文件。为每个开始的epoch指定一个唯一的文件名将使绘图的准确性和随时间的损失变得更容易,如上面的PyImageSearch博文所述。
接下来,我们可以从磁盘加载RGB means并计算批处理大小:
以前,我们的批量大小总是一个固定的数字,因为我们只使用一个CPU/GPU/设备来训练我们的网络。然而,现在我们开始探索多gpu的世界,我们的批处理大小实际上是BATCH_SIZE * NUM_DEVICES。我们将初始批处理大小乘以用于训练网络的cpu / gpu /设备的总数的原因是,每个设备都在并行地解析单独的一批图像。在所有设备处理完它们的批处理后,mxnet更新网络中各自的权值。因此,由于并行化,我们的批处理大小实际上随着我们用于训练的设备数量的增加而增加。
当然,我们需要访问我们的训练数据,以便在ImageNet上训练AlexNet:
第31-42行定义了ImageRecordIter,它负责从训练记录文件中读取我们的图像批。这里我们指出,迭代器的每个输出图像在应用数据增强(包括随机裁剪、随机破坏、随机旋转和剪切)时,应该将大小调整为227×227像素。迭代器中也应用了均值减法。
为了加快训练速度(并确保我们的网络不会等待来自数据迭代器的新样本),我们可以为preprocess_threads提供一个值——生成N个线程,为迭代器轮询图像批处理并应用数据增强。我发现一个很好的经验法则是将preprocess_thread的数量设置为您用来训练网络的设备数量的两倍。
就像我们需要访问我们的训练数据一样,我们也需要访问我们的验证数据:
验证数据迭代器与训练数据迭代器相同,只是没有应用数据增强(但我们应用了均值减法归一化)。
接下来,让我们定义我们的优化器:
就像在Krizhevsky最初的论文中一样,我们将使用SGD训练AlexNet,初始学习率为1e−2,动量项为γ = 0.9, l2权值正则化(即“权值衰减”)为0.0005。SGD优化器中的rescale_grad参数非常重要,因为它根据批处理大小缩放梯度。没有这种缩放,我们的网络可能无法学习。
下一个代码块负责定义输出检查点路径目录的路径,以及初始化网络的参数和辅助参数:
在这种情况下,我们从AlexNet的第一个时代就开始培训,我们需要构建网络架构:
否则,如果我们从一个特定的epoch重新开始训练,我们需要从磁盘加载序列化的权值,并提取参数、辅助参数和模型“符号”(即mxnet所说的编译后的网络):
无论我们是从第一个epoch开始训练,还是从一个特定的epoch重新开始训练,我们都需要初始化表示我们想要训练的网络的前馈对象:
ctx参数控制着我们的培训内容。在这里,我们可以提供用于训练网络的gpu、cpu或设备的列表。在这种情况下,我使用三个gpu来训练AlexNet;但是,您应该根据系统上可用的gpu数量来修改这一行。如果你只有一个GPU,那么第85行是:
初始化器控制网络中稍后所有权值的权值初始化方法—这里我们使用Xavier(也称为Glorot)初始化,这是缺省的初始化方法大多数卷积神经网络(以及Keras默认使用的)。我们将允许AlexNet最多训练100个epoch,但同样,这个值可能会在您训练网络和监视过程时进行调整——它很可能是更少的epoch,也可能是更多的epoch。
最后,begin_epoch参数控制我们应该从哪个epoch开始继续训练。这个参数很重要,因为它允许mxnet保持内部记账变量的顺序。
就像Keras为我们提供了监控训练表现的回调,mxnet也一样:
在第95行,我们定义了一个Speedometer回调,在每个批处理结束时调用它。这次回调会在每个batchSize * 500个批次后给我们提供培训信息。你可以根据你想要的训练更新的频率来降低或提高这个值。较小的值将导致对日志的更新更多,而较大的值则意味着对日志的更新更少。
第96行定义了在每个epoch结束时调用的do_checkpoint回调。这个回调负责将我们的模型权重序列化到磁盘。同样,在每个epoch的末尾,我们的网络权值将被序列化到磁盘,使我们能够从需要的特定epoch开始重新启动训练。
最后,第97和98行初始化指标回调列表。我们将监测1级准确度(accuracy), 5级准确度(TopKAccuracy),以及分类交叉熵损失。
现在要做的就是训练我们的网络
对模型的.fit调用开始(或重新启动)训练过程。这里需要提供训练数据迭代器、验证迭代器和任何回调函数。调用.fit之后,mxnet将开始将结果记录到输出日志文件中,这样我们就可以检查培训过程。
虽然使用mxnet训练网络所需的代码可能看起来有点冗长,但请记住,这实际上是在ImageNet数据集上训练任何卷积神经网络的蓝图。当我们实现和培训其他网络架构时,如VGGNet、googlet等,我们只需要:
- 更改第2行以正确地设置配置文件。
- 更新trainIter和valIter中的data_shape(仅当网络需要不同的输入图像空间维度时)。
- 更新第54行和第55行上的SGD优化器。
- 在第69行更改被初始化的模型的名称。
除了这三个变化之外,在ImageNet数据集上训练各种cnn时,我们的脚本实际上不需要任何其他更新。我特意将我们的培训脚本编写为可移植和可扩展的。ImageNet Bundle的未来章节将需要更少的编码——我们将简单地实现新的网络架构,对优化器和配置文件做一些改变,并在几分钟内启动和运行。我的希望是,当您在自己的深度学习网络架构中实现和试验时,您将使用相同的脚本。
6.3 评估AlexNet
在本节中,我们将学习如何评估在ImageNet数据集上训练的卷积神经网络。本章具体讨论AlexNet;然而,正如我们在本书后面看到的,同样的脚本也可以用来评估VGGNet、GoogLeNet等,只需更改配置导入文件。
要了解如何更改配置,请打开一个新文件,命名为test_alexnet.py,并插入以下代码:
第2-6行导入所需的Python包。特别注意我们导入配置文件的第2行。如上所述,只需更改第2行以匹配您各自网络的配置文件,就可以使用这些完全相同的脚本套件来评估其他网络。
从那里,我们可以解析我们的命令行参数:
我们的脚本需要三个开关,每个开关详细如下:
- -检查点:这是在训练过程中到输出检查点目录的路径。2. -prefix:前缀是我们实际的CNN的名字。当我们运行test_alexnet.py脚本时,我们将为-prefix提供alexnet的值。3.-epoch:这里我们提供了我们的网络的epoch,我们希望使用它来进行评估。例如,如果我们在epoch 100之后停止训练,那么我们将使用第100 epoch在测试数据上评估我们的网络。
这三个命令行参数是必需的,因为它们都用于构建驻留在磁盘上的序列化模型权重的路径。
为了评估我们的网络,我们需要创建一个ImageRecordIter来循环测试数据:
第19行加载整个训练集中的平均Red、Green和Blue像素值,就像我们在训练过程中所做的那样。在测试期间,这些值将从图像中的每个单独的RGB通道中减去,然后通过网络获取我们的输出分类。回想一下,均值减法是数据规范化的一种形式,因此需要在所有三个训练集、测试集和验证集上执行。
第22-28行定义了用于循环测试集中的图像批的测试器。在这种情况下,不需要执行数据扩充,所以我们将简单地提供:
- 测试记录文件的路径。2. 图像的预期空间尺寸(3通道,宽、高分别为227 × 227)。3.在评估期间使用的批量大小——这个参数在测试期间不那么重要,因为它在交易期间,因为我们只是想获得我们的输出预测。4. 用于均值减除/归一化的RGB值。
每个epoch的AlexNet权值都被序列化到磁盘,所以下一步是使用前缀(网络名)和epoch(我们希望加载的特定权值)来定义输出检查点目录的路径:
第32和33行使用模型-前缀定义了输出-检查点目录的路径。我们知道,在训练过程中,输出的序列化权重使用以下文件名约定存储:
checkpointsPath变量包含文件路径的checkpoints_directory/prefix部分。然后使用第34行和第35行:
- 使用提供的-epoch号派生文件路径的其余部分。
- 从磁盘加载序列化的参数。
现在我们的预训练的权重已经加载,我们需要完成模型的初始化:
第38行将模型定义为前馈神经网络。我们将在评估过程中只使用一个GPU(尽管你当然可以使用多个GPU甚至CPU)。参数参数(arg_params)和辅助参数(aux_params)然后通过访问从磁盘加载的模型中各自的值来设置。
对测试集进行预测非常简单:
第46行定义了我们感兴趣的指标列表——分别是rank-1和rank-5。然后调用模型的.score方法来计算第1级和第5级精度。score方法要求我们传入测试集的ImageRecordIter对象,然后是我们想要计算的指标列表。在调用.score时,mxnet将遍历测试集中所有批次的图像,并比较这些分数。最后,第50和51行向我们的终端显示精度。
现在,我们已经具备了在ImageNet数据集上训练AlexNet所需的所有要素。我们有:
- 训练网络的脚本。2. 一个剧本,情节训练损失和准确性随着时间的推移。3.评估网络的脚本。
最后一步是开始进行实验,并应用科学的方法来获得AlexNet模型权重,以复制Krizhevsky等人的性能。
6.4 AlexNet实验
在编写本书的章节时,特别是那些与在ImageNet上培训最先进的网络架构相关的章节时,我想提供的不仅仅是代码和示例结果。相反,我想展示一个真正的“故事”,一个深度学习实践者是如何运行各种实验来获得一个理想的结果。因此,几乎ImageNet Bundle中的每一章都包含了这样的一个部分,在这里我创建了一个实验室期刊和案例研究的混合。在本节的其余部分中,我将描述我运行的实验,详细描述结果,然后描述我为提高AlexNet的准确性所做的更改。
在评估和比较AlexNet性能时,我们通常使用Caffe[16]提供的BVLC AlexNet实现,而不是原来的AlexNet实现。这种比较有很多原因,包括Krizhevsky等人使用了不同的数据增强,以及使用了(现已废弃的)本地响应规范化层(Local Response Normalization layer, LRNs)。此外,“CaffeNet”版本的AlexNet更容易被科学界使用。在本节的其余部分中,我将把我的结果与CaffeNet基准进行比较,但仍然要参考Krizhevsky等人最初的论文。
6.4.1 实验1
在我的第一个AlexNet on ImageNet实验中,我决定通过经验来演示为什么我们把批处理规范化层放在激活之后而不是激活之前。我也使用标准ReLUs而不是ELUs来获得模型性能的基线(Krizhevsky等人在他们的实验中使用ReLUs)。因此,我修改了本章前面详细介绍的mxalexnet.py文件,以反映批处理规范化和激活的变化,如下所示:
注意我的批处理规范化层在激活之前是怎样的,我使用的是ReLU激活函数。我在下面列出了表6.2,以反映我的历元数和相关的学习速率——在本节的其余部分,我们将回顾为什么我选择在每个历元上降低学习速率。
我开始使用SGD训练AlexNet,初始学习速率为1e−2,动量项为0.9,L2权重衰减为0.0005。开始训练过程的命令如下:
我允许我的网络进行训练,大约每10个时代监测一次进度。我看到新的深度学习实践者犯的最严重的错误之一就是过于频繁地检查他们的训练计划。在大多数情况下,你需要在10-15个时代的背景下,才能决定一个网络确实是过拟合、欠拟合等。在第70期之后,我绘制了我的训练损失和准确性(图6.1,左上角)。此时,验证和训练正确率基本停滞在≈49−50%,明显表明可以通过降低学习率来进一步提高正确率。
因此,通过编辑train_alexnet.py的第53行和第54行,我将我的学习速率更新为1e−3:
注意学习速率是如何从1e−2下降到1e−3的,但所有其他SGD参数保持不变。然后我使用以下命令从epoch 50开始重新开始训练:
我再次监视AlexNet的进程,直到第70期(图6.1,右上)。第一个关键您应该检查从这个情节是如何降低我的学习速率从1 e−2比1 e−3急剧上升引起的准确性和戏剧性的下降损失立即过去时代50 -这种精度和减少损失是正常的,当你训练对大型数据集的神经网络。通过降低学习速率,我们允许我们的网络下降到较低的损耗区域,因为之前的学习速率太大,优化器无法找到这些区域。请记住,训练深度学习网络的目标并不一定是寻找全局最小值,甚至是局部最小值;而是简单地找到一个损耗足够低的区域。
然而,在后期,我开始注意到验证丢失/准确性方面的停滞(尽管训练的准确性/准确性继续提高)。这种停滞往往是一个明确的过拟合开始出现的信号,但验证和训练损失之间的差距是可以接受的,所以我并不太担心。我将我的学习速率更新为1e−4(再次,通过编辑train_alexnet.py的第53行和第54行),并从epoch 65开始重新开始训练:
V验证损失/准确性略有提高,但此时,学习速率开始变得太小——而且,我们开始对训练数据过拟合(图6.1,左下)。
我最终允许我的网络以1e−5的学习速率再训练10个epoch (80-90):
图6.1(右下)右边包含了最后十个时代的结果图。由于验证损失/准确性已经停止提高,而训练损失继续下降,因此没有必要在90年代以后进行进一步的训练,这使我们面临过拟合的风险。在epoch 90末,我对验证数据获得了54.14%的rank-1准确度和77.90%的rank-5准确度。这个精度对于第一次实验来说是非常合理的,但与我对alexnet级别性能的预期不太一样,BVLC CaffeNet参考模型报告的1级精度约为57%,5级精度为80%。
我故意不根据测试数据来评估我的实验。我知道还有更多的实验要进行,当我确信我已经获得了一个高性能模型时,我尝试只对测试集进行评估。记住,你的测试集应该非常谨慎地使用——你不想过度适合你的测试集;否则,您将完全破坏模型在数据集中的样本之外泛化的能力。
6.4.2 AlexNet实验2
本实验的目的是建立在前一个实验的基础上,并演示为什么我们在激活之后放置批处理规范化层。我保留了ReLU激活,但交换了批处理正常化的顺序,如下面的代码块所示:
本实验的目的是建立在前一个实验的基础上,并演示为什么我们在激活之后放置批处理规范化层。我保留了ReLU激活,但交换了批处理正常化的顺序,如下面的代码块所示:
我再次使用了完全相同的SGD优化器参数,初始学习率为1e−2,动量为0.9,L2权重衰减为0.0005。表6.3包括我的epoch和相关的学习速率时间表。我开始用下面的命令来训练AlexNet:
在第65期左右,我注意到验证丢失和准确性停滞不前(图6.2,左上角)。因此,我停止训练,调整我的学习率为1e−3,然后从第65纪元开始重新开始训练:
再一次,当验证准确性/损失趋于稳定时,我们可以通过降低学习率看到准确性的显著提升(图6.2,右上)。在第85阶段,我再次降低了我的学习速度,这次从1e−3降低到1e−4,并允许网络再训练15个epoch,之后验证损失/准确性停止提高(图6.2,底部)。
检查我的实验日志,我注意到我的rank-1准确率为56.72%,rank-5准确率为79.62%,比我之前在激活前放置批处理规范化层的实验好得多。此外,这些结果完全符合alexnet级别的真实性能的统计范围。
6.4.3 实验3
检查我的实验日志,我注意到我的rank-1准确率为56.72%,rank-5准确率为79.62%,比我之前在激活前放置批处理规范化层的实验好得多。此外,这些结果完全符合alexnet级别的真实性能的统计范围。
注意批处理规范化层是如何放置在激活之后的,同时elu替换ReLUs。在这个实验中,我使用了与前两次试验完全相同的SGD优化器参数。我也遵循了与第二次实验相同的学习进度表(表6.3)。
要复制我的实验,可以使用以下命令:
第一个命令从第一个epoch开始训练,初始学习速率为1e−2。第二个命令在65 epoch使用1e−3的学习速率重新启动训练。最后一个命令在第85纪元以1e−4的学习速率重新开始训练。
训练和验证损失/准确性的完整图表见图6.3。再一次,你可以看到在第65和85阶段将你的学习速度调整一个数量级的明显特征标记,随着学习速度的下降,跳跃变得不那么明显。我不想训练过去的100世纪,因为AlexNet显然开始过度适应训练数据,而验证准确性/损失仍然停滞不前。这个差距越大,过拟合就越差,因此我们应用“早停”正则化准则来防止进一步的过度拟合。
检查第100个epoch的准确性,我发现在验证数据集上,我获得了57.00%的rank-1准确度和79.52%的rank-5准确度。这个结果只比我的第二个实验稍微好一点,但是非常有趣的是,当我使用test_alexnet.py脚本对测试集进行求值时发生了什么:
我在表6.4中总结了结果。在这里您可以看到,我在测试集上获得了59.80%的rank-1和81.75%的rank-5准确度,当然高于大多数独立论文和出版物报告alexnet级别的准确度。为了方便您,我在您下载的ImageNet Bundle中包含了AlexNet实验的权重。
总的来说,本节的目的是让您了解为了在ImageNet数据集上获得一个合理执行的模型,需要进行哪些类型的实验。实际上,我的实验室日志包括了25个独立的AlexNet + ImageNet实验,太多了,不能包含在这本书中。相反,我选择了我对网络架构和优化器所做的最具代表性的重要改变。请记住,对于大多数深度学习问题,您将运行10-100次(可能更多),在您获得一个在验证和测试数据上都表现良好的模型之前。
深度学习不像其他编程领域,你写一次函数,它就永远工作。相反,有许多旋钮和杠杆需要调整。一旦您调整参数,您将得到一个表现良好的CNN,但在此之前,请耐心,并记录您的结果!记下什么有用,什么没用是非常宝贵的——这些笔记能让你反思自己的实验,并找到新的追求途径。
6.5 总结
在本章中,我们使用mxnet库实现AlexNet架构,然后在ImageNet数据集上训练它。这一章相当长,因为我们需要全面回顾AlexNet架构、培训脚本和评估脚本。现在我们已经定义了我们的培训和评估Python脚本,我们将能够在未来的实验中重用它们,使培训和评估变得非常容易——我们的主要任务将是实现实际的网络架构。
我们所做的实验让我们发现了两个重要的结论:
- 在大多数情况下,将批次归一化放在激活后(而不是激活前)会导致更高的分类精度/更低的损失。
- 将ReLUs替换为ELUs可以略微提高分类精度。
总体而言,我们在ImageNet上获得了59.80%的rank-1和81.75%的rank-5准确度,优于用于AlexNet基准测试的标准Caffe参考模型。
上述实验代码详见本人github仓库: