1. 应用机器学习是高度依赖迭代尝试的,不要指望一蹴而就,必须不断调参数看结果,根据结果再继续调参数。
2. 数据集分成训练集(training set)、验证集(validation/development set)、测试集(test set)。
对于传统的机器学习算法,数据量(比如100、1000、10000),常用的分法是70%训练集/30%测试集、60%训练集/20%验证集/20%测试集。
对于大数据(比如100万),可能分法是98%训练集/1%验证集/1%测试集、99.5%训练集/0.4%验证集/0.1%测试集。
确保训练集、验证集和测试集是同分布的。
也可以没有测试集,只有验证集,这时候的验证集也起到测试集的作用,有的人就直接叫它测试集,但NG认为叫验证集更恰当。
3. 偏差和方差:
偏差描述了训练集上的表现,方差描述了测试集上的泛化表现。如果验证集的误差大于训练集的误差(比如11%和1%),这说明也许模型过拟合了,我们称算法有“高方差(high variance)”。如果训练集的误差就很大(比如15%,测试集16%),这说明也许模型欠拟合,我们称算法有“高偏差(high bias)”。如果训练集的误差很大(15%),测试集误差更大(30%),这时候的算法有高偏差、高方差。如果训练集和测试集的误差都很小(0.5%和1%),我们称算法方差、偏差都很低,这是最好的。
方差、偏差的高低是跟最优误差比的。最优误差也被称为贝叶斯误差(Bayes error),比如对于图片分来来说,我们假设人眼的识别错误率是0%,则最优误差就是0%,上面说的15%的误差就很大了。但如果最优误差是15%(比如图片很模糊,人眼也看不出来),则在训练集、验证集上15%的误差也算非常低了。
4. 机器学习的基本调教方法:
1)首先看偏差是否高?如果偏差很高,这意味着欠拟合,可以1)重新设计一个更大的网络,比如包含更多隐藏层、隐藏单元;2)延长训练时间,这么做也许没用,但总没什么坏处;3)尝试新的神经网络架构。反复尝试直到足够低的偏差。
2)偏差足够低之后,看方差是否高?如果方差很高,可以1)用更多的数据;2)正则化;3)尝试新的神经网络架构。直到找到一个低偏差、低方差的方案。
一定要搞清楚问题是偏差还是方差,还是两者都有问题。如果偏差有问题,那么用更多数据是没用的。
在机器学习研究的早期,会有很多关于“bias variance tradeoff”的讨论,因为很多方法都是改善偏差、方差中的一项,而恶化另一项。但在深度学习、大数据时代,只要网络够复杂、数据足够多,我们可以同时改善偏差和方差。所以在深度学习领域,大家不太讨论两者的tradeoff。只要正则化合适,更大的网络几乎没有任何害处,主要代价只是计算时间。
5. 正则化:
正则化项一般只包含W,NG说也可以包含b,但是没什么用,所以他习惯省略b。
L2正则化,W每一个元素的平方和,最常用的正则化手段。L2正则化也叫Weight decay。
L1正则化,W的每一个元素的绝对值的和,有人说L1正则化的优点是方便压缩,因为这种方式优化出的W会是稀疏的矩阵。但NG不认可这个观点,觉得还是L2好。
正则化参数λ是需要调整的超参数。NG很贴心的提醒说lambda是Python的保留字段,所以他都是用lambd。
Frobenius norm:矩阵的每个元素的平方和。习惯的原因,我们称它为矩阵的Frobenius范数,而不是矩阵的L2范数。
没有正则化的时候 dw = (from backpropagation),现在要加上正则化项对w的导数 dw = (from backpropagation) + λ/m * w。参数m是W的元素的个数。这时候 w = w - α*dw = (1 - α*λ/m) *w - α*(from backpropagation)。正则化也被称为“Weight decay”,因为w先被衰减了,然后才更新。
为什么正则化有效?很直观的解释是:增大正则化项,优化出的W会变小,所以正则化是把已经很小的神经元接近0,从而消除了这些神经元的影响。或者说,正则化是砍掉每一层的一些神经元,把很胖的一个神经网络变瘦一些,但同时保持着很深的层数。
6. Dropout:
Dropout是另一种正则化的手段。具体做法是:对于每一层节点,以p的概率随机删掉一些节点。也是把很胖的神经网络变瘦一些,并保持层数不变。对于不同的样本,会重新随机删一次。
Inverted dropout,这是最常用的。最后一步除以keep_prob的目的是保持a[3]的期望值不受keep_prob的大小的影响。因为z[4] = w[4]a[3]+b[4],dropout之后a[3]只保留了一部分,从而a[3]的变小了,继而z[4]的期望值也变小了,所以除以这个比例。反向传播的时候,dA也要乘以对应的D,dropout前向传播时被抛弃的神经元,然后dA需要再除以keep_prob。
# 拿第3层举例,保留节点的概率是keep_prob,如果keep_prob等于0.8,意味着20%的概率删掉这个节点。 # 前向传播 d3 = np.random.rand(a3.shape[0], a3.shape[1]) < keep_prob #生成和a3一样形状的矩阵,keep_prob的概率的元素为True,其他为False。 a3 = np.multiply(a3, d3) # 乘法计算时,自动把True转为1,False为0。 a3 /= keep_prob
#反向传播
da3 *= d3
da3 /= keep_prob
Dropout只是用在训练阶段,预测阶段则不进行,因为我们不希望输出是随机的。
为什么dropout有效果?直观地解释是,这逼着神经网络不依赖任何一个特征,因为任何一个特征都可能被删掉,所以会把权重散开到每一个特征上。这看起来就很类似L2正则化的效果。
不同的层可以有不同的keep_prob,特征多的层更容易过拟合,可以把keep_prob设的小一点;特征少的不容易过拟合,可以设大一点,甚至1。
计算机视觉领域用dropout非常多,几乎成了默认的选择,因为图像的像素太多了。在其他领域用的少一些。
需要老记得是:dropout只是为了防止过拟合,所以如果没有过拟合就不要用。
Dropout的一个问题是破坏了loss function,如果没有dropout,可以看到loss function随着梯度下降优化的进行越来越小。但使用dropout之后,这个现象可能就被破坏了。所以一般开始调试系统的时候,关闭dropout,确认loss function随着优化的进行越来越小,确认无误后,再打开dropout继续调试。
Dropout可以和正则化一起使用。
7. 其他几种防止过拟合的方法:
Data augmentation:通过已有的数据生成新的训练数据,比如把图像翻转、剪裁、缩放。这虽然不如额外收集新的图片好,但省去了找数据贴标签的时间精力,对结果也有帮助。
Early stopping:在中途中断优化的进行。在训练集上,loss会随着优化的进行越来越小,但是在验证集上,往往先变小,再变大,往往选在验证集小的时候中断优化。直观地解释是,参数W是用很小的随机数初始化的,随着优化的进行,它越来越大,所以中途停止优化是取更小的W的值,这和L2正则化也类似功能。NG觉得early stopping最大的缺点是破坏了正交化(Orthogonalization),这里正交化的意思是我们把深度学习的优化分成两部分,一个时间只做一部分工作,第一部分是优化loss function,希望loss值越小越好,第二部分是防止过拟合,用正则化、data augmentation等手段。而early stopping使我们不能分开处理这两部分任务,把两部分工作混在一起解决,这导致我们需要思考的问题更复杂。NG觉得更好的方法是使用L2正则化,这时候只用管训练就好,时间花的多就多一点。L2正则化的缺点是有超参数λ需要反复调教。相比early stopping的优点是只需要进行一次梯度下降。但是NG依然坚持喜欢L2正则化。
8. 归一化(Normalization)输入:
训练神经网络时,一种加速训练的方法就是归一化输入(对每个特征在所有样本上归一化)。步骤是:1)减去均值;2)除以标准差(一般为了防止除以0,会除以sqrt(σ2+ε))。得到各个维度(每个维度对应一个特征)均值为0,方差为1的数据。如果不是这样的话,一个维度数值很大,另一个维度数值很小,优化的时候会震荡,不能快速收敛。
9. 梯度消失(vanishing)/爆炸(exploding):
优化神经网络的时候,可能会遇到不正常的梯度,或者梯度非常非常大,或者非常非常小,这会导致优化很难进行。最简单的解释这种现象的由来:如果W的元素比1小,由于网络很深,W会指数级减小;而如果W的元素大于1,则W会指数级增加。这个问题在很长的时间阻碍着深度学习的发展。
有一种简单的方式虽然不能完全解决,但是可以改善这个问题,对每一层W初始化时 W[l] = np.random.randn(...)*np.sqrt(1/n[l-1])。直观的解释是 z = w1x1+w2x2+...+wnxn,n越大,z也越大,所以如果我们希望z的大小比较小,那么w的大小应该反比于n。这里写了n[l-1]是因为输入的维度n等于l-1。对于ReLU来说,2/n[l-1]更合理,即W[l] = np.random.randn(...)*np.sqrt(2/n[l-1])。对于tanh,1/n[l-1]更合理。
10. 梯度检验(Gradient checking):目的是检查back propgation是不是正确。NG说梯度检验帮助他节省了大量的时间,以及帮他发现反向传播的bug。
two-sided difference:f'(x) = lim (f(x+ε) - f(x-ε))/(2ε);one-side difference:f'(x) = lim (f(x+ε) - f(x))/ε。双边的要比单边的求法更精确,所以使用双边公式。
首先把所有的参数W[l]、b[l]展开成一维向量并且拼成一个巨大的一维向量θ,这时候loss function就被转化为J(θ)。第二步是把dW[l]、db[l]也展开并拼成一维向量dθ. 第三步,对θ里的每个元素θ[i]用双边法求近似导数dθappro[i] = (J(θ[1], θ[2], ..., θ[i] + ε, ...θ[n]) - J(θ[1], θ[2], ..., θ[i] - ε, ...θ[n]))/(2ε)。接下来是为了除去dθ[i]大小的影响而做归一化,计算||dθappro - dθ||2/(||dθappro||2 + ||dθ||2),对应元素的平方和。一般设定ε为10e-7,如果比值接近10e-7,则可以说明是正确的;如果比值接近10e-5,则需要检查看是不是有问题;如果比值接近10e-3甚至更大,那么很大的概率反向传播算错了。
NG的几个建议:1)梯度检验只是用在debug里,不要用在训练的过程中。因为计算dθappro[i]是很费时的,训练的时候就用反向传播计算题度。2)如果梯度检验发现问题,先θ的每一项都查一下,看具体是哪一项θ[i]导致的梯度检验比值很大。3)如果使用了正则化,则梯度检验的时候不要忘了正则化项。4)梯度检验不能和dropout一起使用。因为dropout随机扔掉了很多神经元,会影响梯度检验的可信度。NG建议关掉dropout,用梯度检验确认算法没问题,再打开dropout。5)随机初始化之后梯度检验一次,有时候在训练一段时间之后,可能需要再梯度检验一次。这一条很少需要用到,它只是针对非常特殊的情况,就是在w、b接近0的时候反向传播是正确的,而w、b远离0时,反向传播就算错了。