TensorFlow2.1入门笔记2:神经网络优化

TensorFlow2.1入门笔记2

    • 神经网络复杂度
      • 空间复杂度
      • 时间复杂度
    • 指数衰减学习率
    • 激活函数
      • Sigmoid函数
      • Tanh函数
      • Relu函数
    • 损失函数
      • 均方误差
      • 自定义
      • 交叉熵
      • softmax与交叉熵结合:
    • 欠拟合和过拟合
      • 缓解欠拟合的方法:
      • 缓解过拟合的方法:
    • 正则化减少过拟合
    • 反向传播优化器更新网络参数
      • 不含动量的普通梯度下降(SGD)
      • 含一阶动量的SGD:(SDGM)
      • 在SGD的基础上增加二阶动量(Adagard)
      • 在SGD的基础上增加二阶动量(RMSProp)
      • 结合SGDM的一阶动量和RMSProp二阶动量(Adam)

tf.where(条件语句,真返回A,假返回B)
np.random.RandomState.rand(维度):返回一个[0,1)之间的随机数
np.vastack(数组1,数组2):将两个数组垂直方向叠加

import tensorflow as tf

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对应位置的元素
print("c:", c)

# c: tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)

import numpy as np

rdm = np.random.RandomState(seed=1)
a = rdm.rand()
b = rdm.rand(2, 3)
print("a:", a)
print("b:", b)

# a: 0.417022004702574
# b: [[7.20324493e-01 1.14374817e-04 3.02332573e-01]
#  [1.46755891e-01 9.23385948e-02 1.86260211e-01]]

import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.vstack((a, b))
print("c:\n", c)

# c:
#  [[1 2 3]
#  [4 5 6]]

np.mgridp[] .ravel() np.c_[]:生成网格坐标点
np.magrid[起始值:结束值:步长,起始值:结束值:步长](多组数据用逗号隔开,起始值闭区间,终止值开区间)
x.ravel():将x变为一维数组
np.c_[数组1,数组2,…]:讲返回的间隔数值点配对输出

import numpy as np
import tensorflow as tf

# 生成等间隔数值点
x, y = np.mgrid[1:3:1, 2:4:0.5]
# 将x, y拉直,并合并配对为二维张量,生成二维坐标点
grid = np.c_[x.ravel(), y.ravel()]
print("x:\n", x)
print("y:\n", y)
print("x.ravel():\n", x.ravel())
print("y.ravel():\n", y.ravel())
print('grid:\n', grid)

# x:
#  [[1. 1. 1. 1.]
#  [2. 2. 2. 2.]]
# y:
#  [[2.  2.5 3.  3.5]
#  [2.  2.5 3.  3.5]]
# x.ravel():
#  [1. 1. 1. 1. 2. 2. 2. 2.]
# y.ravel():
#  [2.  2.5 3.  3.5 2.  2.5 3.  3.5]
# grid:
#  [[1.  2. ]
#  [1.  2.5]
#  [1.  3. ]
#  [1.  3.5]
#  [2.  2. ]
#  [2.  2.5]
#  [2.  3. ]
#  [2.  3.5]]

神经网络复杂度

隐藏层:除输入层和输出层以外的全叫隐藏层
神经网络层数只统计有运算能力的层,输入层只输入数据,因此神经网络的层数=n个隐藏层+1个输出层
以输入层为3个神经元,隐藏层为4个神经元,输出层为2个神经元的二层神经网络为例

空间复杂度

层数=n个隐藏层+1个输出层
总参数=总w+总b
34+4 + 42+2=26

时间复杂度

乘加运算次数
34 +42=20

指数衰减学习率

指数衰减学习率=初始学习率*学习率衰减率(当前轮数/多少轮衰减一次)

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))

激活函数

y=f(x*w+b)其中f()为激活函数
随着非线性函数的加入,不再一直是线性组合,神经网络可以随层数的增加提升表达能力
优秀的激活函数应当具有:
• 非线性: 激活函数非线性时,多层神经网络可逼近所有函数
• 可微性: 优化器大多用梯度下降更新参数
• 单调性: 当激活函数是单调的,能保证单层网络的损失函数是凸函数
• 近似恒等性: f(x)≈x当参数初始化为随机小值时,神经网络更稳定
激活函数输出值的范围:
• 激活函数输出为有限值时,权重对特征的影响更加显著,基于梯度的优化方法更稳定
• 激活函数输出为无限值时,参数的初始值多模型的影响极大,建议调小学习率

Sigmoid函数

tf.nn.sigmoid(x)

当输入很大的负数时输出为0,很大的正数时为1,相当于进行了归一化处理
特点
(1)易于造成梯度消失。深层神经网络在更新参数时,要从输出层到输入层逐层进行链式求导,而sigmoid函数的导数输出是0到0.25之间的小数,链式求导需要多层导数连续相乘,因此会出现多个0到0.25之间连续相乘,结果趋于0,梯度消失,使得参数无法进行更新
(2)输出为非0均值,收敛慢。我们希望输入每层神经网络的特征是以0为均值的小数值,但是过sigmoid激活函数后的数据都是正数。
(3)计算都是幂运算,复杂度大,训练时间长

Tanh函数

tf.math.tanh(x)
特点
(1)输出是0均值
(2)易造成梯度消失
(3)幂运算复杂,训练时间长

Relu函数

tf.nn.relu(x)
f(x)=max(x,0)
优点:
(1) 解决了梯度消失问题 (在正区间)
(2) 只需判断输入是否大于0,计算速度快
(3) 收敛速度远快于sigmoid和tanh
缺点:
(1) 输出非0均值,收敛慢
(2) Dead RelU问题:送入激活函数的特征是负数时,激活函数输出0,反向传播得到的梯度也是0,某些神经元可能永远不会被激活,导致相应的参数永远不能被更新。
(改进随机初始化,避免过多的负数特征送入Relu函数,可以设置更小的学习率减少参数分布的巨大变化,避免训练中产生过多负数特征进入Relu函数)

首选relu激活函数;
学习率设置较小值;
输入特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布;
初始参数中心化,即让随机生成的参数满足以0为均值,sqrt(2/当前层输入特征个数) 为标准差的正态分布。

损失函数

损失函数(loss):前向传播计算出的结果(y)与已知标准答案(y_)的差距
神经网络的优化目标就是找到某套参数,使得y与y_无限接近,也就是loss最小
loss有均方误差(MSE),自定义,交叉熵

均方误差

loss_mse=tf.reduce_mean(tf.(square(y_-y))

自定义

运用均方误差作为损失函数,默认认为预测多/少,损失相同,但是在实际情况下这两种情况往往代价不相等,在这种情况下使用均方误差是无法让利益最大化的。
loss_zdy=tf.reduce_sum(tf.where(tf.greater(y,y_),cost(y-y_),profit(y_-y)))
例子为东西时的情形,cost表示多进货时损失的成本,profit表示少进货损失的利润

交叉熵

表示两个概率分布之间的距离,交叉熵越大,两个概率分布越远,交叉熵越小,交叉熵越小
tf.losses.categorical_crossentropy(y_,y)
公式为H(y_ , y) = − ∑ _ ∗ln(y)
可以判断哪个预测结果与标准答案更近

import tensorflow as tf

loss_ce1 = tf.losses.categorical_crossentropy([1, 0], [0.6, 0.4])
loss_ce2 = tf.losses.categorical_crossentropy([1, 0], [0.8, 0.2])
print("loss_ce1:", loss_ce1)
print("loss_ce2:", loss_ce2)

# 交叉熵损失函数
# loss_ce1: tf.Tensor(0.5108256, shape=(), dtype=float32)
# loss_ce2: tf.Tensor(0.22314353, shape=(), dtype=float32)

softmax与交叉熵结合:

tf.nn.softmax_cross_entropy_with_logits
输出先过softmax函数,使输出符合概率分布,再计算y与y_的交叉熵损失函数

# softmax与交叉熵损失函数的结合
import tensorflow as tf
import numpy as np

y_ = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 0], [0, 1, 0]])
y = np.array([[12, 3, 2], [3, 10, 1], [1, 2, 5], [4, 6.5, 1.2], [3, 6, 1]])
y_pro = tf.nn.softmax(y)
loss_ce1 = tf.losses.categorical_crossentropy(y_,y_pro)
loss_ce2 = tf.nn.softmax_cross_entropy_with_logits(y_, y)

print('分步计算的结果:\n', loss_ce1)
print('结合计算的结果:\n', loss_ce2)


# 输出的结果相同
# 分步计算的结果:
#  tf.Tensor(
# [1.68795487e-04 1.03475622e-03 6.58839038e-02 2.58349207e+00
#  5.49852354e-02], shape=(5,), dtype=float64)
# 结合计算的结果:
#  tf.Tensor(
# [1.68795487e-04 1.03475622e-03 6.58839038e-02 2.58349207e+00
#  5.49852354e-02], shape=(5,), dtype=float64)

欠拟合和过拟合

欠拟合:模型不能有效拟合数据集,对现有数据学习得不够彻底
过拟合:模型对当前数据拟合太好,对于新的数据缺乏判断力,模型缺乏泛化力

缓解欠拟合的方法:

增加输入特征项
增加网络参数
减少正则化参数

缓解过拟合的方法:

数据清洗,减少噪声
增大训练集
采用正则化
增大正则化参数

正则化减少过拟合

正则化再损失函数中引入模型复杂度指标,利用给W加权值,弱化训练数据的噪声(通常只对参数W使用,不对偏置项b使用)
正则化缓解过拟合
正则化在损失函数中引入模型复杂度指标,利用给W加权值,弱化了训练
数据的噪声(一般不正则化b)

loss = loss( y与y_ ) + REGULARIZER*loss(w)
loss( y与y_ ):是以前算出的loss值,描述了预测结果与正确结果的差距如:交叉熵、均方误差
REGULARIZER*loss(w):用超参数REGULARIZER给出参数w在总loss中的
比例,即正则化的权重
w:需要正则化的参数
loss(w)的计算可以使用两种方法:
L1正则化是对所有参数W求和
L2正则化是对所有参数W的平方求和

正则化的选择:
L1正则化大概率会使很多参数变为零,因此该方法可通过稀疏参数
,即减少参数的数量,降低复杂度。
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)

反向传播优化器更新网络参数

待优化的参数w
损失函数loss
学习率lr
每次迭代一个batch(每个batch通常包含2的n次方组数据),t表示当前batch迭代的总次数

更新参数分为4步完成
1:计算出t时刻损失函数关于当前参数的梯度gt=loss对每个w求偏导
2:计算t时刻一阶动量Mt和二阶动量Vt(一阶动量是一个与梯度有关的函数,二阶动量是一个与梯度平方有关的函数,不同的优化器实质上只是定义了不同的一阶动量和二阶动量公式)
3:计算t时刻下降梯度η=lr*Mt/sqrt(Vt)
4:计算t+1时刻的参数:Wt+1=Wt-ηt=Wt-lr * Mt/sqrt(Vt)

不含动量的普通梯度下降(SGD)

Mt=gt
Vt=1
ηt=lr * Mt / sqrt(Vt)=lr * gt
Wt+1=Wt- lr * Mt / sqrt(Vt)

w1.assign_sub(lr * grads[0])  # 参数w1自更新
b1.assign_sub(lr * grads[1])  # 参数b自更新

含一阶动量的SGD:(SDGM)

β:超参数,是一个接近1的数值,经验值是0.9
Mt=β * Mt-1 +(1-β)* gt (Mt-1为上一时刻的一阶动量)
Vt=1
ηt=lr * Mt / sqrt(Vt)=lr * Mt=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)

在SGD的基础上增加二阶动量(Adagard)

Mt=gt
Vt:从开始到现在梯度平方的累计和
ηt=lr * Mt /(sqrt(Vt))
Wt+1=Wt - ηt=Wt - lr * Mt /(sqrt(Vt))

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))

在SGD的基础上增加二阶动量(RMSProp)

Mt=gt
二阶动量V由指数滑动平均值计算,表征的是过去一段时间的平均值
Vt=β * Vt-1 + (1 - β)* gt²
ηt=lr * Mt /(sqrt(Vt))= lr * gt / (sqrt(β * Vt-1 + (1 - β)* gt²))
Wt+1=Wt - ηt = Wt - lr * gt / (sqrt(β * Vt-1 + (1 - β)* gt²))

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))

结合SGDM的一阶动量和RMSProp二阶动量(Adam)

Mt=β1 * Mt-1 +(1-β1)* gt (Mt-1为上一时刻的一阶动量)
修正一阶动量的偏差
Mt(修)= Mt /(1-β1的t次方)
Vt=β2 * Vstep-1 + (1 - β2)* gt²
Vt(修)= Vt /(1-β2的t次方)
ηt=lr * Mt(修) /(sqrt(Vt(修)))
Wt+1=Wt - ηt

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))

附:同样的数据不同优化器对比TensorFlow2.1入门笔记2:神经网络优化_第1张图片在这里插入图片描述

你可能感兴趣的:(TensorFlow2.1笔记)