这一周的主体是调参。
1. 超参数:No. 1最重要,No. 2其次,No. 3其次次。
No. 1学习率α:最重要的参数。在log取值空间随机采样。例如取值范围是[0.001, 1],r = -4*np.random.rand(), α = 10r。
No. 2 Momentum β:0.9是个不错的选择。在1-β的log取值空间随机采样。例如取值范围[0.9, 0.999],则1-β的取值空间[0.001, 0.1]。
No. 2 各个隐含层的神经元数量:可以在线性取值空间随机采样。
No. 2 mini-batch的大小:
No. 3 神经网络层数:可以在线性取值空间随机采样。
No. 3 Learning rate decay:
Adam算法中的 β1、β1、ε:从来不调,用默认值,0.9、0.999、10-8.
调试超参数的时候,不要在参数的取值范围均匀采样(网格),而要随机采样。
从粗到细收缩参数的取值范围。
同一个神经网络用在不同的领域,最优的超参数是很可能不同的;即使在同一个领域,随着时间的推移,数据的变化,最优的超参数也会发生变化。所以要时不时重新调参,看能不能找到更好地。
两大类调参流派:1)对于数据量很大(比如计算机视觉、广告),计算资源不够,一次只能负担起试验一个模型或一小批模型,维护一条loss function,逐渐改良模型,努力让loss function越来越小。2)并行试验多种模型,同时看好多条loss function,直接选最好的那个。NG戏称第一类是熊猫型,每次只生一个(或几个)小孩,悉心照料;第二类则是鱼子(caviar)类,每次生一大堆,也不管不问,就看最后谁长得好。如果有足够的计算资源并行计算模型,那一定选第二类。否则,则选第一类。
2. Batch normalization:
基本想法是,既然把输入归一化可以加速优化,那么可以把中间层对下一层的输出也归一化。NG说这里可以选择是归一化未激活的z[l],也可以归一化激活后的a[l],他建议默认选择是z[l]。
具体算法:
对于神经网络中的某一层,z[l](i), ..., z[l](m),这里是对每个特征在所有样本上的分布。
μ = 1/m*(∑z(i)),#省略了第l层的[l]标记。
σ2 = 1/m*(∑(z(i)-μ)2),
znorm(i) = (z(i) - μ)/sqrt(σ2+ε)
ztilde(i) = γznorm(i) + β,#这里γ和β是神经网络可以学习的参数,这相当于给它自由度,并不像对输入数据一样强制要求均值为0方差为1。
例如对于某个网络可能会是这样的前向传播:
w[1], b[1] β[1], γ[1] w[2], b[2] β[2], γ[2]
x ---------------> z[1] ---------------> ztilt[1] ---------------> a[1] = g[1](ztilt[1]) ---------------> z[2] ---------------> ztilt[2] ---------------> a[2] --------------->...
此时的参数包括 w[1], b[1], w[2], b[2],...,w[l], b[l],β[1], γ[1], β[2], γ[2],...,β[l], γ[l]。注意这里的β和优化中的β(例如Momentum)是完全不同的参数,不要搞混。
由于z[l] = w[l]*a[l-1] + b[l],紧跟着做归一化时,b[l]是被减去了,所以实际可以把公式简化为z[l] = w[l]*a[l-1] 。然后计算znorm[l],接着计算ztilde[l]=γ[l]znorm[l] + β[l]。此时β[l]体现了之前b[l]的作用。所以实际的参数包括w[1], w[2],...,w[l], β[1], γ[1], β[2], γ[2],...,β[l], γ[l]。另外,z[l]的维度是(n[l], 1),β[l]和γ[l]的维度也都是(n[l], 1)。n[l]是第l层的神经元数量。
当和mini-batch结合的时候,则是对每个mini-batch前向计算的时候,在z[l]和a[l]之间插入针对当前mini-batch数据的归一化层。
总结算法:
遍历所有mini-batch
计算当前mini-batch的前向传播
在每一层,用batch normalization计算出ztilt[l]
计算反向传播,得到 dw[l], dβ[l], dγ[l]
更新参数,可以用标准梯度下降、Momentum、RMSprop、Adam等算法。
为什么在中间层做batch normalization有效呢?1)第一个很浅显的原因是和输入层特征归一化一样,各个维度近似分布更利于优化。2)第二个原因是它使得后面的层对于前面层的变化更鲁棒。为了更好地解释这个原因,我们先设想一个算法在数据集A上训练,然后在B上测试,A和B的分布相差约大,越不利于算法泛化。Covariate shift描述的就是这种数据分布发生变化的现象。对于深度学习来说,各个层神经元是处理前面的网络传过来的数据,如果前面网络的参数发生变化(比如因为优化迭代),则传入这一层的数据也会发生变化,这就产生了covaraite shift。Batch normalization就缓解了这个问题,它把不同分布的数据归一化成比较稳定的分布,限制了前面网络产生的数据的shift。或者说它减小了不同层之间的关联,使得每一层网络可以自己学习。这有助于加速整个网络的学习。3)第三个原因是batch normalization有轻微的正则化效果,在归一化的作用下每个mini-batch被它的均值和方差缩放,由于均值、方差只是小部分数据算出来是有噪音的,所以类似dropout(每个神经元有一定概率被删掉),它在每个隐藏层的输出上加了噪音,这迫使后面的层不过分依赖任何一个隐藏单元。但是由于噪音小,所以并不是巨大的正则化效果,可以把batch normalization和dropout一起用。NG强调batch normalizatoin只是有轻微正则化效果,但是还是不要把它当做正则化手段。
训练阶段是用mini-batch训练权重,而在测试阶段,需要根据训练集来估算测试样本的均值μ和方差σ2,一般采用的方法是对mini-batch算出的均值、方差做指数加权平均。
3. Softmax回归:logistic回归的一般形式,从识别两个分类推广到多个分类。
对于多分类的应用,神经网络的最后一层(第L层)是Softmax层,z[L] = w[L]a[L-1] + b[L],中间变量t = exp(z[L]),这是对z[L]的每个元素进行exp操作,如果是四分类的问题,则z[L]的形状是(4, 1),t也是(4, 1)。激活函数 a[L] = exp(z[L])/(∑ti),ti是t的第i个元素,a[L]的形状也是(4, 1),a[L]的第i个元素ai[L] = ti/∑ti。ai[L]的可以看成是每个类的概率。
Softmax和Logistic回归都是对特征空间的线性划分。
Softmax名字的来源是“hard max”,hard max是把最大值设为1,其他值都抹成0,非常强硬,例如向量[0.842, 0.042, 0.002, 0.114]T经过hard max操作就变成[1, 0, 0, 0]T。
单个训练样本的损失函数L(y_hat, y) = -∑(yj*logy_hatj)。想要损失函数尽可能小,就要使正确类别的概率尽可能大。
整个训练集的损失J(w[1], b[1], ...) = 1/m*∑(L(y_hat(i), y(i)))。
反向传播的计算,dz[L] = y_hat - y。
4. 市面上的深度学习框架:
用户只需要定义网络架构以及前向传播,框架自己会搞定反向传播。
NG选框架的标准:1)容易编程;2)运行速度快,尤其是针对大数据;3)是否真的开放,不仅仅开源,而且要有很好的维护。
TensorFlow上写深度学习程序一般包含几个步骤:
1) Create Tensors (variables) that are not yet executed/evaluated.
2) Write operations between those Tensors.
3) Initialize your Tensors.
4) Create a Session.
5) Run the Session. This will run the operations you'd write above.
import numpy as np import tensorflow as tf coefficients = np.array([[1.], [-10.], [25.]]) #传给x的数据。 w = tf.Variable(0, dtype=tf.float32) #希望被优化的参数。 x = tf.placeholder(tf.float32, [3, 1]) #告诉TensorFlow稍后会为x提供数据,这种方式便于把训练数据加入损失方程。 # cost = tf.add(tf.add(w**2, tf.multiply(-10., w)), 25) #这是第一个例子。 # cost = w**2 - 10*w + 25 # 由于TensorFlow重载了运算发,所以可以直接这样写。 cost = x[0][0]w**2 + x[1][0]*w+x[2][0] #第二个例子加入了样本x。 train = tf.train.GradientDescentOptimizer(0.01).minimize(cost) #使用梯度下降法最小化cost,这里0.01是学习率。 init = tf.global_variables_initializer() session = tf.Session() # with命令在python中更方便防止内循环出现错误,这一句也可以写成 with tf.Session() as session: session.run(init) print(session.run(w)) #还没有运行优化,w现在是0. session.run(train, feed_dict={x:coefficients}) #运行一步梯度下降,把coefficients喂给x。如果在做mini-batch梯度下降,每次迭代可以喂不同的mini-batch print(session.run(w)) #一步梯度下降之后,w现在是0.1 for i in range(1000): session.run(train, feed_dict={x:coefficients}) print(session.run(w)) # 运行1000次梯度下降之后,w现在是4.99999