tf_course2神经网络优化

第二讲 神经网络优化

主要学会神经网络的优化过程,使用正则化减少过拟合,使用优化器更新网络参数
在这里插入图片描述

预备知识

tf.where()

tf.where(
    condition, x=None, y=None, name=None
)

功能:
根据condition,取x或y中的值。如果为True,对应位置取x的值;如果为
False,对应位置取y的值。

参数:
condition: bool型张量.
x: 与y shape相同的张量.
y: 与x shape相同的张量.

返回:
shape与x相同的张量

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)

np.random.RandomState.rand()

功能:
返回一个[0,1)之间的随机数

参数:
np.random.RandomState.rand(维度) #维度为空,返回一个标量

import numpy as np

rdm = np.random.RandomState(seed=1) # seed=常数,每次生成随机数相同
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]]

np.vstack()

功能

将两个数组按照垂直方向叠加

np.vstack(数组1, 数组2)

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

np.mgrid[]、np.ravel()、np.c_[]

np.mgrid [起始值, 结束值)

功能:生成网格数

np.mgrid[起始值:结束值:步长, 起始值:结束值:步长, …]

np.ravel()

功能:将x变成一维数组

np.c_[]

功能:使返回的间隔值点配对

np.c_[数组1,数组2,…]

x,y = np.mgrid[1:3:1, 2:4:0.5]
grid = np.c_[x.ravel(), y.ravel()]
print('x:',x)
print('y:',y)
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]]
grid:
 [[1.  2. ]
 [1.  2.5]
 [1.  3. ]
 [1.  3.5]
 [2.  2. ]
 [2.  2.5]
 [2.  3. ]
 [2.  3.5]]

1 神经网络复杂度

1.1 时间复杂度

NN复杂度:多用NN层数和NN参数的个数表示

空间复杂度

  • 层数 = 隐藏层的层数 + 1个输出层
  • 总参数 = 总w + 总b

时间复杂度

  • 乘加运算次数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dP129lWP-1661676265945)(attachment:image.png)]

2 学习率策略

在这里插入图片描述

2.1 指数衰减

在这里插入图片描述

其中, 是初始学习率, 是衰减率, 表示从0到当前的训练次数, 用来控制衰减速度

指数衰减学习率是先使用较大的学习率来快速得到一个较优的解,然后随着迭代的继续,逐步减小
学习率,使得模型在训练后期 更加稳定。指数型学习率衰减法是最常用的衰减方法,在大量模型中都广
泛使用。

TensorFlow API: tf.keras.optimizers.schedules.ExponentialDecay

tf.keras.optimizers.schedules.ExponentialDecay(

    initial_learning_rate, decay_steps, decay_rate, staircase=False, name=None
    
)

功能:指数衰减学习率策略

等价API: tf.optimizers.schedules.ExponentialDecay

参数:

 > initial_learning_rate: 初始学习率
 
 > deacy_steps:衰减步数,staircase为True时有效
 
 > decay_rate: 衰减率.
 
 > staircase: Bool型变量.如果为True, 学习率呈现阶梯型下降趋势

返回: tf.keras.optimizers.schedules.ExponentialDecay(step)返回计算得到的学习率.

链接: tf.keras.optimizers.schedules.ExponentialDecay

import matplotlib.pyplot as plt
N = 400
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(

    0.5,
    decay_steps=10,
    decay_rate=0.9,
    staircase=False)
y = []
for global_step in range(N):
    lr = lr_schedule(global_step)
    y.append(lr)
    
x =range(N)
plt.figure(figsize=(8,5))
plt.plot(x, y,'r-')
plt.ylim([0, max(plt.ylim())])
plt.xlabel('Step')
plt.ylabel('Learning Rate')
plt.title('ExponentialDecay')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yWHLPWBA-1661676265946)(output_11_0.png)]

2.2 分段常数衰减

分段常数衰减可以让调试人员针对不同任务设置不同的学习率,进行精细调参,在任意步长后下降
任意数值的learning rate,要求调试人员对模型和数据集有深刻认识.

tf.keras.optimizers.schedules.PiecewiseConstantDecay

tf.keras.optimizers.schedules.PiecewiseConstantDecay(
    boundaries, values, name=None
)

功能: 分段常数衰减学习率策略.

等价API: tf.optimizers.schedules.PiecewiseConstantDecay

参数:

  • boundaries: [step_1, step_2, …, step_n]定义了在第几步进行学习率衰减.

  • values: [val_0, val_1, val_2, …, val_n]定义了学习率的初始值和后续衰减时的具体取值.

返回: tf.keras.optimizers.schedules.PiecewiseConstantDecay(step)返回计算得到的学习率

N = 400
lr_schedule = tf.keras.optimizers.schedules.PiecewiseConstantDecay(
    boundaries=[100, 200, 300],
    values=[0.1, 0.05, 0.025, 0.001])

y = []
for global_step in range(N):
    lr = lr_schedule(global_step)
    y.append(lr)
x = range(N)
plt.figure(figsize=(8,6))
plt.plot(x, y, 'r-')
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Step')
plt.ylabel('Learning Rate')
plt.title('PiecewiseConstantDecay')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fTQki9lt-1661676265946)(output_14_0.png)]

3 激活函数

激活函数是用来加入非线性因素的,因为线性模型的表达能力不够。引入非线性激活函数,可使深
层神经网络的表达能力更加强大。

优秀的激活函数应满足:

  • 非线性,激活函数非线性时,多层NN可逼近所有函数
  • 可微性,优化器大多用梯度下降更新参数
  • 单调性,当激活函数是单调的,能保证单层网络的损失函数是凸函数
  • ** 近似恒等性:**f(x)≈x,当参数初始化为随机小值时,神经网络更稳定

激活函数输出值的范围:

  • 激活函数输出为有限值时,基于梯度的优化方法更稳定
  • 激活函数输出为无限值时,建议调小学习率

常见的激活函数有:sigmoid,tanh,ReLU,Leaky ReLU,PReLU,RReLU,
ELU(Exponential Linear Units),softplus,softsign,softmax等,下面介绍几个典型的激活
函数:

3.1 sigmoid函数

TensorFlow API: tf.nn.sigmoid()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nS7XpXTH-1661676265947)(attachment:image.png)]

优点:
(1):输出映射在(0,1)之间,单调连续,输出范围有限,优化稳定,可用作输出层

(2):求导容易

特点:

(1):易造成梯度消失

(2):输出非0均值,收敛慢

(3):幂运算复杂,训练时间长

sigmoid函数可应用在训练过程中。然而,当处理分类问题作出输出时,sigmoid却无能为力。简
单地说,sigmoid函数只能处理两个类,不适用于多分类问题。而softmax可以有效解决这个问题,并
且softmax函数大都运用在神经网路中的最后一层网络中,使得值得区间在(0,1)之间,而不是二分类
的。

3.2 tanh

TensoFlow API:tf.math.tanh(x)

在这里插入图片描述

优点:
1、比sigmoid函数收敛速度更快

2、相比sigmoid函数,输出以0为中心

缺点:

1、易造成梯度消失

2、幂运算复杂,训练时间长

3.3 ReLU

TensoFlow API:tf.nn.relu(x)

在这里插入图片描述
在这里插入图片描述

优点:

1、解决了梯度消失问题(在正区间)

2、只需判断输出是否大于0,计算速度快

3、收敛速度远快于sigmoid和tanh,因为sigmoid和tanh涉及很多expensive的操作

4、 提供了NN的稀疏表达能力

缺点:

1、输出非0均值,收敛慢

2、Dead Relu问题:某些神经元可能永远不会被激活,导致相应的参数不能被更新

3.4 Leaky ReLU

TensoFlow API:tf.nn.leaky_relu(x)
在这里插入图片描述

理论上来讲,Leaky ReLU有ReLU的所有优点,外加不会有Dead ReLU问题,但是在实际操作当
中,并没有完全证明Leaky ReLU总是好于ReLU。

3.5 softmax

在这里插入图片描述

TensorFlow API: tf.nn.softmax

对神经网络全连接层输出进行变换,使其服从概率分布,即每个值都位于[0,1]区间且和为1。

3.6 建议

对于初学者建议:

1、首先Relu函数

2、学习率设置较小值

3、输出特征标准化,即让输入特征满足以0为均值,1为标准差的正态分布

4、初始化问题:初始参数中心化,即让随机生成的参数满足以0为均值,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述
为标准差的正态分布

4 损失函数

损失函数(loss):预测值(y)与已知答案(y_)的差距

NN优化目标:loss最小
在这里插入图片描述

4.1 均方误差损失函数

均方误差(Mean Square Error)是回归问题最常用的损失函数。回归问题解决的是对具体数值的预测,比如房价预测、效率预测等。这些问题不是一个事先定义好的类别,而是一个任意实数。均方误差定义如下:
在这里插入图片描述

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

TensorFlow API:tf.keras.losses.MSE

tf.keras.losses.MSE(
    y_true, y_pred
)
y_true = tf.constant([0.5, 0.8])
y_pred = tf.constant([1.0,1.0])
print(tf.keras.losses.MSE(y_true, y_pred))
tf.Tensor(0.145, shape=(), dtype=float32)
print(tf.reduce_mean(tf.square(y_true - y_pred)))
tf.Tensor(0.145, shape=(), dtype=float32)

在这里插入图片描述

#p19_mse.py
import tensorflow as tf
import numpy as np

SEED=23455

rdm = np.random.RandomState(seed=SEED)# 生成[0,1)之间的随机数
x = rdm.rand(32, 2)
y_ = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1, x2) in x] # 生成噪声[0,1)/10=[0,0.1); [0,0.1)-0.05=[-0.05,0.05)

x = tf.cast(x, dtype=tf.float32)

w1 = tf.Variable(tf.random.normal([2,1], stddev=1, seed=1))

epoch = 15000
lr = 0.002

for epoch in range(epoch):
    with tf.GradientTape() as tape:
        y = tf.matmul(x, w1)
        loss_mse = tf.keras.losses.MSE(y_, y)
    grads = tape.gradient(loss_mse, w1)
    w1.assign_sub(lr*grads)
    
    if epoch % 500 == 0:
        print("After %d training steps,w1 is " % (epoch))
        print(w1.numpy(), "\n")
        
print("Final w1 is: ", w1.numpy())
After 0 training steps,w1 is 
[[-1.5136462 ]
 [ 0.35048607]] 

After 500 training steps,w1 is 
[[1.0024375]
 [0.9964705]] 

After 1000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 1500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 2000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 2500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 3000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 3500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 4000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 4500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 5000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 5500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 6000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 6500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 7000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 7500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 8000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 8500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 9000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 9500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 10000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 10500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 11000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 11500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 12000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 12500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 13000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 13500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 14000 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

After 14500 training steps,w1 is 
[[1.0043048]
 [0.9948319]] 

Final w1 is:  [[1.0043048]
 [0.9948319]]

4.2 交叉熵损失函数

交叉熵(Cross Entropy)表征两个概率之间的距离, =交叉熵越小说明二者分布越接近,是分类问题中使用较广泛的损失函数

在这里插入图片描述

其中y_代表数据真实值, y代表神经网络的预测值

对于多分类问题,神经网络的输出一般不是概率分布,因此需要引入softmax层,使得输出服从概
率分布, tensorflow中可计算交叉熵损失函数的API:

**TensorFlow API:tf.keras.losses.categorical_crossentropy **

**TensorFlow API: tf.nn.softmax_cross_entropy_with_logits **

**TensorFlow API: tf.nn.sparse_softmax_cross_entropy_with_logits *

tf.keras.losses.categorical_crossentropy

tf.keras.losses.categorical_crossentropy(
    y_true, y_pred, from_logits=False, label_smoothing=0
)

**功能:**计算交叉熵

**等价API:**tf.losses.categorical_crossentropy

参数:

- y_true: 真实值.
- y_pred:预测值
- from_logits:y_pred是否为logits张量.
- label_smoothing: [0,1]之间的小数.
y_true = [1, 0, 0]
y_pred1 = [0.5, 0.4, 0.1]
y_pred2 = [0.8, 0.1, 0.1]
print(tf.keras.losses.categorical_crossentropy(y_true, y_pred1))
print(tf.keras.losses.categorical_crossentropy(y_true, y_pred2))
tf.Tensor(0.6931472, shape=(), dtype=float32)
tf.Tensor(0.22314353, shape=(), dtype=float32)
#等价实现
print(-tf.reduce_sum(y_true* tf.math.log(y_pred1)))
print(-tf.reduce_sum(y_true * tf.math.log(y_pred2)))
tf.Tensor(0.6931472, shape=(), dtype=float32)
tf.Tensor(0.22314353, shape=(), dtype=float32)

tf.nn.softmax_cross_entropy_with_logits

tf.nn.softmax_cross_entropy_with_logits(

    labels, logits, axis=-1, name=None
    
)

**功能:**logits经过softmax之后,与labels进行交叉熵计算

在机器学习中,对于多分类问题,把未经softmax归一化的向量值称为logits。logits经过softmax
层后,输出服从概率分布的向量(来源

参数:

  • labels: 在类别这一维度上,每个向量应服从有效的概率分布. 例如,在labels的shape为[batch_size, num_classes]的情况下,labels[i]应服从概率分布.

  • logits: 每个类别的激活值,通常是线性层的输出. 激活值需要经过softmax归一化.

  • axis: 类别所在维度,默认是-1,即最后一个维度.

**返回:**softmax交叉熵损失值

labels = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
logits = [[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]]
print(tf.nn.softmax_cross_entropy_with_logits(labels, logits))
tf.Tensor([0.16984604 0.02474492], shape=(2,), dtype=float32)
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)
# 等价实现
print(-tf.reduce_sum(labels * tf.math.log(tf.nn.softmax(logits)), axis=1))
tf.Tensor([0.16984606 0.02474495], shape=(2,), dtype=float32)

tf.nn.sparse_softmax_cross_entropy_with_logits

tf.nn.sparse_softmax_cross_entropy_with_logits(

    labels, logits, name=None
    
)

**功能:**labels经过one-hot编码,logits经过softmax,两者进行交叉熵计算. 通常labels的shape为[batch_size],logits的shape为[batch_size, num_classes]. sparse可理解为对labels进行稀疏化处理(即进行one-hot编码)

参数:

labels: 标签的索引值.

logits: 每个类别的激活值,通常是线性层的输出. 激活值需要经过softmax归一化.

**返回:**softmax交叉熵损失值.

例子:(下例中先对labels进行one-hot编码为[[1,0,0], [0,1,0]],logits经过softmax变为[[0.844,
0.114,0.042], [0.007,0.976,0.018]],两者再进行交叉熵运算)

labels = [0,1]
logits = [[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]]
print(tf.nn.sparse_softmax_cross_entropy_with_logits(labels, logits))
tf.Tensor([0.16984604 0.02474492], shape=(2,), dtype=float32)
# 等价实现
print(-tf.reduce_sum(tf.one_hot(labels, tf.shape(logits)[1]) * tf.math.log(tf.nn.softmax(logits)), axis=1))
tf.Tensor([0.16984606 0.02474495], shape=(2,), dtype=float32)

4.3 自定义损失函数

根据具体任务和目的,可设计不同的损失函数。从老师课件和讲解中对于酸奶预测损失函数的设计,我们可以得知损失函数的定义能极大影响模型预测效果。好的损失函数设计对于模型训练能够起到良好的引导作用。

例如,我们可以看目标检测中的多种损失函数。目标检测的主要功能是定位和识别,损失函数的功
能主要就是让定位更精确,识别准确率更高。目标检测任务的损失函数由分类损失(Classificition
Loss)和回归损失(Bounding Box Regeression Loss)两部分构成。近几年来回归损失主要有
Smooth L1 Loss(2015), IoU Loss(2016 ACM), GIoU Loss(2019 CVPR), DIoU Loss & CIoU Loss(2020
AAAI)等,分类损失有交叉熵、softmax loss、logloss、focal loss等。在此由于篇幅原因不细究,有兴
趣的同学可自行研究。主要是给大家一个感性的认知:需要针对特定的背景、具体的任务设计损失函

在这里插入图片描述

#p20_custom.py
import tensorflow as tf
import numpy as np

SEED = 23455
COST = 1
PROFIT = 99

rdm = np.random.RandomState(SEED)
x = rdm.rand(32, 2)
y_ = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1, x2) in x]  # 生成噪声[0,1)/10=[0,0.1); [0,0.1)-0.05=[-0.05,0.05)
x = tf.cast(x, dtype=tf.float32)

w1 = tf.Variable(tf.random.normal([2, 1], stddev=1, seed=1))

epoch = 10000
lr = 0.002

for epoch in range(epoch):
    with tf.GradientTape() as tape:
        y = tf.matmul(x, w1)
        loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * COST, (y_ - y) * PROFIT))

    grads = tape.gradient(loss, w1)
    w1.assign_sub(lr * grads)

    if epoch % 500 == 0:
        print("After %d training steps,w1 is " % (epoch))
        print(w1.numpy(), "\n")
print("Final w1 is: ", w1.numpy())
After 0 training steps,w1 is 
[[3.4457052]
 [3.2526264]] 

After 500 training steps,w1 is 
[[1.1493957]
 [1.0595962]] 

After 1000 training steps,w1 is 
[[1.1397758]
 [1.09088  ]] 

After 1500 training steps,w1 is 
[[1.1301556]
 [1.1221637]] 

After 2000 training steps,w1 is 
[[1.1791688]
 [1.1647406]] 

After 2500 training steps,w1 is 
[[1.1487305]
 [1.019554 ]] 

After 3000 training steps,w1 is 
[[1.1391103]
 [1.0508376]] 

After 3500 training steps,w1 is 
[[1.1294906]
 [1.0821217]] 

After 4000 training steps,w1 is 
[[1.1198705]
 [1.1134055]] 

After 4500 training steps,w1 is 
[[1.1688839]
 [1.1559825]] 

After 5000 training steps,w1 is 
[[1.1384457]
 [1.0107961]] 

After 5500 training steps,w1 is 
[[1.1288261]
 [1.0420803]] 

After 6000 training steps,w1 is 
[[1.1192057]
 [1.0733637]] 

After 6500 training steps,w1 is 
[[1.1095855]
 [1.1046473]] 

After 7000 training steps,w1 is 
[[1.1585989]
 [1.1472243]] 

After 7500 training steps,w1 is 
[[1.1489792]
 [1.1785084]] 

After 8000 training steps,w1 is 
[[1.1185408]
 [1.0333217]] 

After 8500 training steps,w1 is 
[[1.1089209]
 [1.0646057]] 

After 9000 training steps,w1 is 
[[1.1579342]
 [1.1071826]] 

After 9500 training steps,w1 is 
[[1.1483142]
 [1.1384665]] 

Final w1 is:  [[1.1289538]
 [1.0160426]]

5 欠拟合与过拟合

欠拟合的解决方法:

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

过拟合的解决方法

  • 数据清洗
  • 增大训练集
  • 采用正则化
  • 增大正则化参数

正则化缓解过拟合

正则化在损失函数中引入模型复杂度指标,利用给W加权值,弱化了训练数据的噪声(一般不正则化)
在这里插入图片描述

正则化的选择:

  • L1正则化大概率会使很多参数变为0,因此该方法可通过稀疏参数,即减少参数的数量,降低复杂度
  • L2正则化会使参数很接近0但不为0,因此该方法可通过减小参数的值来降低复杂度
# 导入所需模块
import tensorflow as tf
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd

# 读入数据/标签 生成x_train y_train
df = pd.read_csv('E:\BaiduNetdiskDownload\中国大学MOOCTF笔记2.1共享给所有学习者\class2\dot.csv')
x_data = np.array(df[['x1', 'x2']])
y_data = np.array(df['y_c'])

x_train = x_data
y_train = y_data.reshape(-1, 1)

Y_c = [['red' if y else 'blue'] for y in y_train]

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型问题报错
x_train = tf.cast(x_train, dtype = tf.float32)
y_train = tf.cast(y_train, tf.float32)

# from_tensor_slices函数切分传入的张量的第一个维度,生成相应的数据集,使输入特征和标签值一一对应
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)

# 生成神经网络的参数,输入层为4个神经元,隐藏层为32个神经元,2层隐藏层,输出层为3个神经元
# 用tf.Variable()保证参数可训练
w1 = tf.Variable(tf.random.normal([2,11]), dtype = tf.float32)
b1 = tf.Variable(tf.constant(0.01, shape=[11]))

w2 = tf.Variable(tf.random.normal([11, 1]), dtype=tf.float32)
b2 = tf.Variable(tf.constant(0.01, shape=[1]))

lr = 0.005  # 学习率为
epoch = 800  # 循环轮数

## 训练部分
for epoch in range(epoch):
    for step, (x_train, y_train) in enumerate(train_db):
        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)
            losses_mse = tf.reduce_mean(tf.square(y_train - y))
            
            #添加l2正则化
            loss_regularization = []
            loss_regularization.append(tf.nn.l2_loss(w1))
            loss_regularization.append(tf.nn.l2_loss(w2))
            
            loss_regularization = tf.reduce_sum(loss_regularization)
            loss = losses_mse + 0.03 * loss_regularization
            
        #计算loss对各个参数的梯度
        variables = [w1, b1, w2, b2]
        grads = tape.gradient(loss, variables)
        
        # 实现梯度更新
        # w1 = w1 - lr * w1_grad
        w1.assign_sub(lr * grads[0])
        b1.assign_sub(lr * grads[1])
        w2.assign_sub(lr * grads[2])
        b2.assign_sub(lr * grads[3])
        
    # 每200个epoch,打印loss信息
    if epoch % 40 == 0:
        print('epoch:', epoch, 'loss:', float(loss))
        
        
# 预测部分
print("*******predict*******")
# xx在-3到3之间以步长为0.01,yy在-3到3之间以步长0.01,生成间隔数值点
xx,yy = np.mgrid[-3:3:.1, -3:3:.1]

# 将xx, yy拉直,并合并配对为二维张量,生成二维坐标点
grid = np.c_[xx.ravel(), yy.ravel()]
grid = tf.cast(grid, tf.float32)
# 将网格坐标点喂入神经网络,进行预测,probs为输出
probs = []
for x_predict in grid:
    # 使用训练好的参数进行预测
    h1 = tf.matmul([x_predict], w1) + b1
    h1 = tf.nn.relu(h1)
    y = tf.matmul(h1, w2) + b2 # y为预测结果
    probs.append(y)
    
# 取第0列给x1,取第1列给x2
x1 = x_data[:, 0]
x2 = x_data[:, 1]
# probs的shape调整成xx的样子
probs = np.array(probs).reshape(xx.shape)
plt.scatter(x1, x2, color=np.squeeze(Y_c))
# 把坐标xx yy和对应的值probs放入contour函数,给probs值为0.5的所有点上色  plt.show()后 显示的是红蓝点的分界线
plt.contour(xx, yy, probs, levels=[.5])
plt.show()
epoch: 0 loss: 3.5462732315063477
epoch: 40 loss: 0.41763484477996826
epoch: 80 loss: 0.363573282957077
epoch: 120 loss: 0.32501208782196045
epoch: 160 loss: 0.2921335995197296
epoch: 200 loss: 0.26344984769821167
epoch: 240 loss: 0.23859119415283203
epoch: 280 loss: 0.2170136421918869
epoch: 320 loss: 0.1982187032699585
epoch: 360 loss: 0.18189465999603271
epoch: 400 loss: 0.16760754585266113
epoch: 440 loss: 0.15512298047542572
epoch: 480 loss: 0.14434203505516052
epoch: 520 loss: 0.13469034433364868
epoch: 560 loss: 0.12616385519504547
epoch: 600 loss: 0.1187690868973732
epoch: 640 loss: 0.11242642998695374
epoch: 680 loss: 0.10703661292791367
epoch: 720 loss: 0.10248921066522598
epoch: 760 loss: 0.09857495129108429
*******predict*******

在这里插入图片描述

6 优化器

优算法可以分成一节优化和二阶优化算法, 其中一阶优化就是至的梯度算法及其变种,而二阶梯度一般是用二阶导数(Hessian矩阵)运算,如牛顿法,由于需要计算Hessian矩阵和逆矩阵,计算量较大,因此没有流传开来。这里主要总结一阶矩阵的各种梯度下降方法

深度学习算法经历了 SGD->SGDM-> NAG -> AdGrad ->AdaDelta -> Adam -> Nadam的发展历程

定义:待优化参数 w,损失函数loss,初始学习率lr,每次迭代一个batch,t表示当前batch迭代的总次数:
在这里插入图片描述

其中步骤3,4对于各算法都是一致的,主要差别体现在步骤1和步骤2上

一阶动量:与梯度相关的函数

二阶动量:与梯度平方相关的函数

6.1 SGD

TensorFlow API: tf.keras.optimizers.SGD

6.1.1 vanilla SGD

最初版本的SGD没有动量的概念,因此梯度下降是最简单的:
在这里插入图片描述

vanilla SGD最大的缺点是下降速度慢,而且可能会在沟壑的两边持续震荡,停留在一个局部最优

# SGD(无moumentum)
w1.assign_sub(lr * grads[0]) # 参数w1自更新
b1.assign_sub(lr * grads[1]) # 参数b1自更新
<tf.Variable 'UnreadVariable' shape=(11,) dtype=float32, numpy=
array([ 0.8673193 ,  0.04430589, -0.21500733, -0.05271503, -0.10523601,
        0.07320521,  0.02682167, -0.0286732 , -0.0505752 , -0.01886143,
        0.06064572], dtype=float32)>

6.1.2 SGD with Momentum

动量法是一种使梯度向量向相关方向加速变化,抑制震荡,最终实现加速收敛的方法
(Momentum is a method that helps accelerate SGD in the right direction and dampens oscillations. It adds a fraction β of the update vector of the past time step to the current update vector. The momentum term increases for dimensions whose gradients point in the same directions and reduces updates for dimensions whose gradients change directions.)

为了抑制SGD的震荡,SGDM认为梯度下降过程可以加入惯性。下坡的时候,如果发现是陡坡,那
就利用惯性跑的快一些。SGDM全称是SGD with Momentum,在SGD基础上引入了一阶动量:在这里插入图片描述

一阶动量是各个时刻梯度方向的指数移动平均值,约等于最近1/(1 - β1)个时刻的梯度向量和的平均值。也就是说,t 时刻的下降方向,不仅由当前点的梯度方向决定,而且有此前累积的下降方向决定,β1的经验值为0.9,这就意味着下降方向主要偏向此前累积的下降方向,并略微偏向当前时刻的下
降方向。|
在这里插入图片描述

# 代码实现
m_w, m_b = 0,0
beta = 0.9
#SGD-Momentum

m_w = beta * m_w + (1 - beta) * grads[0]
m_b = beta * m_b + (1 - beta) * grads[1]
w1.assign_sub(learning_rate * m_w)
b1.assign_sub(learning_rate * m_b)

6.2 AdaGrad

TensorFlow API: tf.keras.optimizers.Adagrad

上述SGD算法一直存在一个超参数(Hyper-parameter),即学习率。超参数是训练前需要手动选择的参数,前缀“hyper”就是用于区别训练过程中可自动更新的参数。学习率可以理解为参数 w 沿着梯度 g 反方向变化的步长。

SGD对所有的参数使用统一的、固定的学习率,一个自然的想法是对每个参数设置不同的学习率,
然而在大型网络中这是不切实际的。因此,为解决此问题,AdaGrad算法被提出,其做法是给学习率
一个缩放比例,从而达到了自适应学习率的效果(Ada = Adaptive)。其思想是:对于频繁更新的参
数,不希望被单个样本影响太大,我们给它们很小的学习率;对于偶尔出现的参数,希望能多得到一些信息,我们给它较大的学习率。
那怎么样度量历史更新频率呢?为此引入二阶动量——该维度上,所有梯度值的平方和:
在这里插入图片描述

是一个
其中一般为了防止分母为0, 会对二阶动量加一个平滑项,即在这里插入图片描述
,ε一般取10-8

AdaGrad 在稀疏数据场景下表现最好。因为对于频繁出现的参数,学习率衰减得快;对于稀疏的
参数,学习率衰减得更慢。然而在实际很多情况下,二阶动量呈单调递增,累计从训练开始的梯度,学
习率会很快减至 0 ,导致参数不再更新,训练过程提前结束

# adagrad
v_w += tf.square(grads[0])
v_b += tf.square(grads[1])
w1.assign_sub(learning_rate * grads[0] / tf.sqrt(v_w))
b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

6.3 RMSProp

TensorFlow API: tf.keras.optimizers.RMSprop

RMSProp算法的全称叫 Root Mean Square Prop,是由Geoffrey E. Hinton提出的一种优化算法
(Hinton的课件见下图)。由于 AdaGrad 的学习率衰减太过激进,考虑改变二阶动量的计算策略:不
累计全部梯度,只关注过去某一窗口内的梯度。修改的思路很直接,前面我们说过,指数移动平均值大
约是过去一段时间的平均值,反映“局部的”参数信息,因此我们用这个方法来计算二阶累积动量:
在这里插入图片描述

下图是来自Hinton的Lecture:
在这里插入图片描述

# RMSProp
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(learning_rate * grads[0] / tf.sqrt(v_w))
b1.assign_sub(learning_rate * grads[1] / tf.sqrt(v_b))

6.4 AdaDelta

TensorFlow API: tf.keras.optimizers.Adadelta
为解决AdaGrad的学习率递减太快的问题,RMSProp和AdaDelta几乎同时独立被提出。
我们先看论文的AdaDelta算法,下图来自原论文
在这里插入图片描述

在这里插入图片描述

# AdaDelta
beta = 0.999
v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
delta_w = tf.sqrt(u_w) * grads[0] / tf.sqrt(v_w)
delta_b = tf.sqrt(u_b) * grads[1] / tf.sqrt(v_b)
u_w = beta * u_w + (1 - beta) * tf.square(delta_w)
u_b = beta * u_b + (1 - beta) * tf.square(delta_b)
w1.assign_sub(delta_w)
b1.assign_sub(delta_b)

6.5 Adam

TensorFlow API: tf.keras.optimizers.Adam

Adam名字来源是adaptive moment estimation。Our method is designed to combine the
advantages of two recently popular methods: AdaGrad (Duchi et al., 2011), which works well
with sparse gradients, and RMSProp (Tieleman & Hinton, 2012), which works well in on-line and
non-stationary settings。也就是说,adam融合了Adagrad和RMSprop的思想。

谈到这里,Adam的出现就很自然而然了——它们是前述方法的集大成者。我们看到,SGDM在
SGD基础上增加了一阶动量,AdaGrad、RMSProp和AdaDelta在SGD基础上增加了二阶动量。把一
阶动量和二阶动量结合起来,再修正偏差,就是Adam了。
在这里插入图片描述

# 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(learning_rate * m_w_correction / tf.sqrt(v_w_correction))
b1.assign_sub(learning_rate * m_b_correction / tf.sqrt(v_b_correction))

对上述算法非常好的可视化:https://imgur.com/a/Hqolp

6.5 优化器选择

很难说某一个优化器在所有情况下都表现很好,我们需要根据具体任务选取优化器。一些优化器在
计算机视觉任务表现很好,另一些在涉及RNN网络时表现很好,甚至在稀疏数据情况下表现更出色。

总结上述,基于原始SGD增加动量和Nesterov动量,RMSProp是针对AdaGrad学习率衰减过快
的改进,它与AdaDelta非常相似,不同的一点在于AdaDelta采用参数更新的均方根(RMS)作为分
子。Adam在RMSProp的基础上增加动量和偏差修正。如果数据是稀疏的,建议用自适用方法,即
Adagrad, RMSprop, Adadelta, Adam。RMSprop, Adadelta, Adam 在很多情况下的效果是相似
的。随着梯度变的稀疏,Adam 比 RMSprop 效果会好。总的来说,Adam整体上是最好的选择。

然而很多论文仅使用不带动量的vanilla SGD和简单的学习率衰减策略。SGD通常能够达到最小
点,但是相对于其他优化器可能要采用更长的时间。采取合适的初始化方法和学习率策略,SGD更加可
靠,但也有可能陷于鞍点和极小值点。因此,当在训练大型的、复杂的深度神经网络时,我们想要快速
收敛,应采用自适应学习率策略的优化器。

如果是刚入门,优先考虑Adam或者SGD+Nesterov Momentum


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值