超参数调优、批量归一化、深度学习框架
1 超参数调优
在深度神经网络训练中,我们面对大量超参数。包括学习速率α;如果使用动量算法的话,还包括动量超参数β; 还有Adam优化算法里的超参数 β1、 β2 和 ε;也许还包括网络层数以及每层网络中隐藏单元的数量;也许你还想使用学习率衰减,这种情况下不可能只有单一的学习速率α;在小批量梯度下降算法中,还需要选择 Mini-Batch 的大小。
那么这些超参数应该如何取值呢?下面介绍一些常用的调节这些超参数的方法。
1.1 网格定点取样与均匀随机取样
如果你有两个超参数,假设是超参数 1 和超参数 2, 一种方法是在网格中进行定点取样,然后系统化地让这些超参数尝试这些点所代表的值。 在这里放的是一个 5*5 的网格,实际上可能比这个大或者比这个小。如果有 3 个超参数,那么我们将在三维立方体中抽样。在这个例子中,当我们尝试过所有25个点后就可以选择出最优的超参数组合。
假设超参数 1 是学习速率 α,参数2 是Adam优化算法分母中的 ε。 实际上 α 的选择是很关键的,而 ε 的选择却影响不大。那么如果通过网格顶点取样,当我们尝试过 5个 α 的值之后,可能会发现即使 ε 的值有所不同,但得到的结果却基本上相同。所以相当于我们训练了25个模型,但是只尝试了5个有用的 α 的值。
所以我们更倾向于在网格中进行随机取样,即在相同的范围内我们随机均匀地选择25个点,然后系统化地让这些超参数尝试这些点所代表的值。 相对而言,如果在网格中随机均匀取样,那么你将获得25个不同的学习速率 α,因此你找到理想值的概率也就变得更大。
通过网格定点取样来选择超参数的方法,往往只适用于超参数数量较少的情况。实际上,我们可能会面对更多的超参数。有时真正的困难在于,我们事先不知道哪一个超参数对于模型更重要,这种情况下弃用网格定点取样,而是使用随机取样将帮助我们更充分地为最重要的超参数尝试尽可能多的值组合。
另一个常见的做法是,使用区域定位的抽样方案。比如在这个二维的例子中,也许我们发现某个点能产生最好的结果,并且旁边的一些点的结果也不错。那么在这个方案中,你需要做的是对这些点所在的区域进行限定,然后在这个区域内再进行密度更高的超参数取样。
1.2 使用适当的比例来选择超参数
随机取样将帮助我们更充分地为最重要的超参数尝试尽可能多的值组合。但实际上,我们还应该考虑到超参数对分布区域的敏感程度。在越敏感的区域,超参数微小的变化便可能对算法性能造成巨大的影响,所以在越敏感的区域就应该让超参数尝试越多的值。
比如你正在搜索超参数 alpha,即学习率。假设你认为它的下限是 0.0001 上限是1 ,现在在 [ 0.0001 , 1 ] [0.0001, \ 1] [0.0001, 1] 这个区间内均匀随机地抽取样本值,那么 10% 的样本值将落在 ( 0.0001 , 0.1 ) (0.0001, \ 0.1) (0.0001, 0.1) 的范围内,90% 的样本值将落在 ( 0.1 , 1 ) (0.1, \ 1) (0.1, 1) 的范围内。看起来不大对。
更合理的方法似乎应该以对数尺度(log scale)来搜索,而不是用线性尺度(linear scale),用Python实现就是:
r = -4 * np.random.rand()
alpha = np.power(10, r)
这样一来,第一行运行之后,r 为 (-4, 0) 区间内的均匀随机数.
r 的
3
4
\frac{3}{4}
43 的样本值将落在
(
−
4
,
−
1
)
(-4, \ -1)
(−4, −1) 的范围内,即 alpha 的
3
4
\frac{3}{4}
43 的样本值在
(
1
0
−
4
,
1
0
−
1
)
(10^{-4}, \ 10^{-1})
(10−4, 10−1)之间,即
(
0.0001
,
0.1
)
(0.0001, \ 0.1)
(0.0001, 0.1) 。
r 的
1
4
\frac{1}{4}
41 的样本值将落在
(
−
1
,
0
)
(-1, \ 0)
(−1, 0) 的范围内,即 alpha 的
1
4
\frac{1}{4}
41 的样本值在
(
1
0
−
1
,
1
0
0
)
(10^{-1}, \ 10^{0})
(10−1, 100)之间,即
(
0.1
,
1
)
(0.1, \ 1)
(0.1, 1) 。
另一个棘手的情况是超参数 beta 的取样。beta 用于计算指数加权平均值,假设你认为 beta 值应该在 [ 0.9 , 0.999 ] [0.9, \ 0.999] [0.9, 0.999] 之间,用beta = 0.9 来计算指数加权平均值,相当于计算最后10个值的平均值,而使用 beta = 0.999 就相当于计算1000个值的平均值。随着beta趋近于 1,其结果对于beta的改变非常敏感,即使是对beta非常小的改变。
- 如果beta从0.9变成0.9005 这没什么大不了,求出的指数加权平均值几乎没有任何变化。
- 如果 beta 从 0.999 变成 0.9995,即由求 1000 个值得平均数变成了求 2000 个值得平均数,这将会对结果产生巨大的影响。
所以越靠近0.999,我们希望 beta 能尝试越多的值,这刚好和上面那一个例子相反,所以考虑这个问题的最好的方法是将这个范围映射为1-beta,即由原来的 [ 0.9 , 0.999 ] [0.9, \ 0.999] [0.9, 0.999] 映射到 [ 0.001 , 0.1 ] [0.001, \ 0.1] [0.001, 0.1] ,用Python实现就是:
r = -2 * np.random.rand() - 1
beta = 1 - np.power(10, r)
这样一来,第一行运行之后,r 为 (-3, -1) 区间内的均匀随机数。beta 的 1 2 \frac{1}{2} 21 的样本值在 ( 1 − 1 0 − 2 , 1 − 1 0 − 3 ) (1-10^{-2}, \ 1-10^{-3}) (1−10−2, 1−10−3)之间,即 ( 0.99 , 0.999 ) (0.99, \ 0.999) (0.99, 0.999) , 另外 1 2 \frac{1}{2} 21 的样本值在 ( 1 − 1 0 − 1 , 1 − 1 0 − 2 ) (1-10^{-1}, \ 1-10^{-2}) (1−10−1, 1−10−2)之间,即 ( 0.9 , 0.99 ) (0.9, \ 0.99) (0.9, 0.99) 。虽然两个区间上 beta 的样本值各占 1 2 \frac{1}{2} 21,但 ( 0.9 , 0.99 ) (0.9, \ 0.99) (0.9, 0.99) 比 ( 0.99 , 0.999 ) (0.99, \ 0.999) (0.99, 0.999) 的区间长度长了 10 倍。也就是说,beta 越趋近于 0.999 ,beta 的样本密度更大,得到的样本分布更高效。
1.3 精心训练一个模型 VS 同时训练多个模型
在深度学习领域,不同领域的研究者通过参考日益增多的其他领域的论文,可以跨领域找到灵感。在超参数设置的问题上,无论是直觉还是思路都在与时俱进。 因此,原先那些被我们定义为最优的超参数, 很可能已经成为明日黄花。所以我们应该至少每隔几个月重新检测或者重新评估一次我们认为最优的超参数,以此来确保这些数值依然是最优的。
那么关于如何探寻超参数的问题,有两种不同途径。
其一是精心照料某个单一的模型 通常情况是我们需要处理一个非常庞大的数据集但没有充足的计算资源。那我们只能一次训练一个或者非常少量的模型。在好几天甚至几周的训练过程中都不离不弃地照看着模型,观察性能曲线,耐心地微调学习率,即使再枯燥也需要我们不断地根据训练的反馈信息调节超参数。这个形式比较像熊猫育崽,当熊猫产子时,往往数量很稀少,通常一次都只有一个,它们就会投入全部精力确保自己的孩子能平安成长,非常无微不至,而我们的模型就像是成长中的熊猫宝宝。
另一种就是并行训练多个模型,每个模型尝试设置不同的超参数,然后让模型运行一天或几天,最终根据评估指标找出最优的超参数。这可以让超参数选择变得简单。这个模式则更像鱼类的繁衍策略,鱼在交配后能产下数不清的鱼类,然后无需投入太多精力去照看这些鱼卵,最终生存环境会淘汰掉大量的鱼卵或幼苗,只有其中一个或者一小部分能够存活。
那么如何挑选适合我们的模式呢? 这取决于有多少计算资源比如 GPU 与 CPU。如果你有足够的GPU来进行并行训练,那不用考虑别的采用鱼子酱模式就行,尝试大量不同的超参数然后再根据最优的结果对超参数作出选择。 但是在某些应用领域,例如计算机视觉的一些任务,都有海量的数据和大规模的模型需要去训练,而同时训练大量模型变成了极其困难的事情。
2 批量归一化(Batch Normalization)
在深度学习不断兴起的过程中,最重要的创新之一批量归一化 (Batch Normalization) 。它可以让你的超参搜索变得很简单,让你的神经网络变得更加具有鲁棒性,可以让你的神经网络对于超参数的选择上不再那么敏感,而且可以让你更容易地训练非常深的网络。接下来让我们来看看批量归一化是如何工作的。
2.1 批量归一化的具体过程
为了加深理解,有兴趣的话可以读一下原文: https://arxiv.org/pdf/1502.03167.pdf
针对输入的一个批量
B
=
{
x
1
,
x
2
,
.
.
.
,
x
m
}
B=\{x_1,x_2, \ ... ,x_m\}
B={x1,x2, ...,xm},批量归一化通过以下的步骤将
B
B
B 种某个向量
x
i
x_i
xi 转化变形为
y
i
y_i
yi :
在反向传播种,批量归一化的梯度计算:
2.2 神经网络中的批量归一化
你也许记得当训练一个模型比如说逻辑回归模型时,对输入特征进行归一化可以加速学习过程。所以对于浅层神经网络或逻辑回归,针对输入值的归一化处理是有用的。那么对于层数更多的模型呢? 你不仅有输入特征值x,每一层还有激活函数的输出结果 a[1] 、a[2] 等等,所以如果你想训练参数 w[3]、b[3],除非你能对 a[2] 进行标准差归一化,否则对于 w[3]、b[3] 的训练都不会太有效率。
普通的归一化是对输入进行归一化。而 Batch Norm 则是对隐藏层进行归一化。实际上,我们对隐层归一化时针对的并不是每一层的激活值 A,而是激活值之前的 Z。 而对于在激活函数之前做归一化,还是在激活函数之后做归一化,这一点上学术界还是有一些争。实际上在激活函数之前做归一化要普遍的多。也就是我们所呈现的方法。
假如每个 mini-batch 包含 m 个样本,则对于某个单层的 Batch Norm 具体实现为:
那么在整个神经网络中,使用 Batch Norm 算法的前向传播可以描述为:
这里需要注意三点:
1、
γ
[
i
]
\gamma^{[i]}
γ[i] 与
β
[
i
]
\beta^{[i]}
β[i] 的形状与
b
[
i
]
b^{[i]}
b[i] 一样,均为
n
[
i
]
∗
1
n[i]*1
n[i]∗1 的列向量。
2、 在计算
Z
[
i
]
=
W
[
i
]
A
[
i
−
1
]
+
b
[
i
]
Z^{[i]} = W^{[i]}A^{[i-1]}+b^{[i]}
Z[i]=W[i]A[i−1]+b[i] 时,可以去掉
b
[
i
]
b^{[i]}
b[i],即
Z
[
i
]
=
W
[
i
]
A
[
i
−
1
]
Z^{[i]} = W^{[i]}A^{[i-1]}
Z[i]=W[i]A[i−1],因为无论b为何值,在 mini batch variance 过程中都会被减掉,而这个偏置的效果 scale and shift 过程中能够体现。
3、 注意将 Batch Norm 里的
β
\beta
β 与动量梯度下降算法中的
β
\beta
β 区分开。
2.3 对批量归一化的理解
- 在批量梯度下降算法中,受 covariate shift 问题影响,隐层单元的值的分布一直在改变。这导致学到的参数不具有鲁棒性。Batch Norm 算法所做的就是,它减少了这些隐藏单元值的分布的不稳定性。当处理新的样本时,隐层单元的值的变化确实会变化,但BN算法确保的是无论它怎么变,隐层单元的值的均值和方差将保持不变。
- BN算法削弱了前面层参数和后层参数之间的耦合,所以它允许网络的每一层独立学习,所以这将有效提升整个网络学习速度。
- BN算法意味着,从神经网络某一后层角度来看,前面的层的影响并不会很大,因为它们被同一均值和方差所限制,所以这使后层的学习工作变得更加简单。
- Batch Norm算法具有一定的正则化效果,使用更大尺寸的 min-batch 将会削弱正则化效果。
看到了一位仁兄的博文对批量归一化的总结非常好,自己羡慕不来,就直接贴上链接了。
https://www.cnblogs.com/guoyaohua/p/8724433.html
2.4 预测时的批量归一化
批标准化每次处理一个最小批的数据,但是在测试时你大概会需要一个一个实例来处理。如果只有一个样本,那么计算这一个样本的平均值和方差显然是不合理的。所以,我们通过指数加权平均运算来估算
μ
\mu
μ 和
σ
2
\sigma^2
σ2。
理论上,我们可以用最后的网络运行整个训练集来得到
μ
\mu
μ 和
σ
2
\sigma^2
σ2。 但实际上,人们通常会记住在训练时见到的
μ
\mu
μ 和
σ
2
\sigma^2
σ2 的值,然后再运用指数加权平均运算进行估计来得到当前 mu 和 sigma 平方的粗略的估算。然后我们再利用估算出的
μ
\mu
μ 和
σ
2
\sigma^2
σ2 值进行预测。
3 多元分类
对于多元分类任务,一种方法是单独训练多个二元分类器,此外,还可以在一个分类器中同时输出各类别对应概率,这种算法就是 Softmax Regression,可以看成是逻辑回归在多元分类问题上的一种推广。
3.1 Softmax 回归
一般的二分类网络的输出层采用 sigmoid 函数,只有一个输出节点。而 Softmax 多元分类是 sigmoid 的推广,它的输出层则是采用 softmax 激活函数,若有 C 个类别,则输出层有 C+1 个输出节点。其中 C 个节点输出当前样本属于对应类别的概率,多出的一个节点输出当前样本不属于任何一类的概率,且这 C+1 个节点的输出之和等于 1。除此之外,Softmax 多元分类网络与普通的二元分类网络没有其他的区别。
前向传播时,Softmax 层的计算分为两步:
最终输出的
A
[
L
]
A^{[L]}
A[L] 是一个
(
C
+
1
)
∗
1
(C+1)*1
(C+1)∗1 的列向量。第一个输出节点的输出
A
0
[
L
]
A_0^{[L]}
A0[L] 表示不属于任何一类的概率 ,第
j
j
j 个输出节点的输出
A
j
[
L
]
A_j^{[L]}
Aj[L] 表示属于第
j
j
j 个类别的概率,其中
j
∈
[
1
,
C
]
j\in[1,C]
j∈[1,C]。下面以 C = 3 举个例子:
以下图左这个只有一个 softmax 层的网络为例,其输入只包含
x
1
x_1
x1 和
x
2
x_2
x2 两个特征。下图右给出了别数 C 尝试不同值时的分类结果:
这些图展示了softmax 分类器在没有隐藏层的时候,做出的都是一些线性决策平面。当然加入更多的隐藏层以及更多的隐藏单元时,你就可以发现 softmax 可以用更复杂的非线性决策平面来区分这些不同的类别。
3.2 训练 Softmax 分类器
softmax 回归的损失函数: L ( y ^ , y ) = − ∑ i = 1 C + 1 y i l o g ( y i ^ ) . L(\hat{y},y)=-\sum_{i=1}^{C+1}\ y_i\ log{(\hat{y_i})} . L(y^,y)=−i=1∑C+1 yi log(yi^).那么代价函数: J ( W , b ) = 1 m ∑ j = 1 m L ( y ^ , y ) . J(W,b)=\frac{1}{m}\sum_{j=1}^mL(\hat{y},y). J(W,b)=m1j=1∑mL(y^,y).
4 机器学习框架
4.1 常用深度学习框架与选择标准
当你要实现复杂的模型时,例如卷积神经网络(CNN)或循环神经网络(RNN) ,你会发现越来越难操作。 对于大多数人,所有事情从头亲力亲为是不现实的。 幸运的是,深度学习已经发展到一个成熟的阶段,现在有很多好的深度学习框架,可以使一些任务变得更实际、更高效。市面上常见的一些深度学习框架如下:
比起强烈推荐某一个框架,如何选择深度学习框架的标准更为重要。
- 编程的简便性。 这有利于开发神经网络和对它进行迭代改善,以及在生产环境中进行实战布署。
- 运行速度。特别是在大数据集上进行训练时,有些框架比起其他框架能够让你运行和训练神经网络时更为高效 。
- 框架是否真正的开源。
4.2 Tensorflow 基础知识与基本操作
基础知识:
- tensorflow的核心是计算代价函数并自动求导。只要定义了前向传播和代价函数,tensorflow会自动根据运算符来求导。所以编程时我们不用关心反向传播。
- 核心概念:张量tensor、计算图graph、会话session、计算内核。
基本操作:
1、会话的创建
# 第一种方式
sess = tf.Session()
sess.run(graph)
...
sess.close()
# 第二种方式
with tf.Session( ) as sess:
sess.run(graph)
...
sess.close()
2、常量与变量
# 常量的创建
tf.constant(value, dtype=, name="...")
# 与常量不同,变量创建后还需要初始化
tf.Variable(value, dtype=, name="...")
sess.run(tf.global_variables_initializer())
3、四则运算与运算符
y1 = tf.add(x1, x2) #加
y2 = tf.subtract(x1, x2) #减
y3 = tf.multiply(x1, x2) #乘
y4 = tf.divide(x1, x2) #除
4、占位符 placeholder 与值的传入
X = tf.placeholder(dtype=, shape=, name="...") # shape 中某个维度不确定值时可用 None 代替
Y = tf.add(X, 10)
sess.run(Y, feed_dict={X: 5}) #运行时,用字典指定 X 的值
5、常见激活函数
tf.sigmoid(x) # sigmoid 函数
tf.nn.relu(...) # relu 函数
6、常见损失函数
# 交叉熵损失函数
tf.nn.sigmoid_cross_entropy_with_logits(logits = , labels = )
# softmax 损失函数
tf.nn.softmax_cross_entropy_with_logits(logits = logits, labels = )
7、常见优化算法
# 梯度下降
optimizer = tf.train.GradientDescentOptimizer(learning_rate = ).minimize(cost)
# adam 算法
optimizer = tf.train.AdamOptimizer(learning_rate = ).minimize(cost)
8、初始化
tf.zeros(shape, dtype=, name="") #全 0 矩阵
tf.ones(shape, dtype=, name="") #全 1 矩阵
tf.get_variable(name, shape, initializer = tf.contrib.layers.xavier_initializer(seed = 1))
tf.get_variable(name, shape, initializer = tf.zeros_initializer())
9、数据类型dtype
tf.int8 # 8位整数
tf.int16 # 16位整数
tf.int32 # 32位整数
tf.int64 # 64位整数
tf.uint8 # 8位无符号整数
tf.uint16 # 16位无符号整数
tf.float16 # 16位浮点数
tf.float32 # 32位浮点数
tf.float64 # 64位浮点数
tf.double # 等同于tf.float64
tf.string # 字符串
tf.bool # 布尔型
tf.complex64 # 64位复数
tf.complex128 # 128位复数
12、其他
tf.one_hot(labels, depth, axis=) #实现独热编码