lecture3中主要讲了如何构建一个ML/DL任务,主要包括:选择问题、获得数据、设计模型、训练模型、测试模型、部署以及维护。然后coursera中的课程主要讲实际的应用例如调参、正则化等,以及几个DL常用优化算法。
目录
调参
-
深度学习的超参数一般包括:网络的深度(层数) L L L、隐层的大小 n [ l ] n^{[l]} n[l]、学习率 α \alpha α、迭代次数、以及正则化参数等。
-
一般把数据集分为:训练集(Train set)、开发/验证集(Dev set)、测试集(Test set)。
在大数据量下一般开发集和测试集所占比例都很小;
调参过程是在训练集上训练出模型,然后再开发集上看效果来调整具体的超参数。 -
高偏差(high bias)/ 欠拟合(underfitting):
更大、更深的网络;
增加迭代次数 / 尝试其他的优化算法;
尝试其他的神经网络模型 -
高方差(high variance)/ 过拟合(overfitting):
更多的数据;
采取正则化手段;
尝试其他的神经网络模型 -
初始参数中心化:
随机初始化参数用来打破对称性,确保不同的隐藏单元可以学习不同的东西;合适的参数初始化可以在一定程度上缓解梯度消失/梯度爆炸(vanishing / exploding gradients)的问题。
随机初始化不要过大,保证随机生成的参数满足0为均值, 2 n [ l − 1 ] \sqrt{2\over n^{[l-1]}} n[l−1]2或 1 n [ l − 1 ] \sqrt{1\over n^{[l-1]}} n[l−1]1为标准差的正态分布。
He初始化适用于ReLu,即初始化需要乘上 2 n [ l − 1 ] \sqrt{2\over n^{[l-1]}} n[l−1]2;
Xavier初始化的乘数为 1 n [ l − 1 ] \sqrt{1\over n^{[l-1]}} n[l−1]1 -
输入特征标准化(Normalizing inputs)
让输入特征满足0为均值,1为标准差的正态分布;
这样做可以加速训练,让代价函数优化起来更简单快速。
但值得注意的是,训练集和测试集需要做同样的归一化操作,即用同样的方法调整测试集,而不是在训练集和测试集上分别进行归一化处理。
正则化
1、Frobenius范数/ L2范数
- ∥ W ∥ F 2 = ∥ W ∥ 2 2 = ∑ i ∑ j w i . j 2 \|W\|^2_F=\|W\|^2_2=\sum_i\sum_j w_{i.j}^2 ∥W∥F2=∥W∥22=∑i∑jwi.j2
- L2正则化依赖于这样一个假设,即具有小权重的模型比具有大权重的模型更简单。因此,通过惩罚成本函数中权重的平方值,可以对权重进行压缩 J = 1 n ∑ i = 1 n ℓ ( y ^ i , y i ) + λ 2 n ∑ l = 1 L ∥ W ∥ F 2 J={1\over n}\sum_{i=1}^n\ell(\hat y_i,y_i)+{\lambda\over 2n}\sum_{l=1}^L\|W\|^2_F J=n1i=1∑nℓ(y^i,yi)+2nλl=1∑L∥W∥F2
- 权重衰减(weight decay):导致在每次迭代中梯度下降使权重变小的一种正则化技术(如L2正则化)。
因为在反向传播时,会多出一项关于权重的导数项(根据链式求导法则就可以求出来)
dW3 = 1./n * np.dot(dZ3, A2.T) + lambd/n * W3
这样在后面更新权重时需要多减去一项,即 W [ l ] = W [ l ] − α ⋅ d W [ l ] = W [ l ] − α ⋅ ( d W o [ l ] + λ n W [ l ] ) = ( 1 − α λ n ) W [ l ] − α ⋅ d W o [ l ] \begin{aligned}W^{[l]}&=W^{[l]}-\alpha \cdot dW^{[l]}\\ &=W^{[l]}-\alpha \cdot (dW^{[l]}_o+{\lambda\over n}W^{[l]})\\ &=(1-{\alpha\lambda\over n})W^{[l]}-\alpha \cdot dW^{[l]}_o\end{aligned} W[l]=W[l]−α⋅dW[l]=W[l]−α⋅(dWo[l]+nλW[l])=(1−nαλ)W[l]−α⋅dWo[l]
其中 d W o [ l ] dW^{[l]}_o dWo[l]表示没有正则化之前的导数。因此,由于 ( 1 − α λ n ) < 1 (1-{\alpha\lambda\over n})<1 (1−nαλ)<1,所以每次迭代更新参数都会让原本的参数衰减之后再进行更新。
2、dropout 随机失活
- dropout是随机地消除一些神经单元,这种消除并不是真正意义上拿掉这些神经单元,只是暂时性的让他们失活,这样可以在某种程度上使得整个网络变小,而且会起到正则化的作用。
- 下面是反向随机失活(inverted dropout)的做法:
# 在前向传播时:
D1 = np.random.randn(A1.shape[0], A1.shape[1]) < keep_prob # 设置一个阈值
A1 = A1 * D1 # 超过阈值的位置就dropout
A1 = A1 / keep_prob # 使得期望值不变
# 在反向传播是需要进行相同的操作:
dA1 = np.dot(W2.T, dZ2) * D1 / keep_prob # 这里的D1要一样
-
为什么能起到正则化的作用?
使用dropout可以使得下一层的神经元不能完全依赖上层的某一个神经元(因为他们可以随机失活),这样减少了对单个神经元的信任程度,所以只能分散权重(shrink weight),这样就起到了类似L2正则化的作用。 -
缺点:
使用dropout后由于每次迭代都会随机地移除一些神经元,导致代价函数并不是每次迭代后都会下降,尽管总的趋势还是随着迭代次数增加而下降,所以不能根据这样的调试工具来直接地评价优化算法的性能以及学习率的调整。
-
可以让每层的留存率都一样,或者不一样。一般说来,神经元数量多的隐层可以设置一个较小的留存率。
3、early stop 早停法
- 训练误差随着迭代次数增加而持续减小,开发集误差则先减少后增加,绘制出如下过程,然后选择在两种误差都比较小的阶段选择停止训练,这样可以起到防止过拟合的效果。
- 当迭代过程刚开始时,参数接近0,因为随机初始化时,它的值可能都是较小的随机值,而在迭代过程和训练过程中的值会变得越来越大,所以early stop做就是在中间点停止迭代过程,这样就得到一个值中等大小的 ∥ W ∥ F 2 \|W\|^2_F ∥W∥F2。这样来看,early stop作用与L2正则化类似,都是偏向参数范数较小的神经网络。
- 缺点:
它需要在“降低代价”和“防止过拟合”之间平衡,不能单独对以上某一目标进行优化,若想要降低代价,则需要牺牲泛化性能,若想要泛化性能较好则代价值不会很低。而其他正则化手段例如L2正则化是可以将两项任务进行分开操作的。
4、data augmentation 数据增强
- 想增加数据量有时很难做到,但是可以通过旋转、缩放、尺度变换、扭曲、噪声扰动等手段,可以对图片进行变换,以达到人工增加训练集的大小的目的。
优化算法
1、Mini-batch Gradient Descent 小批量梯度下降
-
批量梯度下降(GD)就是将所有的训练样本全都进行一次计算,得到参数的梯度来更新一次;而随机梯度下降(SGD)则是只随机的选取其中一个样本进行计算,来更新一次参数;小批量梯度下降则是介于两者中间,每次迭代只随机选取部分(大于1且小于n)的样本来进行计算,然后更新一次参数,这样将所有的样本全部用完一次就称为一次epoch。
-
具体做法是:
1、先随机打乱训练集,得到一个重排的训练集
2、给定mini_batch_size,第一次迭代选取前mini_batch_size的样本来更新参数,第二次迭代选择第mini_batch_size到2*mini_batch_size的样本来更新参数,这样依次直到最后一个batch中样本数量小于或等于mini_batch_size,这样就完成了一次epoch -
优点:
1、与GD相比,这样可以不用一次就遍历所有的数据,特别是在数据量很大时,可以加速优化过程
2、与SGD相比,可以利用向量化并行加速,也在一定程度上减少了随机性
EWMA(Exponentially Weighted Moving-Average) 指数加权滑动平均
-
V t = β V t − 1 + ( 1 − β ) θ t , 0 < β < 1 V_t=\beta V_{t-1}+(1-\beta)\theta_t,\ \ 0<\beta<1 Vt=βVt−1+(1−β)θt, 0<β<1
其中 V t V_t Vt表示到 t t t为止的累积量, θ t \theta_t θt为 t t t时刻的量。上式表示到当前时刻的累积量是由之前的累积量与当前量加权求和得到。将上式展开得到: V t = ( 1 − β ) θ t + β [ ( 1 − β ) θ t − 1 + β V t − 2 ] = ( 1 − β ) θ t + β ( 1 − β ) θ t − 1 + β 2 [ ( 1 − β ) θ t − 2 + β V t − 3 ] = ( 1 − β ) θ t + β ( 1 − β ) θ t − 1 + β 2 ( 1 − β ) θ t − 2 + ⋯ + β t − 1 ( 1 − β ) θ 1 + β t V 0 \begin{aligned} V_t&=(1-\beta)\theta_t+\beta[ (1-\beta)\theta_{t-1}+\beta V_{t-2}]\\ &=(1-\beta)\theta_t+\beta(1-\beta)\theta_{t-1}+\beta^2[(1-\beta)\theta_{t-2}+\beta V_{t-3}]\\ &=(1-\beta)\theta_t+\beta(1-\beta)\theta_{t-1}+\beta^2(1-\beta)\theta_{t-2}+\cdots+\beta^{t-1} (1-\beta)\theta_{1}+\beta^tV_0\\ \end{aligned} Vt=(1−β)θt+β[(1−β)θt−1+βVt−2]=(1−β)θt+β(1−β)θt−1+β2[(1−β)θt−2+βVt−3]=(1−β)θt+β(1−β)θt−1+β2(1−β)θt−2+⋯+βt−1(1−β)θ1+βtV0由于 V 0 = 0 V_0=0 V0=0,所以 V t V_t Vt就是前面 t t t次量的一个加权平均,而且这个权重是指数衰减的,离当前时间越近权重越大。 -
偏差修正(bias correction)解决冷启动问题
一般来说, β ⩾ 0.9 \beta\geqslant0.9 β⩾0.9,如果取 β = 0.9 \beta=0.9 β=0.9,由于 V 0 = 0 V_0=0 V0=0,所以 V 1 = 0.1 θ 1 , V 2 = 0.09 θ 1 + 0.1 θ 2 , … V_1=0.1\theta_1, V_2=0.09\theta_1+0.1\theta_2, \dots V1=0.1θ1,V2=0.09θ1+0.1θ2,…,这样导致刚开始的值都会特别小,随着 t t t的增加才会慢慢接近正常值。因此为了避免这种冷启动问题,需要对每个权重都进行偏差修正,即每个权重乘上一个修正系数 1 1 − β t 1\over 1-\beta^t 1−βt1
从这个系数可以看出,当 t t t很小时 β t \beta^t βt还是接近于1,例如 t = 2 t=2 t=2时 β 2 = 0.81 \beta^2=0.81 β2=0.81,那么乘上这个系数就相当于乘上一个比较大的数,将比较小的初始值放大;而当 t t t越来越大时, β t \beta^t βt趋于0,这时这个系数趋于1,基本上就不进行放大了,就和正常的值差不多。
但是为什么是 1 1 − β t 1\over 1-\beta^t 1−βt1呢?因为 V t = ( 1 − β ) θ t + β ( 1 − β ) θ t − 1 + β 2 ( 1 − β ) θ t − 2 + ⋯ + β t − 1 ( 1 − β ) θ 1 V_t=(1-\beta)\theta_t+\beta(1-\beta)\theta_{t-1}+\beta^2(1-\beta)\theta_{t-2}+\cdots+\beta^{t-1} (1-\beta)\theta_{1} Vt=(1−β)θt+β(1−β)θt−1+β2(1−β)θt−2+⋯+βt−1(1−β)θ1这 t t t个权重构成一个等比数列,求和得到权重之和为 1 − β t 1-\beta^t 1−βt,因此,修正系数取为 1 1 − β t 1\over 1-\beta^t 1−βt1,这样 V t V_t Vt就是所有 θ \theta θ的加权求和了(权重之和为1)。 -
近似解释:
在数学上有 lim n → ∞ ( 1 − 1 n ) n = 1 e ≈ 0.3679 \lim_{n\to\infin}(1-{1\over n})^{n}={1\over e}\approx0.3679 limn→∞(1−n1)n=e1≈0.3679
令 n = 1 1 − β n={1\over 1-\beta} n=1−β1,则 ( 1 − 1 n ) n = ( 1 − ( 1 − β ) ) n = β 1 1 − β (1-{1\over n})^{n}=(1-(1-\beta))^n=\beta^{1\over 1-\beta} (1−n1)n=(1−(1−β))n=β1−β1,当 β → 1 \beta\to1 β→1时, n → ∞ n\to\infin n→∞,所以 β 1 1 − β → 1 e \beta^{1\over 1-\beta}\to{1\over e} β1−β1→e1
这意味着当 β \beta β的次数等于 1 1 − β {1\over 1-\beta} 1−β1时, θ \theta θ的权重就已经衰减为 ( 1 − β ) (1-\beta) (1−β)的大概1/3了。
因此,若将 1 e ≈ 0.3679 {1\over e}\approx0.3679 e1≈0.3679当做阈值,即小于它就认为权重过小可以忽略不计,那么 V t V_t Vt就相当于只是对最近的 1 1 − β {1\over 1-\beta} 1−β1个 θ \theta θ值进行加权平均。
所以当 β = 0.9 \beta=0.9 β=0.9时, 1 1 − 0.9 = 10 {1\over 1-0.9}=10 1−0.91=10, V t V_t Vt近似是对最近的10个 θ \theta θ值进行加权平均。
所以当 β = 0.98 \beta=0.98 β=0.98时, 1 1 − 0.98 = 50 {1\over 1-0.98}=50 1−0.981=50, V t V_t Vt近似是对最近的50个 θ \theta θ值进行加权平均。
2、Momentum 动量法
- 动量法在梯度下降中加入了EWMA,它用梯度的指数加权滑动平均,而不仅仅是梯度来更新参数。
- 这样做可以减少由梯度的波动导致的参数优化过慢,因为使用了指数加权滑动平均,所以更新参数时参考的是以前多个梯度的方向,而不仅仅是本次更新的这个梯度的方向,即使是本次梯度方向有些偏移,但是经过加权平均,那些震荡的方向有一部分相互抵消,而保持主要的优化方向一致,所以动量法会比梯度下降更优。
- 具体的做法是:
先初始化EWMA为零(矩阵)
在每次迭代时,计算当前梯度的EWMA,即: V d W = β V d W + ( 1 − β ) d W V d b = β V d b + ( 1 − β ) d b V_{dW}=\beta V_{dW}+(1-\beta)dW\\V_{db}=\beta V_{db}+(1-\beta)db VdW=βVdW+(1−β)dWVdb=βVdb+(1−β)db然后用梯度的EWMA来更新参数,即: W = W − α V d W b = b − α V d b W=W-\alpha V_{dW}\\b=b-\alpha V_{db} W=W−αVdWb=b−αVdb - β \beta β一般取0.9,且一般来说动量法不需要做偏差修正。
3、RMSprop(Root Mean Square propagation) 均方根传递
-
RMSprop也采用了EWMA,但与动量法不同的是,它是对梯度的平方求指数加权滑动平均,然后用梯度除以这个EWMA的均方根来更新参数。
-
我个人结合参考资料从两个方面来理解RMSprop:
1、从梯度的角度来理解:RMSprop计算了梯度平方的指数加权滑动平均,对于震荡较大的梯度方向,它的平方的EWMA也比较大,我们希望在更新的时候尽可能减少它对于优化方向的影响,所以除上较大的EWMA的均方根后这个梯度变小;而对于震荡较小的梯度方向,则它的平方的EWMA也较小,所以除上较小的EWMA的均方根后放大了这个梯度对于优化方向的指导性,所以能保持较为一致的优化方向。
2、从学习率的角度来理解:RMSprop适应地调整所有参数的学习率,它将参数的学习率缩放反比于其所有历史梯度平方的指数加权滑动平均的平方根。这样,如果参数的梯度较大,那么这个参数的学习率将下降较大,而梯度小的参数在学习率上有相对较小的下降,从而达到加速的目的,其净效果是在参数空间中更为平缓的倾斜方向会取得更大的进步。 -
具体的做法是:
先初始化EWMA为零(矩阵)
在每次迭代时,计算当前梯度平方的EWMA,即: S d W = β S d W + ( 1 − β ) d W 2 S d b = β S d b + ( 1 − β ) d b 2 S_{dW}=\beta S_{dW}+(1-\beta)dW^2\\S_{db}=\beta S_{db}+(1-\beta)db^2 SdW=βSdW+(1−β)dW2Sdb=βSdb+(1−β)db2然后除以梯度的EWMA的均方根(这里对应第一种理解方式),即: W = W − α d W S d W + ϵ b = b − α d b S d b + ϵ W=W-\alpha {dW\over \sqrt{S_{dW}+\epsilon}}\\b=b-\alpha {db\over \sqrt{S_{db}+\epsilon}} W=W−αSdW+ϵdWb=b−αSdb+ϵdb为了防止分母为零, ϵ \epsilon ϵ是为了维持数值稳定性而添加的常数,一般取值为1e-6(如果放在根号外面就取1e-8)
4、Adam(Adaptive Moment Estimation) 自适应矩估计
- Adam结合了动量法和RMSprop,并且添加了偏差修正。
- 具体的做法是:
先初始化EWMA为零(矩阵)
在迭代 t t t时,计算当前梯度以及梯度平方的EWMA,即: V d W = β 1 V d W + ( 1 − β 1 ) d W , V d b = β 1 V d b + ( 1 − β 1 ) d b S d W = β 2 S d W + ( 1 − β 2 ) d W 2 , S d b = β 2 S d b + ( 1 − β 2 ) d b 2 V_{dW}=\beta_1 V_{dW}+(1-\beta_1)dW,\ V_{db}=\beta_1 V_{db}+(1-\beta_1)db\\S_{dW}=\beta_2 S_{dW}+(1-\beta_2)dW^2,\ S_{db}=\beta_2 S_{db}+(1-\beta_2)db^2 VdW=β1VdW+(1−β1)dW, Vdb=β1Vdb+(1−β1)dbSdW=β2SdW+(1−β2)dW2, Sdb=β2Sdb+(1−β2)db2然后计算修正后的EWMA: V d W c o r r e c t e d = V d W / ( 1 − β 1 t ) , V d b c o r r e c t e d = V d b / ( 1 − β 1 t ) S d W c o r r e c t e d = S d W / ( 1 − β 2 t ) , S d b c o r r e c t e d = S d b / ( 1 − β 2 t ) V_{dW}^{corrected}=V_{dW}/(1-\beta_1^t),\ V_{db}^{corrected}=V_{db}/(1-\beta_1^t)\\S_{dW}^{corrected}=S_{dW}/(1-\beta_2^t),\ S_{db}^{corrected}=S_{db}/(1-\beta_2^t) VdWcorrected=VdW/(1−β1t), Vdbcorrected=Vdb/(1−β1t)SdWcorrected=SdW/(1−β2t), Sdbcorrected=Sdb/(1−β2t)用修正后的EWMA更新参数,即: W = W − α V d W c o r r e c t e d S d W c o r r e c t e d + ϵ b = b − α V d b c o r r e c t e d S d b c o r r e c t e d + ϵ W=W-\alpha {V_{dW}^{corrected}\over \sqrt{S_{dW}^{corrected}+\epsilon}}\\b=b-\alpha {V_{db}^{corrected}\over \sqrt{S_{db}^{corrected}+\epsilon}} W=W−αSdWcorrected+ϵVdWcorrectedb=b−αSdbcorrected+ϵVdbcorrected - RMSProp可能在训练初期有很高的偏置,而Adam通常被认为对超参数的选择相当鲁棒。一般来说, β 1 \beta_1 β1取0.9, β 2 \beta_2 β2取0.999, ϵ \epsilon ϵ取1e-6。
实际效果
在对比了Batch Gradient Descent、Mini-batch Gradient Descent、Momentum、RMSprop以及Adam在数据集上的效果之后,得到他们的精度分别为0.66、0.796667、0.796667、0.94以及0.94,收敛速度反应在下图(第一幅是Batch Gradient Descent,也就是mini_batch_size=n时):
由于小批量的随机性导致损失函数的震荡,这里我把每100次或者每1000次epoch的损失改成这100次或者这1000次epoch的平均损失,然后得到下图:
可以看出在这个数据集上,动量法作用基本上可以忽略不计,作业中也指出在学习率较小且数据集较简单时,单纯的动量法与普通参数更新效果差异不大。而RMSprop带来的效果十分明显,而且Adam效果好也是由于RMSprop所带来的,他们俩之间有些微区别是因为在RMSprop中没有使用偏差修正,这似乎让RMSprop在实际表现中下降得更快,但是从震荡的代价图中看出Adam在迭代初期似乎更加稳健一些。
代码
在核对我的代码时发现GitHub上的那个同学在写random_mini_batches函数的时候把打乱后的数据和源数据用混了,所以导致那个作业结果有错误,根据作业最后的描述来说我这里是正确的。
import numpy as np
import matplotlib.pyplot as plt
import scipy.io
import math
import sklearn
import sklearn.datasets
from opt_utils import load_params_and_grads, initialize_parameters, forward_propagation, backward_propagation
from opt_utils import compute_cost, predict, predict_dec, plot_decision_boundary, load_dataset
from testCases import *
%matplotlib inline
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
# 导入数据
train_X, train_Y = load_dataset()
'''批量梯度下降'''
def update_parameters(parameters, grads, learning_rate):
L = len(parameters) // 2
for i in range(1, L+1):
parameters['W'+str(i)] += - learning_rate * grads['dW'+str(i)]
parameters['b'+str(i)] += - learning_rate * grads['db'+str(i)]
return parameters
def model_gb(X, Y, learning_rate = 0.0007, num_iterations = 10000, print_cost = True):
grads = {}
costs = [] # to keep track of the loss
m = X.shape[1] # number of examples
layers_dims = [X.shape[0], 5, 2, 1]
# Initialize parameters dictionary.
parameters = initialize_parameters(layers_dims)
# Loop (gradient descent)
for i in range(0, num_iterations):
# Forward propagation: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID.
a3, cache = forward_propagation(X, parameters)
# cost
cost = compute_cost(a3, Y)
# Backward propagation.
grads = backward_propagation(X, Y, cache)
# Update parameters.
parameters = update_parameters(parameters, grads, learning_rate)
# Print the loss every 1000 iterations
if print_cost and i % 1000 == 0:
print("Cost after iteration {}: {}".format(i, cost))
costs.append(cost)
# plot the loss
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
parameters = model_gb(train_X, train_Y)
predictions = predict(train_X, train_Y, parameters)
# Plot decision boundary
plt.title("Model with Gradient Descent optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, np.squeeze(train_Y))
# 在上面的基础上一点点扩充优化方法
def model(X, Y, optimizer, num_epochs=10000, mini_batch_size=64, learning_rate=0.0007,
beta1=0.9, beta2=0.999, epsilon=1e-6, print_cost=True):
grads = {}
costs = [] # to keep track of the loss
costs_ave = [] # to plot
m = X.shape[1] # number of examples
t = 0
layers_dims = [X.shape[0], 5, 2, 1]
# Initialize parameters dictionary.
parameters = initialize_parameters(layers_dims)
if optimizer == 'mini_batch_gd':
pass
if optimizer == 'momentum':
V = initialize_v(parameters)
if optimizer == 'RMSprop':
S = initialize_v(parameters)
if optimizer == 'Adam':
V = initialize_v(parameters)
S = initialize_v(parameters)
# Loop
for i in range(num_epochs):
mini_batches = random_mini_batches(X, Y, mini_batch_size)
for mini_batch in mini_batches:
(mini_batch_X, mini_batch_Y) = mini_batch
# Forward propagation: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID.
a3, cache = forward_propagation(mini_batch_X, parameters)
# cost 代价应该是一次epoch的平均代价
cost = compute_cost(a3, mini_batch_Y)
costs.append(cost)
# Backward propagation.
grads = backward_propagation(mini_batch_X, mini_batch_Y, cache)
# Update parameters.
if optimizer == 'mini_batch_gd':
parameters = update_parameters(parameters, grads, learning_rate)
if optimizer == 'momentum':
parameters, V = update_parameters_with_momentum(parameters, grads, V, beta1, learning_rate)
if optimizer == 'RMSprop':
parameters, S = update_parameters_with_RMSprop(parameters, grads, S, beta2, epsilon, learning_rate)
if optimizer == 'Adam':
t = t + 1
parameters, V, S = update_parameters_with_Adam(parameters, grads, V, S, beta1, beta2, t, epsilon, learning_rate)
# Print the cost every 1000 epoch
if print_cost and i % 1000 == 0:
if i == 0:
print("Average cost after epoch {}: {}".format(i, cost))
costs_ave.append(cost)
else:
cost_ave = np.sum(costs[i-1000:i]) / 1000
print("Average cost after epoch {}: {}".format(i, cost_ave))
if print_cost and i % 100 == 0 and i > 0:
costs_ave.append(np.sum(costs[i-100:i]) / 100)
plt.plot(costs_ave)
plt.title(optimizer)
plt.ylabel('average cost')
plt.xlabel('epoch (per hundreds)')
plt.show()
return parameters
''' mini_batch gradient descent '''
# 对训练集进行分批,输出一个list,里面每个元素是X和Y切片的tuple
def random_mini_batches(X, Y, mini_batch_size = 64):
n = X.shape[1]
mini_batches = []
# np.random.seed(0)
permutation = np.random.permutation(n)
shuffled_X = X[:, permutation]
shuffled_Y = Y[:, permutation]
batch_floor = n//mini_batch_size
for i in range(0, batch_floor):
mini_batch_X = shuffled_X[:, i*mini_batch_size:(i+1)*mini_batch_size]
mini_batch_Y = shuffled_Y[:, i*mini_batch_size:(i+1)*mini_batch_size]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
if n % mini_batch_size != 0:
mini_batch_X = shuffled_X[:, batch_floor*mini_batch_size:n]
mini_batch_Y = shuffled_Y[:, batch_floor*mini_batch_size:n]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
p_mini_batch = model(train_X, train_Y, optimizer='mini_batch_gd')
# Predict
predictions = predict(train_X, train_Y, p_mini_batch)
# Plot decision boundary
plt.title("Model with Gradient Descent optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(p_mini_batch, x.T), train_X, np.squeeze(train_Y))
''' momentum '''
def initialize_v(parameters):
V = {}
L = len(parameters) // 2
for i in range(1, L+1):
V['dW'+str(i)] = np.zeros(parameters['W'+str(i)].shape)
V['db'+str(i)] = np.zeros(parameters['b'+str(i)].shape)
return V
def update_parameters_with_momentum(parameters, grads, V, beta1, learning_rate):
L = len(parameters) // 2
for i in range(1, L+1):
V['dW'+str(i)] = beta1 * V['dW'+str(i)] + (1-beta1) * grads['dW'+str(i)]
V['db'+str(i)] = beta1 * V['db'+str(i)] + (1-beta1) * grads['db'+str(i)]
parameters['W'+str(i)] += - learning_rate * V['dW'+str(i)]
parameters['b'+str(i)] += - learning_rate * V['db'+str(i)]
return parameters, V
p_momentum = model(train_X, train_Y, optimizer='momentum')
# Predict
predictions = predict(train_X, train_Y, p_momentum)
# Plot decision boundary
plt.title("Model with Momentum optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(p_momentum, x.T), train_X, np.squeeze(train_Y))
''' RMSprop '''
def update_parameters_with_RMSprop(parameters, grads, S, beta2, epsilon, learning_rate):
L = len(parameters) // 2
for i in range(1, L+1):
S['dW'+str(i)] = beta2 * S['dW'+str(i)] + (1-beta2) * grads['dW'+str(i)]**2
S['db'+str(i)] = beta2 * S['db'+str(i)] + (1-beta2) * grads['db'+str(i)]**2
parameters['W'+str(i)] += - learning_rate * (grads['dW'+str(i)] / (np.sqrt(S['dW'+str(i)] + epsilon)))
parameters['b'+str(i)] += - learning_rate * (grads['db'+str(i)] / (np.sqrt(S['db'+str(i)] + epsilon)))
return parameters, S
p_RMSprop = model(train_X, train_Y, optimizer='RMSprop')
# Predict
predictions = predict(train_X, train_Y, p_RMSprop)
# Plot decision boundary
plt.title("Model with RMSprop optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(p_RMSprop, x.T), train_X, np.squeeze(train_Y))
''' Adam '''
def update_parameters_with_Adam(parameters, grads, V, S, beta1, beta2, t, epsilon, learning_rate):
L = len(parameters) // 2
for i in range(1, L+1):
V['dW'+str(i)] = beta1 * V['dW'+str(i)] + (1-beta1) * grads['dW'+str(i)]
V['db'+str(i)] = beta1 * V['db'+str(i)] + (1-beta1) * grads['db'+str(i)]
S['dW'+str(i)] = beta2 * S['dW'+str(i)] + (1-beta2) * grads['dW'+str(i)]**2
S['db'+str(i)] = beta2 * S['db'+str(i)] + (1-beta2) * grads['db'+str(i)]**2
parameters['W'+str(i)] += - learning_rate * ((V['dW'+str(i)]/(1-beta1**t)) / (np.sqrt(S['dW'+str(i)]/(1-beta2**t) + epsilon)))
parameters['b'+str(i)] += - learning_rate * ((V['db'+str(i)]/(1-beta1**t)) / (np.sqrt(S['db'+str(i)]/(1-beta2**t) + epsilon)))
return parameters, V, S
p_Adam = model(train_X, train_Y, optimizer='Adam')
# Predict
predictions = predict(train_X, train_Y, p_Adam)
# Plot decision boundary
plt.title("Model with Adam optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(p_Adam, x.T), train_X, np.squeeze(train_Y))
# 用震荡的代价
def model(X, Y, optimizer, num_epochs=10000, mini_batch_size=64, learning_rate=0.0007,
beta1=0.9, beta2=0.999, epsilon=1e-6, print_cost=True):
grads = {}
costs = [] # to keep track of the loss
m = X.shape[1] # number of examples
t = 0
layers_dims = [X.shape[0], 5, 2, 1]
# Initialize parameters dictionary.
parameters = initialize_parameters(layers_dims)
if optimizer == 'mini_batch_gd':
pass
if optimizer == 'momentum':
V = initialize_v(parameters)
if optimizer == 'RMSprop':
S = initialize_v(parameters)
if optimizer == 'Adam':
V = initialize_v(parameters)
S = initialize_v(parameters)
# Loop
for i in range(num_epochs):
mini_batches = random_mini_batches(X, Y, mini_batch_size)
for mini_batch in mini_batches:
(mini_batch_X, mini_batch_Y) = mini_batch
# Forward propagation: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID.
a3, cache = forward_propagation(mini_batch_X, parameters)
# cost 代价应该是一次epoch的平均代价
cost = compute_cost(a3, mini_batch_Y)
# Backward propagation.
grads = backward_propagation(mini_batch_X, mini_batch_Y, cache)
# Update parameters.
if optimizer == 'mini_batch_gd':
parameters = update_parameters(parameters, grads, learning_rate)
if optimizer == 'momentum':
parameters, V = update_parameters_with_momentum(parameters, grads, V, beta1, learning_rate)
if optimizer == 'RMSprop':
parameters, S = update_parameters_with_RMSprop(parameters, grads, S, beta2, epsilon, learning_rate)
if optimizer == 'Adam':
t = t + 1
parameters, V, S = update_parameters_with_Adam(parameters, grads, V, S, beta1, beta2, t, epsilon, learning_rate)
# Print the cost every 1000 epoch
if print_cost and i % 1000 == 0:
print ("Cost after epoch %i: %f" %(i, cost))
if print_cost and i % 100 == 0:
costs.append(cost)
# plot the cost
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('epochs (per 100)')
plt.title(optimizer)
plt.show()
return parameters
额外的参考资料:
《动手学深度学习》李沐
《深度学习》‘’花书‘’