(本教程假设读者已熟悉神经网络的基础知识)
“神经网络”早已不再是计算机科学界,甚至整个社会的陌生词。之所以如此“酷”,原因不仅在于它解决了大量真实世界的问题,更在于它所能解决问题的类型之丰富。为什么它们可以如此多样?
无论是认知心理学领域、网络安全领域,还是医疗健康领域(暂且不谈计算机视觉、计算机图形学、自然语言处理等)。就连那些不那么常见的行业!几乎每个行业都在从神经网络所带来的智能与自动化中获益良多。
但为什么?这个问题总是反复出现!答案至今仍在积极研究中,因为神经网络本质上是一个“黑箱”,而它与大脑的相似性又让问题更加复杂。无论如何,回答这个问题并不是本文的目标。
可以肯定的一点是:想要让神经网络取得预期效果,必须确保其训练 有效。而到现在,您可能已经发现,训练非常大的 网络需要巨大的算力。没有优秀的 GPU、SSD,要训练一个非常大的 神经网络几乎 是不可能的。那多大算非常大 ?大到能在 ImageNet 数据集上取得好结果——因为这算是一种基准。
但当 Fast.ai 的一支杰出研究团队仅用 18 分钟、仅花 $40 就打败了谷歌的模型并达到了 93% 的准确率时,这种“训练庞大神经网络”的传统认知被彻底颠覆了。
有趣吗?请继续阅读!
但支撑这一结果的关键要素是什么?最先进的 GPU?最先进的 TPU?最先进的 SSD?
完全不是。团队的配置相当朴素——毕竟成本只有$40 。关键在于使用了最先进的算法来训练神经网络。这篇博客 中,著名研究者与教育者 Jeremy Howard 讨论了这次大胜的主要原因。
这是一个经典案例:昂贵硬件的力量不敌强大算法的力量。本文将深入介绍其中一种技术,它可以确保神经网络以尽可能合适的学习率进行训练。这一技术称为循环学习率 (CLR)。它最早由 Leslie N. Smith 于 2015 年提出。您可以在这里 查看原始论文。
但为什么只讨论学习率,而不涉及诸如 dropout 比例和激活函数等其他关键超参数?因为学习率是其中最重要的。就这么简单!
在本文中,您将学习:
快速回顾为什么需要学习率?
寻找神经网络最合适学习率的可用技术
循环学习率简介
循环学习率的内部机制
一个 Python 案例研究
为什么需要学习率?
我们快速回顾一下在训练神经网络时使用学习率的主要目的。
学习率是一个超参数 ,用于控制相对于损失梯度,您对网络权重的调整幅度。什么?为什么会出现梯度?因为您正在用梯度下降 来优化刚构建的神经网络。梯度下降的目标,本质上就是找到您的神经网络试图优化的损失函数的最小值。看看下面这张图[摘自 Andrew Ng 在 Coursera 上的深度学习课程]:
矩形中的项是网络开始学习其参数 $\theta$1 的更新规则,其中 $\alpha$ 是学习率。
在第一条曲线上,最底部的点就是损失函数的最小值。假设在当前迭代中,您的网络位于左上方附近。为了收敛到最低点,您对损失函数 $J(\theta1)$ 求偏导并计算梯度,以获得通往最低点的方向。
为了更快到达该点,您引入另一项 $\alpha$,即学习率。值越小,沿着下坡前进得越慢。虽然这在确保不遗漏任何局部极小值方面可能是个好主意(使用较小的学习率),但也可能意味着收敛时间会很长——尤其是在平台区域“卡住”时。
以上是用通俗语言对学习率目标的快速回顾。接下来将学习为您的神经网络选择合适学习率的技术。
来源:Jeremy Jordan 的博客
寻找神经网络最合适学习率的可用技术
神经网络并不存在固定的学习率。它取决于您所处理的问题类型、馈入网络的数据类型,以及更重要的,网络结构——而这会因问题而异。手工挑选学习率非常痛苦,因为如果您在训练大型网络,可能会产生巨大的成本,同时也非常耗时。
您是否应该运行诸如网格搜索或随机搜索等标准超参数优化方法?
对于大型网络同样糟糕。为什么总在谈大型网络?因为几乎所有真实世界的复杂问题都需要一个规模庞大的神经网络。
在 CLR 之前,曾提出过自适应学习率 ,可视作 CLR 的竞争者,但对自适应学习率的试验在计算上代价高昂,而 CLR 则不是。
直到现在,最常见的做法仍是将学习率设为常数,一旦准确率进入平台期便按数量级降低。
因此,显然需要一种系统化的技术,来简化为特定神经网络选择良好学习率的过程。不仅如此,还需要有充分的理由证明这种方法值得信赖。
看来循环学习率(CLR)来得正是时候。
循环学习率简介
循环学习率(CLR)的目标有二:
CLR 提供了一种设置训练神经网络全局学习率的方法,无需大量实验即可找到最佳取值,且不增加额外计算。
CLR 通过引入LR 范围测试 的概念,为实验提供了一个极佳的学习率范围(LR range)。
本节仅用于建立您对 CLR 工作原理的直观认识。下一节将深入更多细节。在前面的部分,您已大致了解了为什么要使用学习率。让我们再回顾一下。
理想的学习率应能使网络损失迅速下降。CLR 的“魔法”在此体现。原始的 CLR 论文讨论了一个实验,您可以观察学习率相对于损失的行为。这个实验非常易于可视化:在每个小批量 之后逐步增加学习率,并在每次递增时记录损失。这个逐步增加可以是线性的,也可以是指数的。没错,这本质上就是LR 范围测试 。
根据 Leslie 展示的实验,对于过低的学习率,损失可能下降,但速度很慢。当进入最优学习率区间时,您会观察到损失函数快速下降。如果进一步增大学习率,可能会导致网络的参数发散,从而使损失上升。因此,从这个实验可以明确,您感兴趣的是损失函数的陡降;为此,您可以在训练的不同阶段分析损失函数的梯度。
来源:Jeremy Jordan 的博客
从上图,您可以轻松分辨三个阶段:先是损失几乎不变,接着出现一次陡降,然后损失又开始缓慢上升。
您能看出其中最陡的那次下降吗?是的,您本质上希望落在那个范围内,而 CLR 将为您提供一种有纪律的方法来找到它。了解了这一点,我们继续深入。
更深入地了解 CLR
上述观察带来了一个重要的思考点:
CLR 的直觉源于这样一个想法:让学习率在一个范围内变化,而不是采用线性或指数式的单调递减。您可以通过设定一个明确的学习率范围,然后不进行线性或指数变化,而是在这个范围内循环变化学习率。Leslie 考虑了以下函数形式来循环改变学习率:
三角窗(线性)
Welch 窗(抛物线)
Hann 窗(正弦)
但所有形式都产生了等效结果,因此在原始论文中仅以三角形式(线性增加再线性减少)呈现这一思想,因其简单性。该策略也被称为三角学习率策略 。实现也很直接。下面是三角学习率策略 的通用实现:
local cycle = math. floor( 1 + epochCounter / ( 2 ∗ stepsize) )
local x = math. abs ( epochCounter / stepsize − 2 ∗ cycle + 1 )
local lr = opt. LR + ( maxLR − opt. LR) ∗ math. max ( 0 , ( 1 −x ) ) 技术支持
其中
opt.LR 是指定的较低(即基础)学习率,
epochCounter 是训练的轮数,
lr 是计算得到的学习率,
stepsize 是半个周期或循环长度,
max_lr 是最大学习率边界
参考下图,以便从视觉上理解三角形式:
来源:本教程开头提到的 CLR 原始论文。
除了三角学习率策略,论文中还提出了以下策略:
triangular2 ——与三角策略相同,不同之处在于每个周期结束时将学习率差减半。也就是说每个周期后学习率跨度会缩小。
exp range ——在此策略中,学习率在最小值与最大值边界之间变化,且每个边界值按指数因子衰减。
此时您可能会立刻问一个问题——如何估计合理的最小与最大边界值? 还记得刚刚学到的 LR 范围测试吗?现在您应该能更好地理解它的相关性了。
Leslie 提出了一个简单便捷的方式来确定学习率范围:
在若干个周期内运行模型,同时让学习率在较低与较高值之间线性增加(使用三角学习率策略)。
随后绘制准确率-学习率 曲线。记录准确率开始上升时的学习率,以及准确率增长放缓、变得不稳定或开始下降时的学习率。这两个学习率是定义学习率范围的不错选择。
现在来做一个快速的案例研究,看看 CLR 如何带来出色结果。
(在本教程的最后,您将看到一些对 CLR 的最新改进。但到目前为止,您已经理解了 CLR 的价值。)
Python 中的 CLR 案例研究
我们将使用经典的 MNIST 数据集,这可能是入门计算机视觉与深度学习最流行的数据集。如果您想深入了解 MNIST,请查看这篇博客 。在整个实验中将广泛使用 keras。keras 提供了该数据集的内置版本。我们将从导入数据集并进行一些基础 EDA 开始。
from keras. datasets import mnist
技术支持
Using TensorFlow backend.
技术支持
您已成功导入数据集。现在,对数据集进行一些基础可视化。
import matplotlib. pyplot as plt
( X_train, y_train) , ( X_test, y_test) = mnist. load_data( )
plt. subplot( 152 )
plt. imshow( X_train[ 0 ] , cmap= plt. get_cmap( 'gray' ) )
plt. subplot( 153 )
plt. imshow( X_train[ 1 ] , cmap= plt. get_cmap( 'gray' ) )
plt. subplot( 154 )
plt. imshow( X_train[ 2 ] , cmap= plt. get_cmap( 'gray' ) )
plt. subplot( 155 )
plt. imshow( X_train[ 3 ] , cmap= plt. get_cmap( 'gray' ) )
plt. show( )
技术支持
很好!我们将直接开始构建一个简单的多层神经网络。但在此之前,先做一些基础的数据预处理。
num_pixels = X_train. shape[ 1 ] * X_train. shape[ 2 ]
X_train = X_train. reshape( X_train. shape[ 0 ] , num_pixels) . astype( 'float32' )
X_test = X_test. reshape( X_test. shape[ 0 ] , num_pixels) . astype( 'float32' )
技术支持
我们做了什么?
数据集中的图像是 28×28 的尺寸,在一个简单的多层神经网络中不易直接处理。因此,我们将图像转换为一维,每张图像包含 784 个像素数据,使用 reshape() 函数完成。
图像中的像素值范围为 0 - 255。一个好主意是进一步将其归一化到 0 - 1 的范围。
X_train = X_train / 255
X_test = X_test / 255
技术支持
输出变量是 0 到 9 之间的整数。这是一个多分类问题。我们将对类别标签进行 one-hot 编码,把表示类别的整数向量转换为二进制矩阵。这样做是为了对类别进行“二值化”,从而可以将其作为特征包含到神经网络训练中。
您可以使用 keras 中的内置辅助函数 np_utils.to_categorical() 轻松完成。
from keras. utils import np_utils
y_train = np_utils. to_categorical( y_train)
y_test = np_utils. to_categorical( y_test)
num_classes = y_test. shape[ 1 ]
技术支持
现在来定义网络结构。本例将使用一个简单的全连接网络。在 keras 中通常分三步:
import numpy as np
from keras. models import Sequential
from keras. layers import Dense
model = Sequential( )
model. add( Dense( num_pixels, input_dim= num_pixels, kernel_initializer= 'normal' , activation= 'relu' ) )
model. add( Dense( num_classes, kernel_initializer= 'normal' , activation= 'softmax' ) )
model. compile ( loss= 'categorical_crossentropy' , optimizer= 'adam' , metrics= [ 'accuracy' ] )
技术支持
看看上面的代码做了什么。您按顺序构建网络(线性堆叠各层)。然后开始向网络中添加层:第一层加入的神经元数等于图像的像素数(784),并指定图像的输入维度,与像素数相同。您让网络用正态 分布初始化权重。最后,为第一层提供了 relu 作为激活函数。
在最后一层,将神经元数设为 10(即类别数),并使用 softmax 作为激活函数,将输出转换为类似概率的数值,从 10 个类别中选出一个作为模型的输出预测。
随后编译模型,以确定模型的优化方法(此处为 ADAM)以及该方法要优化的损失类型(此处为 categorical_loss)。
现在开始训练模型,并记录训练所用时间。同时测试其性能。
import timeit
from numpy. random import seed
seed( 1 )
startTime = timeit. default_timer( )
model. fit( X_train, y_train, validation_data= ( X_test, y_test) , epochs= 10 , batch_size= 200 , verbose= 2 )
elapsedTime = timeit. default_timer( ) - startTime
print ( "Time taken for the Network to train : " , elapsedTime)
scores = model. evaluate( X_test, y_test, verbose= 0 )
print ( "Baseline Error: %.2f%%" % ( 100 - scores[ 1 ] * 100 ) )
技术支持
Train on 60000 samples, validate on 10000 samples
Epoch 1 / 10
- 10s - loss: 0.2774 - acc: 0.9207 - val_loss: 0.1362 - val_acc: 0.9610
Epoch 2 / 10
- 8s - loss: 0.1113 - acc: 0.9675 - val_loss: 0.0951 - val_acc: 0.9720
Epoch 3 / 10
- 8s - loss: 0.0712 - acc: 0.9793 - val_loss: 0.0802 - val_acc: 0.9750
Epoch 4 / 10
- 8s - loss: 0.0503 - acc: 0.9857 - val_loss: 0.0687 - val_acc: 0.9788
Epoch 5 / 10
- 8s - loss: 0.0360 - acc: 0.9897 - val_loss: 0.0632 - val_acc: 0.9797
Epoch 6 / 10
- 8s - loss: 0.0267 - acc: 0.9928 - val_loss: 0.0643 - val_acc: 0.9784
Epoch 7 / 10
- 8s - loss: 0.0201 - acc: 0.9951 - val_loss: 0.0633 - val_acc: 0.9802
Epoch 8 / 10
- 8s - loss: 0.0150 - acc: 0.9962 - val_loss: 0.0613 - val_acc: 0.9808
Epoch 9 / 10
- 8s - loss: 0.0108 - acc: 0.9978 - val_loss: 0.0625 - val_acc: 0.9806
Epoch 10 / 10
- 8s - loss: 0.0076 - acc: 0.9988 - val_loss: 0.0612 - val_acc: 0.9809
Time taken for the Network to train : 86.4216202873591
Baseline Error: 1.91 %
技术支持
这个简单模型表现不错,大约 87 秒就达到了 1.91% 的错误率。现在来看看 CLR 的威力。先从 Github 克隆 keras 的 CLR 实现 。
克隆成功后,您的本地工作目录中应包含以下文件。
CLR 策略在此以keras callback 的形式实现。
from keras. callbacks import *
from clr_callback import *
from keras. optimizers import Adam
clr_triangular = CyclicLR( mode= 'triangular' )
model. compile ( optimizer= Adam( 0.1 ) , loss= 'categorical_crossentropy' , metrics= [ 'accuracy' ] ) 技术支持
在拟合网络时,将此 clr_triangular 传给 callbacks 参数。这次将使用更大的 batch_size,并记录时间。
startTime = timeit. default_timer( )
model. fit( X_train, y_train, validation_data= ( X_test, y_test) , epochs= 10 , batch_size= 2000 , callbacks= [ clr_triangular] , verbose= 2 )
elapsedTime = timeit. default_timer( ) - startTime
print ( "Time taken for the Network to train : " , elapsedTime)
scores = model. evaluate( X_test, y_test, verbose= 0 )
print ( "Baseline Error: %.2f%%" % ( 100 - scores[ 1 ] * 100 ) )
技术支持
Train on 60000 samples, validate on 10000 samples
Epoch 1 / 10
- 4s - loss: 0.0046 - acc: 0.9996 - val_loss: 0.0571 - val_acc: 0.9831
Epoch 2 / 10
- 4s - loss: 0.0030 - acc: 0.9998 - val_loss: 0.0575 - val_acc: 0.9829
Epoch 3 / 10
- 4s - loss: 0.0023 - acc: 0.9999 - val_loss: 0.0594 - val_acc: 0.9827
Epoch 4 / 10
- 4s - loss: 0.0018 - acc: 0.9999 - val_loss: 0.0589 - val_acc: 0.9835
Epoch 5 / 10
- 4s - loss: 0.0014 - acc: 1.0000 - val_loss: 0.0595 - val_acc: 0.9831
Epoch 6 / 10
- 4s - loss: 0.0011 - acc: 1.0000 - val_loss: 0.0600 - val_acc: 0.9836
Epoch 7 / 10
- 4s - loss: 0.0010 - acc: 1.0000 - val_loss: 0.0612 - val_acc: 0.9832
Epoch 8 / 10
- 4s - loss: 8.2190e-04 - acc: 1.0000 - val_loss: 0.0621 - val_acc: 0.9829
Epoch 9 / 10
- 4s - loss: 6.6182e-04 - acc: 1.0000 - val_loss: 0.0624 - val_acc: 0.9836
Epoch 10 / 10
- 4s - loss: 5.7177e-04 - acc: 1.0000 - val_loss: 0.0635 - val_acc: 0.9836
Time taken for the Network to train : 43.23223609056981
Baseline Error: 1.64 %
技术支持
看出优势了吗?这真的很惊艳!您的模型仅用 44 秒就完成训练,且错误率比之前更低。非常棒!
恭喜!
您已读到最后。在本教程中,您学习了寻找合适学习率这一关键问题,以及 CLR 如何彻底改变了您以往的思路。您较为深入地学习了 CLR,并通过小实验看到 CLR 如何在更短时间内产生优异结果。
接下来呢?由 CLR 引申出的两个“副产物”是:
学习以上两种方法,将获得此主题的更多洞见。此外,在 CLR 之后,Leslie 又发表了论文 A disciplined approach to neural network hyper-parameters: Part 1 -- learning rate, batch size, momentum, and weight decay ,重访 CLR,并讨论了为神经网络其他重要超参数选择取值的高效方法。Leslie 还在文中重访了他的一种技术——超收敛(Super Convergence) 。这篇论文对于那些“思考、进食、睡觉都离不开神经网络”的人来说,是必读之作。
CLR 的一大缺点是适用性有限。在投入生产之前,它还需要更加周全。
如果您想进一步了解神经网络,可以学习 DataCamp 的 Python 深度学习入门 课程,由 Dan Becker(Kaggle Learn 负责人)精心讲授,课程设计出色。
(本教程假设读者已熟悉神经网络的基础知识)
“神经网络”早已不再是计算机科学界,甚至整个社会的陌生词。之所以如此“酷”,原因不仅在于它解决了大量真实世界的问题,更在于它所能解决问题的类型之丰富。为什么它们可以如此多样?
无论是认知心理学领域、网络安全领域,还是医疗健康领域(暂且不谈计算机视觉、计算机图形学、自然语言处理等)。就连那些不那么常见的行业!几乎每个行业都在从神经网络所带来的智能与自动化中获益良多。
但为什么?这个问题总是反复出现!答案至今仍在积极研究中,因为神经网络本质上是一个“黑箱”,而它与大脑的相似性又让问题更加复杂。无论如何,回答这个问题并不是本文的目标。
可以肯定的一点是:想要让神经网络取得预期效果,必须确保其训练有效。而到现在,您可能已经发现,训练非常大的网络需要巨大的算力。没有优秀的 GPU、SSD,要训练一个非常大的神经网络几乎是不可能的。那多大算非常大?大到能在 ImageNet 数据集上取得好结果——因为这算是一种基准。
但当 Fast.ai 的一支杰出研究团队仅用 18 分钟、仅花 $40 就打败了谷歌的模型并达到了 93% 的准确率时,这种“训练庞大神经网络”的传统认知被彻底颠覆了。
有趣吗?请继续阅读!
但支撑这一结果的关键要素是什么?最先进的 GPU?最先进的 TPU?最先进的 SSD?
完全不是。团队的配置相当朴素——毕竟成本只有$40。关键在于使用了最先进的算法来训练神经网络。这篇博客中,著名研究者与教育者 Jeremy Howard 讨论了这次大胜的主要原因。
这是一个经典案例:昂贵硬件的力量不敌强大算法的力量。本文将深入介绍其中一种技术,它可以确保神经网络以尽可能合适的学习率进行训练。这一技术称为循环学习率(CLR)。它最早由 Leslie N. Smith 于 2015 年提出。您可以在这里查看原始论文。
但为什么只讨论学习率,而不涉及诸如 dropout 比例和激活函数等其他关键超参数?因为学习率是其中最重要的。就这么简单!
在本文中,您将学习:
为什么需要学习率?
我们快速回顾一下在训练神经网络时使用学习率的主要目的。
学习率是一个超参数,用于控制相对于损失梯度,您对网络权重的调整幅度。什么?为什么会出现梯度?因为您正在用梯度下降来优化刚构建的神经网络。梯度下降的目标,本质上就是找到您的神经网络试图优化的损失函数的最小值。看看下面这张图[摘自 Andrew Ng 在 Coursera 上的深度学习课程]:
矩形中的项是网络开始学习其参数 $\theta$1 的更新规则,其中 $\alpha$ 是学习率。
在第一条曲线上,最底部的点就是损失函数的最小值。假设在当前迭代中,您的网络位于左上方附近。为了收敛到最低点,您对损失函数 $J(\theta1)$ 求偏导并计算梯度,以获得通往最低点的方向。
为了更快到达该点,您引入另一项 $\alpha$,即学习率。值越小,沿着下坡前进得越慢。虽然这在确保不遗漏任何局部极小值方面可能是个好主意(使用较小的学习率),但也可能意味着收敛时间会很长——尤其是在平台区域“卡住”时。
以上是用通俗语言对学习率目标的快速回顾。接下来将学习为您的神经网络选择合适学习率的技术。
来源:Jeremy Jordan 的博客
寻找神经网络最合适学习率的可用技术
神经网络并不存在固定的学习率。它取决于您所处理的问题类型、馈入网络的数据类型,以及更重要的,网络结构——而这会因问题而异。手工挑选学习率非常痛苦,因为如果您在训练大型网络,可能会产生巨大的成本,同时也非常耗时。
您是否应该运行诸如网格搜索或随机搜索等标准超参数优化方法?
在 CLR 之前,曾提出过自适应学习率,可视作 CLR 的竞争者,但对自适应学习率的试验在计算上代价高昂,而 CLR 则不是。
直到现在,最常见的做法仍是将学习率设为常数,一旦准确率进入平台期便按数量级降低。
因此,显然需要一种系统化的技术,来简化为特定神经网络选择良好学习率的过程。不仅如此,还需要有充分的理由证明这种方法值得信赖。
看来循环学习率(CLR)来得正是时候。
循环学习率简介
循环学习率(CLR)的目标有二:
本节仅用于建立您对 CLR 工作原理的直观认识。下一节将深入更多细节。在前面的部分,您已大致了解了为什么要使用学习率。让我们再回顾一下。
理想的学习率应能使网络损失迅速下降。CLR 的“魔法”在此体现。原始的 CLR 论文讨论了一个实验,您可以观察学习率相对于损失的行为。这个实验非常易于可视化:在每个小批量之后逐步增加学习率,并在每次递增时记录损失。这个逐步增加可以是线性的,也可以是指数的。没错,这本质上就是LR 范围测试。
根据 Leslie 展示的实验,对于过低的学习率,损失可能下降,但速度很慢。当进入最优学习率区间时,您会观察到损失函数快速下降。如果进一步增大学习率,可能会导致网络的参数发散,从而使损失上升。因此,从这个实验可以明确,您感兴趣的是损失函数的陡降;为此,您可以在训练的不同阶段分析损失函数的梯度。
来源:Jeremy Jordan 的博客
从上图,您可以轻松分辨三个阶段:先是损失几乎不变,接着出现一次陡降,然后损失又开始缓慢上升。
您能看出其中最陡的那次下降吗?是的,您本质上希望落在那个范围内,而 CLR 将为您提供一种有纪律的方法来找到它。了解了这一点,我们继续深入。
更深入地了解 CLR
上述观察带来了一个重要的思考点:
CLR 的直觉源于这样一个想法:让学习率在一个范围内变化,而不是采用线性或指数式的单调递减。您可以通过设定一个明确的学习率范围,然后不进行线性或指数变化,而是在这个范围内循环变化学习率。Leslie 考虑了以下函数形式来循环改变学习率:
但所有形式都产生了等效结果,因此在原始论文中仅以三角形式(线性增加再线性减少)呈现这一思想,因其简单性。该策略也被称为三角学习率策略。实现也很直接。下面是三角学习率策略的通用实现:
其中
参考下图,以便从视觉上理解三角形式:
来源:本教程开头提到的 CLR 原始论文。
除了三角学习率策略,论文中还提出了以下策略:
此时您可能会立刻问一个问题——如何估计合理的最小与最大边界值?还记得刚刚学到的 LR 范围测试吗?现在您应该能更好地理解它的相关性了。
Leslie 提出了一个简单便捷的方式来确定学习率范围:
现在来做一个快速的案例研究,看看 CLR 如何带来出色结果。
(在本教程的最后,您将看到一些对 CLR 的最新改进。但到目前为止,您已经理解了 CLR 的价值。)
Python 中的 CLR 案例研究
我们将使用经典的 MNIST 数据集,这可能是入门计算机视觉与深度学习最流行的数据集。如果您想深入了解 MNIST,请查看这篇博客。在整个实验中将广泛使用
keras。keras提供了该数据集的内置版本。我们将从导入数据集并进行一些基础 EDA 开始。您已成功导入数据集。现在,对数据集进行一些基础可视化。
很好!我们将直接开始构建一个简单的多层神经网络。但在此之前,先做一些基础的数据预处理。
我们做了什么?
数据集中的图像是 28×28 的尺寸,在一个简单的多层神经网络中不易直接处理。因此,我们将图像转换为一维,每张图像包含 784 个像素数据,使用
reshape()函数完成。图像中的像素值范围为 0 - 255。一个好主意是进一步将其归一化到 0 - 1 的范围。
输出变量是 0 到 9 之间的整数。这是一个多分类问题。我们将对类别标签进行 one-hot 编码,把表示类别的整数向量转换为二进制矩阵。这样做是为了对类别进行“二值化”,从而可以将其作为特征包含到神经网络训练中。
您可以使用
keras中的内置辅助函数np_utils.to_categorical()轻松完成。现在来定义网络结构。本例将使用一个简单的全连接网络。在
keras中通常分三步:看看上面的代码做了什么。您按顺序构建网络(线性堆叠各层)。然后开始向网络中添加层:第一层加入的神经元数等于图像的像素数(784),并指定图像的输入维度,与像素数相同。您让网络用正态分布初始化权重。最后,为第一层提供了
relu作为激活函数。在最后一层,将神经元数设为 10(即类别数),并使用
softmax作为激活函数,将输出转换为类似概率的数值,从 10 个类别中选出一个作为模型的输出预测。随后编译模型,以确定模型的优化方法(此处为
ADAM)以及该方法要优化的损失类型(此处为categorical_loss)。现在开始训练模型,并记录训练所用时间。同时测试其性能。
这个简单模型表现不错,大约 87 秒就达到了 1.91% 的错误率。现在来看看 CLR 的威力。先从 Github 克隆
keras的 CLR 实现。克隆成功后,您的本地工作目录中应包含以下文件。
CLR 策略在此以
keras callback的形式实现。在拟合网络时,将此
clr_triangular传给callbacks参数。这次将使用更大的batch_size,并记录时间。看出优势了吗?这真的很惊艳!您的模型仅用 44 秒就完成训练,且错误率比之前更低。非常棒!
恭喜!
您已读到最后。在本教程中,您学习了寻找合适学习率这一关键问题,以及 CLR 如何彻底改变了您以往的思路。您较为深入地学习了 CLR,并通过小实验看到 CLR 如何在更短时间内产生优异结果。
接下来呢?由 CLR 引申出的两个“副产物”是:
学习以上两种方法,将获得此主题的更多洞见。此外,在 CLR 之后,Leslie 又发表了论文 A disciplined approach to neural network hyper-parameters: Part 1 -- learning rate, batch size, momentum, and weight decay,重访 CLR,并讨论了为神经网络其他重要超参数选择取值的高效方法。Leslie 还在文中重访了他的一种技术——超收敛(Super Convergence)。这篇论文对于那些“思考、进食、睡觉都离不开神经网络”的人来说,是必读之作。
CLR 的一大缺点是适用性有限。在投入生产之前,它还需要更加周全。
如果您想进一步了解神经网络,可以学习 DataCamp 的 Python 深度学习入门课程,由 Dan Becker(Kaggle Learn 负责人)精心讲授,课程设计出色。