Tensorflow2.1入门 第二章:神经网络优化过程
- 一、预备知识
- 二、复杂度/学习率
- 三、激活函数
- 1.什么是激活函数
- 2.Sigmoid: f ( x ) = 1 1 + e − x f(x)=\frac{1}{1+e^{-x}} f(x)=1+e−x1
- 3.Tanh: f ( x ) = 1 − e − 2 x 1 + e − 2 x f(x)=\frac{1-e^{-2x}}{1+e^{-2x}} f(x)=1+e−2x1−e−2x
- 4.Relu: f ( x ) = m a x ( x , 0 ) f(x)=max(x, 0) f(x)=max(x,0)
- 5.Leaky Relu: f ( x ) = m a x ( a x , x ) f(x)=max(ax, x) f(x)=max(ax,x)
- 6.对于初学者建议
- 四、损失函数
- 五、欠拟合与过拟合
- 六、优化器
一、预备知识
1.where函数
tf.where(条件语句,A,B)
若条件语句为真,则返回A;否则,返回B
a = tf.constant([1, 2, 3, 1, 1])
b = tf.constant([0, 1, 3, 4, 5])
c = tf.where(tf.greater(a, b), a, b) # 若a>b,返回a对应位置的元素,否则返回b对应位置的元素
结果为:[1,2,3,4,5]
p4_where.py
2.随机数生成
np.random.RandomState.rand(维度)
返回整个数组[0,1)之间的随机数。
p5_RandomState.py
3.数组连接
np.vstack(数组1,数组2)
两个数组按垂直方向叠加。
p6_vstack.py
4.生成网格坐标点
np.mgrid[起始值:结束值:步长,起始值:结束值:步长,…]
返回若干组维度相同的等差数列,注意值域范围为[起始值,结束值)。x.ravel()
将x变为一位数组,即把x拉直。np.c_[数组1,数组2,…]
使返回的间隔数值点配对。
p7_mgrid.py
二、复杂度/学习率
1.空间复杂度
- 层数: 隐藏层的层数+1个输出层
- 总参数:总w数+总b数
某两层间w数=上一层神经元个数*下一层神经元个数;b数=下一层神经元个数
2.时间复杂度
时间复杂度=乘加运算次数
3.学习率
学习率若过小,则需要多次运算才可以找到最优值;学习率如过大,可能在最优值两边反复横跳。为了解决这个问题,可以先用大学习率快速找到最优值附近,再逐步减小学习率使模型在后期稳定。
4.指数衰减学习率
作为使学习率先大后小的解决方法,提出了指数衰减学习率。
指
数
衰
减
学
习
率
=
初
始
学
习
率
∗
学
习
率
衰
减
率
(
当
前
轮
数
/
多
少
轮
衰
减
一
次
)
指数衰减学习率=初始学习率*学习率衰减率^{(当前轮数/多少轮衰减一次)}
指数衰减学习率=初始学习率∗学习率衰减率(当前轮数/多少轮衰减一次)
p10_backpropagation_decaylr.py
三、激活函数
1.什么是激活函数
激活函数的加入使得神经网络的输出不再是输入x的线性组合,可以拟合为任意的非线性函数。
- 优秀的激活函数应该有以下性质:
(1)非线性:激活函数非线性,多层神经网络可以逼近拟合所有函数。
(2)可微性:优化器大多用梯度下降更新参数。
(3)单调性:当激活函数单调时,能保证单层网络的损失函数是凸函数,更容易收敛。
(4)近似恒等性:当参数初始化为随机小值时,若满足f(x)≈x,神经网络就更稳定。 - 激活函数输出值的范围:
(1)激活函数输出为有限值时,基于梯度的优化方法更稳定。
(2)激活函数输出为无限值时,建议调小学习率。
2.Sigmoid: f ( x ) = 1 1 + e − x f(x)=\frac{1}{1+e^{-x}} f(x)=1+e−x1
tf.nn.sigmoid(x)
(1)将输出变为(0,1)之间
(2)容易造成梯度消失:深层神经网络在更新参数时需要从输出层到输入层逐层进行链式求导,而Sigmoid函数的导数值域为0到0.25之间的小数。链式求导需要多层导数连续相乘,结果将趋于0,产生梯度消失,使得参数无法继续更新。
(3)输出非0均值,收敛慢
(4)幂运算复杂,训练时间长
3.Tanh: f ( x ) = 1 − e − 2 x 1 + e − 2 x f(x)=\frac{1-e^{-2x}}{1+e^{-2x}} f(x)=1+e−2x1−e−2x
tf.math.tanh(x)
(1)输出是0均值
(2)容易造成梯度消失
(3)幂运算复杂,训练时间长
4.Relu: f ( x ) = m a x ( x , 0 ) f(x)=max(x, 0) f(x)=max(x,0)
tf.nn.relu(x)
- 优点:
(1)满足了激活函数应具备的近似恒等性
(2)解决了梯度消失问题(在正区间)
(3)只需判断输入是否大于0,计算速度快
(4)收敛速度远快于sigmoid和tanh - 缺点:
(1)输出非0均值,收敛慢
(2)Dead Relu问题:送入激活函数的输入特征是负数时,激活函数输出是0,反向传播得到的梯度也是0,导致参数无法更新,造成神经元死亡。 - 如何解决Dead Relu问题:
(1)初始化:避免有过多的负数特征送入relu函数。
(2)通过设置更小的学习率,减小参数分布的巨大变化,避免训练中产生过多负数特征进入relu函数
5.Leaky Relu: f ( x ) = m a x ( a x , x ) f(x)=max(ax, x) f(x)=max(ax,x)
tf.nn.leaky_relu(x)
理论上说,Leaky Relu有Relu的所有优点,外加不会有Dead Relu问题,但是在实际操作来说,并未证明Leaky Relu总是好于Relu,现在神经网络中还是使用Relu的较多。
6.对于初学者建议
- 首选Relu激活函数。
- 学习率设置较小值。
- 输入特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布。
- 初始参数中心化,即让随机生成的参数满足以0为均值, 2 当 前 层 输 入 特 征 个 数 \sqrt{\frac{2}{当前层输入特征个数}} 当前层输入特征个数2为标准差的正态分布。
四、损失函数
1.什么是损失函数(loss)
损失函数:预测值(y)与已知答案(y_)的差距。
2.均方误差mse
lose_mse=tf.reduce_mean(tf.square(y_, y))
M S E ( y _ , y ) = Σ ( y − y _ ) 2 n MSE(y\_, y) = \frac{\Sigma(y-y\_)^2}{n} MSE(y_,y)=nΣ(y−y_)2
p19_mse.py
3.交叉熵损失函数CE(Cross Entropy)
tf.losses.categorical_crossentropy(y_, y)
表征两个概率分布之间的距离。
H
(
y
_
,
y
)
=
−
Σ
(
y
_
∗
l
n
(
y
)
)
H(y\_, y) = -\Sigma (y\_*ln(y))
H(y_,y)=−Σ(y_∗ln(y))
p22_ce.py
4.softmax与交叉熵结合(对于分类问题)
tf.nn.softmax_cross_entropy_with_logits(y_, y)
让输出先过softmax函数,再计算y与y_的交叉熵损失函数。
p23_softmaxce.py
五、欠拟合与过拟合
1.解决方法
- 欠拟合:
(1)增加输入特征项
(2)增加网络参数
(3)减少正则化参数 - 过拟合:
(1)数据清洗
(2)增大训练集
(3)采用正则化
(4)增大正则化参数
2.正则化缓解过拟合
(1)什么是正则化
正则化在损失函数中引入模型复杂度指标,利用给W加权重,弱化了训练数据的噪声(一般不正则化b)。
l o s s = l o s s ( y , y _ ) + R E G U L A R I Z E R ∗ l o s s ( w ) loss=loss(y, y\_)+REGULARIZER*loss(w) loss=loss(y,y_)+REGULARIZER∗loss(w)
其中loss(y, y_) 是模型中所有参数的损失函数;超参数REGULARIZER 给出参数w在总loss中的比例,即正则化的权重;W 是需要正则化的参数。
(2)L1正则化: l o s s L 1 ( w ) = Σ ∣ w i ∣ loss_{L1}(w)=\Sigma |w_i| lossL1(w)=Σ∣wi∣
L1正则化大概率会使很多参数变为零,因此该方法可通过稀疏参数,即减少参数的数量,来降低复杂度。
(3)L2正则化: l o s s L 2 ( w ) = Σ ∣ w i 2 ∣ loss_{L2}(w)=\Sigma |w_i^2| lossL2(w)=Σ∣wi2∣
L2正则化会使参数很接近零但不为零,因此该方法可通过减少参数值的大小降低复杂度。
p29_regularizationfree.py
p29_regularizationcontain.py
六、优化器
1.什么是参数优化器
优化器是引导神经网络更新参数的工具。
优化器优化过程:
待优化参数w,损失函数loss,学习率lr,每次迭代一个batch,t表示当前batch迭代的总次数:
- 计算t时刻损失函数关于当前参数的梯度 g t = ∇ l o s s = ∂ l o s s ∂ w t g_t=\nabla loss=\frac{\partial loss}{\partial w_t} gt=∇loss=∂wt∂loss
- 计算t时刻一阶动量 m t m_t mt和二阶动量 V t V_t Vt
- 计算t时刻下降梯度: η t = l r ∗ m t / V t \eta_t=lr*m_t/\sqrt{V_t} ηt=lr∗mt/Vt
- 计算t+1时刻参数: w t + 1 = w t − η t = w t − l r ∗ m t / V t w_{t+1}=w_t-\eta_t=w_t-lr*m_t/\sqrt{V_t} wt+1=wt−ηt=wt−lr∗mt/Vt
其中:
一阶动量:与梯度相关的函数
二阶动量:与梯度平方相关的函数
2.SGD(无momentum)
m t = g t , V t = 1 m_t=g_t,V_t=1 mt=gt,Vt=1
η t = l r ∗ m t / V t = l r ∗ g t \eta_t=lr*m_t/\sqrt{V_t}=lr*g_t ηt=lr∗mt/Vt=lr∗gt
w
t
+
1
=
w
t
−
η
t
=
w
t
−
l
r
∗
g
t
=
w
t
−
l
r
∗
∂
l
o
s
s
∂
w
t
w_{t+1}=w_t-\eta_t=w_t-lr*g_t=w_t-lr*\frac{\partial loss}{\partial w_t}
wt+1=wt−ηt=wt−lr∗gt=wt−lr∗∂wt∂loss
即为常用的梯度下降法。
# sgd
w1.assign_sub(lr * grads[0]) # 参数w1自更新
b1.assign_sub(lr * grads[1]) # 参数b自更新
p32_sgd.py
3.SGDM(含momentum的SGD)
在SGD的基础上增加了一阶动量。
m t = β ∗ m t − 1 + ( 1 − β ) ∗ g t , V t = 1 m_t=\beta *m_{t-1}+(1-\beta)*g_t,V_t=1 mt=β∗mt−1+(1−β)∗gt,Vt=1
η t = l r ∗ m t / V t = l r ∗ ( β ∗ m t − 1 + ( 1 − β ) ∗ g t ) \eta_t=lr*m_t/\sqrt{V_t}=lr*(\beta*m_{t-1}+(1-\beta)*g_t) ηt=lr∗mt/Vt=lr∗(β∗mt−1+(1−β)∗gt)
w t + 1 = w t − η t = w t − l r ∗ ( β ∗ m t − 1 + ( 1 − β ) ∗ g t ) w_{t+1}=w_t-\eta_t=w_t-lr*(\beta*m_{t-1}+(1-\beta)*g_t) wt+1=wt−ηt=wt−lr∗(β∗mt−1+(1−β)∗gt)
其中 β \beta β为超参数,经验值为0.9
m_w, m_b = 0, 0
beta = 0.9
# sgd-momentun
m_w = beta * m_w + (1 - beta) * grads[0]
m_b = beta * m_b + (1 - beta) * grads[1]
w1.assign_sub(lr * m_w)
b1.assign_sub(lr * m_b)
p33_sgdm.py
4.Adagrad
在SGD基础上增加二阶动量,可以对模型中每一个参数分配自适应学习率了。
m
t
=
g
t
,
V
t
=
∑
τ
=
1
t
g
τ
2
m_t=g_t,V_t=\displaystyle\sum_{\tau=1}^tg_\tau^2
mt=gt,Vt=τ=1∑tgτ2
η
t
=
l
r
∗
m
t
/
V
t
=
l
r
∗
g
t
/
(
∑
τ
=
1
t
g
τ
2
)
\eta_t=lr*m_t/\sqrt{V_t}=lr*g_t/(\sqrt{\displaystyle\sum_{\tau=1}^tg_\tau^2})
ηt=lr∗mt/Vt=lr∗gt/(τ=1∑tgτ2)
w
t
+
1
=
w
t
−
η
t
=
w
t
−
l
r
∗
g
t
/
(
∑
τ
=
1
t
g
τ
2
)
w_{t+1}=w_t-\eta_t=w_t-lr*g_t/(\sqrt{\displaystyle\sum_{\tau=1}^tg_\tau^2})
wt+1=wt−ηt=wt−lr∗gt/(τ=1∑tgτ2)
v_w, v_b = 0, 0
# adagrad
v_w += tf.square(grads[0])
v_b += tf.square(grads[1])
w1.assign_sub(lr * grads[0] / tf.sqrt(v_w))
b1.assign_sub(lr * grads[1] / tf.sqrt(v_b))
p36_adagrad.py
5.RMSProp
在SGD基础上增加二阶动量,二阶动量使用指数滑动平均值计算。
m t = g t , V t = β ∗ V t − 1 + ( 1 − β ) ∗ g t 2 m_t=g_t,V_t=\beta *V_{t-1}+(1-\beta)*g_t^2 mt=gt,Vt=β∗Vt−1+(1−β)∗gt2
η t = l r ∗ m t / V t = l r ∗ g t / ( β ∗ V t − 1 + ( 1 − β ) ∗ g t 2 ) \eta_t=lr*m_t/\sqrt{V_t}=lr*g_t/(\sqrt{\beta *V_{t-1}+(1-\beta)*g_t^2}) ηt=lr∗mt/Vt=lr∗gt/(β∗Vt−1+(1−β)∗gt2)
w t + 1 = w t − η t = w t − l r ∗ g t / ( β ∗ V t − 1 + ( 1 − β ) ∗ g t 2 ) w_{t+1}=w_t-\eta_t=w_t-lr*g_t/(\sqrt{\beta *V_{t-1}+(1-\beta)*g_t^2}) wt+1=wt−ηt=wt−lr∗gt/(β∗Vt−1+(1−β)∗gt2)
v_w, v_b = 0, 0
beta = 0.9
# rmsprop
v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
w1.assign_sub(lr * grads[0] / tf.sqrt(v_w))
b1.assign_sub(lr * grads[1] / tf.sqrt(v_b))
p38_rmsprop.py
6.Adam
同时结合SGDM的一阶动量和RMSProp二阶动量,并在此基础上增加了两个修正项。
m
t
=
β
1
∗
m
t
−
1
+
(
1
−
β
1
)
∗
g
t
m_t=\beta_1 *m_{t-1}+(1-\beta_1)*g_t
mt=β1∗mt−1+(1−β1)∗gt
(修正一阶动量的偏差:
m
t
^
=
m
t
1
−
β
1
2
\widehat{m_t}=\frac{m_t}{1-\beta_1^2}
mt
=1−β12mt)
V
t
=
β
2
∗
V
t
−
1
+
(
1
−
β
2
)
∗
g
t
2
V_t=\beta_2 *V_{t-1}+(1-\beta_2)*g_t^2
Vt=β2∗Vt−1+(1−β2)∗gt2
(修正二阶动量的偏差:
V
t
^
=
V
t
1
−
β
2
2
\widehat{V_t}=\frac{V_t}{1-\beta_2^2}
Vt
=1−β22Vt)
η t = l r ∗ m t ^ / V t ^ = l r ∗ m t 1 − β 1 2 / V t 1 − β 2 2 \eta_t=lr*\widehat{m_t}/\sqrt{\widehat{V_t}}=lr*\frac{m_t}{1-\beta_1^2}/\sqrt{\frac{V_t}{1-\beta_2^2}} ηt=lr∗mt /Vt =lr∗1−β12mt/1−β22Vt
w t + 1 = w t − η t = w t − l r ∗ m t 1 − β 1 2 / V t 1 − β 2 2 w_{t+1}=w_t-\eta_t=w_t-lr*\frac{m_t}{1-\beta_1^2}/\sqrt{\frac{V_t}{1-\beta_2^2}} wt+1=wt−ηt=wt−lr∗1−β12mt/1−β22Vt
m_w, m_b = 0, 0
v_w, v_b = 0, 0
beta1, beta2 = 0.9, 0.999
delta_w, delta_b = 0, 0
global_step = 0
# adam
m_w = beta1 * m_w + (1 - beta1) * grads[0]
m_b = beta1 * m_b + (1 - beta1) * grads[1]
v_w = beta2 * v_w + (1 - beta2) * tf.square(grads[0])
v_b = beta2 * v_b + (1 - beta2) * tf.square(grads[1])
m_w_correction = m_w / (1 - tf.pow(beta1, int(global_step)))
m_b_correction = m_b / (1 - tf.pow(beta1, int(global_step)))
v_w_correction = v_w / (1 - tf.pow(beta2, int(global_step)))
v_b_correction = v_b / (1 - tf.pow(beta2, int(global_step)))
w1.assign_sub(lr * m_w_correction / tf.sqrt(v_w_correction))
b1.assign_sub(lr * m_b_correction / tf.sqrt(v_b_correction))
p40_adam.py
7.优化器对比
(1) lr=0.1, epochs=500, batch_size=32
- SGD
|
|
- SGDM
|
|
- Adagrad
|
|
- RMSProp
|
|
- Adam
|
|
训练耗时对比:
SGD | SGDM | Adagrad | Rmsprop | Adam |
---|---|---|---|---|
6.1726 | 6.8901 | 6.6914 | 6.9369 | 8.8312 |
(2) lr=0.01, epochs=100, batch_size=32
- SGD
|
|
- SGDM
|
|
- Adagrad
|
|
- RMSProp
|
|
- Adam
|
|
训练耗时对比:
SGD | SGDM | Adagrad | Rmsprop | Adam |
---|---|---|---|---|
1.5009 | 1.5211 | 1.4601 | 1.3376 | 1.7148 |