深度学习三部曲:
第一步:搭建神经网络结构
第二步:找到一个合适的损失函数:交叉熵损失(cross entropy loss),均方误差(MSE),…
第三步:找到一个合适的优化函数,更新参数:反向传播(BP),随机梯度下降(SGD),…
一、神经网络基础
损失函数
常用分类损失:
1、交叉熵损失:
L
o
s
s
=
−
∑
i
y
i
ln
y
i
p
Loss=-\sum_{i}y_i\ln{y_i^p}
Loss=−i∑yilnyip
2、hinge loss:
L
(
y
,
f
(
x
)
)
=
m
a
x
(
0
,
1
−
y
f
(
x
)
)
L(y,f(x))=max(0,1-yf(x))
L(y,f(x))=max(0,1−yf(x))
常用回归损失:
1、均方误差:
M
S
E
=
∑
i
=
1
n
(
y
i
−
y
i
p
)
2
MSE=\sum_{i=1}^{n}(y_i-y_i^p)^2
MSE=i=1∑n(yi−yip)2
2、平均绝对值误差(L1损失):
M
A
E
=
∑
i
=
1
n
∣
y
i
−
y
i
p
∣
MAE=\sum_{i=1}^{n}|y_i-y_i^p|
MAE=i=1∑n∣yi−yip∣
softmax:
S
i
=
e
i
∑
j
e
j
S_i=\frac{e^i}{\sum_je^j}
Si=∑jejei
防止过拟合
- 及早停止 early stopping
- 正则化 regularization
L1正则化倾向于减少特征的个数,可用于降维;
L2正则化倾向于减小每一个特征的权重,使用更加普遍 - Dropout
在每一epoch中,随机按照给定的概率关闭某些结点,使得其他节点得到充分训练。
激活函数
- sigmoid函数:输出0-1之间的概率
s ( x ) = 1 1 + e − x s(x)=\frac{1}{1+e^{-x}} s(x)=1+e−x1
- softmax函数:多类别分类
s o f t m a x ( x ) = e x ∑ e x softmax(x)=\frac{e^x}{\sum_{}e^x} softmax(x)=∑exex - 双曲正切(hyperbolic tangent function):
t a n h ( x ) = e x − e − x e x + e − x tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}} tanh(x)=ex+e−xex−e−x
- 修正线性单元(rectified linear unit, ReLU)常用
梯度消失
初始值与梯度爆炸或梯度消失
sigmoid函数两端的导数非常小,几乎为0;
误差函数相对权重的导数(三层为例):
∂
E
∂
W
11
(
1
)
=
∂
E
∂
y
^
∂
y
^
∂
h
∂
h
∂
h
1
∂
h
1
∂
W
11
(
1
)
\frac{\partial E}{\partial W_{11}^{(1)}}=\frac{\partial E}{\partial \hat{y}}\frac{\partial \hat{y}}{\partial h}\frac{\partial h}{\partial h_1}\frac{\partial h_1}{\partial W_{11}^{(1)}}
∂W11(1)∂E=∂y^∂E∂h∂y^∂h1∂h∂W11(1)∂h1
由链式法则可知,计算得到的偏导数非常小,这会使得训练过程很难。
解决这一问题的办法是重新选择激活函数。
学习速率衰退
rule:
If steep(陡峭):long steps
If plain(平坦):small steps
学习速率随着模型越来越接近解决方案时降低。
局部极小值的解决办法
- 随机重新开始;
从几个随机的不同地点开始,从所有这些地点进行梯度下降,这就增加了到达全局最低点(或者说非常低的局部最低点)的概率。 - 基于动量(momentum,0到1之间的常量
β
\beta
β)
梯度下降优化
Keras中提供了许多优化程序,建议访问此链接或这篇精彩博文,详细了解这些优化程序。我这里简单的自己进行总结学习。前段时间发现国内也有学者对这篇综述进行了翻译,详细请见梯度下降优化算法综述。
梯度下降法是最小化目标函数 J ( θ ) J(θ) J(θ)的一种方法,其中, θ ∈ R d θ∈\R^d θ∈Rd为模型参数,梯度下降法利用目标函数关于参数的梯度 ∇ θ J ( θ ) ∇_θJ(θ) ∇θJ(θ)的反方向更新参数。学习率 η η η决定达到最小值或者局部最小值过程中所采用的步长的大小。即,我们沿着目标函数的斜面下降的方向,直到到达谷底。如果你对梯度下降法不熟悉,你可以从这里找到介绍神经网络优化的材料。
梯度下降变体
梯度下降有三种变体,区别于我们用于计算目标函数梯度的数据量。根据数据量,我们在参数更新的准确性和执行更新所需的时间之间进行权衡。
1. Batch gradient descent(批梯度下降)
Vanilla梯度下降法,又称为批梯度下降法(batch gradient descent),在整个训练数据集上计算损失函数关于参数θ的梯度过程如下:
- 对目标函数求偏导:
Δ J ( θ 0 , θ 1 ) Δ θ j = 1 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \frac{\Delta J(\theta_0,\theta_1)}{\Delta \theta_j} = \frac{1}{m} \sum_{i=1}^{m} (h_{\theta}(x^{(i)})-y^{(i)})x_j^{(i)} ΔθjΔJ(θ0,θ1)=m1i=1∑m(hθ(x(i))−y(i))xj(i) - 每次迭代更新参数:
θ j : = θ j − α 1 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \theta_j := \theta_j - \alpha \frac{1}{m} \sum_{i=1}^{m} (h_{\theta}(x^{(i)})-y^{(i)})x_j^{(i)} θj:=θj−αm1i=1∑m(hθ(x(i))−y(i))xj(i)
注意到这里更新时存在一个求和函数,因为在执行每次更新时,我们需要在整个数据集上计算所有的梯度,所以批梯度下降法的速度会很慢,同时,批梯度下降法无法处理超出内存容量限制的数据集。批梯度下降法同样也不能在线更新模型,即在运行的过程中,不能增加新的样本。
如果手动求导的话,批梯度下降的代码如下:
for i in range(nb_epochs):
params_grad = evaluate_gradient(loss_function, data, params)
params = params - learning_rate * params_grad
事实上,最新的深度学习库已经集成了自动求导的功能,如果手动求导的话,梯度检查是很重要的一个步骤。然后,我们在梯度的相反方向更新我们的参数,学习速率决定了我们执行的更新有多大。批量梯度下降保证收敛到凸误差表面的全局最小值和非凸表面的局部最小值。
优点:
- 一次迭代是对所有样本进行计算,此时利用矩阵进行操作,实现了并行。
- 由全数据集确定的方向能够更好地代表样本总体,从而更准确地朝向极值所在的方向。当目标函数为凸函数时,BGD一定能够得到全局最优。
缺点: 当样本数目 m 很大时,每迭代一步都需要对所有样本计算,训练过程会很慢。
2. Stochastic gradient descent(随机梯度下降, SGD)
相反,随机梯度下降法(stochastic gradient descent, SGD)根据每一条训练样本 x ( i ) x^{(i)} x(i)和标签 y ( i ) y^{(i)} y(i)更新参数:
- 对目标函数求偏导:
Δ J ( i ) ( θ 0 , θ 1 ) θ j = ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \frac{\Delta J^{(i)}(\theta_0,\theta_1)}{\theta_j} = (h_{\theta}(x^{(i)})-y^{(i)})x^{(i)}_j θjΔJ(i)(θ0,θ1)=(hθ(x(i))−y(i))xj(i) - 每次迭代更新参数:
θ j : = θ j − α ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \theta_j := \theta_j - \alpha (h_{\theta}(x^{(i)})-y^{(i)})x^{(i)}_j θj:=θj−α(hθ(x(i))−y(i))xj(i)
批量梯度下降为大型数据集执行冗余计算,因为它会在每次参数更新之前重新计算类似示例的渐变。 SGD通过一次执行一次更新来消除此冗余。因此,它通常更快,也可用于在线学习。SGD以高差异执行频繁更新,导致目标函数如图中那样大幅波动。
由于SGD的波动性,可以使得SGD跳出当前的局部最优,寻找到另外一个新的潜在更好地局部最优,这与批梯度下降陷入局部最优之后不在继续计算形成对比。但是,这也会带来问题,这使得最终收敛到特定最小值的过程变得复杂。事实上,已经证明当我们缓慢减小学习率,SGD与批梯度下降法具有相同的收敛行为,对于非凸优化和凸优化,可以分别收敛到局部最小值和全局最小值。与批梯度下降的代码相比,SGD的代码片段仅仅是在对训练样本的遍历和利用每一条样本计算梯度的过程中增加一层循环。
for i in range(nb_epochs):
np.random.shuffle(data)
for example in data:
params_grad = evaluate_gradient(loss_function, example, params)
params = params - learning_rate * params_grad
优点:
由于不是在全部训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上的损失函数,这样每一轮参数的更新速度大大加快。
缺点:
(1)准确度下降。由于即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。
(2)可能会收敛到局部最优,由于单个样本并不能代表全体样本的趋势。
(3)不易于并行实现。
3. Mini-batch gradient descent(小批量梯度下降)
将上述的两个变体结合,充分发挥各自的优势,于是就形成了小批量梯度下降,在每次更新时使用n个小批量训练样本,这里的n我们称之为batch_size。
θ
=
θ
−
η
⋅
∇
θ
J
(
θ
;
x
(
i
:
i
+
n
)
;
y
(
i
:
i
+
n
)
)
\theta = \theta - \eta \cdot \nabla_\theta J( \theta; x^{(i:i+n)}; y^{(i:i+n)})
θ=θ−η⋅∇θJ(θ;x(i:i+n);y(i:i+n))这种方法,
a)减少参数更新的方差,这样可以得到更加稳定的收敛结果;
b)可以利用最新的深度学习库中高度优化的矩阵优化方法,高效地求解每个小批量数据的梯度。
通常,小批量数据的大小在50到256之间,也可以根据不同的应用有所变化。当训练神经网络模型时,小批量梯度下降法是典型的选择算法,当使用小批量梯度下降法时,也将其称为SGD。注意:在下文的改进的SGD中,为了简单,我们省略了参数
x
(
i
:
i
+
n
)
;
y
(
i
:
i
+
n
)
x^{(i:i+n)}; y^{(i:i+n)}
x(i:i+n);y(i:i+n)。
在代码中,不是在所有样本上做迭代,我们现在只是在大小为50的小批量数据上做迭代:
for i in range(nb_epochs):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
params_grad = evaluate_gradient(loss_function, batch, params)
params = params - learning_rate * params_grad
梯度下降优化
挑战
- 选择合适的学习率可能很困难。学习率太小会导致痛苦的缓慢收敛,而学习率太大会阻碍收敛并导致损失函数在最小值附近波动甚至发散。
- 学习率调整试图在训练的过程中通过例如退火的方法调整学习率,即根据预定义的策略或者当相邻两代之间的下降值小于某个阈值时减小学习率。然而,策略和阈值需要预先设定好,因此无法适应数据集的特点。
- 此外,对所有的参数更新使用同样的学习率。如果数据是稀疏的,同时,特征的频率差异很大时,我们也许不想以同样的学习率更新所有的参数,对于出现次数较少的特征,我们对其执行更大的学习率。
- 高度非凸误差函数普遍出现在神经网络中,在优化这类函数时,另一个关键的挑战是使函数避免陷入无数次优的局部最小值。Dauphin等人指出出现这种困难实际上并不是来自局部最小值,而是来自鞍点,即那些在一个维度上是递增的,而在另一个维度上是递减的。这些鞍点通常被具有相同误差的点包围,因为在任意维度上的梯度都近似为0,所以SGD很难从这些鞍点中逃开。
优化
下面列举一些深度学习中广泛用于处理上述挑战的一些算法。
动量法(Momentum)
SGD很难通过陡谷,即在一个维度上的表面弯曲程度远大于其他维度的区域,这种情况通常出现在局部最优点附近。在这种情况下,SGD摇摆地通过陡谷的斜坡,同时,沿着底部到局部最优点的路径上只是缓慢地前进,这个过程如图2a所示。
如图所示,动量法是一种帮助SGD在相关方向上加速并抑制摇摆的一种方法。动量法将历史步长的更新向量的一个分量
γ
γ
γ增加到当前的更新向量中(部分实现中交换了公式中的符号)
v
t
=
γ
v
t
−
1
+
η
∇
θ
J
(
θ
)
v_t=γv_{t−1}+η∇_θJ(θ)
vt=γvt−1+η∇θJ(θ)
θ
=
θ
−
v
t
θ=θ−v_t
θ=θ−vt
动量项
γ
γ
γ通常设置为0.9或者类似的值。
从本质上说,动量法,就像我们从山上推下一个球,球在滚下来的过程中累积动量,变得越来越快(直到达到终极速度,如果有空气阻力的存在,则γ<1)。同样的事情也发生在参数的更新过程中:对于在梯度点处具有相同的方向的维度,其动量项增大,对于在梯度点处改变方向的维度,其动量项减小。因此,我们可以得到更快的收敛速度,同时可以减少摇摆。
除此之外还有许多其他的优化算法,比如自适应矩估计(Adaptive Moment Estimation,Adam)、RMSprop、Nadam、Adamax、Adadelta、Adagrad等,keras.optimizers中提供了非常丰富的接口供调用,不必完全理解每一个优化算法的原理,但是需要知道的是哪个场景下我们应该利用哪种优化算法,根据需求学习,然后按照文档的说明使用。
如果输入数据是稀疏的,选择任一自适应学习率算法可能会得到最好的结果。选用这类算法的另一个好处是无需调整学习率,选用默认值就可能达到最好的结果。
总的来说,RMSprop是Adagrad的扩展形式,用于处理在Adagrad中急速递减的学习率。RMSprop与Adadelta相同,所不同的是Adadelta在更新规则中使用参数的均方根进行更新。最后,Adam是将偏差校正和动量加入到RMSprop中。在这样的情况下,RMSprop、Adadelta和Adam是很相似的算法并且在相似的环境中性能都不错。Kingma等人[9]指出在优化后期由于梯度变得越来越稀疏,偏差校正能够帮助Adam微弱地胜过RMSprop。综合看来,Adam可能是最佳的选择。
有趣的是,最近许多论文中采用不带动量的SGD和一种简单的学习率的退火策略。已表明,通常SGD能够找到最小值点,但是比其他优化的SGD花费更多的时间,与其他算法相比,SGD更加依赖鲁棒的初始化和退火策略,同时,SGD可能会陷入鞍点,而不是局部极小值点。因此,如果你关心的是快速收敛和训练一个深层的或者复杂的神经网络,你应该选择一个自适应学习率的方法。
其他策略
- 批量归一化(Batch normalization)
为了便于学习,我们通常用0均值和单位方差初始化我们的参数的初始值来归一化。 随着不断训练,参数得到不同的程度的更新,我们失去了这种归一化,随着网络变得越来越深,这种现象会降低训练速度,且放大参数变化。
批量归一化在每次小批量数据反向传播之后重新对参数进行0均值单位方差标准化。通过将模型架构的一部分归一化,我们能够使用更高的学习率,更少关注初始化参数。批量归一化还充当正则化的作用,减少(有时甚至消除)Dropout的必要性。 - 及早停止 (Early stopping)
- 梯度噪音 (Gradient noise)
在每个梯度更新中增加满足高斯分布 N ( 0 , σ t 2 ) N(0,σ^2_t) N(0,σt2)的噪音,使得网络对不好的初始化更加鲁棒,同时对深层的和复杂的网络的训练特别有益。他们猜测增加的噪音使得模型更优机会逃离当前的局部最优点,以发现新的局部最优点,这在更深层的模型中更加常见。
数据增强(data augmentation)
参考博客:Building powerful image classification models using very little data
我经常听到的一条信息是“深度学习只有在你拥有大量数据时才有意义”。虽然不完全不正确,但这有点误导。当然,深度学习需要能够自动从数据中学习特征,这通常只有在有大量训练数据可用时才有可能 - 特别是对于输入样本非常高维的问题,如图像。然而,卷积神经网络 - 深度学习的支柱算法 - 是设计用于大多数“感知”问题(例如图像分类)的最佳模型之一,即使只有很少的数据需要学习。在小图像数据集上从头开始训练一个卷积层仍然会产生合理的结果,而不需要任何自定义特征工程。 Convnets很简单。他们是这项工作的正确工具。
但更重要的是,深度学习模型本质上是高度可再利用的:例如,您可以采用在大规模数据集上训练的图像分类或语音到文本模型,然后将其重新用于一个显着不同的问题,只需进行微小的更改,如我们将在这篇文章中看到。特别是在计算机视觉的情况下,许多预先训练的模型(通常在ImageNet数据集上训练)现在可以公开下载,并且可以用于从非常少的数据中引导强大的视觉模型。
为了充分利用我们的一些训练样例,我们将通过一系列随机变换来“扩充”它们,这样我们的模型就不会看到完全相同的两次图像。 这有助于防止过度拟合,并有助于模型更好地概括。
在Keras
中,可以通过keras.preprocessing.image.ImageDataGenerator
类完成:
配置在训练期间对图像数据执行的随机变换和标准化操作
通过.flow(数据,标签)
或.flow_from_directory(目录)
实例化增强图像批次(及其标签)的生成器。 然后,这些生成器可以与Keras模型方法一起使用,该方法接受数据生成器作为输入,fit_generator
,evaluate_generator
和predict_generator
。
我们看一个简单的例子:
from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
rescale=1./255,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
参数说明见官方文档
现在让我们开始使用这个工具生成一些图片并将它们保存到临时目录中,这样我们就可以了解我们的扩充策略正在做什么 - 我们在这种情况下禁用重新缩放以保持图像可显示:
from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
img = load_img('data/train/cats/cat.0.jpg') # this is a PIL image
x = img_to_array(img) # this is a Numpy array with shape (3, 150, 150)
x = x.reshape((1,) + x.shape) # this is a Numpy array with shape (1, 3, 150, 150)
# the .flow() command below generates batches of randomly transformed images
# and saves the results to the `preview/` directory
i = 0
for batch in datagen.flow(x, batch_size=1,
save_to_dir='preview', save_prefix='cat', save_format='jpeg'):
i += 1
if i > 20:
break # otherwise the generator would loop indefinitely
二、卷积神经网络
全连接网络处理图像的问题:参数太多:权重矩阵的参数太多,导致过拟合
卷积神经网络的解决方式:局部关联、参数共享
基本网络结构组成
卷积层、RELU激活层、池化层和全连接层
卷积层(conv layer):
二维卷积:
卷积核中的每一个值称为权重(weights);每一次卷积操作input中与kernel进行内积运算的区域叫做卷积核对应的感受野(receptive field);每一次卷积操作输出的矩阵叫做特征图(feature map);channel的个数和filter的个数保持一致。
padding:
当卷积出现下图所示的情形时,需要采用padding操作。
在输入的两边都进行补零操作:
输出的特征图大小计算:
未加padding时:
(
N
−
F
)
/
s
t
r
i
d
e
+
1
(N-F)/stride + 1
(N−F)/stride+1
有padding时:
(
N
+
p
a
d
d
i
n
g
∗
2
−
F
)
/
s
t
r
i
d
e
+
1
(N+padding*2-F)/stride + 1
(N+padding∗2−F)/stride+1
参数公式:
(
F
∗
F
∗
c
h
a
n
n
e
l
s
+
1
)
∗
f
i
l
t
e
r
s
(F*F*channels + 1)*filters
(F∗F∗channels+1)∗filters
池化层(pooling layer)
pooling:
保留了主要特征的同时减少参数和计算量,防止过拟合,提高模型泛华能力;
一般处于卷积层和卷积层之间,全连接层和全连接层之间。
pooling的类型:
Max pooling:最大值池化
Average pooling:平均池化
分类任务中常用最大值池化。
卷积神经网络典型结构
AlexNet
ReLU函数:
DropOut(随机失活):防止过拟合,训练时随机关闭部分神经元,测试时整合所有神经元。
Data Augmentation(数据增强):
- 平移、翻转、对称
随机crop;水平翻转,相当于样本倍增。 - 改变RGB通道强度
对RGB空间做一个高斯扰动。
ResNet(残差学习网络)
残差学习网络(deep residual learning network):