神经网络优化
一、神经网络(NN)复杂度
NN复杂度:多用NN层数和NN参数的个数表示
-
空间复杂度
层数 = 隐藏层的层数 + 1个输出层
总参数 = 总w + 总b
上图3x4+4 + 4x2+2 = 26 -
时间复杂度
乘加运算次数
上图 3x4 + 4x2 = 20
二、学习率
w
t
+
1
=
w
t
−
l
r
∗
∂
l
o
s
s
∂
w
t
w_{t+1}=w_t-lr*\frac{\partial loss}{\partial w_t}
wt+1=wt−lr∗∂wt∂loss
w
t
+
1
w_{t+1}
wt+1:更新后的参数,
w
t
w_{t}
wt:当前参数,
l
r
lr
lr:学习率,
∂
l
o
s
s
∂
w
t
\frac{\partial loss}{\partial w_t}
∂wt∂loss:损失函数的梯度
指数衰减学习率
可以先用较大的学习率,快速得到较优解,然后逐步减小学习率,使模型在训练后期稳定。
指数衰减学习率
=
初始学习率
∗
学习率衰减
率
(
当前轮数
/
多少轮衰减一次
)
指数衰减学习率 = 初始学习率 * 学习率衰减率^{(当前轮数 / 多少轮衰减一次)}
指数衰减学习率=初始学习率∗学习率衰减率(当前轮数/多少轮衰减一次)
import tensorflow as tf
w = tf.Variable(tf.constant(5, dtype=tf.float32))
epoch = 40
LR_BASE = 0.2 # 最初学习率
LR_DECAY = 0.99 # 学习率衰减率
LR_STEP = 1 # 喂入多少轮BATCH_SIZE后,更新一次学习率
for epoch in range(epoch): # for epoch 定义顶层循环,表示对数据集循环epoch次,此例数据集数据仅有1个w,初始化时候constant赋值为5,循环100次迭代。
lr = LR_BASE * LR_DECAY ** (epoch / LR_STEP)
with tf.GradientTape() as tape: # with结构到grads框起了梯度的计算过程。
loss = tf.square(w + 1)
grads = tape.gradient(loss, w) # .gradient函数告知谁对谁求导
w.assign_sub(lr * grads) # .assign_sub 对变量做自减 即:w -= lr*grads 即 w = w - lr*grads
print("After %s epoch,w is %f,loss is %f,lr is %f" % (epoch, w.numpy(), loss, lr))
运行结果如下:
After 0 epoch,w is 2.600000,loss is 36.000000,lr is 0.200000
After 1 epoch,w is 1.174400,loss is 12.959999,lr is 0.198000
After 2 epoch,w is 0.321948,loss is 4.728015,lr is 0.196020
After 3 epoch,w is -0.191126,loss is 1.747547,lr is 0.194060
After 4 epoch,w is -0.501926,loss is 0.654277,lr is 0.192119
After 5 epoch,w is -0.691392,loss is 0.248077,lr is 0.190198
三、激活函数
3.1 简化模型与MP模型
y
=
x
∗
w
+
b
y=x*w+b
y=x∗w+b
y
=
f
(
x
∗
w
+
b
)
y=f(x*w+b)
y=f(x∗w+b)
f
f
f 为激活函数
3.2 优秀的激活函数
- 非线性: 激活函数非线性时,多层神经网络可逼近所有函数
- 可微性: 优化器大多用梯度下降更新参数
- 单调性: 当激活函数是单调的,能保证单层网络的损失函数是凸函数
- 近似恒等性: f(x)≈x当参数初始化为随机小值时,神经网络更稳定
激活函数输出值的范围:
- 激活函数输出为有限值时,基于梯度的优化方法更稳定
- 激活函数输出为无限值时,建议调小学习率
3.3 常用激活函数
3.3.1 Sigmoid函数
tf.nn.sigmoid(x)
f
(
x
)
=
1
1
+
e
−
x
f(x)=\frac{1}{1+e^{-x}}
f(x)=1+e−x1
特点:
(1)易造成梯度消失
(2)输出非0均值,收敛慢
(3)幂运算复杂,训练时间长
3.3.2 Tanh函数
tf.math. tanh(x)
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
特点:
(1)输出是0均值
(2)易造成梯度消失
(3)幂运算复杂,训练时间长
3.3.3 Relu函数
tf.nn.relu(x)
f
(
x
)
=
m
a
x
(
x
,
0
)
f(x)=max(x,0)
f(x)=max(x,0)
优点:
(1) 解决了梯度消失问题 (在正区间)
(2) 只需判断输入是否大于0,计算速度快
(3) 收敛速度远快于sigmoid和tanh
缺点:
(1) 输出非0均值,收敛慢
(2) Dead Relu问题:某些神经元可能永远不会被激活,导致相应的参数永远不能被更新。
3.3.4 Leaky Relu函数
tf.nn.leaky_relu(x)
f
(
x
)
=
m
a
x
(
α
x
,
x
)
f(x)=max(\alpha x,x)
f(x)=max(αx,x)
3.4 对初学者的建议
- 首选relu激活函数;
- 学习率设置较小值;
- 输入特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布;
- 初始参数中心化,即让随机生成的参数满足以0为均值, 2 当前层输入特征个数 \frac{2}{\sqrt{当前层输入特征个数}} 当前层输入特征个数2为标准差的正态分布。
四、损失函数
损失函数(loss):预测值(y)与已知答案(y_)的差距
神经网络优化目标:使损失函数loss最小
4.1 均方误差MSE
loss_mse = tf.reduce_mean(tf.square(y_ -y))
M
S
E
(
y
_
−
y
)
=
∑
i
−
1
n
(
y
−
y
_
)
2
n
MSE(y\_-y)=\frac{\sum_{i-1}^n(y-y\_)^2}{n}
MSE(y_−y)=n∑i−1n(y−y_)2
4.2 交叉熵CE
交叉熵损失函数CE (Cross Entropy):表征两个概率分布之间的距离
h
(
y
_
,
y
)
=
−
∑
y
_
∗
ln
y
h(y\_,y)=-\sum y\_*\ln y
h(y_,y)=−∑y_∗lny
tf.losses.categorical_crossentropy(y_,y)
例:二分类已知答案y_=(1, 0) 预测y1=(0.6, 0.4) y2=(0.8, 0.2) 哪个更接近标准答案?
H
1
(
(
1
,
0
)
,
(
0.6
,
0.4
)
)
=
−
(
1
∗
l
n
0.6
+
0
∗
l
n
0.4
)
≈
−
(
−
0.511
+
0
)
=
0.511
H
2
(
(
1
,
0
)
,
(
0.8
,
0.2
)
)
=
−
(
1
∗
l
n
0.8
+
0
∗
l
n
0.2
)
≈
−
(
−
0.223
+
0
)
=
0.223
H1((1,0),(0.6,0.4)) = -(1*ln0.6 + 0*ln0.4) ≈ -(-0.511 + 0) = 0.511 \\ H2((1,0),(0.8,0.2)) = -(1*ln0.8 + 0*ln0.2) ≈ -(-0.223 + 0) = 0.223
H1((1,0),(0.6,0.4))=−(1∗ln0.6+0∗ln0.4)≈−(−0.511+0)=0.511H2((1,0),(0.8,0.2))=−(1∗ln0.8+0∗ln0.2)≈−(−0.223+0)=0.223因为H1> H2,所以y2预测更准
softmax与交叉熵结合
输出先过softmax函数,再计算y与y_的交叉熵损失函数。
tf.nn.softmax_cross_entropy_with_logits(y_,y)
五、欠拟合与过拟合
上图依次为欠拟合、正确拟合与过拟合
欠拟合的解决方法
- 增加输入特征项
- 增加网络参数
- 减少正则化参数
过拟合的解决方法
- 数据清洗
- 增大训练集
- 采用正则化
- 增大正则化参数
六、正则化缓解过拟合
正则化在损失函数中引入模型复杂度指标,利用给W加权值,弱化了训练数据的噪声(一般不正则化b)
L1正则化
l o s s L 1 ( w ) = ∑ i ∣ w i ∣ loss_{L1}(w)=\sum_i |w_i| lossL1(w)=i∑∣wi∣
L2正则化
l o s s L 2 ( w ) = ∑ i ∣ w i 2 ∣ loss_{L2}(w)=\sum_i |w_i^2| lossL2(w)=i∑∣wi2∣
正则化的选择
L1正则化大概率会使很多参数变为零,因此该方法可通过稀疏参数,即减少参数的数量,降低复杂度。
L2正则化会使参数很接近零但不为零,因此该方法可通过减小参数值的大小降低复杂度。
with tf.GradientTape() as tape: # 记录梯度信息
h1 = tf.matmul(x_train, w1) + b1 # 记录神经网络乘加运算
h1 = tf.nn.relu(h1)
y = tf.matmul(h1, w2) + b2
# 采用均方误差损失函数mse = mean(sum(y-out)^2)
loss_mse = tf.reduce_mean(tf.square(y_train - y))
# 添加l2正则化
loss_regularization = []
# tf.nn.l2_loss(w)=sum(w ** 2) / 2
loss_regularization.append(tf.nn.l2_loss(w1))
loss_regularization.append(tf.nn.l2_loss(w2))
loss_regularization = tf.reduce_sum(loss_regularization)
loss = loss_mse + 0.03 * loss_regularization #REGULARIZER = 0.03
七、神经网络参数优化器
待优化参数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
一阶动量:与梯度相关的函数
二阶动量:与梯度平方相关的函数
7.1 SGD
SGD(无momentum),常用的梯度下降法
m
t
=
g
v
t
=
1
η
t
=
l
r
∗
m
t
/
V
t
=
l
r
∗
g
t
w
t
+
1
=
w
t
−
η
t
=
w
t
−
l
r
∗
g
t
m_t=g\\ v_t=1\\ \eta_t=lr*m_t/\sqrt{V_t}=lr*g_t\\ w_{t+1}=w_t-\eta_t=w_t-lr*g_t
mt=gvt=1ηt=lr∗mt/Vt=lr∗gtwt+1=wt−ηt=wt−lr∗gt
# 计算loss对各个参数的梯度
grads = tape.gradient(loss, [w1, b1])
# 实现梯度更新 w1 = w1 - lr * w1_grad b = b - lr * b_grad
w1.assign_sub(lr * grads[0]) # 参数w1自更新
b1.assign_sub(lr * grads[1]) # 参数b自更新
7.2 SGDM
SGDM(含momentum的SGD),在SGD基础上增加一阶动量
m
t
=
β
⋅
m
t
−
1
+
(
1
−
β
)
⋅
g
t
η
t
=
l
r
∗
m
t
/
V
t
=
l
r
∗
(
β
⋅
m
t
−
1
+
(
1
−
β
)
⋅
g
t
)
w
t
+
1
=
w
t
−
η
t
=
w
t
−
l
r
∗
(
β
⋅
m
t
−
1
+
(
1
−
β
)
⋅
g
t
)
m_t=\beta \cdot m_{t-1} + (1-\beta)\cdot g_t\\ \eta_t=lr*m_t/\sqrt{V_t}=lr*(\beta \cdot m_{t-1} + (1-\beta)\cdot g_t)\\ w_{t+1}=w_t-\eta_t=w_t-lr*(\beta \cdot m_{t-1} + (1-\beta)\cdot g_t)
mt=β⋅mt−1+(1−β)⋅gtηt=lr∗mt/Vt=lr∗(β⋅mt−1+(1−β)⋅gt)wt+1=wt−ηt=wt−lr∗(β⋅mt−1+(1−β)⋅gt)
m_w, m_b = 0, 0
beta = 0.9
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)
7.3 Adagrad
Adagrad,在SGD基础上增加二阶动量
m
t
=
g
t
V
t
=
∑
τ
=
1
t
g
τ
2
η
t
=
l
r
∗
m
t
/
V
t
=
l
r
∗
g
t
/
∑
τ
=
1
t
g
τ
2
w
t
+
1
=
w
t
−
η
t
=
w
t
−
l
r
∗
g
t
/
∑
τ
=
1
t
g
τ
2
m_t=g_t\\ V_t=\sum^t_{\tau=1}g^2_{\tau}\\ \eta_t=lr*m_t/\sqrt{V_t}=lr*g_t/\sqrt{\sum^t_{\tau=1}g^2_{\tau}}\\ w_{t+1}=w_t-\eta_t=w_t-lr*g_t/\sqrt{\sum^t_{\tau=1}g^2_{\tau}}
mt=gtVt=τ=1∑tgτ2ηt=lr∗mt/Vt=lr∗gt/τ=1∑tgτ2wt+1=wt−ηt=wt−lr∗gt/τ=1∑tgτ2
v_w, v_b = 0, 0
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))
7.4 RMSProp
RMSProp,SGD基础上增加二阶动量
m
t
=
g
t
V
t
=
β
⋅
V
t
−
1
+
(
1
−
β
)
⋅
g
t
2
η
t
=
l
r
∗
m
t
/
V
t
=
l
r
∗
g
t
/
β
⋅
V
t
−
1
+
(
1
−
β
)
⋅
g
t
2
w
t
+
1
=
w
t
−
l
r
∗
m
t
/
V
t
=
w
t
−
l
r
∗
g
t
/
β
⋅
V
t
−
1
+
(
1
−
β
)
⋅
g
t
2
m_t=g_t\\ V_t=\beta \cdot V_{t-1}+(1-\beta)\cdot g_t^2\\ \eta_t = lr*m_t/\sqrt{V_t}=lr*g_t/\sqrt{\beta \cdot V_{t-1}+(1-\beta)\cdot g_t^2}\\ w_{t+1}=w_t-lr*m_t/\sqrt{V_t}=w_t-lr*g_t/\sqrt{\beta \cdot V_{t-1}+(1-\beta)\cdot g_t^2}
mt=gtVt=β⋅Vt−1+(1−β)⋅gt2ηt=lr∗mt/Vt=lr∗gt/β⋅Vt−1+(1−β)⋅gt2wt+1=wt−lr∗mt/Vt=wt−lr∗gt/β⋅Vt−1+(1−β)⋅gt2
v_w, v_b = 0, 0
beta = 0.9
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))
7.5 Adam
Adam, 同时结合SGDM一阶动量和RMSProp二阶动量
m
t
=
β
⋅
m
t
−
1
+
(
1
−
β
)
⋅
g
t
修正一阶动量的偏差:
m
t
^
=
m
t
1
−
β
1
t
V
t
=
β
⋅
V
t
−
1
+
(
1
−
β
)
⋅
g
t
2
修正一阶动量的偏差:
V
t
^
=
V
t
1
−
β
2
t
η
t
=
l
r
∗
m
t
^
/
V
t
^
=
l
r
∗
m
t
1
−
β
1
t
/
V
t
1
−
β
2
t
w
t
+
1
=
w
t
−
η
t
=
w
t
−
l
r
∗
m
t
1
−
β
1
t
/
V
t
1
−
β
2
t
m_t=\beta \cdot m_{t-1} + (1-\beta)\cdot g_t\\ 修正一阶动量的偏差:\hat{m_t}=\frac{m_t}{1-\beta_1^t}\\ V_t=\beta \cdot V_{t-1}+(1-\beta)\cdot g_t^2\\ 修正一阶动量的偏差:\hat{V_t}=\frac{V_t}{1-\beta_2^t}\\ \eta_t = lr*\hat{m_t}/\sqrt{\hat{V_t}}=lr*\frac{m_t}{1-\beta_1^t}/\sqrt{\frac{V_t}{1-\beta_2^t}}\\ w_{t+1}=w_t-\eta_t=w_t-lr*\frac{m_t}{1-\beta_1^t}/\sqrt{\frac{V_t}{1-\beta_2^t}}
mt=β⋅mt−1+(1−β)⋅gt修正一阶动量的偏差:mt^=1−β1tmtVt=β⋅Vt−1+(1−β)⋅gt2修正一阶动量的偏差:Vt^=1−β2tVtηt=lr∗mt^/Vt^=lr∗1−β1tmt/1−β2tVtwt+1=wt−ηt=wt−lr∗1−β1tmt/1−β2tVt
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
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))