本讲目标:学会神经网络优化过程,使用正则化减少过拟合,使用优化器更新网络参数。
预备知识
在正式学习前,我们需要先来了解一下使用到的函数,扫清代码关。
关注代码的注释,为学习注意点。
函数1:tf.where() 代码格式 :tf.where(条件语句,真返回A,假返回B)
函数作用 :条件语句真返回A,条件语句假返回B示例 :
函数2:np.random.RandomState.rand() 代码格式 :np.random.RandomState.rand(维度)
#维度为空,返回标量函数作用 : 返回一个[0,1) 之间的随机数
示例 :
1 2 3 4 5 6 7 import numpy as nprdm=np.random.RandomState(seed=1 ) a=rdm.rand() b=rdm.rand(2 ,3 ) print ("a:" ,a)print ("b:" ,b)
运行结果 :
函数3:np.vstack() 代码格式 :np.vstack(数组1,数组2)
函数作用 :将两个数组按垂直方向叠加示例 :
1 2 3 4 5 6 7 8 9 10 11 import numpy as npa = np.array([1 ,2 ,3 ]) b = np.array([4 ,5 ,6 ]) c = np.vstack((a,b)) print ("c:\n" ,c)a = np.array([[1 , 2 , 3 ], [1 , 3 , 5 ]]) b = np.array([[4 , 5 , 6 ], [4 , 7 , 9 ]]) c = np.vstack((a, b)) print ("c:\n" , c)
运行结果 :
函数3:三个组合函数 代码格式 :np.mgrid[ 起始值 : 结束值 : 步长 ,起始值 : 结束值 : 步长 , … ]
函数作用 :生成等间隔数值点用法补充 :http://www.manongjc.com/detail/18-fnervybjowyulpw.html
代码格式 :x.ravel( )
函数作用 :将x变为一维数组,“把 . 前变量拉直”
代码格式 :np.c_[ 数组1,数组2, … ]
函数作用 :np.c_[ ] 使返回的间隔数值点配对
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import numpy as npimport tensorflow as tfx, y = np.mgrid[1 :3 :1 , 2 :4 :0.5 ] print ("x:\n" , x)print ("y:\n" , y)grid = np.c_[x.ravel(), y.ravel()] print ("x.ravel():\n" , x.ravel())print ("y.ravel():\n" , y.ravel())print ('grid:\n' , grid)
运行结果:
注 :使用np.mgrid()生成矩阵时,如果是多维,其大小依据单个数组 的大小。例如上例,x, y = np.mgrid[1:3:1, 2:4:0.5],逗号分开维度 ,逗号前是一维,其大小为2;逗号后是二维,其大小为4;所以最后结果为2x4矩阵。 其中x沿着水平向右的方向扩展(即是:每列都相同 ),观察x。y沿着垂直的向下的方向扩展(即是:每行都相同 )。观察y。
复杂度与学习率
本节内容将介绍复杂度与学习率,先通过ppt进行大体框架的整理,再通过笔记进行细致补充。
神经网络的复杂度
分为:时间复杂度和空间复杂度。
下图为例子:
时间复杂度
即模型的运算次数 ,可用浮点运算次数(FPLOPs, FLoating-point OPerations)或者乘加运算次数衡 量
注:输入层的节点没有计算过程,只是接收数据。
空间复杂度
空间复杂度(访存量),严格来讲包括两部分:总参数量 + 各层输出特征图 。
参数量:模型所有带参数的层的权重参数总量;
特征图:模型在实时运行过程中每层所计算出的输出特征图大小。
注:层数不包含输入层。
学习率策略
我们先来复习一下前面提到过的损失函数与参数的更新。
从图片可以看到,参数更新的公式与学习率有关。所以学习率的大小对loss甚至更新后的参数w有着直接影响。 我们不可能一开始就设定好一个合适的学习率,但是我们可以设置一个自己变化的学习率。
指数衰减学习率
先用较大的学习率,快速得到较优解,然后逐步减小学习率,使模型在训练后期稳定。
指数衰减学习率 = 初始学习率 * 学习率衰减率^( 当前轮数 / 多少轮衰减一次 )
也就是说学习率会随着训练次数的增大而逐渐减小。使用参数 :
初始学习率:刚开始进行设定,作为减小的基准。
学习率衰减率:也是设定好初始值,而后随着指数的变化而变化。
当前轮数:上公式的变量,也就是x。
多少轮衰减一次:设定的常数
变化图像 :
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import tensorflow as tfw = tf.Variable(tf.constant(5 , dtype=tf.float32)) epoch = 40 LR_BASE = 0.2 LR_DECAY = 0.99 LR_STEP = 1 for epoch in range (epoch): lr = LR_BASE * LR_DECAY ** (epoch / LR_STEP) with tf.GradientTape() as tape: loss = tf.square(w + 1 ) grads = tape.gradient(loss, w) w.assign_sub(lr * grads) print ("After %s epoch,w is %f,loss is %f,lr is %f" % (epoch, w.numpy(), loss, lr))
运行结果 :
分段常数衰减 激活函数
激活函数是用来加入非线性因素 的,因为线性模型的表达能力不够。引入非线性激活函数,可使深层神经网络的表达能力 更加强大。
优秀的激活函数应满足 :
非线性 : 激活函数非线性时,多层神经网络可逼近所有函数
可微性 : 优化器大多用梯度下降更新参数
单调性 : 当激活函数是单调的,能保证单层网络的损失函数是凸函数
近似恒等性 : . 当参数初始化为随机小值时,神经网络更稳定
激活函数输出值的范围:
激活函数输出为有限值 时,基于梯度的优化方法更稳定
激活函数输出为无限值 时,建议调小学习率
常见的激活函数 有:sigmoid
,tanh
,ReLU
,Leaky ReLU
,PReLU
,RReLU
,ELU(Exponential Linear Units)
,softplus
,softsign
,softmax
等。
sigmoid函数
补充: sigmoid函数可应用在训练过程 中。然而,当处理分类问题作出输出时,sigmoid却无能为力。简单地说sigmoid函数只能处理两个类 ,不适用于多分类问题。而softmax可以有效解决这个问题,并且softmax函数大都运用在神经网路中的最后一层网络 中,使得值的区间在(0,1)之间,而不是二分类的。
Tanh函数
Relu函数
Leaky Relu函数
Softmax函数
给初学者的建议
在之后的学习里进行实践。
损失函数(loss)
神经网络模型的效果及优化的目标是通过损失函数 来定义的。回归和分类是监督学习中的两个大类。
均方误差损失函数
均方误差(Mean Square Error)是回归问题 最常用的损失函数。 回归问题解决的是对具体数值的预测 ,比如房价预测、销量预测等。这些问题需要预测的不是一个事先定义好的类别,而是一个任意实数。
代码使用 :loss_mse = tf.reduce_mean(tf.square(y_ - y))
其中y_为标签值,y为预测值。
酸奶例子: 题干 : 预测酸奶日销量y,x1、x2是影响日销量的因素。 建模前,应预先采集的数据有:每日x1、x2和销量y_(即已知答案,最佳情况:产量=销量) 拟造数据集X,Y_: y_ = x1 + x2 噪声:-0.05 ~ +0.05 拟合可以预测销量的函数
代码实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import tensorflow as tfimport numpy as npSEED = 23455 rdm = np.random.RandomState(seed=SEED) x = rdm.rand(32 , 2 ) 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 , 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.reduce_mean(tf.square(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())
运行结果 : \
可见,经过15000次训练,我们的参数已经得出,符合定义的条件。 但是这里使用均方差损失函数并不可以正确反映差距,理由如下。
自定义损失函数 以前面的例子为例,如预测商品销量,预测多了,损失成本;预测少了,损失利润。如果利润与成本不相等,前方法就无法得到利益最大化。所以我们要根据实际情况自己去定义损失函数。
通过上图可以看到,这里我们定义了一个损失函数,其过程很明确。需要注意的是这里使用了我们前面介绍的两个函数:tf.where()
和tf.greater()
。
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import tensorflow as tfimport numpy as npSEED = 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] 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())
运行结果 :
预测结果随着利润和成本的变化而变化,符合预期。
根据具体任务和目的,可设计不同的损失函数。从老师课件和讲解中对于酸奶预测损失函数的设计,我们可以得知损失函数的定义能极大影响模型预测效果。好的损失函数设计对于模型训练能够起到良好的引导作用。
例如,我们可以看目标检测中的多种损失函数。目标检测的主要功能是定位和识别,损失函数的功能主要就是让定位更精确,识别准确率更高 。目标检测任务的损失函数由分类损失(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等。在此由于篇幅原因不细究,有兴趣的同学可自行研究。主要是给大家一个感性的认知:需要针对特定的背景、具体的任务设计损失函数 。
交叉熵损失函数
交叉熵(Cross Entropy)表征两个概率分布之间的距离,交叉熵越小说明二者分布越接近,是分类问题 中使用较广泛的损失函数。
函数作用 :交叉熵损失函数CE (Cross Entropy)用于表征两个概率分布 之间的距离求解公式 :H(y_ , y) = − ∑ 𝑦_ ∗ lny
eg . 二分类 已知答案y_=(1, 0) 预测y1=(0.6, 0.4) y2=(0.8, 0.2) 哪个更接近标准答案? H1((1,0),(0.6,0.4)) = -(1ln0.6 + 0 ln0.4) ≈ -(-0.511 + 0) = 0.511 H2((1,0),(0.8,0.2)) = -(1ln0.8 + 0 ln0.2) ≈ -(-0.223 + 0) = 0.223 因为H1 > H2,所以y2预测更准
在TensorFlow里,已经把这个损失函数封装好,调用方法如下:tf.losses.categorical_crossentropy(y_,y)
代码示例 :
1 2 3 4 5 6 7 8 import tensorflow as tfloss_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)
运行结果 :
softmax与交叉熵结合
我们要求解交叉熵损失函数,需要使得y_和y符合概率分布,这就需要使用softmax函数。
输出先过softmax函数,再计算y与y_的交叉熵损失函数。 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)
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import tensorflow as tfimport numpy as npy_ = 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)
运行结果 :
欠拟合与过拟合 一张图看清:
欠拟合的解决方法 :
过拟合的解决方法 :
正则化缓解过拟合
正则化在损失函数中引入模型复杂度指标,利用给W加权值 ,弱化了训练数据的噪声(一般不正则化b)
计算公式如下 :
正则化的选择 :
L1正则化大概率会使很多参数变为零,因此该方法可通过稀疏参数,即减少参数的数量,降低复杂度 。
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)
不使用L2正则化:
代码示例 : 注:此代码请认真阅读,理解其过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 import tensorflow as tffrom matplotlib import pyplot as pltimport numpy as npimport pandas as pddf = 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_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 ) 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 loss = tf.reduce_mean(tf.square(y_train - y)) 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)) print ("*******predict*******" )xx, yy = np.mgrid[-3 :3 :.1 , -3 :3 :.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 = tf.matmul(h1, w2) + b2 probs.append(y) x1 = x_data[:, 0 ] x2 = x_data[:, 1 ] probs = np.array(probs).reshape(xx.shape) plt.scatter(x1, x2, color=np.squeeze(Y_c)) plt.contour(xx, yy, probs, levels=[.5 ]) plt.show()
运行结果 :
可见,这种情况属于过拟合。
使用L2正则化缓解过拟合
代码示例 : 注:与上大体一致,只是增加了L2正则化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 import tensorflow as tffrom matplotlib import pyplot as pltimport numpy as npimport pandas as pddf = 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_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 ) 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 loss_mse = tf.reduce_mean(tf.square(y_train - y)) 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 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)) print ("*******predict*******" )xx, yy = np.mgrid[-3 :3 :.1 , -3 :3 :.1 ] grid = np.c_[xx.ravel(), yy.ravel()] grid = tf.cast(grid, tf.float32) probs = [] for x_predict in grid: h1 = tf.matmul([x_predict], w1) + b1 h1 = tf.nn.relu(h1) y = tf.matmul(h1, w2) + b2 probs.append(y) x1 = x_data[:, 0 ] x2 = x_data[:, 1 ] probs = np.array(probs).reshape(xx.shape) plt.scatter(x1, x2, color=np.squeeze(Y_c)) plt.contour(xx, yy, probs, levels=[.5 ]) plt.show()
运行结果 :
经过L2正则化后得到正确拟合。
神经网络参数优化器
所谓神经网络优化器就是参数的更新方法。
优化算法可以分成一阶优化和二阶优化算法,其中一阶优化就是指的梯度算法及其变种,而二阶优化一般是用二阶导数(Hessian 矩阵)来计算,如牛顿法,由于需要计算Hessian阵和其逆矩阵,计算量较大,因此没有流行开来。这里主要总结一阶优化 的各种梯度下降方法。
深度学习优化算法经历了SGD
-> SGDM
-> NAG
->AdaGrad
-> AdaDelta
-> Adam
-> Nadam
这样的发展历程。
认识变量
从上图可以看到神经网络参数的更新公式。下面我们介绍的五种优化方法都是依据上述公式,只是mt和vt 定义的数据不同。接下来我们简要介绍一下这5中优化算法。(只是进行一下简要介绍,更加具体的内容留待以后)
SGD方法
无momentum,常用的梯度下降法
求解流程 :
核心代码 : w1.assign_sub(lr * grads[0]) # 参数w1自更新
b1.assign_sub(lr * grads[1]) # 参数b自更新
代码实例 :(以前面的鸢尾花的数据集为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 import tensorflow as tffrom sklearn import datasetsfrom matplotlib import pyplot as pltimport numpy as npimport time x_data = datasets.load_iris().data y_data = datasets.load_iris().target np.random.seed(116 ) 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 ) 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 train_loss_results = [] test_acc = [] epoch = 500 loss_all = 0 now_time = time.time() 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, w1) + b1 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() grads = tape.gradient(loss, [w1, b1]) w1.assign_sub(lr * grads[0 ]) b1.assign_sub(lr * grads[1 ]) print ("Epoch {}, loss: {}" .format (epoch, loss_all / 4 )) train_loss_results.append(loss_all / 4 ) loss_all = 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 ) pred = tf.cast(pred, dtype=y_test.dtype) correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32) correct = tf.reduce_sum(correct) total_correct += int (correct) total_number += x_test.shape[0 ] acc = total_correct / total_number test_acc.append(acc) print ("Test_acc:" , acc) print ("--------------------------" ) total_time = time.time() - now_time print ("total_time" , total_time) plt.title('Loss Function Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Loss' ) plt.plot(train_loss_results, label="$Loss$" ) plt.legend() plt.show() plt.title('Acc Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Acc' ) plt.plot(test_acc, label="$Accuracy$" ) plt.legend() plt.show()
运行结果 : (包括Loss、Acc图像和训练时间)
SGDM
含momentum的SGD,在SGD基础上增加一阶动量。
一阶动量就是指梯度的函数
求解流程 :
核心代码 :
1 2 3 4 5 6 7 8 9 10 # 初始变量的设置 m_w, m_b = 0, 0 beta = 0.9 # grad[0]和grad[1]分别为loss关于w和b的导数。 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)
代码实例 : (将核心代码带入鸢尾花代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 import tensorflow as tffrom sklearn import datasetsfrom matplotlib import pyplot as pltimport numpy as npimport time x_data = datasets.load_iris().data y_data = datasets.load_iris().target np.random.seed(116 ) 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 ) 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 train_loss_results = [] test_acc = [] epoch = 500 loss_all = 0 m_w, m_b = 0 , 0 beta = 0.9 now_time = time.time() 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, w1) + b1 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() grads = tape.gradient(loss, [w1, b1]) 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) print ("Epoch {}, loss: {}" .format (epoch, loss_all / 4 )) train_loss_results.append(loss_all / 4 ) loss_all = 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 ) pred = tf.cast(pred, dtype=y_test.dtype) correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32) correct = tf.reduce_sum(correct) total_correct += int (correct) total_number += x_test.shape[0 ] acc = total_correct / total_number test_acc.append(acc) print ("Test_acc:" , acc) print ("--------------------------" ) total_time = time.time() - now_time print ("total_time" , total_time) plt.title('Loss Function Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Loss' ) plt.plot(train_loss_results, label="$Loss$" ) plt.legend() plt.show() plt.title('Acc Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Acc' ) plt.plot(test_acc, label="$Accuracy$" ) plt.legend() plt.show()
运行结果 : (包括Loss、Acc图像和训练时间)
Adagrad优化器
在SGD基础上增加二阶动量
求解过程 :
核心代码 :
1 2 3 4 5 6 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))
代码实例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 import tensorflow as tffrom sklearn import datasetsfrom matplotlib import pyplot as pltimport numpy as npimport time x_data = datasets.load_iris().data y_data = datasets.load_iris().target np.random.seed(116 ) 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 ) 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 train_loss_results = [] test_acc = [] epoch = 500 loss_all = 0 v_w, v_b = 0 , 0 now_time = time.time() 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, w1) + b1 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() grads = tape.gradient(loss, [w1, b1]) 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)) print ("Epoch {}, loss: {}" .format (epoch, loss_all / 4 )) train_loss_results.append(loss_all / 4 ) loss_all = 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 ) pred = tf.cast(pred, dtype=y_test.dtype) correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32) correct = tf.reduce_sum(correct) total_correct += int (correct) total_number += x_test.shape[0 ] acc = total_correct / total_number test_acc.append(acc) print ("Test_acc:" , acc) print ("--------------------------" ) total_time = time.time() - now_time print ("total_time" , total_time) plt.title('Loss Function Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Loss' ) plt.plot(train_loss_results, label="$Loss$" ) plt.legend() plt.show() plt.title('Acc Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Acc' ) plt.plot(test_acc, label="$Accuracy$" ) plt.legend() plt.show()
运行结果 :
RMSProp优化器
SGD基础上增加二阶动量
求解过程 :
核心代码 :
1 2 3 4 5 6 7 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))
代码实例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 import tensorflow as tffrom sklearn import datasetsfrom matplotlib import pyplot as pltimport numpy as npimport time x_data = datasets.load_iris().data y_data = datasets.load_iris().target np.random.seed(116 ) 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 ) 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 train_loss_results = [] test_acc = [] epoch = 500 loss_all = 0 v_w, v_b = 0 , 0 beta = 0.9 now_time = time.time() 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, w1) + b1 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() grads = tape.gradient(loss, [w1, b1]) 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)) print ("Epoch {}, loss: {}" .format (epoch, loss_all / 4 )) train_loss_results.append(loss_all / 4 ) loss_all = 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 ) pred = tf.cast(pred, dtype=y_test.dtype) correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32) correct = tf.reduce_sum(correct) total_correct += int (correct) total_number += x_test.shape[0 ] acc = total_correct / total_number test_acc.append(acc) print ("Test_acc:" , acc) print ("--------------------------" ) total_time = time.time() - now_time print ("total_time" , total_time) plt.title('Loss Function Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Loss' ) plt.plot(train_loss_results, label="$Loss$" ) plt.legend() plt.show() plt.title('Acc Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Acc' ) plt.plot(test_acc, label="$Accuracy$" ) plt.legend() plt.show()
运行结果 :
Adam优化器
同时结合SGDM一阶动量和RMSProp二阶动量
求解过程 :
核心代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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))
代码实例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 import tensorflow as tffrom sklearn import datasetsfrom matplotlib import pyplot as pltimport numpy as npimport time x_data = datasets.load_iris().data y_data = datasets.load_iris().target np.random.seed(116 ) 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 ) 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 train_loss_results = [] test_acc = [] epoch = 500 loss_all = 0 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() for epoch in range (epoch): for step, (x_train, y_train) in enumerate (train_db): global_step += 1 with tf.GradientTape() as tape: y = tf.matmul(x_train, w1) + b1 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() grads = tape.gradient(loss, [w1, b1]) 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)) print ("Epoch {}, loss: {}" .format (epoch, loss_all / 4 )) train_loss_results.append(loss_all / 4 ) loss_all = 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 ) pred = tf.cast(pred, dtype=y_test.dtype) correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32) correct = tf.reduce_sum(correct) total_correct += int (correct) total_number += x_test.shape[0 ] acc = total_correct / total_number test_acc.append(acc) print ("Test_acc:" , acc) print ("--------------------------" ) total_time = time.time() - now_time print ("total_time" , total_time) plt.title('Loss Function Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Loss' ) plt.plot(train_loss_results, label="$Loss$" ) plt.legend() plt.show() plt.title('Acc Curve' ) plt.xlabel('Epoch' ) plt.ylabel('Acc' ) plt.plot(test_acc, label="$Accuracy$" ) plt.legend() plt.show()
运行结果 :
优化算法的选择 前面我们介绍了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
。
算法没有好坏,最适合数据的才是最好的,永远记住:No free lunch theorem 。
优化算法的常用tricks
首先,各大算法孰优孰劣并无定论。如果是刚入门,优先考虑SGD+Nesterov Momentum或者Adam.
选择你熟悉的算法——这样你可以更加熟练地利用你的经验进行调参。
充分了解你的数据——如果模型是非常稀疏 的,那么优先考虑自适应学习率 的算法。
根据你的需求来选择——在模型设计实验过程中,要快速验证新模型的效果,可以先用Adam 进行快速实验优化 ;在模型上线或者结果发布前,可以用精调的SGD 进行模型的极致优化 。
先用小数据集 进行实验。有论文研究指出,随机梯度下降算法的收敛速度和数据集的大小的关系不大。因此可以先用一个具有代表性 的小数据集进行实验,测试一下最好的优化算法,并通过参数搜索 来寻找最优的训练参数。
考虑不同算法的组合 。先用Adam进行快速下降,而后再换到SGD进行充分的调优。
充分打乱数据集 (shuffle)。这样在使用自适应学习率算法的时候,可以避免某些特征集中出现,而导致的有时学习过度、有时学习不足,使得下降方向出现偏差的问题。在每一轮迭代 后对训练数据打乱是一个不错的主意。
训练过程中持续监控 训练数据和验证数据上的目标函数值以及精度或者AUC等指标的变化情况。对训练数据的监控是要保证模型进行了充分的训练——下降方向正确,且学习率足够高;对验证数据的监控是为了避免出现过拟合。
制定一个合适的学习率衰减策略 。可以使用分段常数衰减策略 ,比如每过多少个epoch就衰减一次;或者利用精度或者AUC等性能指标来监控,当测试集上的指标不变或者下跌时,就降低学习率。
Early stopping 。如Geoff Hinton所说:“Early Stopping是美好的免费午餐”。你因此必须在训练的过程中时常在验证集上监测误差,在验证集 上如果损失函数不再显著地降低,那么应该提前结束训练 。
算法参数的初始值选择 。 初始值不同,获得的最小值也有可能不同,因此梯度下降求得的只是局部最小值;当然如果损失函数是凸函数则一定是最优解。由于有局部最优解的风险,需要多次用不同初始值运行算法,关键损失函数的最小值,选择损失函数最小化的初值。
写在最后:至此已经完成ch2的学习,但是也只是跟着课程过了一遍而已,想要融会贯通还要大量练习。 在本章学了神经网络的优化过程,对前向传播、激活函数、学习率、正则化、损失函数、优化算法等方面有了系统的认识,对如何选取合适的方法有了初步了解。 在笔记里有一个常用TensorFlow API及代码实现的附录,要多看看。