第二章Tensorflow 神经网络的优化

注:学习资源源自PKU《人工智能实践-TensorFlow2.0》课程

2.1 函数知识补充

除了第一章知道的一些基础的函数,还需要了解一些优化要用到的函数!

2.1.1 三元运算符

经过云玩家大佬的指点,我了解了这个高级而高大上的名字~

通俗地来讲呢~ 这个和C里面的一句很类似!

c = a > b ? a : b;

Tf 就更厉害了,可以比较数组~ 和上面的意思一样!

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)
print("c:",c)

结果:

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

2.1.2 随机数的生成

(这个在后面的生成随机噪声的时候要用到!)

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

rdm = np.random.RandomState()
a = rdm.rand()  # 返回随机标量
b = rdm.rand(2,3) # 返回2行3列的随机矩阵
print("a:",a)
print("b:",b)

结果:

a: 0.4480133972317114

b: [[0.95748805 0.41854942 0.1422722 ]

[0.87244405 0.12577127 0.12382545]]

2.1.3 叠加数组

np.vstack()将两个数组按垂直方向叠加!

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

2.1.4 网格点的生成

(我觉得这个好厉害!可以把特征联系起来,在后面也会用到!)

这个比较难理解,我当时想了好久才明白!

# np.mgrid[起始:结束:步长,起始:结束:步长,...,起始:结束:步长]
# x.ravel() 将x变为一维数组
# np.c_[数组1,数组2..] 将数组配对
x,y = np.mgrid[1:3:1,2:4:0.5]
print("x:\n",x)
print("y:\n",y)

结果:(这里为了保证两个数不同但是格式相同,因此都为两行四列!!)

x:

[[1. 1. 1. 1.]

[2. 2. 2. 2.]]

y:

[[2. 2.5 3. 3.5]

[2. 2.5 3. 3.5]]

接着拉直后配对:

# 拉直
grid = np.c_[x.ravel(),y.ravel()]
print("grid:\n",grid)

结果:

grid: [[1. 2. ] [1. 2.5] [1. 3. ] [1. 3.5] [2. 2. ] [2. 2.5] [2. 3. ] [2. 3.5]]

这样就配好对啦!

2.2 神经网络(NN)复杂度

这里的知识有不少概念

首先是时间空间复杂度计算~

NN复杂度概念

我们之前的学习率都是取一个定值,但是其实应该先用较大的学习率,找到最优解,再逐步减小学习率!

于是呢,就要学下指数衰减学习率

其实很方便,只要将原来的 lr 每次都变化就可以!

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

代码也就是:lr = lr_base lr_decay * (epoch / lr_step)

# 可以设定
epoch = 40
lr_base = 0.2
lr_decay = 0.99
lr_step = 1

# 写进for循环中
for epoch in range(epoch):
  lr = lr_base * lr_decay ** (epoch / lr_step)
  # 再跟with结构

2.3 激活函数

介绍

首先要知道激活函数到底是个啥!

因为在第一章里用了MP的简化模型,也就是没有非线性函数的,但是这样不行的,因为线性的不管多少层神经网络都是线性的,不能准确预测!(也就是画的图1)

而省略的非线性函数就是高大上的激活函数~ (图2多的那地方)

激活函数概念

类型

激活函数有很多种类型~下面说4种!

第一种第二种有好多毛病,反正现在不怎么用了,因为有幂运算很复杂,导致训练的时间较长!

图中左面是函数图,右面是导数~

第三种:

tf.nn.relu(x)

只要判断输入是否大于0就行了,速度快,但是有些会为0,使得神经元死亡!

(这里的死亡是类似参数不会更新的意思!)

有缺点但是还是很常用的,不过我们需要避免输入太多的负数!

第四种:

tf.nn.leaky_relu(x)

是第三种的改进版,非常高明,以至于我这种小白不咋用诶嘿嘿嘿嘿。

4种激活函数

2.4 损失函数

2.4.1 介绍

损失函数loss:就是真实值与预测值答案的差距!

我们的目标就是让loss的值最小

有三种方法:

  1. 均方误差 MSE(就是第一章用过的)
  2. 自定义方法
  3. 交叉熵方法 CE

下面的例子是要预测一下进货量~

2.4.2 均方误差

这里我们制造数据集,y = 1 x1 + 1 x2, 与后面的结果相比来验证~

看看最后得到的w是不是接近1!

# 均方误差
import tensorflow as tf
import numpy as np

rdm = np.random.RandomState()
x = rdm.rand(32,2)  # 32行2列的输入特征,包含了32行随机生成的x1,x2

# 我们制造数据集,y = 1*x1 + 1*x2
y_ = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1,x2) in x]
x = tf.cast(x,dtype=tf.float32)

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

epoch = 15000
lr = 0.002

for epoch in range(epoch):
  with tf.GradientTape() as tape:
    y = tf.matmul(x,w1)
    loss_mse = tf.reduce_mean(tf.square(y_ - y))

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

  # 500 次迭代输出一次
  if epoch % 500 == 0:
    print("epoch: %d"%(epoch))
    print("w1 : ",w1.numpy())
print("Final w1 is ",w1.numpy())

结果:

epoch: 0 w1 : [[ 0.00571332] [-0.7400653 ]]

epoch: 500 w1 : [[0.75261366] [0.11750585]]

......

epoch: 14500 w1 : [[1.0093216 ] [0.98676074]]

Final w1 is [[1.0081028] [0.9879307]]

但是实际上,进货量还要考虑成本和单品的利润问题!

显然没办法满足,因此需要引入自定义的函数~

2.4.3 自定义函数

这里我们设定成本COST和利润PROFIT

COST = 1
PROFIT = 99

计算损失值的时候,如果进货多了就要乘以COST,进少了要乘以利润PROFIT

于是自定义的loss是:

loss_zdy = tf.reduce_sum(tf.where(tf.greater(y,y_),(y-y_) * COST,(y_ - y) * PROFIT))

将更改重新写到上面均方误差里面:

import tensorflow as tf
import numpy as np

COST = 1
PROFIT = 99

rdm = np.random.RandomState()
x = rdm.rand(32,2)  # 32行2列的输入特征,包含了32行随机生成的x1,x2

# 我们制造数据集,y = 1*x1 + 1*x2; 与后面的结果相比来验证
y_ = [[x1 + x2 + (rdm.rand() / 10.0 - 0.05)] for (x1,x2) in x]
x = tf.cast(x,dtype=tf.float32)

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

epoch = 15000
lr = 0.002

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

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

  # 500 次迭代输出一次
  if epoch % 500 == 0:
    print("epoch: %d"%(epoch))
    print("w1 : ",w1.numpy())
print("Final w1 is ",w1.numpy())

结果:

.....Final w1 is [[1.4591465] [1.2553241]]

这里由于利润是99,成本1,所以自然会多进货合适!输出的w1和预测的也很正确,确实比均方误差计算的1大了~

这里如果把利润改成1,成本改大到99,就会发现预测的w1比1小了~

看来这个定义的还不错!

2.4.4 交叉熵损失函数

交叉熵损失函数 CE,我的理解觉得它很类似K - 近邻算法,计算得到的结果与答案的距离!

公式: H(y_,y) = - sum(y_ * lny)

得到的H越小,就越准确

这里我们将准确值设为(1,1)

一个预测结果为(0.6,0.4),一个为(0.8,0.2)

明显第二个更准!我们用代码验证一下是不是第二个的H小呢~

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("loss1 : ",loss_ce1)
print("loss2 : ",loss_ce2)

结果:

loss1 : tf.Tensor(0.5108256, shape=(), dtype=float32)

loss2 : tf.Tensor(0.22314353, shape=(), dtype=float32)

很正确,说明这个也可行!

2.5 欠拟合和过拟合

2.5.1介绍

欠拟合通俗来讲是太大条了,基本就是用一条直线想表达我的图?想得美,所以肯定不行。

过拟合则是太精细了,线曲了拐弯的,照顾到了每一个点但是就不能对未来有很好的预测了,没表达出趋势!

这个图很形象!

欠拟合过拟合

2.5.2 解决

我们通常用正则化的方法解决过拟合现象!

正则化是在损失函数中引入模型复杂度指标,利用给W加权值,弱化了训练数据的噪声。

有两种正则化方法:

L1正则化通过系数参数,减少参数数量,降低复杂度。

L2正则化减小参数的数值,有效缓解因噪声的过拟合。

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)

现在应该还不知所措,所以举个例子!

2.5.3 举个栗子

我们想把一堆点分类!分成0和1两类。

那老师交我们的想法就是先在网格里把点表示出来,再用一条线画出两类的分割线!

这里的分割线其实就是可能性为0.5时的x与y关系的函数!

这里神经网络的设计:

设计两层的神经网络~

两个特征,所以输入层为两个神经元;隐藏层设置为11个神经元(也可以其他数量),1层隐藏层,1层输出层(因为只有一个结果也就是最后的类别是啥,所以用1个神经元)

这是我用到的数据:我存在了我的bucket里~

首先,没使用正则化:

# 导入模块
import tensorflow as tf
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd

# 读入数据/标签
df = pd.read_csv('/content/drive/My Drive/Tensorflow/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_train = tf.cast(x_train,tf.float32)
y_train = tf.cast(y_train,tf.float32)

# 特征标签一一对应
train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train)).batch(32)

# 生成神经网络的参数,两个特征所以输入层为两个神经元,隐藏层设置为11个神经元(也可以其他数量),1层隐藏层,1层输出层(1个神经元)
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;
      # 激活函数用relu
      h1 = tf.nn.relu(h1)
      y = tf.matmul(h1,w2) + b2

      # 均方差损失函数mse
      loss = tf.reduce_mean(tf.square(y_train - y))
    
    # 计算loss对各个参数的梯度
    variables = [w1,b1,w2,b2]
    grads = tape.gradient(loss,variables)

     # 实现梯度更新
    w1.assign_sub(lr * grads[0])
    b1.assign_sub(lr * grads[1])
    w2.assign_sub(lr * grads[2])
    b2.assign_sub(lr * grads[3])

  if epoch % 20 == 0:
    print("epoch : ",epoch," loss : ",float(loss))

# 预测部分
# 用网格坐标点
xx,yy = np.mgrid[-3:3:0.1,-3:3:0.1]
grid = np.c_[xx.ravel(),yy.ravel()]
grid = tf.cast(grid,tf.float32)

probs = []
for x_test in grid:
  # 使用训练好的神经网络进行预测
  h1 = tf.matmul([x_test],w1) + b1
  h1 = tf.nn.relu(h1)
  # y为预测结果
  y = tf.matmul(h1,w2) + b2
  probs.append(y)

# 画出散点图
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))
plt.contour(xx,yy,probs,levels = [0.5])
plt.show()

这里的结果:

曲线好多拐弯折角,所以过拟合了!

没正则化的结果

接下来,我们用L2正则化加在with结构里面!

代码只在我写diff的地方有不同!

# L2正则化缓解过拟合

# 导入模块
import tensorflow as tf
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd

# 读入数据/标签
df = pd.read_csv('/content/drive/My Drive/Tensorflow/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_train = tf.cast(x_train,tf.float32)
y_train = tf.cast(y_train,tf.float32)

# 特征标签一一对应
train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train)).batch(32)

# 生成神经网络的参数,两个特征所以输入层为两个神经元,隐藏层设置为11个神经元(也可以其他数量),1层隐藏层,1层输出层(1个神经元)
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;
      # 激活函数用relu
      h1 = tf.nn.relu(h1)
      y = tf.matmul(h1,w2) + b2

      # 均方差损失函数mse
      loss_mse = tf.reduce_mean(tf.square(y_train - y))

      # diff!!!!
      # 添加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 = loss_mse + 0.03 * loss_regularization
    
    # 计算loss对各个参数的梯度
    variables = [w1,b1,w2,b2]
    grads = tape.gradient(loss,variables)

     # 实现梯度更新
    w1.assign_sub(lr * grads[0])
    b1.assign_sub(lr * grads[1])
    w2.assign_sub(lr * grads[2])
    b2.assign_sub(lr * grads[3])

  if epoch % 20 == 0:
    print("epoch : ",epoch," loss : ",float(loss))

# 预测部分
# 用网格坐标点
xx,yy = np.mgrid[-3:3:0.1,-3:3:0.1]
grid = np.c_[xx.ravel(),yy.ravel()]
grid = tf.cast(grid,tf.float32)

probs = []
for x_test in grid:
  # 使用训练好的神经网络进行预测
  h1 = tf.matmul([x_test],w1) + b1
  h1 = tf.nn.relu(h1)
  # y为预测结果
  y = tf.matmul(h1,w2) + b2
  probs.append(y)

# 画出散点图
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))
plt.contour(xx,yy,probs,levels = [0.5])
plt.show()

结果:

正则化后

这次的结果曲线明显平滑了很多!说明有效果!

2.6 神经网络参数优化器

这里介绍五种优化器,我觉得优化器在数学方面有很多理解的难点,感觉我还没太记住,为了“把自顶向下学习”贯彻落实,先掌握理解关键代码!

虽然数学知识很重要,但是通过第三章的学习,我发现强大的Tf给我们写好了接口,我们甚至连这些简短的关键代码都不用写了..

2.6.1 SGD 优化器

也就是常用的梯度下降法!

关键代码也见过很多次了,就是一个简单的求导后自减!

这里用第一章的鸢尾花的代码来比较每个优化器的速度~

因为是第一个优化器,所以再放一下改后的完整代码~

(可以看到改动的地方只有四处!用diff!!!标注了~)

import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np

# diff!!!
import time

x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

np.random.seed(116) # 使用相同的seed,保持输入特征和标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

x_train = tf.cast(x_train,tf.float32)
x_test = tf.cast(x_test,tf.float32)

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)

w = tf.Variable(tf.random.truncated_normal([4,3],stddev=0.1,seed=1))
b = tf.Variable(tf.random.truncated_normal([3],stddev=0.1,seed=1))

# 定义参数和画图用的存储的空列表
lr = 0.1 # 学习率为0.1
train_loss_result = []  # 每轮的loss记录在这里,画图用
test_acc_result = []  # 每轮的acc记录在这里,画图用
epoch = 500  # 循环迭代500轮
loss_all = 0  # 每轮4个step,loss_all记录4个loss的和

# diff!!!! 记录训练起始的时间
now_time = time.time()

# 每个epoch循环一个数据集
for epoch in range(epoch):
  for step,(x_train,y_train) in enumerate(train_db):
    with tf.GradientTape() as tape:
      y = tf.matmul(x_train,w) + b
      y = tf.nn.softmax(y)
      y_ = tf.one_hot(y_train,depth = 3)
      loss = tf.reduce_mean(tf.square(y_ - y))
      loss_all += loss.numpy()
    # 求导,计算loss对各个参数的梯度
    grads = tape.gradient(loss,[w,b])
    # diff!!!!两个参数的自更新
    w.assign_sub(lr * grads[0])
    b.assign_sub(lr * grads[1])
  

  # 测试部分
  total_correct,total_number = 0,0
  for x_test,y_test in test_db:
    # 使用更新后的参数进行预测
    y = tf.matmul(x_test,w) + b
    y = tf.nn.softmax(y)  # 概率分布
    pred = tf.argmax(y,axis=1)
    pred = tf.cast(pred,dtype=y_test.dtype)
    # 布尔型转化为整形:true->1,false->0
    correct = tf.cast(tf.equal(pred,y_test),dtype=tf.int32)
    correct = tf.reduce_sum(correct)
    total_correct += int(correct) # 每个betch的加起来
    total_number += x_test.shape[0] # 将所有betch的加起来
  
  acc = total_correct / total_number

  # 每个epoch,打印loss信息和acc信息
  print("Epoch {}, loss {},\t acc {}".format(epoch,loss_all / 4,acc))

  # 同步在要画图的里面
  train_loss_result.append(loss_all / 4)
  test_acc_result.append(acc)

  # 归零
  loss_all = 0

# diff!!!! 记录结束时间
total_time = time.time() - now_time
print("total time : ",total_time)

plt.title("Result: Loss & Acc")
plt.xlabel('Epoch')
plt.ylabel('Acc')
plt.ylabel('Loss')
plt.plot(test_acc_result,label = "$Accuracy$")

plt.plot(train_loss_result,label = "$Loss$")
plt.legend()
plt.show()

结果:

...... total time : 6.360609292984009

2.6.2 SGDM 优化器

这个是上一个的改进!

关键代码:

# 设置超参数
m_v,m_b = 0,0
beta = 0.9

# 运算
m_v = beta * m_v + (1 - beta) * grads[0]
m_b = beta * m_b + (1 - beta) * grads[1]
w1.assign_sub(lr * m_v)
b1.assign_sub(lr * m_b)

我们在应用的时候从上面的代码增加超参数的定义重写运算部分就可以!

增加在很明显的我画了两行线中间的地方~

import tensorflow as tf
....
loss_all = 0  # 每轮4个step,loss_all记录4个loss的和

# ######################################################
# diff!!! 设置参数
m_w,m_b = 0,0
beta = 0.9
# ######################################################

# diff!!!! 记录训练起始的时间
now_time = time.time()

....
    # 求导,计算loss对各个参数的梯度
    grads = tape.gradient(loss,[w,b])

    # ################################################
    # diff!!!!! SGDM
    m_w = beta * m_w + (1 - beta) * grads[0]
    m_b = beta * m_b + (1 - beta) * grads[1]
    # 两个参数的自更新
    w.assign_sub(lr * m_w)
    b.assign_sub(lr * m_b)  
    # ###############################################
    
  # 测试部分
  total_correct,total_number = 0,0
....
plt.show()

结果:

....... total time : 5.955986499786377

2.6.3 Adagrad 优化器

关键代码:

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

改动的地方和上面很类似!

import tensorflow as tf
....
loss_all = 0  # 每轮4个step,loss_all记录4个loss的和

# ######################################################
# diff!!! 设置参数
v_w,v_b = 0,0
# ######################################################

# diff!!!! 记录训练起始的时间
now_time = time.time()

....
    # 求导,计算loss对各个参数的梯度
    grads = tape.gradient(loss,[w,b])

    # ################################################
    # diff!!!!! Adagrad
    v_w += tf.square(grads[0])
    v_b += tf.square(grads[1])
    w.assign_sub(lr * grads[0] / tf.sqrt(v_w))
    b.assign_sub(lr * grads[1] / tf.sqrt(v_b))  
    # ###############################################
    
  # 测试部分
  total_correct,total_number = 0,0
....
plt.show()

结果:

..... total time : 6.0544352531433105

2.6.4 RMSProp 优化器

这个优化器很高明!在SGD的基础上增加了二阶动量~

(虽然我说实在也不知道理论知识,它为啥好用了)

关键代码:

依然是超参数和运算的改变!

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

改动类似!

import tensorflow as tf
....
loss_all = 0  # 每轮4个step,loss_all记录4个loss的和

# ######################################################
# diff!!! 设置参数
v_w,v_b = 0,0
beta = 0.9
# ######################################################

# diff!!!! 记录训练起始的时间
now_time = time.time()

....
    # 求导,计算loss对各个参数的梯度
    grads = tape.gradient(loss,[w,b])

    # ################################################
    # diff!!!! RMSProp
    v_w = beta * v_w + (1 - beta) * tf.square(grads[0])
    v_b = beta * v_b + (1 - beta) * tf.square(grads[1])
    w.assign_sub(lr * grads[0] / tf.sqrt(v_w))
    b.assign_sub(lr * grads[1] / tf.sqrt(v_b)) 
    # ###############################################
    
  # 测试部分
  total_correct,total_number = 0,0
....
plt.show()

结果:

....... total time : 6.306703805923462

注:这个得到的图像很诡异,是锯齿形的,老师说要调整学习率lr来纠正!需要记住。

2.6.5 Adam 优化器

这个优化器最好也最常用,结合了SGDM的一阶动量和RMSProp的二阶动量!

关键代码:

这里增加了需要记录经过总batch数的global_batch!

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_v = beta1 * m_v + (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])

# global_step是从开始训练到现在经历的总batch数
m_v_correction = m_v / (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(beta1,int(global_step)))
v_b_correction = v_b / (1 - tf.pow(beta1,int(global_step)))

w.assign_sub(lr * m_w_correction / tf.sqrt(v_w_correction))
b.assign_sub(lr * m_b_correction / tf.sqrt(v_b_correction))

全部代码与之前略有不同!需要在每个batch中添加对global_batch 的递增

import tensorflow as tf
....
loss_all = 0  # 每轮4个step,loss_all记录4个loss的和

# #######################################
# diff!!! 设置参数
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
# #######################################

# diff!!!! 记录训练起始的时间
now_time = time.time()

# 每个epoch循环一个数据集
for epoch in range(epoch):
  # betch级别的循环
  for step,(x_train,y_train) in enumerate(train_db):
        
    # ##################################
    # diff!!!
    global_step += 1
    # ##################################
    # with结构记录梯度信息
    with tf.GradientTape() as tape:
        ....
    # 求导,计算loss对各个参数的梯度
    grads = tape.gradient(loss,[w,b])


    # diff!!!!! 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])

    # global_step是从开始训练到现在经历的总batch数
    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(beta1,int(global_step)))
    v_b_correction = v_b / (1 - tf.pow(beta1,int(global_step)))

    w.assign_sub(lr * m_w_correction / tf.sqrt(v_w_correction))
    b.assign_sub(lr * m_b_correction / tf.sqrt(v_b_correction))
    # ##############################################################
    
  # 测试部分
  total_correct,total_number = 0,0
....
plt.show()

结果:

..... total time : 7.119876146316528

这一章主要多各种神经网络的优化做介绍,有理论的和代码的,我对理论知识的理解并不透彻。

我理解现在这一章是在第一章已有框架的基础上做了框架的优化,而神经网络的复杂度还相对较小。

下一章是对框架的简化~调用API接口实现以上代码的选项化(我瞎说的,没有选项化这词....但是就是这意思嘛)

Last modification:July 29th, 2020 at 11:25 pm
请赏我杯奶茶,让我快乐长肉