第三周:超参数调整,批量标准化,编程框架
本文是改善深层神经网络的笔记。
3.1 调试处理
重要性排序
目前我们见到的一些超参数:
- 学习率 α \alpha α: 重要性1
- Momentum参数 β \beta β:重要性2
- Adam参数 β 1 , β 2 , ϵ \beta_1,\beta_2,\epsilon β1,β2,ϵ
- 隐层数量:重要性3
- 隐藏神经元数量:重要性2
- Learning rate decay:重要性3
- mini-batch size:重要性2
如何选择超参数的值?
- 用一个规整的Grid
- 随便取一些参数(随机取值、精确搜索):
- Coarse to find [在一个大区域里面取随机点找出一个小的区域,然后循环]
这里说明随机选取要比Grid要好一点,如果Hyperparameters 2是
ϵ
\epsilon
ϵ,则Grid Search的结果中实际上只起到了测5组Hyperparameters 1的作用,因为
ϵ
\epsilon
ϵ对结果的影响不大。然而在随机选取中,我们得以测量25组数据对结果的影响。所以更优一点。
3.2 ⚠️ 为超参数选择合适的范围
- 可以随机取值的例子:
- 隐层层数
- 每个隐层的神经元数量
- 学习率 α \alpha α
- Momentum β \beta β
- 如果直接均匀分布地选取:
# 如果直接均匀分布地选取:
a = np.random.rand(100000)
plt.hist(a,bins=10)
plt.show()
import math
print(f"Total Num = {len(a)}")
for i in range(-4,0):
print(math.pow(10,i+1),">a>",math.pow(10,i)," Num = ",np.sum((math.pow(10,i)<a)*(a<math.pow(10,i+1))))
输出:
Total Num = 100000
0.001 >a> 0.0001 Num = 93
0.01 >a> 0.001 Num = 902
0.1 >a> 0.01 Num = 8945
1.0 >a> 0.1 Num = 90052
实际上比较合理的方法:在对数尺寸上选
一般为了在 [ 1 0 a , 1 0 b ] [10^a,10^b] [10a,10b]量级里面,每个量级都拥有较为均衡的数据,我们使用如下的方法:
r = b-(a+b)*np.random.rand()
a=np.array([math.pow(10,i) for in r])
- 如果是在对数尺寸里均匀分布地选取:(相当于每个数量级都均匀地取了一点)
# 如果是在对数尺寸里均匀分布地选取:(相当于每个数量级都均匀地取了一点)
r = -4*np.random.rand(10000)
a = np.array([math.pow(10,i) for i in r])
plt.hist(a,bins=10)
plt.show()
import math
print(f"Total Num = {len(a)}")
for i in range(-4,0):
print(math.pow(10,i+1),">a>",math.pow(10,i)," Num = ",np.sum((math.pow(10,i)<a)*(a<math.pow(10,i+1))))
Total Num = 10000
0.001 >a> 0.0001 Num = 2404
0.01 >a> 0.001 Num = 2544
0.1 >a> 0.01 Num = 2587
1.0 >a> 0.1 Num = 2465
Momentum上 β \beta β的例子
假设我们希望模拟 β \beta β的范围在 [ 0.9 , … , 0.999 ] [0.9,\dots,0.999] [0.9,…,0.999]我们可以这样做:
- 考虑 1 − β ∈ [ 0.001 , 0.1 ] 1-\beta \in [0.001,0.1] 1−β∈[0.001,0.1]
- 利用上面的方法,模拟 r ∈ [ − 3 , − 1 ] r \in [-3,-1] r∈[−3,−1]
- 因此得到 β = 1 − 1 0 r \beta =1 - 10^r β=1−10r
3.3 超参数训练的实践
- 照看一个模型:每天换一次参数,不对了就回到上一次的模型。“Panda”方法
- 同时训练多个模型:“Caviar”(鱼子酱)方法
3.4 ⚠️正则化网络(Batch Normalization)的激活函数
-
类似于我们对于输入的操作:减去均值+处以方差
-
我们对于每一层的 z z z在输入激活层之前进行此操作 = >
Batch Normalization
-
但我们并不希望一直是 N ( 0 , 1 ) N(0,1) N(0,1)这样的分布,因为这样对于一些激活函数,如sigmoid不太友好。因为它没有利用到它的非线性区域。
具体实现
我们引入 β [ l ] \beta^{[l]} β[l], γ [ l ] \gamma^{[l]} γ[l]作为参数。这里的 β \beta β和Momentum之间是不同的。
鉴于我们希望在激活层之前完成Batch Normalization,所以我们看的是 z z z。下面求的是某一层输出神经元的平均值,即计算对m个样本某一层的参数的平均值和方差。
μ = 1 m ∑ i = 1 m z [ l ] ( i ) \mu=\frac{1}{m}\sum_{i=1}^{m}z^{[l](i)} μ=m1i=1∑mz[l](i)
σ 2 = 1 m ∑ i = 1 m ( z [ l ] ( i ) − μ ) 2 \sigma^2=\frac{1}{m}\sum_{i=1}^{m}(z^{[l](i)}-\mu)^2 σ2=m1i=1∑m(z[l](i)−μ)2
z n o r m [ l ] ( i ) = z [ l ] ( i ) − μ σ 2 + ϵ z^{[l](i)}_{norm}=\frac{z^{[l](i)}-\mu}{\sqrt{\sigma^2+\epsilon}} znorm[l](i)=σ2+ϵz[l](i)−μ
z ~ [ l ] ( i ) = γ [ l ] z n o r m [ l ] ( i ) + β [ l ] \tilde{z}^{[l](i)}=\gamma^{[l]} z_{norm}^{[l](i)}+\beta^{[l]} z~[l](i)=γ[l]znorm[l](i)+β[l]
a [ l ] = g [ l ] ( z ~ [ l ] ) a^{[l]}=g^{[l]}(\tilde{z}^{[l]}) a[l]=g[l](z~[l])
3.5 将Batch Norm拟合进神经网络
- 此时的
b
[
l
]
=
0
b^{[l]}=0
b[l]=0因为每次BN过程中都要减去均值,所以均值一定为0。
b
[
l
]
b^{[l]}
b[l]增加偏差的作用不明显。
3.6 为什么BN奏效?
- 解释一:
- 可以使更深层的网络减少受到第一层的影响。
- "Covariate Shift":即训练数据和测试数据并不是同一个分布。
- BN降低了Covariate Shift,从而使得层与层之间的输入更加集中。BN改变了输入值变化太大的问题,换言之它减少了前几层网络的作用。
- 解释二:
- ⚠️ BN有一定的正则化作用。因为我们是对于MiniBatch进行作用,它是包含噪音的。所以我们计算出来的 μ \mu μ和 σ 2 \sigma^2 σ2也是包含噪音的,从而减少了过拟合的风险。也正因为这个想法,如果MiniBatch太大,噪音就相对小一点,从而正则化(Regularization)的效果就差一点。
3.7 ⚠️测试时的BN
- Recap+Pbm:
在训练的时候我们是对多个样本求均值,然后再处以方差从而达到BN的作用的。但是,实际上,我们在测试这个训练好的模型的时候是一个一个输入数据的,应该怎么处理?
鉴于我们测试时候每次只输入一个数据,所以我们得想个另外的方法得到每一个 μ \mu μ和 σ 2 \sigma^2 σ2。从而可以计算 z ~ \tilde{z} z~。
- Sol:
- 方法一:
对每个MiniBatch来用指数加权平均来估算:
X { 1 } X^{\{1\}} X{1} | X { 2 } X^{\{2\}} X{2} | X { 3 } X^{\{3\}} X{3} |
---|---|---|
μ { 1 } [ 1 ] \mu^{\{1\}[1]} μ{1}[1] | μ { 2 } [ 1 ] \mu^{\{2\}[1]} μ{2}[1] | μ { 3 } [ 1 ] \mu^{\{3\}[1]} μ{3}[1] |
σ { 1 } [ 1 ] \sigma^{\{1\}[1]} σ{1}[1] | σ { 2 } [ 1 ] \sigma^{\{2\}[1]} σ{2}[1] | σ { 3 } [ 1 ] \sigma^{\{3\}[1]} σ{3}[1] |
即在每一个BN层里面加入
μ
[
l
]
:
=
β
μ
[
l
]
+
(
1
−
β
)
μ
[
l
]
{
i
}
\mu^{[l]}:=\beta \mu^{[l]} + (1-\beta) \mu^{[l]\{i\}}
μ[l]:=βμ[l]+(1−β)μ[l]{i}
σ
[
l
]
:
=
σ
μ
[
l
]
+
(
1
−
β
)
σ
[
l
]
{
i
}
\sigma^{[l]}:=\sigma \mu^{[l]} + (1-\beta) \sigma^{[l]\{i\}}
σ[l]:=σμ[l]+(1−β)σ[l]{i}
- 方法二:
对于训练好的网络,将整个训练集的样本全部输入进去,基于整个样本,求出各BN层的平均值和方差。
3.8 SoftMax回归
- 对于Logistic Regression回归的generalize,用于处理多分类的任务。因为SoftMax的决策边界也是线性的。
- 记号:
- C = C l a s s e s C = Classes C=Classes:有多少个类别
- SoftMax即换一个激活函数(不用平常使用的ReLU)
- SoftMax激活函数
g
:
R
C
×
1
→
R
C
×
1
g: \mathbb{R}^{C\times 1} \to \mathbb{R}^{C\times1}
g:RC×1→RC×1
- SoftMax激活函数
g
:
R
C
×
1
→
R
C
×
1
g: \mathbb{R}^{C\times 1} \to \mathbb{R}^{C\times1}
g:RC×1→RC×1
3.9 训练一个SoftMax分类器
-
HardMax,会找出所有元素中最大的元素。相反的是SoftMax。
-
如果C=2,SoftMax就会退化为Logistic Regression:
e z 1 e z 1 + e z 2 = 1 1 + e z 2 − z 1 \frac{e^{z1}}{e^{z1}+e^{z2}}=\frac{1}{1+e^{z_2-z_1}} ez1+ez2ez1=1+ez2−z11
例子
y
^
=
g
[
l
]
(
z
[
l
]
)
=
[
0.842
0.042
0.002
0.114
]
,
y
=
[
0
1
0
0
]
\hat{y}=g^{[l]}(z^{[l]})=\begin{bmatrix} 0.842 \\ 0.042 \\ 0.002 \\ 0.114 \end{bmatrix},y=\begin{bmatrix} 0 \\ 1 \\ 0 \\ 0 \end{bmatrix}
y^=g[l](z[l])=⎣⎢⎢⎡0.8420.0420.0020.114⎦⎥⎥⎤,y=⎣⎢⎢⎡0100⎦⎥⎥⎤
则我们可以通过下面的Cost Function得到当前样本的损失
L
(
y
^
,
y
)
=
−
1
∗
l
o
g
(
y
^
2
)
=
−
l
o
g
(
0.042
)
L(\hat{y},y)=-1*log(\hat{y}_2)=-log(0.042)
L(y^,y)=−1∗log(y^2)=−log(0.042),此时希望最小化L等价于最大化
l
o
g
(
y
^
2
)
log(\hat{y}_2)
log(y^2),即最大化
y
^
\hat{y}
y^中正确项(第二项)的概率(概率最大为1)。
SoftMax 常用的Loss Function和Cost Function
- 其实是最大似然法的结果,类似于交叉熵的推广:
L ( y ^ , y ) = − ∑ j = 1 C y j l o g ( y ^ j ) L(\hat{y},y)=-\sum_{j=1}^Cy_jlog(\hat{y}_j) L(y^,y)=−j=1∑Cyjlog(y^j)
J ( w [ 1 ] , b [ 1 ] , … ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) J(w^{[1]},b^{[1]},\dots)=\frac{1}{m}\sum_{i=1}^mL(\hat{y}^{(i)},y^{(i)}) J(w[1],b[1],…)=m1i=1∑mL(y^(i),y(i))
J 是m个样本的均值。
- 引入
Y = [ y ( 1 ) , y ( 2 ) , … , y ( m ) ] Y = [y^{(1)},y^{(2)},\dots,y^{(m)}] Y=[y(1),y(2),…,y(m)]
其中每一个样本 y ( i ) y^{(i)} y(i)是一个列向量,里面为1的元素表示对应正确类别。
反向传播
d z [ L ] = ∂ J ∂ z [ L ] = y ^ − y dz^{[L]}=\frac{\partial J}{\partial z^{[L]}}=\hat{y}-y dz[L]=∂z[L]∂J=y^−y【待证明❓❓】
3.10 深度学习框架
- 选择框架的标准:
- 易于编程
- 训练速度快
- Truly Open
3.11 TensorFlow
- 例子:
最小化 J ( w ) = w 2 − 10 w + 25 J(w)=w^2-10w+25 J(w)=w2−10w+25 - 关键操作:
tf.Variable(0,dtype=tf.float32)
:创建一个初始值为0的参数w- 梯度下降:
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost) # 学习率为0.01
- 开始训练
init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
session.run(train,feed_dict={x:coefficients}) #执行一步梯度下降,x是placeholders的名字
print(session.run(w)) #打印出更新完之后的参数w
- 代价函数的定义:
- (不太常用)
cost = tf.add(tf.add(w**2,tf.multiply(-10.,w)),25)
,利用tensorflow内部的函数 - 鉴于对于
tf.Variable
加减乘法已经重载为TensorFlow里面的tf.add
这种,所以可以直接用来构造代价函数,如cost = w**2 - 10*w +25
- 需要传递的参数
- (不太常用)
x = tf.placeholder(tf.float32,[3,1]) # placeholder 表示到时候由它提供数据,x是一个3*1的数组
coefficients = np.array([[1.],[-20.],[100.]])
cost = x[0][0]*w**2 + x[1][0]*w+x[2][0]
- 全部代码:
w = tf.Variable(0,dtype=tf.float32) #w是一个初始值为3的tf.float32的变量
# 将训练数据导入
x = tf.placeholder(tf.float32,[3,1]) # placeholder 表示到时候由它提供数据,x是一个3*1的数组
coefficients = np.array([[1.],[-20.],[100.]])
# cost = tf.add(tf.add(w**2,tf.multiply(-10.,w)),25) #定义损失函数 w^2-10w+25,方向传播的方法已经写在这里面了
# cost = w**2 - 10*w +25#如果w是一个tf.Variable,则所有的+-*/已经被重载了,所以上面的代码等价于当前行的代码
cost = x[0][0]*w**2 + x[1][0]*w+x[2][0]
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost) # 学习率为0.01
init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print(session.run(w)) #评估一个变量(参数),我们这里目的想寻找最小值对应的w的值。
session.run(train,feed_dict={x:coefficients}) #执行一步梯度下降,x是placeholders的名字
print(session.run(w)) #评估一个变量
for i in range(1000):
session.run(train,feed_dict={x:coefficients}) #执行一步梯度下降
print(session.run(w)) #评估一个变量