0-5.优化算法

mac2025-07-08  7

训练集和测试集:GitHub上的Desktop.rar中GitHub链接

本文内容有:(完整代码在文章底部Optimization_Adam.py)

1.mini_batch梯度下降
2.指数加权平均数(及其偏差修正)
3.动量梯度下降法(gradient descent with Momentum)
4.均方根梯度下降法(RMSprop,全称root mean square prop)
5.Adam优化算法
6.学习率衰减和局部最优问题

注:这些内容只是简要概括,具体细节可以去观看视频。

一.mini_batch梯度下降: 将一个庞大的训练集均等划分多个小型的训练集(也就是mini_batch),然后对这些mini_batch分开进行训练(和前文中一样)。这样可以提升算法效率以及防止神经网络喂入数据过多,导致神经网络被”噎到“。

1.例如:对于500万张图片的训练集,我们mini_batch可以选取为1000,这样就将训练集均分成了5000个且每一个都有1000张图片的mini_batch训练集。对这些mini_batch进行前文中的运算就可以了,只需要在外面再加一个for循环遍历这5000个mini_batch。 2.如果训练集较小,直接使用 batch 梯度下降法,样本集较小就没必要使用 mini-batch 梯度下降法,你可以快速处理整个训练集,这里的小是说小于 2000 个样本,这样比较适合使用 batch 梯度下降法。样本数目较大的话,一般的 mini-batch 大小为 64 到 512,考虑到电脑内存设置和使用的方式,如果 mini-batch 大小是 2 的n次方,代码会运行地快一些。

二.指数加权平均数(及其偏差修正):

1.加权平均数:y = (数据1 * 权值1+数据2 * 权值2…数据n*权值n)/权值之和
2.指数加权平均数: V t = β V t − 1 + ( 1 − β ) Θ t V_{t}= \beta V_{t-1} + (1 - \beta )\Theta _{t} Vt=βVt1+(1β)Θt
3.偏差修正: V t = V t 1 − β t V_{t}=\frac{V_{t}}{1-\beta^{t} } Vt=1βtVt

注: 1.求指数加权平均数是一个迭代过程,迭代个数取决于β 2.表示求 1 1 − β \frac{1}{1-\beta } 1β1的平均值,实际过程中经常跳过偏差修正部分,β取值通常取0.9,表示前10次的平均值。

三.动量梯度下降法(gradient descent with Momentum):将成本函数J变化图像假想为一个平面同心圆,随着训练轮数增加,J逐渐接近最小值。使用momentum后,可以理解为对纵轴指数加权平均,取平均值正负相抵消,是个很小的值;对横轴取平均值后,横轴是训练轮数,平均值是越来越大的。这样纵轴摆动不会过大,可以使用较大学习率;横轴平均值越来越大,可以加快后期训练,最少不会减缓训练。

公式:

V d w = β V d w + ( 1 − β ) d w V_{dw}= \beta V_{dw} + (1 - \beta )dw Vdw=βVdw+(1β)dw V d b = β V d b + ( 1 − β ) d b V_{db}= \beta V_{db} + (1 - \beta )db Vdb=βVdb+(1β)db W = W − α V d w , b = b − α V d b W = W - αV_{dw},b = b - αV_{db} W=WαVdw,b=bαVdb

代码:

def back_momentum(w,b,X,Y,learning,m,L,lambd,beta,Vdw,Vdb):#使用momentum梯度下降的反向传播 dw, db, J = backward(w,b,X,Y,m,L,lambd) # 不使用偏差修正不影响最后结果 for i in range(0,L):#注意dw和db是由后往前存的,位置与w和b相反 Vdw[i] = beta * Vdw[i] + (1 - beta) * dw[L-i-1] Vdb[i] = beta * Vdb[i] + (1 - beta) * db[L-i-1] w[i] = w[i] - learning * Vdw[i] b[i] = b[i] - learning * Vdb[i]

运行结果:

四.均方根梯度下降法(RMSprop,全称root mean square prop):原理与动量梯度下降法类似。

公式:

S d w = β V d w + ( 1 − β ) d w 2 S_{dw}= \beta V_{dw} + (1 - \beta )dw^{2} Sdw=βVdw+(1β)dw2 S d b = β V d b + ( 1 − β ) d b 2 S_{db}= \beta V_{db} + (1 - \beta )db^{2} Sdb=βVdb+(1β)db2 W = W − α d w S d w , b = b − α d b S d b W = W - α{\frac{dw}{\sqrt{S_{dw}}}},b = b - α{\frac{db}{\sqrt{S_{db}}}} W=WαSdw dw,b=bαSdb db

代码:

def back_RMSprop(w,b,X,Y,learning,m,L,lambd,beta,Sdw,Sdb):#使用RMSprop梯度下降的反向传播 dw, db, J = backward(w,b,X,Y,m,L,lambd) # 不使用偏差修正不影响最后结果 for i in range(0,L):#注意dw和db是由后往前存的,位置与w和b相反 Sdw[i] = beta * Sdw[i] + (1 - beta) * np.power(dw[L-i-1],2) Sdb[i] = beta * Sdb[i] + (1 - beta) * np.power(db[L-i-1],2) w[i] = w[i] - learning * dw[L-i-1] / (np.power(Sdw[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大 b[i] = b[i] - learning * db[L-i-1] / (np.power(Sdb[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大 return w,b,J,Sdw,Sdb

运行结果:

五.Adam优化算法:动量梯度下降法和RMSprop梯度下降法的结合,也是最常用的。

公式:

V d w = β V d w + ( 1 − β ) d w V_{dw}= \beta V_{dw} + (1 - \beta )dw Vdw=βVdw+(1β)dw V d b = β V d b + ( 1 − β ) d b V_{db}= \beta V_{db} + (1 - \beta )db Vdb=βVdb+(1β)db S d w = β V d w + ( 1 − β ) d w 2 S_{dw}= \beta V_{dw} + (1 - \beta )dw^{2} Sdw=βVdw+(1β)dw2 S d b = β V d b + ( 1 − β ) d b 2 S_{db}= \beta V_{db} + (1 - \beta )db^{2} Sdb=βVdb+(1β)db2 V d w = V d w 1 − β t V_{dw}=\frac{V_{dw}}{1-\beta^{t} } Vdw=1βtVdw V d b = V d b 1 − β t V_{db}=\frac{V_{db}}{1-\beta^{t} } Vdb=1βtVdb S d w = S d w 1 − β t S_{dw}=\frac{S_{dw}}{1-\beta^{t} } Sdw=1βtSdw S d b = S d b 1 − β t S_{db}=\frac{S_{db}}{1-\beta^{t} } Sdb=1βtSdb W = W − α V d w S d w + ε , b = b − α V d b S d b + ε W = W - α{\frac{Vdw}{\sqrt{S_{dw}}+\varepsilon }},b = b - α{\frac{Vdb}{\sqrt{S_{db}}+\varepsilon}} W=WαSdw +εVdw,b=bαSdb +εVdb

代码:

def back_Adam(w,b,X,Y,learning,m,L,lambd,beta,Vdw,Vdb,Sdw,Sdb,t):#使用Adam梯度下降的反向传播 dw, db, J = backward(w,b,X,Y,m,L,lambd) # 通常使用偏差修正 for i in range(0,L):#注意dw和db是由后往前存的,位置与w和b相反 Vdw[i] = beta * Vdw[i] + (1 - beta) * dw[L - i - 1] Vdb[i] = beta * Vdb[i] + (1 - beta) * db[L - i - 1] Sdw[i] = beta * Sdw[i] + (1 - beta) * np.power(dw[L-i-1],2) Sdb[i] = beta * Sdb[i] + (1 - beta) * np.power(db[L-i-1],2) ''' Vdw[i] /= 1 - np.power(beta, t)#t表示代数(所有mini_batch训练一次称为一代) Vdb[i] /= 1 - np.power(beta, t) Sdw[i] /= 1 - np.power(beta, t) Sdb[i] /= 1 - np.power(beta, t) ''' w[i] = w[i] - learning * Vdw[i] / (np.power(Sdw[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大 b[i] = b[i] - learning * Vdb[i] / (np.power(Sdb[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大

运行结果:

观察上述运行结果,训练轮数比之不使用优化算法的代码更少了,很明显算法是有效的。后期训练更快,可以使用较大学习率。

六.学习率衰减和局部最优问题:

1.由于使用学习率是固定值,因此在迭代过程中会有噪音(蓝色线),下降朝向中间的最小值,但是不会精确地收敛,所以你的算法最后在附近摆动,并不会真正收敛。
2.但要慢慢减少学习率𝑎的话,在初期的时候,𝑎学习率还较大,你的学习还是相对较快,但随着𝑎变小,你的步伐也会变慢变小,所以最后你的曲线(绿色线)会在最小值附近的一小块区域里摆动,而不是在训练过程中,大幅度在最小值附近摆动。所以慢慢减少𝑎的本质在于,在学习初期,你能承受较大的步伐,但当开始收敛的时候,小一些的学习率能让你步伐小一些。

减小学习率的几种方法:

第 一 种 : a = 1 1 + 代 数 ∗ 学 习 率 衰 减 率 a 0 第一种:a = \frac{1}{1 + 代数*学习率衰减率}a_{0} a=1+1a0

第 二 种 ( 指 数 衰 减 ) : a = k 代 数 a 0 , 例 如 a = 0.9 5 代 数 a 0 第二种(指数衰减):a = k^{代数}a_{0},例如a = 0.95^{代数}a_{0} a=ka0a=0.95a0

第 三 种 : a = k 代 数 a 0 第三种:a = \frac{k}{\sqrt{代数}}a_{0} a= ka0

第 四 种 ( 离 散 下 降 ) : 每 训 练 时 间 t 分 钟 后 减 少 一 半 , 不 断 减 小 第四种(离散下降):每训练时间t分钟后减少一半,不断减小 t

我们来看一下运行结果:下面这张图片是不使用衰减学习率的运行结果,后期损失函数(也就是成本函数J)在最小值附近摆动,一下上升,一下下降。

下面这张是使用学习率衰减的运行结果:没有在出现摆动情况。学习率,衰减率,训练轮数,正则化参数,神经网络层数,节点个数,以及使用不同的算法,这些超参数要多调,运行结果也不一样,希望你能找到比我这个更合适超参数。可是会发现测试准确率无法达到90%以上,我认为这是训练集太少的原因,后面我们会用mnist上面的图片进行训练,可以解决这个问题。

完整代码:Optimization_Adam.py

# conding:utf-8 import numpy as np import matplotlib.pyplot as plt import h5py def load_dataset(): train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r") train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # 保存的是训练集里面的图像数据(本训练集有209张64x64的图像)。 train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # 保存的是训练集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。 test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r") test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # 保存的是测试集里面的图像数据(本训练集有50张64x64的图像)。 test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # 保存的是测试集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。 classes = np.array(test_dataset["list_classes"][:]) # 保存的是以bytes类型保存的两个字符串数据,数据为:[b’non-cat’ b’cat’]。 train_set_y = train_set_y_orig.reshape((1, train_set_y_orig.shape[0])) test_set_y = test_set_y_orig.reshape((1, test_set_y_orig.shape[0])) print("训练集_图片的维数 : " + str(train_set_x_orig.shape)) print("训练集_标签的维数 : " + str(train_set_y.shape)) print("测试集_图片的维数: " + str(test_set_x_orig.shape)) print("测试集_标签的维数: " + str(test_set_y.shape)) print() return train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes def tanh(z):#tanh函数 return (np.exp(z)-np.exp(-z))/(np.exp(z)+np.exp(-z)) def relu(z):#relu函数 return np.maximum(0,z) def tanh_1(z):#tanh函数的导数 return 1-tanh(z)**2 def relu_1(z):#relu函数的导数 return np.maximum(0, z/np.abs(z)) def sigmoid(z): return 1/(1+np.exp(-z)) def ward(L,n,m,dim):#对参数进行初始化 np.random.seed(1) w = [] b = [] Vdw, Vdb, Sdw, Sdb = [],[],[],[]#优化算法的值 for i in range(0, L): if i != 0 and i != L - 1: #p = np.random.randn(dim, dim) *0.001 p = np.random.randn(dim, dim) * np.sqrt(2 / dim) elif i == 0: #p = np.random.randn(dim, m) * 0.001 p = np.random.randn(dim, m) * np.sqrt(2 / m) else: #p = np.random.randn(1, dim) * 0.001 p = np.random.randn(1, dim) * np.sqrt(2 / dim) w.append(p) b.append(1) Vdw.append(0)#初始化为0 Vdb.append(0) Sdw.append(0) Sdb.append(0) return w, b,Vdw,Vdb,Sdw,Sdb def forward(w,b,a,Y,L,m,lambd):#前向传播 z = [] J = 0 add = 0 for i in range(0, L): zl = np.dot(w[i], a) + b[i] add += np.sum((lambd / (2 * m)) * np.dot(w[i], w[i].T))#L2正则化项 z.append(zl) a = relu(zl) a = sigmoid(zl) J = (-1/m)*np.sum(1 * Y * np.log(a) + (1 - Y) * np.log(1 - a)) + add # 损失函数 return z, a, J def backward(w,b,X,Y,m,L,lambd):#反向传播 z,a,J = forward(w,b,X,Y,L,m,lambd) dw,db = [],[] for i in range(L - 1, 0, -1): if i == L - 1: dz = a - Y else: dz = np.dot(w[i + 1].T, dz) * relu_1(z[i]) Dw = 1 / m * (np.dot(dz, relu(z[i - 1]).T)) + (lambd / m) * w[i] Db = 1 / m * np.sum(dz, axis=1, keepdims=True) dw.append(Dw) db.append(Db) dz = np.dot(w[1].T, dz) * relu_1(z[0]) Dw = 1 / m * np.dot(dz, X.T) + (lambd / m) * w[0] Db = 1 / m * np.sum(dz, axis=1, keepdims=True) dw.append(Dw) db.append(Db) return dw, db, J def back_momentum(w,b,X,Y,learning,m,L,lambd,beta,Vdw,Vdb):#使用momentum梯度下降的反向传播 dw, db, J = backward(w,b,X,Y,m,L,lambd) # 不使用偏差修正不影响最后结果 for i in range(0,L):#注意dw和db是由后往前存的,位置与w和b相反 Vdw[i] = beta * Vdw[i] + (1 - beta) * dw[L-i-1] Vdb[i] = beta * Vdb[i] + (1 - beta) * db[L-i-1] w[i] = w[i] - learning * Vdw[i] b[i] = b[i] - learning * Vdb[i] return w,b,J,Vdw,Vdb def back_RMSprop(w,b,X,Y,learning,m,L,lambd,beta,Sdw,Sdb):#使用RMSprop梯度下降的反向传播 dw, db, J = backward(w,b,X,Y,m,L,lambd) # 不使用偏差修正不影响最后结果 for i in range(0,L):#注意dw和db是由后往前存的,位置与w和b相反 Sdw[i] = beta * Sdw[i] + (1 - beta) * np.power(dw[L-i-1],2) Sdb[i] = beta * Sdb[i] + (1 - beta) * np.power(db[L-i-1],2) w[i] = w[i] - learning * dw[L-i-1] / (np.power(Sdw[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大 b[i] = b[i] - learning * db[L-i-1] / (np.power(Sdb[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大 return w,b,J,Sdw,Sdb def back_Adam(w,b,X,Y,learning,m,L,lambd,beta,Vdw,Vdb,Sdw,Sdb):#使用Adam梯度下降的反向传播 dw, db, J = backward(w,b,X,Y,m,L,lambd) # 通常使用偏差修正 for i in range(0,L):#注意dw和db是由后往前存的,位置与w和b相反 Vdw[i] = beta * Vdw[i] + (1 - beta) * dw[L - i - 1] Vdb[i] = beta * Vdb[i] + (1 - beta) * db[L - i - 1] Sdw[i] = beta * Sdw[i] + (1 - beta) * np.power(dw[L-i-1],2) Sdb[i] = beta * Sdb[i] + (1 - beta) * np.power(db[L-i-1],2) ''' Vdw[i] /= 1 - np.power(beta, t)#t表示代数(所有mini_batch训练一次称为一代) Vdb[i] /= 1 - np.power(beta, t) Sdw[i] /= 1 - np.power(beta, t) Sdb[i] /= 1 - np.power(beta, t) ''' w[i] = w[i] - learning * Vdw[i] / (np.power(Sdw[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大 b[i] = b[i] - learning * Vdb[i] / (np.power(Sdb[i],1/2) + 0.00000001)#加上一个较小的值,防止整个值变成无穷大 return w,b,J,Vdw,Vdb,Sdw,Sdb def train(x,y,w,b,L,m,lambd):#查看训练集准确率 A = np.zeros(shape=(1, x.shape[1])) z, a,J = forward(w, b, x, y,L,m,lambd) for i in range(x.shape[1]): # A.append(0 if a[0,i] <0.5 else 1) A[0, i] = 0 if a[0, i] < 0.5 else 1 lop = 100 * (1 - np.mean(np.abs(y - A))) print("训练集准确性:{0}%".format(lop)) return 0 def text(x,y,w,b,L,m,lambd):#查看测试集准确率 A = np.zeros(shape=(1, x.shape[1])) z, a,J = forward(w, b, x, y, L, m,lambd) for i in range(x.shape[1]): # A.append(0 if a[0,i] <0.5 else 1) A[0, i] = 0 if a[0, i] < 0.5 else 1 lop = 100 * (1 - np.mean(np.abs(y - A))) print("测试集准确性:{0}%".format(lop)) return 0 if __name__ == "__main__": L = 10#神经网络层数 dim = 10#隐藏层节点个数 learning_rate = 0.01#学习率 loss= []#损失函数 lambd = 0.01 # L2正则化参数 beta = 0.9#β值;1/(1-β)表示平均前多少轮的指数加权平均数 decay_rate = 0.00009#学习率衰减率 mini_batch = 209#由于训练集合太小,我们设置的这个刚好是训练集合图片张数,准确率也不高 train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset() train_set_x = train_set_x_orig.reshape((train_set_x_orig.shape[0], -1)).T / 255 # 降维,化为区间(0,1)内的数 test_set_x = test_set_x_orig.reshape((test_set_x_orig.shape[0], -1)).T / 255 # 降维,化为区间(0,1)内的数 print("训练集降维后的维度: " + str(train_set_x.shape)) print("训练集_标签的维数 : " + str(train_set_y.shape)) print("测试集降维后的维度: " + str(test_set_x.shape)) print("测试集_标签的维数 : " + str(test_set_y.shape)) print() w,b,Vdw,Vdb,Sdw,Sdb = ward(L,train_set_x.shape[1],train_set_x.shape[0],dim)#vdw表示momentum,Sdw表示RMSprop for i in range(0,400): for j in range(0,(train_set_x.shape[1]//mini_batch)):#j表示第几个mini_batch #w,b,J,Vdw,Vdb = back_momentum(w,b,train_set_x,train_set_y,learning_rate,train_set_x.shape[1],L,lambd,beita,Vdw,Vdb) #w,b,J,Vdw,Vdb = back_RMSprop(w, b, train_set_x,train_set_y,learning_rate,train_set_x.shape[1],L,lambd,beita, Sdw, Sdb) w,b,J,Vdw,Vdb,Sdw,Sdb = back_Adam(w,b,((train_set_x.T)[j*mini_batch:(j+1)*mini_batch]).T,train_set_y[0][j*mini_batch:(j+1)*mini_batch],learning_rate,mini_batch,L,lambd,beta,Vdw,Vdb, Sdw, Sdb) learning_rate = learning_rate * (1 / (1 + i*decay_rate) )#使用学习率衰减 if i % 50 == 0: print("loss:",J) loss.append(J) plt.plot(loss)#打印损失函数 plt.show() train(train_set_x,train_set_y,w,b,L,train_set_x.shape[1],lambd) text(test_set_x,test_set_y,w,b,L,test_set_x.shape[1],lambd)
最新回复(0)