训练集和测试集: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=βVt−1+(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=k代数a0,例如a=0.95代数a0
第
三
种
:
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)