tensorflow 学习笔记(二):神经网络的优化过程

news2024/9/23 1:39:19

前言:

学习跟随 如何原谅奋力过但无声的 tensorflow 笔记笔记。

本章主要讲解神经网络的优化过程:神经网络的优化方法,掌握学习率、激活函数、损失函数和正则化的使用,用 Python 语言写出 SGD、Momentum、Adagrad、RMSProp、Adam 五种反向传播优化器。


文章目录

  • 一、预备知识--几个函数
    • 1.1、tf.where()
    • 1.2、np.random.RandomState.rand()
    • 1.3、np.vstack()
    • 1.4、np.mgrid[]、x.ravel()、np.c_[]
  • 二、神经网络(NN)复杂度
    • 2.1、学习率 lr
  • 三、激活函数
    • 3.1、Sigmoid 函数:tf.nn.sigmoid(x)
    • 3.2、Tanh 函数:tf.math.tanh(x)
    • 3.3、Relu 函数:tf.nn.relu(x)
    • 3.4、 Leaky Rulu 函数:tf.nn.leaky_relu(x)
    • 3.5 总结
  • 四、损失函数
    • 4.1、均方误差 MSE(Mean Squared Error)
    • 4.2、自定义损失函数
    • 4.4、softmax 与交叉熵结合
  • 五、缓解过拟合
    • 5.1、欠拟合
    • 5.2、过拟合
    • 5.3、正则化缓解过拟合
  • 六、优化器
    • 6.1、SGD(无 momentum)
    • 6.2、SGDM(含 momentum 的 SGD)
    • 6.3、Adagrad
    • 6.4、RMSProp(SGD 基础上增加二阶动量)
    • 6.5 Adam(同时结合 SGDM 一阶动量和 RMSProp 二阶动量,并在此基础上增加了两个修正项)
    • 6.5、对比实验 - 用神经网络实现鸢尾花分类
  • 7、总结
    • 7.1、优化器选择
    • 7.2、优化算法的常用 tricks


一、预备知识–几个函数

1.1、tf.where()

# 条件语句真返回 A,条件语句假返回 B
tf.where(条件语句,真返回 A,假返回 B)

# demo
import tensorflow as tf

a = tf.constant([1, 2, 3, 1, 1]) # 一维张量 a
b = tf.constant([0, 1, 3, 4, 5]) # 一维张量 b
#  若 a > b,则返回 a 对应位置的元素;否则返回 b 对应位置的元素
c = tf.where(tf.greater(a, b), a, b)  # 若a>b,返回a对应位置的元素,否则返回b对应位置的元素
print("c:", c)

1.2、np.random.RandomState.rand()

# 返回一个 [0,1) 之间的随机数
np.random.RandomState.rand(维度) # 维度为空,返回标量

# demo
import numpy as np
# seed = 常数:每次生成随机数相同
rdm = np.random.RandomState(seed=1)
a = rdm.rand() # 返回一个随机标量
b = rdm.rand(2,3) # 返回维度为 2行3列 的随机数矩阵
print('a:',a)
print('b:',b)

1.3、np.vstack()

# 将两个数组按垂直方向进行叠加,也就是对数组进行 行方向 的扩展
np.vstack(数组1,数组2)

# demo
import numpy as np
a = np.array([1,2,3])
b = np.array([4,5,6])
c = np.vstack((a,b))  # 注意要加括号,因为该函数的输入参数只有 1 个
print('c:\n',c) # 数组 c 变成二维数组了

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

# 以下三个函数一起使用可以生成网格坐标点
np.mgrid[]
# 返回若干组维度相同的等差数组;左闭右开区间:[起始值,结束值)
np.mgrid[起始值:结束值:步长,起始值,结束值:步长,...] # 第一个“起始值:结束值:步长”决定了二维数组的行,第二个“起始值:结束值:步长”决定了二维数组的列
x.ravel() # 将多维数组 x 变为一维数组,“把 . 之前的变量拉直”
np.c_[] # 使返回的间隔数值点配对
np.c_[数组1,数组2...]

# demo
import numpy as np
# 左闭右开区间:[起始值,结束值)
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)

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


二、神经网络(NN)复杂度

空间复杂度:用神经网络层数和神经网络中待优化的参数个数表示。计算神经网络的层数时,只统计具有运算能力的层:输入层仅把数据传输过来,没有运算,故在统计神经网络层数时,不算输入层;输入层和输出层之间的所有层都叫隐藏层。层数 = n 个隐藏层的层数 + 1 个输出层。每个具有计算机能力的神经元小球都要计算前一层的每一个输入特征乘以各自线上的权重,再加上这个神经元的偏置项 b。有几条权重线就有几次乘加运算。

在这里插入图片描述

2.1、学习率 lr

在这里插入图片描述
指数衰减学习率,根据当前迭代次数,动态改变学习率的值,一般写在 for 循环中。

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

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

在这里插入图片描述


三、激活函数

对于线性函数,即使有多层神经元首尾相接,构成深层神经网络,依旧是线性组合,模型的表达力不够。激活函数是用来加入非线性因素的,因为线性模型的表达能力不够。引入非线性激活函数,可使深层神经网络的表达能力更加强大。
在这里插入图片描述
在这里插入图片描述

3.1、Sigmoid 函数:tf.nn.sigmoid(x)

**sigmoid函数把输入值变换到0-1之间输出。**若输入值是非常大的负数,那么输出值为 0;若输入值是非常大的正数,那么输出值就接近 1。相当于对输入进行了归一化。

近年来使用 sigmoid 作为激活函数的网络越来越少了,因为深层神经网络更新参数时,需要从输出层到输入层需要进行逐层链式求导,而 sigmoid 的导数为 0 ~ 0.25 之间的小数,链式求导需要多层导数连续相乘,会出现多个 0 ~ 0.25 之间的小数连续相乘,结果将趋于 0,产生梯度消失,使得参数无法更新。我们希望输入每层的神经网络特征是以 0 为均值的小数值,但是过 sigmoid 函数之后的数据都是正数,会使收敛变慢。另外 sigmoid 函数存在幂运算,计算复杂度大,训练时间长。

sigmoid 函数可应用在训练过程中:

  • sigmoid 函数只能处理两个类(多分类问题用 softmax);
  • sigmoid 函数大多运用在神经网络中的最后一层,使值在(0,1)之间;
    在这里插入图片描述

3.2、Tanh 函数:tf.math.tanh(x)

该激活函数的输出值为零均值,但依旧存在**梯度消失和幂运算(复杂、训练时间长)**的问题,但比 sigmoid 函数的收敛速度快。
在这里插入图片描述

3.3、Relu 函数:tf.nn.relu(x)

分段函数,符合“激活函数具备近似恒等性”。

进入激活函数的输入特征为负数时,激活函数输出为 0,反向传播得到的梯度为 0,导致参数无法更新,造成神经元死亡。造成神经元死亡的根本原因是经过 relu 函数的负数过多导致,可以:

  • 改进随机初始化,避免过多的负数特征送入 relu 函数;
  • 通过设置更小的学习率,减少参数分布的巨大变化,避免训练中产生过多负数特征进入 relu 函数;
    在这里插入图片描述

3.4、 Leaky Rulu 函数:tf.nn.leaky_relu(x)

此激活函数是为了解决 rule 函数负区间为 0,引起神经元死亡问题而设计的。Leaky ReLU负区间引入了一个固定的斜率α,使其负区间不再恒等于 0。虽然 Leaky Relu 函数比 Relu 函数效果更好,但是在实际使用过程中,使用 relu 作为激活函数的网络更多。
在这里插入图片描述

3.5 总结

在这里插入图片描述


四、损失函数

损失函数 :前向传播计算出来的预测值(y)与已知标准答案(y_)的差距。神经网络的优化目标是找到某套参数,使得计算出来的结果(y)和已知标准答案(y_)无限接近,也就是它们的差距 loss 值最小。
在这里插入图片描述

4.1、均方误差 MSE(Mean Squared Error)

是回归问题最常用的损失函数。回归问题解决的是对具体数值的预测,如房价预测、销量预测,这些问题需要预测的不是一个事先定义好的类别,而是一个任意实数:
在这里插入图片描述

demo:预测酸奶日销量y(即产量),影响日销量的因素为x1、x2,销量y_。建模前,应预先采集的数据有每日 x1、x2和销量 y(即已知答案,最佳情况:产量=销量)

随机生成x1和x2,拟造数据集y_=x1+x2,添加-0.05~0.05的噪声。拟合可以预测销量的函数。

# 自制数据集,构建一层神经网络,预测酸奶日销量
import tensorflow as tf
import numpy as np

SEED = 23455  # 随机种子,保证每次生成的数据集一样(实际应用中不写SEED)

rdm = np.random.RandomState(seed=SEED)  # 生成[0,1)之间的随机数
x = rdm.rand(32, 2)  # 生成32行2列的输入特征x,包含了32组0-1之间的随机数x1和x2

# .rand()生成[0,1)之间的随机数
# 生成标准答案y_
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)  # x转变数据类型

w1 = tf.Variable(tf.random.normal([2, 1], stddev=1, seed=1))  # 随机初始化参数w1,初始化为两行一列

epoch = 15000  # 数据集迭代次数
lr = 0.002  # 学习率

for epoch in range(epoch):
    with tf.GradientTape() as tape:
        y = tf.matmul(x, w1)  # for循环中用with结构,求前向传播计算结果y
        loss_mse = tf.reduce_mean(tf.square(y_ - y))  # 求均方误差损失函数loss_mse
    grads = tape.gradient(loss_mse, w1)  # 损失函数对待训练参数w1求偏导
    w1.assign_sub(lr * grads)  # 更新参数w1

    if epoch % 500 == 0:  # 每迭代500轮数据打印当前参数w1
        print("After %d training steps,w1 is " % (epoch))
        print(w1.numpy(), "\n")
print("Final w1 is: ", w1.numpy())

在这里插入图片描述
拟合结果为 y = 1.000972 * X1+0.9977485 * X2,与构造数据集的 y = X1 + X2 一致,说明拟合正确。

使用均方误差作为损失函数,默认认为销量预测的多了或者少了,损失是一样的,但真实情况并非如此。

4.2、自定义损失函数

根据具体任务和目的,可设计不同的损失函数。损失函数的定义能极大影响模型预测效果,好的损失函数设计对于模型训练能够起到良好的引导作用。

预测商品销量,预测多了损失成本,预测少了损失利润,而利润往往 ≠ 成本,这种情况下使用均方误差计算 loss,无法使利益最大化。

在这里插入图片描述
demo:预测酸奶销量,假设酸奶成本COST=1元,酸奶利润PROFIT=99元。预测少了损失利润99元,预测多了损失成本1元,显然预测少了损失大,希望拟合的函数往多了预测

# 希望拟合的函数尽量往多了预测,用自定义损失函数拟合出来的预测酸奶日销量会不会智能地往多了预测?
# 以下代码相比上面,仅改动了loss函数
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)
        # 只改动了损失函数:当预测的y多了时损失成本,当预测的y少了时损失利润
        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())
 
# 自定义损失函数
# 酸奶成本1元,酸奶利润99元
# 成本很低,利润很高,人们希望多预测些,生成模型系数大于1,往多了预测

在这里插入图片描述

可见拟合出的参数均>1,偏大,模型的确在尽量往多了预测
![在这里插入图片描述](https://img-blog.csdnimg.cn/e3737f68dd1746c1b97c0b0ef87a9ea8.png) ## 4.3、交叉熵损失函数 CE(Cross Entropy) 交叉熵表示两个概率分布之间的距离。交叉熵越大,两个概率分布越远;交叉熵越小,两个概率分布越近。通过判断交叉熵的值来判断哪个预测结果与标准答案更接近。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f9e0cfe8b6ae4e019e3bd9c5dfa8df94.png)
# 传入的  y_ 和 y 为列表或array
tf.losses.categorical_crossentropy(y_,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)

4.4、softmax 与交叉熵结合

在执行分类问题时,先使用 softmax 函数让输出结果符合概率分布,再求 y 和 y_ 的交叉熵损失函数。

// 同时计算概率分布和交叉熵的函数
tf.nn.softmax_cross_entropy_with_logits(y_,y)

# 等同于下面两句:
# y_pro = tf.nn.softmax(y)
# tf.losses.categorical_crossentropy(y_,y_pro
# 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)
# 输出的结果相同

五、缓解过拟合

在这里插入图片描述

5.1、欠拟合

欠拟合:是模型不能有效拟合数据集,对现有数据集学习的不够彻底。

欠拟合的解决方法:

  • 增加输入特征项:给网络更多维度的输入特征
  • 增加网络参数:扩展网络规模,增加网络深度,提升模型表达力
  • 减少正则化参数

5.2、过拟合

过拟合:是模型对当前数据拟合地太好了,但对从未见过的新数据却难以做出正确的判断,模型缺乏泛化力。

过拟合的解决方法:

  • 数据清洗:减少数据集中的噪声,使数据集更纯净
  • 增加训练集:让模型见到更多的数据
  • 采用正则化
  • 增大正则化参数

5.3、正则化缓解过拟合

正则化在损失函数中引入模型复杂度指标,利用给 W 加权值(抑制训练数据集中的噪声),弱化了训练数据中的噪声(正则化通常只对参数 w 使用,不对偏执量 b 使用)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

dot.csv
![在这里插入图片描述](https://img-blog.csdnimg.cn/bdc6dafc09884ff8bf60f5f91be702df.png) **未加入 L2 正则化的代码:**
# 导入所需模块
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('dot.csv')
x_data = np.array(df[['x1', 'x2']])
y_data = np.array(df['y_c'])

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

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

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型问题报错
x_train = tf.cast(x_train, 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)

# 生成神经网络的参数,输入层为2个神经元,隐藏层为11个神经元,1层隐藏层,输出层为1个神经元
# 用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)
            loss = tf.reduce_mean(tf.square(y_train - y))

        # 计算loss对各个参数的梯度
        variables = [w1, b1, w2, b2]
        grads = tape.gradient(loss, variables)

        # 实现梯度更新
        # w1 = w1 - lr * w1_grad tape.gradient是自动求导结果与[w1, b1, w2, b2] 索引为0,1,2,3 
        w1.assign_sub(lr * grads[0])
        b1.assign_sub(lr * grads[1])
        w2.assign_sub(lr * grads[2])
        b2.assign_sub(lr * grads[3])

    # 每20个epoch,打印loss信息
    if epoch % 20 == 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_test in grid:
    # 使用训练好的参数进行预测
    h1 = tf.matmul([x_test], 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))  # squeeze去掉纬度是1的纬度,相当于去掉[['red'],[''blue]],内层括号变为['red','blue']
# 把坐标xx yy和对应的值probs放入contour函数,给probs值为0.5的所有点上色  plt.show()后 显示的是红蓝点的分界线
plt.contour(xx, yy, probs, levels=[.5])
plt.show()

# 读入红蓝点,画出分割线,不包含正则化
# 不清楚的数据,建议print出来查看

从下图中可以看出,随着迭代轮数的增加,loss 值逐渐减小。

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

轮廓不够平滑,存在过拟合现象。

加入 L2 正则化的代码:

# 导入所需模块
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('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, 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)
            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))
            # 求和
            # 例:x=tf.constant(([1,1,1],[1,1,1]))
            #   tf.reduce_sum(x)
            # >>>6
            loss_regularization = tf.reduce_sum(loss_regularization)
            loss = loss_mse + 0.03 * loss_regularization  # REGULARIZER = 0.03

        # 计算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 % 20 == 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()

# 读入红蓝点,画出分割线,包含正则化
# 不清楚的数据,建议print出来查看

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

从图片中可以看出,加入 L2 正则化之后的曲线更平缓,有效缓解了过拟合。

六、优化器

参考 五种反向传播优化器总结及Python实现(SGD、SGDM、Adagrad、RMSProp、Adam)。

神经网络是基于连接的人工智能,当网络结构固定后,不同参数选取对模型的表达力影响很大,更新模型参数的过程:仿佛是在教一个孩子理解世界,达到学龄的孩子脑神经元的结构、规模是相似的,他们都具备了学习的潜力,但是不同的引导方法会让孩子具备不同的能力,达到不同的高度。

优化器就是引导神经网络更新参数的工具。优化算法可以分为一阶和二阶,其中:

  • 一阶优化:梯度算法及其变种。
  • 二阶优化:一般是用二阶矩阵(Hessian矩阵)计算,如牛顿法。由于需要计算 Hessian 阵及其逆矩阵,计算量较大,因此不常用。

定义:待优化参数为 w,总损失函数为 loss,学习率为 lr。训练时为了提高效率,数据集中的数据并不是一次喂入神经网络一组,而是以 batch 为单位批量喂入神经网络,每个 batch 通常包含 2^n 组数据。t 表示当前 batch 迭代的总次数。

更新参数分为四步完成:
在这里插入图片描述

步骤3和4对于各算法都是一致的,主要差别体现在步骤1和2上
  • 一阶动量:与梯度相关的函数
  • 二阶动量:与梯度平方相关的函数
  • 不同的优化器实质上只是定义了不同的一阶动量和二阶动量公式

6.1、SGD(无 momentum)

最常用的即是随机梯度下降(Stochastic Gradient Decent,SGD)
在这里插入图片描述

与“反向传播参数更新公式”一致

反向传播参数更新公式:
在这里插入图片描述

# SGD(对于单层网络)
w1.assign_sub(learning_rate * grads[0]) # 参数 w1 的自更新
b1.assign_sub(learning_rate * grads[1]) # 参数 b 的自更新 

6.2、SGDM(含 momentum 的 SGD)

在 SGD 的基础上引入了一阶动量。

动量法是一种使梯度向量向相关方向加速变化、抑制振荡、最终实现加速收敛的方法。为了抑制 SGD 的振荡,SGDM 认为梯度下降的过程可以加入惯性:下坡时若发现是陡坡,那就利用惯性跑的更快一些。
在这里插入图片描述
SGDM 一阶动量是各个时刻梯度方向的指数移动平均值,约等于最近 1/(1-β) 个时刻的梯度向量和的平均值。也就是说,t时刻的下降方向,不仅由当前点的梯度方向(次要)决定,还由此前累积的下降方向(主要)决定。β的经验值为0.9,意味着下降方向主要偏向此前累积的下降方向,略微偏向当前时刻的下降方向。
在这里插入图片描述
每一时刻的一阶动量 m,由上一时刻的一阶动量 m 和当前时刻的梯度(用 with 结构直接算出损失函数对各个参数的偏导数)决定。

# SGDM
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]

# 自减操作实现参数 w 和 b 的自更新
w1.assign_sub(lr * m_w)
b1.assign_sub(lr * m_b)

6.3、Adagrad

在 SGD 基础上增加了二阶动量,可以对模型中的每个参数分配自适应学习率。

上述 SGD 一直存在一个超参数,即学习率(可以理解为参数 w 沿着梯度反方向变化的步长)

超参数(hyper-parameter):训练前需要手动选择的参数,前缀 “hyper” 就是利用区别训练工程中可自动更新的参数。

SGD 对所有参数使用统一、固定的学习率,一个自然的想法是对每个参数设置不同的学习率,然而在大型网络中这是不切实际的。为了解决此问题,Adagrad 被提出,其做法是给学习率一个缩放比例,从而达到自适应学习率的效果(Ada = Adaptive)。其基本思想是:对于频繁更新的参数,不希望被单个样本影响太大,给很小的学习率;对于偶尔出现的参数,希望能多得到一些信息,给较大的学习率。
在这里插入图片描述

  • 优点:Adagrad 在稀疏数据场景下表现最好,因为对于频繁出现的参数,学习率衰减快;对于稀疏的参数,学习率衰减的更慢。
  • 缺点:在实际很多情况下,二阶动量呈单调递增,累积从训练开始的梯度,学习率会很快减至 0,导致参数不再更新,训练过程提前结束。
    在这里插入图片描述
  • Adagrad 的一阶动量和 SGD 一样,是当前的梯度
  • 二阶动量是从开始到现在梯度平方的累计和
# Adagrad
v_w, v_b = 0, 0 # 0 时刻,w 和 b 的二阶动量初始值为 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))

6.4、RMSProp(SGD 基础上增加二阶动量)

全称为 Root Mean Square Prop

由于 Adagrad 的学习率衰减太过激进,改变二阶动量的计算策略:不累计全部梯度,只关注过去某一窗口内的梯度。指数移动平均值大约是过去一段时间的平均值,反映局部的参数信息,用这个方法来计算二阶累积动量。
在这里插入图片描述

# RMSProp
v_w, v_b = 0, 0 # 0 时刻,w 和 b 的二阶动量初始值均为 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))

6.5 Adam(同时结合 SGDM 一阶动量和 RMSProp 二阶动量,并在此基础上增加了两个修正项)

名称来源于 Adaptive Moment Estimation,是前述方法的集大成者。

SGDM 在 SGD 基础上增加了一阶动量,Adagrad、RMSProp 在 SGD 基础上增加了二阶动量,把一阶动量和二阶动量结合起来,再修正偏差,把修正后的一阶动量和二阶动量带入参数更新公式,实现参数自更新,即为 Adam:
在这里插入图片描述
在这里插入图片描述

# Adam
m_w, m_b = 0, 0 # 0 时刻一阶动量为 0
v_w, v_b = 0, 0 # 0 时刻二阶动量为 0
beta1, beta2 = 0.9, 0.999
global_step = 0  # 训练的总batch数

global_step += 1   # 在嵌套循环内自加一计数

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)))   # int(global_step)为训练开始到当前时刻所经历的总batch数
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)

6.5、对比实验 - 用神经网络实现鸢尾花分类

利用鸾尾花数据集,实现前向传播、反向传播、可视化曲线

  • 1)准备数据:又包括4步——数据集读入、数据集乱序、生成 train 和 test、把数据配成 [输入特征,标签] 对;
  • 2)搭建网络:定义神经网络中的所有可训练参数;
  • 3)更新参数:优化可训练参数,利用嵌套循环在with结构中求得损失函数loss对每个可训练参数的偏导数,更改这些可训练参数;
  • 4)画曲线图:为了查看效果,程序中可以加入每遍历一次数据集显示当前准确率,还可以画出准确率 acc 和损失函数 loss 的变化曲线图;

对比五种优化器的处理速度(引入时间模块),以及 loss 曲线和 acc 曲线。

SGD:

# 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线

# 本文件较 class1\p45_iris.py 仅添加四处时间记录  用 ##n## 标识
# 请将loss曲线、ACC曲线、total_time记录到 class2\优化器对比.docx  对比各优化器收敛情况

# 导入所需模块
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np
import time  ##1##

# 导入数据,分别为输入特征和标签
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

# 随机打乱数据(因为原始数据是顺序的,顺序不打乱会影响准确率)
# seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样(为方便教学,以保每位同学结果一致)
np.random.seed(116)  # 使用相同的seed,保证输入特征和标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

# 将打乱后的数据集分割为训练集和测试集,训练集为前120行,测试集为后30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)

# from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
# 用tf.Variable()标记参数可训练
# 使用seed使每次生成的随机数相同(方便教学,使大家结果都一致,在现实使用时不写seed)
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

lr = 0.1  # 学习率为0.1
train_loss_results = []  # 将每轮的loss记录在此列表中,为后续画loss曲线提供数据
test_acc = []  # 将每轮的acc记录在此列表中,为后续画acc曲线提供数据
epoch = 500  # 循环500轮
loss_all = 0  # 每轮分4个step,loss_all记录四个step生成的4个loss的和

# 训练部分
now_time = time.time()  ##2## 用时间戳记录训练的起始时间
for epoch in range(epoch):  # 数据集级别的循环,每个epoch循环一次数据集
    for step, (x_train, y_train) in enumerate(train_db):  # batch级别的循环 ,每个step循环一个batch
        with tf.GradientTape() as tape:  # with结构记录梯度信息
            y = tf.matmul(x_train, w1) + b1  # 神经网络乘加运算
            y = tf.nn.softmax(y)  # 使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
            y_ = tf.one_hot(y_train, depth=3)  # 将标签值转换为独热码格式,方便计算loss和accuracy
            loss = tf.reduce_mean(tf.square(y_ - y))  # 采用均方误差损失函数mse = mean(sum(y-out)^2)
            loss_all += loss.numpy()  # 将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
        # 计算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自更新

    # 每个epoch,打印loss信息
    print("Epoch {}, loss: {}".format(epoch, loss_all / 4))
    train_loss_results.append(loss_all / 4)  # 将4个step的loss求平均记录在此变量中
    loss_all = 0  # loss_all归零,为记录下一个epoch的loss做准备

    # 测试部分
    # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新后的参数进行预测
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
        # 将pred转换为y_test的数据类型
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 将每个batch的correct数加起来
        correct = tf.reduce_sum(correct)
        # 将所有batch中的correct数加起来
        total_correct += int(correct)
        # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
        total_number += x_test.shape[0]
    # 总的准确率等于total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")
# 用时间戳记录训练结束时间
total_time = time.time() - now_time  ##3##
print("total_time", total_time)  ##4##

# 绘制 loss 曲线
plt.title('Loss Function Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Loss')  # y轴变量名称
plt.plot(train_loss_results, label="$Loss$")  # 逐点画出trian_loss_results值并连线,连线图标是Loss
plt.legend()  # 画出曲线图标
plt.show()  # 画出图像

# 绘制 Accuracy 曲线
plt.title('Acc Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Acc')  # y轴变量名称
plt.plot(test_acc, label="$Accuracy$")  # 逐点画出test_acc值并连线,连线图标是Accuracy
plt.legend()
plt.show()

SGDM:

# 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线

# 导入所需模块
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np
import time  ##1##

# 导入数据,分别为输入特征和标签
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

# 随机打乱数据(因为原始数据是顺序的,顺序不打乱会影响准确率)
# seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样(为方便教学,以保每位同学结果一致)
np.random.seed(116)  # 使用相同的seed,保证输入特征和标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

# 将打乱后的数据集分割为训练集和测试集,训练集为前120行,测试集为后30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)

# from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
# 用tf.Variable()标记参数可训练
# 使用seed使每次生成的随机数相同(方便教学,使大家结果都一致,在现实使用时不写seed)
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

lr = 0.1  # 学习率为0.1
train_loss_results = []  # 将每轮的loss记录在此列表中,为后续画loss曲线提供数据
test_acc = []  # 将每轮的acc记录在此列表中,为后续画acc曲线提供数据
epoch = 500  # 循环500轮
loss_all = 0  # 每轮分4个step,loss_all记录四个step生成的4个loss的和

##########################################################################
# 加入超参数
m_w, m_b = 0, 0
beta = 0.9
##########################################################################

# 训练部分
now_time = time.time()  ##2##
for epoch in range(epoch):  # 数据集级别的循环,每个epoch循环一次数据集
    for step, (x_train, y_train) in enumerate(train_db):  # batch级别的循环 ,每个step循环一个batch
        with tf.GradientTape() as tape:  # with结构记录梯度信息
            y = tf.matmul(x_train, w1) + b1  # 神经网络乘加运算
            y = tf.nn.softmax(y)  # 使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
            y_ = tf.one_hot(y_train, depth=3)  # 将标签值转换为独热码格式,方便计算loss和accuracy
            loss = tf.reduce_mean(tf.square(y_ - y))  # 采用均方误差损失函数mse = mean(sum(y-out)^2)
            loss_all += loss.numpy()  # 将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
        # 计算loss对各个参数的梯度
        grads = tape.gradient(loss, [w1, b1])

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

    # 每个epoch,打印loss信息
    print("Epoch {}, loss: {}".format(epoch, loss_all / 4))
    train_loss_results.append(loss_all / 4)  # 将4个step的loss求平均记录在此变量中
    loss_all = 0  # loss_all归零,为记录下一个epoch的loss做准备

    # 测试部分
    # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新后的参数进行预测
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
        # 将pred转换为y_test的数据类型
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 将每个batch的correct数加起来
        correct = tf.reduce_sum(correct)
        # 将所有batch中的correct数加起来
        total_correct += int(correct)
        # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
        total_number += x_test.shape[0]
    # 总的准确率等于total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")
total_time = time.time() - now_time  ##3##
print("total_time", total_time)  ##4##

# 绘制 loss 曲线
plt.title('Loss Function Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Loss')  # y轴变量名称
plt.plot(train_loss_results, label="$Loss$")  # 逐点画出trian_loss_results值并连线,连线图标是Loss
plt.legend()  # 画出曲线图标
plt.show()  # 画出图像

# 绘制 Accuracy 曲线
plt.title('Acc Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Acc')  # y轴变量名称
plt.plot(test_acc, label="$Accuracy$")  # 逐点画出test_acc值并连线,连线图标是Accuracy
plt.legend()
plt.show()

# 请将loss曲线、ACC曲线、total_time记录到 class2\优化器对比.docx  对比各优化器收敛情况

Adagrad:

# 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线

# 导入所需模块
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np
import time  ##1##

# 导入数据,分别为输入特征和标签
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

# 随机打乱数据(因为原始数据是顺序的,顺序不打乱会影响准确率)
# seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样(为方便教学,以保每位同学结果一致)
np.random.seed(116)  # 使用相同的seed,保证输入特征和标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

# 将打乱后的数据集分割为训练集和测试集,训练集为前120行,测试集为后30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)

# from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
# 用tf.Variable()标记参数可训练
# 使用seed使每次生成的随机数相同(方便教学,使大家结果都一致,在现实使用时不写seed)
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

lr = 0.1  # 学习率为0.1
train_loss_results = []  # 将每轮的loss记录在此列表中,为后续画loss曲线提供数据
test_acc = []  # 将每轮的acc记录在此列表中,为后续画acc曲线提供数据
epoch = 500  # 循环500轮
loss_all = 0  # 每轮分4个step,loss_all记录四个step生成的4个loss的和

##########################################################################
# 加入超参数
v_w, v_b = 0, 0
##########################################################################

# 训练部分
now_time = time.time()  ##2##
for epoch in range(epoch):  # 数据集级别的循环,每个epoch循环一次数据集
    for step, (x_train, y_train) in enumerate(train_db):  # batch级别的循环 ,每个step循环一个batch
        with tf.GradientTape() as tape:  # with结构记录梯度信息
            y = tf.matmul(x_train, w1) + b1  # 神经网络乘加运算
            y = tf.nn.softmax(y)  # 使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
            y_ = tf.one_hot(y_train, depth=3)  # 将标签值转换为独热码格式,方便计算loss和accuracy
            loss = tf.reduce_mean(tf.square(y_ - y))  # 采用均方误差损失函数mse = mean(sum(y-out)^2)
            loss_all += loss.numpy()  # 将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
        # 计算loss对各个参数的梯度
        grads = tape.gradient(loss, [w1, b1])

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

    # 每个epoch,打印loss信息
    print("Epoch {}, loss: {}".format(epoch, loss_all / 4))
    train_loss_results.append(loss_all / 4)  # 将4个step的loss求平均记录在此变量中
    loss_all = 0  # loss_all归零,为记录下一个epoch的loss做准备

    # 测试部分
    # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新后的参数进行预测
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
        # 将pred转换为y_test的数据类型
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 将每个batch的correct数加起来
        correct = tf.reduce_sum(correct)
        # 将所有batch中的correct数加起来
        total_correct += int(correct)
        # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
        total_number += x_test.shape[0]
    # 总的准确率等于total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")
total_time = time.time() - now_time  ##3##
print("total_time", total_time)  ##4##

# 绘制 loss 曲线
plt.title('Loss Function Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Loss')  # y轴变量名称
plt.plot(train_loss_results, label="$Loss$")  # 逐点画出trian_loss_results值并连线,连线图标是Loss
plt.legend()  # 画出曲线图标
plt.show()  # 画出图像

# 绘制 Accuracy 曲线
plt.title('Acc Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Acc')  # y轴变量名称
plt.plot(test_acc, label="$Accuracy$")  # 逐点画出test_acc值并连线,连线图标是Accuracy
plt.legend()
plt.show()

# 请将loss曲线、ACC曲线、total_time记录到 class2\优化器对比.docx  对比各优化器收敛情况

RMSProp:

# 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线

# 导入所需模块
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np
import time  ##1##

# 导入数据,分别为输入特征和标签
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

# 随机打乱数据(因为原始数据是顺序的,顺序不打乱会影响准确率)
# seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样(为方便教学,以保每位同学结果一致)
np.random.seed(116)  # 使用相同的seed,保证输入特征和标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

# 将打乱后的数据集分割为训练集和测试集,训练集为前120行,测试集为后30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)

# from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
# 用tf.Variable()标记参数可训练
# 使用seed使每次生成的随机数相同(方便教学,使大家结果都一致,在现实使用时不写seed)
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

lr = 0.1  # 学习率为0.1
train_loss_results = []  # 将每轮的loss记录在此列表中,为后续画loss曲线提供数据
test_acc = []  # 将每轮的acc记录在此列表中,为后续画acc曲线提供数据
epoch = 500  # 循环500轮
loss_all = 0  # 每轮分4个step,loss_all记录四个step生成的4个loss的和

##########################################################################
# 加入超参数
v_w, v_b = 0, 0
beta = 0.9
##########################################################################

# 训练部分
now_time = time.time()  ##2##
for epoch in range(epoch):  # 数据集级别的循环,每个epoch循环一次数据集
    for step, (x_train, y_train) in enumerate(train_db):  # batch级别的循环 ,每个step循环一个batch
        with tf.GradientTape() as tape:  # with结构记录梯度信息
            y = tf.matmul(x_train, w1) + b1  # 神经网络乘加运算
            y = tf.nn.softmax(y)  # 使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
            y_ = tf.one_hot(y_train, depth=3)  # 将标签值转换为独热码格式,方便计算loss和accuracy
            loss = tf.reduce_mean(tf.square(y_ - y))  # 采用均方误差损失函数mse = mean(sum(y-out)^2)
            loss_all += loss.numpy()  # 将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
        # 计算loss对各个参数的梯度
        grads = tape.gradient(loss, [w1, b1])

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

    # 每个epoch,打印loss信息
    print("Epoch {}, loss: {}".format(epoch, loss_all / 4))
    train_loss_results.append(loss_all / 4)  # 将4个step的loss求平均记录在此变量中
    loss_all = 0  # loss_all归零,为记录下一个epoch的loss做准备

    # 测试部分
    # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新后的参数进行预测
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
        # 将pred转换为y_test的数据类型
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 将每个batch的correct数加起来
        correct = tf.reduce_sum(correct)
        # 将所有batch中的correct数加起来
        total_correct += int(correct)
        # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
        total_number += x_test.shape[0]
    # 总的准确率等于total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")
total_time = time.time() - now_time  ##3##
print("total_time", total_time)  ##4##

# 绘制 loss 曲线
plt.title('Loss Function Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Loss')  # y轴变量名称
plt.plot(train_loss_results, label="$Loss$")  # 逐点画出trian_loss_results值并连线,连线图标是Loss
plt.legend()  # 画出曲线图标
plt.show()  # 画出图像

# 绘制 Accuracy 曲线
plt.title('Acc Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Acc')  # y轴变量名称
plt.plot(test_acc, label="$Accuracy$")  # 逐点画出test_acc值并连线,连线图标是Accuracy
plt.legend()
plt.show()

# 请将loss曲线、ACC曲线、total_time记录到 class2\优化器对比.docx  对比各优化器收敛情况

Adam:

# 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线

# 导入所需模块
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np
import time  ##1##

# 导入数据,分别为输入特征和标签
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

# 随机打乱数据(因为原始数据是顺序的,顺序不打乱会影响准确率)
# seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样(为方便教学,以保每位同学结果一致)
np.random.seed(116)  # 使用相同的seed,保证输入特征和标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

# 将打乱后的数据集分割为训练集和测试集,训练集为前120行,测试集为后30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)

# from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
# 用tf.Variable()标记参数可训练
# 使用seed使每次生成的随机数相同(方便教学,使大家结果都一致,在现实使用时不写seed)
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

lr = 0.1  # 学习率为0.1
train_loss_results = []  # 将每轮的loss记录在此列表中,为后续画loss曲线提供数据
test_acc = []  # 将每轮的acc记录在此列表中,为后续画acc曲线提供数据
epoch = 500  # 循环500轮
loss_all = 0  # 每轮分4个step,loss_all记录四个step生成的4个loss的和

##########################################################################
# 加入超参数
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
##########################################################################

# 训练部分
now_time = time.time()  ##2##
for epoch in range(epoch):  # 数据集级别的循环,每个epoch循环一次数据集
    for step, (x_train, y_train) in enumerate(train_db):  # batch级别的循环 ,每个step循环一个batch
 ##########################################################################       
        global_step += 1
 ##########################################################################       
        with tf.GradientTape() as tape:  # with结构记录梯度信息
            y = tf.matmul(x_train, w1) + b1  # 神经网络乘加运算
            y = tf.nn.softmax(y)  # 使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
            y_ = tf.one_hot(y_train, depth=3)  # 将标签值转换为独热码格式,方便计算loss和accuracy
            loss = tf.reduce_mean(tf.square(y_ - y))  # 采用均方误差损失函数mse = mean(sum(y-out)^2)
            loss_all += loss.numpy()  # 将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
        # 计算loss对各个参数的梯度
        grads = tape.gradient(loss, [w1, b1])

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

    # 每个epoch,打印loss信息
    print("Epoch {}, loss: {}".format(epoch, loss_all / 4))
    train_loss_results.append(loss_all / 4)  # 将4个step的loss求平均记录在此变量中
    loss_all = 0  # loss_all归零,为记录下一个epoch的loss做准备

    # 测试部分
    # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新后的参数进行预测
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
        # 将pred转换为y_test的数据类型
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 将每个batch的correct数加起来
        correct = tf.reduce_sum(correct)
        # 将所有batch中的correct数加起来
        total_correct += int(correct)
        # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
        total_number += x_test.shape[0]
    # 总的准确率等于total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")
total_time = time.time() - now_time  ##3##
print("total_time", total_time)  ##4##

# 绘制 loss 曲线
plt.title('Loss Function Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Loss')  # y轴变量名称
plt.plot(train_loss_results, label="$Loss$")  # 逐点画出trian_loss_results值并连线,连线图标是Loss
plt.legend()  # 画出曲线图标
plt.show()  # 画出图像

# 绘制 Accuracy 曲线
plt.title('Acc Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Acc')  # y轴变量名称
plt.plot(test_acc, label="$Accuracy$")  # 逐点画出test_acc值并连线,连线图标是Accuracy
plt.legend()
plt.show()

# 请将loss曲线、ACC曲线、total_time记录到 class2\优化器对比.docx  对比各优化器收敛情况

对比各优化器收敛情况(lr = 0.1;epoch = 500 ;batch = 32):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


7、总结

7.1、优化器选择

在这里插入图片描述

7.2、优化算法的常用 tricks

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357228.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023-02-18干活记录

MathBERT: 耗时:2-3hours(昨天和人聊天聊完了,今天九点才到实验室,呜呜呜一早上就看了个论文) 读论文:BERT-Based Embedding Model for Formula Retrieval Corpus Description: resource:from MSE;the formulas ex…

腾讯云——负载均衡CLB

负载均衡 CLB 提供四层(TCP 协议/UDP 协议/TCP SSL 协议)和七层(HTTP 协议/HTTPS 协议)负载均衡。您可以通过 CLB 将业务流量分发到多个后端服务器上,消除单点故障并保障业务可用性。CLB 自身采用集群部署,…

电子技术——共栅和共源共栅放大器的高频响应

电子技术——共栅和共源共栅放大器的高频响应 我们在之前学过无论是是CS放大器还是CE放大器,都可以看做是一个带通(IC低通)滤波器。在高频处的响应收到输入电容 CinC_{in}Cin​ 的限制(主要是米勒效应)。因此&#xff…

中南民族大学数字电路实验一

数字电路实验一基本逻辑门实验1.与非门实现与门2.与非门实现或门3.与非门实现或非门4.与非门实现异或门5.与非门实现与或门6.与非门实现与或非门实验报告结果分析基本逻辑门实验 一、实验目的 1.掌握 logisim 软件的使用方法; 2.学习基于该软…

基于蜣螂算法优化Kmeans图像分割-附代码

基于蜣螂优化Kmeans图像分割算法 - 附代码 文章目录基于蜣螂优化Kmeans图像分割算法 - 附代码1.Kmeans原理2.基于蜣螂算法的Kmeans聚类3.算法实验结果4.Matlab代码摘要:基于蜣螂优化Kmeans图像分割算法。1.Kmeans原理 K-Means算法是一种无监督分类算法,…

蚂蚁感冒---第五届蓝桥杯真题

目录 题目链接 题目描述 分析: 代码: y总综合​ 666 题目链接 1211. 蚂蚁感冒 - AcWing题库 题目描述 分析: y总真牛逼,掉头等价于穿过,以第一个点为分界点,分别判断 代码: (自…

Java线程池的创建以及原理

一、为什么要使用线程池 在外面的日常开发中,也使用了不少池化技术,比如线程池、数据库连接池、HTTP连接池等等都是对这个思想的应用。 池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。 线程池提供了一种限制和管理资…

centos7系统-kubeadm安装k8s集群(v1.26版本)亲测有效,解决各种坑可供参考

文章目录硬件要求可省略的步骤配置虚拟机ip设置阿里镜像源各服务器初始化配置配置主节点的主机名称配置从节点的主机名称配置各节点的Host文件关闭各节点的防火墙关闭selinux永久禁用各节点的交换分区同步各节点的时间将桥接的IPv4流量传递到iptables的链(三台都执行…

PHP面向对象01:面向对象基础

PHP面向对象01:面向对象基础一、关键字说明二、技术实现1. 定义类2. 类成员三、 访问修饰限定符1. public2. protected3. private4. 空修饰限定符四、类内部对象五、构造和析构1. 构造方法2. 析构方法六、范围解析操作符1. 访问类常量2. 静态成员3. self关键字七、类…

自动驾驶:时钟同步

文章目录 一、自动驾驶时间同步简介二、时间同步需要的服务1、PTP1.1 ptp4l三、UTC转换UNIX时间戳(timestamp)一、自动驾驶时间同步简介 二、时间同步需要的服务 1、PTP ptp4l -i mgbe3_0 -f /etc/automotive-slave.cfg & phc2sys -s mgbe3_0 -O 0

IDEA插件 RestfulTool插件——Restful服务开发辅助工具集

IDEA插件 RestfulTool插件——Restful服务开发辅助工具集 目录IDEA插件 RestfulTool插件——Restful服务开发辅助工具集1.插件介绍2.安装方式3.使用方法1.插件介绍 RestfulTool插件。一套 Restful 服务开发辅助工具集: 提供了一个 Services tree 的显示窗口 双击 …

Linux C/C++ 多线程TCP/UDP服务器 (监控系统状态)

Linux环境中实现并发TCP/IP服务器。多线程在解决方案中提供了并发性。由于并发性,它允许多个客户端同时连接到服务器并与服务器交互。 Linux多线程编程概述 许多应用程序同时处理多项杂务。服务器应用程序处理并发客户端;交互式应用程序通常在处理后台…

80211无线网络架构

无线网络架构物理组件BSS(Basic Service Set)基本服务集BSSID(BSS Identification)ssid(Service Set Identification)ESS(Extended Service Set)扩展服务集物理组件 无线网络包含四…

【C++学习】基础语法(三)

众所周知C语言是面向过程的编程语言,关注的是过程;解决问题前,需要分析求解的步骤,然后编辑函数逐步解决问题。C是基于面向对象的,关注的是对象,将一件事拆分成不同的对象,不同对象间交互解决问…

C++:类与对象

文章目录一.面向过程和面向对象的初步认识二.类1.类的初步认识2.类的定义3.类的访问限定符4.类的作用域5.类的实例化6.类对象模型三.this指针1.什么是this指针2.this指针的特性3.this指针的空指针问题四.浅谈封装五.类的默认成员函数1.构造函数1.1构造函数的概念1.2构造函数的用…

jenkins 安装 -适用于在线安装 后续写个离线安装的

jenkins安装1.下载jenkins2.安装启动3.附件卸载jdk的命令4.配置jenkins一、在jenkins配置文件中配置jdk环境变量二、修改jenkins默认的操作用户1.下载jenkins jenkins官网下载 https://www.jenkins.io/ 点击下载 我是centos系统所以选择centos,点击后按着官方提供…

golang简单实现chatgpt网页聊天

效果如图: 安装openai的sdk: go get github.com/sashabaranov/go-gpt3go代码: main.go package mainimport ("fmt""net/http""os"gogpt "github.com/sashabaranov/go-gpt3" )var client gogpt.N…

高可用 - 02 Keepalived_VRRP工作原理

文章目录Keepalived VS HeartbeatKeepalived的用途VRRP与工作原理物理路由器和虚拟路由器Keepalived VS Heartbeat Keepalived是Linux下一个轻量级的高可用解决方案,它与Heartbeat、RoseHA实现的功能类似,都可以实现服务或者网络的高可用,但…

SmS-Activate一款好用的短信验证码接收工具

前言 有些国外应用在使用应用上的功能时需要注册账号,由于某种不可抗因素,我们的手机号一般不支持注册,接收不到信息验证码,于是我们可以使用SmS-Activate提供的服务,使用$实现我们的需求(大概一次验证1-5…

Python练习系统

用python给自己做个练习系统刷题吧! #免费源码在文末公众号哈# 选择题 def xuanze():global flag2if flag21:def insert():numvar1.get()questionvar2.get()choicevar3.get()answervar4.get()with open(d:\\练习系统\\练习三3.1.pickle,rb) as file:lst1pickle.lo…