神经网络之反向传播算法(自适应矩估计算法Adam变形形式之Adamax、Nadam)
文章目录
自适应矩估计算法(Adam)的两种变形形式
自适应矩估计算法的基础上还进一步提出了两种变形,一种变形的英文名称为Adamax,另一种变形的英文名称为Nadam。
自适应矩估计算法原理参考:
神经网络之反向传播算法(自适应矩估计算法Adam)
1、Adamax算法原理
Adamax主要对最后参数的更新量计算公式进行了调整,原式中的分母部分为对二阶矩估计进行如下开方:
而Adamax采用了下列公式进行该部分的确定:
ρ_2为衰减率,g_t为当前第t次迭代时的梯度值。
梯度g的计算原理参考:
神经网络之反向传播算法(梯度、误差反向传播算法BP)
之后采用下列公式对参数的调整量进行计算:
此算法的主要特点在于为学习率提供了一个更加简单的上限范围。
2、Nadam算法原理
Nadam相对Adamax来说更加简单,此算法相当于将Nestrov动量法的临时梯度思想引入了自适应矩估计算法之中,在每次对梯度进行计算时先获得一个参数临时更新量,对参数进行临时更新后计算获得临时梯度,利用临时梯度对一阶矩与二阶矩进行估计,并使用临时一阶矩与二阶矩来计算参数的更新量。
Nestrov动量法原理参考:
神经网络之反向传播算法(加入Nesterov动量的误差反向传播算法)
3、算法实现
以数据预测为例,下面分别介绍Adamax与Nadam的实现过程,将Adamax与Nadam算法分别应用于普通的三层神经网络(输入层、隐含层、输出层)的反向传播过程。
选用某省市的表层土壤重金属元素数据集作为实验数据,该数据集总共96组,随机选择其中的24组作为测试数据集,72组作为训练数据集。选取重金属Ti的含量作为待预测的输出特征,选取重金属Co、Cr、Mg、Pb作为模型的输入特征。
3.1 Adamax训练过程
#库的导入
import numpy as np
import pandas as pd#激活函数tanh
def tanh(x):return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
#激活函数偏导数
def de_tanh(x):return (1-x2)#用于计算一阶矩估计与二阶矩估计的函数,其中s为一阶矩估计,r为二阶矩估计,delta为梯度值,
#,0.9和0.999分别表示两个衰减系数,0.1为1-衰减系数的计算结果
def accumulation(s,r,delta):s = 0.9 * s + 0.1 * deltar = max((0.999*r),abs(delta))return s,r
#参数更新函数,w为待更新参数,s与r分别为修正后的一阶矩估计和二阶矩估计
def adjust(s,r,w):change1 =r + 0.000001change2 =s/change1change = (-0.001)*change2w = w + changereturn wmaxepochs = 1000 #迭代训练次数
errorfinal = 0.65*10(-3) #停止训练误差阈值
samnum = 72 #输入数据数量
indim = 4 #输入层节点数
outdim = 1 #输出层节点数
hiddenunitnum = 8 #隐含层节点数#输入数据的导入
df = pd.read_csv("train.csv")
df.columns = ["Co", "Cr", "Mg", "Pb", "Ti"]
Co = df["Co"]
Co = np.array(Co)
Cr = df["Cr"]
Cr = np.array(Cr)
Mg=df["Mg"]
Mg=np.array(Mg)
Pb = df["Pb"]
Pb =np.array(Pb)
Ti = df["Ti"]
Ti = np.array(Ti)
samplein = np.mat([Co,Cr,Mg,Pb])
sampleout = np.mat([Ti])
#数据归一化,将输入数据压缩至0到1之间,便于计算,后续通过反归一化恢复原始值
sampleinminmax = np.array([samplein.min(axis=1).T.tolist()[0],samplein.max(axis=1).T.tolist()[0]]).transpose()
sampleoutminmax = np.array([sampleout.min(axis=1).T.tolist()[0],sampleout.max(axis=1).T.tolist()[0]]).transpose()
sampleinnorm = (2*(np.array(samplein.T)-sampleinminmax.transpose()[0])/(sampleinminmax.transpose()[1]-sampleinminmax.transpose()[0])-1).transpose()
sampleoutnorm = (2*(np.array(sampleout.T)-sampleoutminmax.transpose()[0])/(sampleoutminmax.transpose()[1]-sampleoutminmax.transpose()[0])-1).transpose()
sampleinmax = np.array([sampleinnorm.max(axis=1).T.tolist()]).transpose()
sampleinmin = np.array([sampleinnorm.min(axis=1).T.tolist()]).transpose()
#为归一化后的数据添加噪声
noise = 0.03*np.random.rand(sampleoutnorm.shape[0],sampleoutnorm.shape[1])
sampleoutnorm += noise
sampleinnorm = np.mat(sampleinnorm)#利用归一化后的输入数据初始化参数w1、b1、w2、b2
dvalue = sampleinmax-sampleinmin
valuemid=(sampleinmin+sampleinmax)/2
wmag=0.7*(hiddenunitnum(1/indim))
rand1=np.random.rand(hiddenunitnum,outdim)
rand2=np.random.randn(hiddenunitnum,indim)
rand1=rand1*wmag
rand2=rand2*wmag
b1=rand1-np.dot(rand2,valuemid)
for i in range(hiddenunitnum):for j in range(indim):rand2[i][j]=(2*rand2[i][j])/dvalue[j]
w1=rand2
w2 = np.random.uniform(low=-1, high=1, size=[outdim,hiddenunitnum])
b2 = np.random.uniform(low=-1, high=1, size=[outdim,1])#参数w1、b1、w2、b2均为矩阵形式参与计算,其形状依次为8*4,8*1,1*8,1*1
w1 = np.mat(w1)
b1 = np.mat(b1)
w2 = np.mat(w2)
b2 = np.mat(b2)
#errhistory存储每次训练后的预测值与真实值的误差
errhistory = []#sw1、sb1,sw2,sb2分别保存参数w1、b1、w2、b2的一阶矩估计,其形状与w1、b1、w2、b2一一对应
sw2 = np.zeros((1,8))
sb2 = np.zeros((1,1))
sw1 = np.zeros((8,4))
sb1 = np.zeros((8,1))#rw1、rb1,rw2,rb2分别保存参数w1、b1、w2、b2的二阶矩估计,其形状与w1、b1、w2、b2一一对应
rw2 = np.zeros((1,8))
rb2 = np.zeros((1,1))
rw1 = np.zeros((8,4))
rb1 = np.zeros((8,1))#t用于对一阶矩估计和二阶矩估计进行修正,随训练次数不断累加
t = 0
for i in range(maxepochs):t = t + 1#前向传播#计算隐含层输出hiddenout,输出层输出networkouthiddenout = tanh((np.dot(w1,sampleinnorm).transpose()+b1.transpose())).transpose()networkout = np.dot(w2,hiddenout).transpose()+b2.transpose()for j in range(samnum):networkout[j,:] = tanh(networkout[j,:])networkout = networkout.transpose()#计算损失函数err = sampleoutnorm - networkoutloss = np.sum(np.abs(err))/samnumsse = np.sum(np.square(err))#判断是否满足停止训练条件errhistory.append(sse)if sse < errorfinal:break#反向传播#利用损失函数计算结果和激活函数偏导数,来计算参数w1、b1、w2、b2的梯度值delta2 = np.zeros((outdim,samnum))for n in range(samnum):delta2[:,n] = (-1) * err[:,n] * de_tanh(networkout[:,n])delta1 = np.zeros((hiddenunitnum,samnum))for e in range(samnum):for f in range(hiddenunitnum):delta1[f,e] = w2[:,f] * delta2[:,e] * de_tanh(hiddenout[f,e])dw2now = np.dot(delta2,hiddenout.transpose()) #1*8db2now = np.dot(delta2,np.ones((samnum,1))) #1*1dw1now = np.dot(delta1,sampleinnorm.transpose()) #8*4db1now = np.dot(delta1,np.ones((samnum,1))) #8*1#先更新输出层参数#w2更新for m in range(hiddenunitnum):#计算一阶矩估计sw2和二阶矩估计rw2sw2[:, m],rw2[:,m] = accumulation(sw2[:,m],rw2[:,m],dw2now[:,m])#使用t值对一阶矩估计进行修正saw2 = sw2[:,m] / (1 - (0.9t))#利用修正后的一阶矩估计和二阶矩估计对w2进行更新w2[:,m] = adjust(saw2,rw2[:,m],w2[:,m])#b2更新#计算一阶矩估计sb2和二阶矩估计rb2sb2,rb2 = accumulation(sb2,rb2,db2now)#使用t值对一阶矩估计进行修正sab2 = sb2/(1 - (0.9t))#利用修正后的一阶矩估计和二阶矩估计对b2进行更新b2 = adjust(sab2, rb2, b2)#更新隐含层参数#w1更新#计算一阶矩估计sw1和二阶矩估计rw1for a in range(hiddenunitnum):for b in range(indim):sw1[a,b],rw1[a,b] = accumulation(sw1[a,b],rw1[a,b],dw1now[a,b])#使用t值对一阶矩估计进行修正saw1 = sw1[a,b]/(1 - (0.9t))#利用修正后的一阶矩估计和二阶矩估计对w1进行更新w1[a,b] = adjust(saw1,rw1[a,b],w1[a,b])#b1更新#计算一阶矩估计sb1和二阶矩估计rb1for n in range(hiddenunitnum):sb1[n,:],rb1[n,:] = accumulation(sb1[n,:],rb1[n,:],db1now[n,:])#使用t值对一阶矩估计进行修正sab1 = sb1[n,:]/(1 - (0.9t))#利用修正后的一阶矩估计和二阶矩估计对b1进行更新b1[n,:] = adjust(sab1,rb1[n,:],b1[n,:])print("the generation is:",i,",the loss is:",loss)#达到最大训练次数,保存此时的参数w1、b1、w2、b2
np.save("w1.npy",w1)
np.save("b1.npy",b1)
np.save("w2.npy",w2)
np.save("b2.npy",b2)
3.2 Adamax测试过程及结果
测试过程只需要利用训练过程生成的相关参数,对测试数据执行一次前向传播过程来获得预测值,之后可使用相关的误差指标对预测值进行评价,详细的测试过程源码见参考源码及数据集。
3.3 Nadam训练过程
#库的导入
import numpy as np
import pandas as pd#激活函数tanh
def tanh(x):return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
#激活函数偏导数
def de_tanh(x):return (1-x2)#用于计算一阶矩估计与二阶矩估计的函数,其中s为一阶矩估计,r为二阶矩估计,delta为梯度值,
#,0.9和0.999分别表示两个衰减系数,0.1和0.001均为1-衰减系数的计算结果
def accumulation(s,r,delta):s = 0.9 * s + 0.1 * deltar = 0.999 * r + 0.001 * (delta2)return s,r
#参数更新量计算函数,w为待更新参数,s与r分别为修正后的一阶矩估计和二阶矩估计
def adjust(s,r):change1 =r0.5 + 0.000001change2 =s/change1#0.001为学习率change = (-0.001)*change2return changemaxepochs = 1000 #迭代训练次数
errorfinal = 0.65*10(-3) #停止训练误差阈值
samnum = 72 #输入数据数量
indim = 4 #输入层节点数
outdim = 1 #输出层节点数
hiddenunitnum = 8 #隐含层节点数#输入数据的导入
df = pd.read_csv("train.csv")
df.columns = ["Co", "Cr", "Mg", "Pb", "Ti"]
Co = df["Co"]
Co = np.array(Co)
Cr = df["Cr"]
Cr = np.array(Cr)
Mg=df["Mg"]
Mg=np.array(Mg)
Pb = df["Pb"]
Pb =np.array(Pb)
Ti = df["Ti"]
Ti = np.array(Ti)
samplein = np.mat([Co,Cr,Mg,Pb])
sampleout = np.mat([Ti])
#数据归一化,将输入数据压缩至0到1之间,便于计算,后续通过反归一化恢复原始值
sampleinminmax = np.array([samplein.min(axis=1).T.tolist()[0],samplein.max(axis=1).T.tolist()[0]]).transpose()
sampleoutminmax = np.array([sampleout.min(axis=1).T.tolist()[0],sampleout.max(axis=1).T.tolist()[0]]).transpose()
sampleinnorm = (2*(np.array(samplein.T)-sampleinminmax.transpose()[0])/(sampleinminmax.transpose()[1]-sampleinminmax.transpose()[0])-1).transpose()
sampleoutnorm = (2*(np.array(sampleout.T)-sampleoutminmax.transpose()[0])/(sampleoutminmax.transpose()[1]-sampleoutminmax.transpose()[0])-1).transpose()
sampleinmax = np.array([sampleinnorm.max(axis=1).T.tolist()]).transpose()
sampleinmin = np.array([sampleinnorm.min(axis=1).T.tolist()]).transpose()
#为归一化后的数据添加噪声
noise = 0.03*np.random.rand(sampleoutnorm.shape[0],sampleoutnorm.shape[1])
sampleoutnorm += noise
sampleinnorm = np.mat(sampleinnorm)#利用归一化后的输入数据初始化参数w1、b1、w2、b2
dvalue = sampleinmax-sampleinmin
valuemid=(sampleinmin+sampleinmax)/2
wmag=0.7*(hiddenunitnum(1/indim))
rand1=np.random.rand(hiddenunitnum,outdim)
rand2=np.random.randn(hiddenunitnum,indim)
rand1=rand1*wmag
rand2=rand2*wmag
b1=rand1-np.dot(rand2,valuemid)
for i in range(hiddenunitnum):for j in range(indim):rand2[i][j]=(2*rand2[i][j])/dvalue[j]
w1=rand2
w2 = np.random.uniform(low=-1, high=1, size=[outdim,hiddenunitnum])
b2 = np.random.uniform(low=-1, high=1, size=[outdim,1])#参数w1、b1、w2、b2均为矩阵形式参与计算,其形状依次为8*4,8*1,1*8,1*1
w1 = np.mat(w1)
b1 = np.mat(b1)
w2 = np.mat(w2)
b2 = np.mat(b2)
#errhistory存储每次训练后的预测值与真实值的误差
errhistory = []#sw1、sb1,sw2,sb2分别保存参数w1、b1、w2、b2的一阶矩估计,其形状与w1、b1、w2、b2一一对应
sw2 = np.zeros((1,8))
sb2 = np.zeros((1,1))
sw1 = np.zeros((8,4))
sb1 = np.zeros((8,1))#rw1、rb1,rw2,rb2分别保存参数w1、b1、w2、b2的二阶矩估计,其形状与w1、b1、w2、b2一一对应
rw2 = np.zeros((1,8))
rb2 = np.zeros((1,1))
rw1 = np.zeros((8,4))
rb1 = np.zeros((8,1))#deltaw1、deltab1、deltaw2 、deltab2分别保存参数w1、b1、w2、b2的临时更新量
deltaw2 = np.zeros((1,8))
deltab2 = np.zeros((1,1))
deltaw1 = np.zeros((8,4))
deltab1 = np.zeros((8,1))#t用于对一阶矩估计和二阶矩估计进行修正,随训练次数不断累加
t = 0
for i in range(maxepochs):t = t + 1#利用参数临时更新量对参数w1、b1、w2、b2进行临时更新w1 += deltaw1b1 += deltab1w2 += deltaw2b2 += deltab2# 前向传播# 计算隐含层输出hiddenout,输出层输出networkouthiddenout = tanh((np.dot(w1, sampleinnorm).transpose() + b1.transpose())).transpose()networkout = np.dot(w2, hiddenout).transpose() + b2.transpose()for j in range(samnum):networkout[j, :] = tanh(networkout[j, :])networkout = networkout.transpose()# 计算损失函数err = sampleoutnorm - networkoutloss = np.sum(np.abs(err)) / samnumsse = np.sum(np.square(err))# 判断是否满足停止训练条件errhistory.append(sse)if sse < errorfinal:break#反向传播#利用损失函数计算结果和激活函数偏导数,来计算参数w1、b1、w2、b2的梯度值delta2 = np.zeros((outdim,samnum))for n in range(samnum):delta2[:,n] = (-1) * err[:,n] * de_tanh(networkout[:,n])delta1 = np.zeros((hiddenunitnum,samnum))for e in range(samnum):for f in range(hiddenunitnum):delta1[f,e] = w2[:,f] * delta2[:,e] * de_tanh(hiddenout[f,e])dw2now = np.dot(delta2,hiddenout.transpose()) #1*8db2now = np.dot(delta2,np.ones((samnum,1))) #1*1dw1now = np.dot(delta1,sampleinnorm.transpose()) #8*4db1now = np.dot(delta1,np.ones((samnum,1))) #8*1#先更新输出层参数#w2更新for m in range(hiddenunitnum):#计算一阶矩估计sw2和二阶矩估计rw2sw2[:, m], rw2[:, m] = accumulation(sw2[:, m], rw2[:, m], dw2now[:, m])#使用t值对一阶矩估计和二阶矩估计进行修正saw2 = sw2[:, m] / (1 - (0.9 t))raw2 = rw2[:, m] / (1 - (0.999 t))#获得参数更新量,并作为下一次训练时的临时更新量deltaw2[:,m] = adjust(saw2,raw2)#对参数w2进行更新w2[:,m] += deltaw2[:,m]#b2更新#计算一阶矩估计sb2和二阶矩估计rb2sb2,rb2 = accumulation(sb2,rb2,db2now)#使用t值对一阶矩估计和二阶矩估计进行修正sab2 = sb2/(1 - (0.9t))rab2 = rb2/(1-(0.999t))#获得参数更新量,并作为下一次训练时的临时更新量deltab2 = adjust(sab2,rab2)#对参数b2进行更新b2 += deltab2#更新隐含层参数#w1更新#计算一阶矩估计sw1和二阶矩估计rw1for a in range(hiddenunitnum):for b in range(indim):#计算一阶矩估计sw1和二阶矩估计rw1sw1[a,b],rw1[a,b] = accumulation(sw1[a,b],rw1[a,b],dw1now[a,b])#使用t值对一阶矩估计和二阶矩估计进行修正saw1 = sw1[a,b]/(1 - (0.9t))raw1 = rw1[a,b]/(1 - (0.999t))#获得参数更新量,并作为下一次训练时的临时更新量deltaw1[a,b] = adjust(saw1,raw1)#对参数w1进行更新w1[a, b] += deltaw1[a,b]#b1更新#计算一阶矩估计sb1和二阶矩估计rb1for n in range(hiddenunitnum):#计算一阶矩估计sb1和二阶矩估计rb1sb1[n,:],rb1[n,:] = accumulation(sb1[n,:],rb1[n,:],db1now[n,:])#使用t值对一阶矩估计和二阶矩估计进行修正sab1 = sb1[n,:]/(1 - (0.9t))rab1 = rb1[n,:]/(1 - (0.999t))#获得参数更新量,并作为下一次训练时的临时更新量deltab1[n,:] = adjust(sab1,rab1)#对参数b1进行更新b1[n,:] += deltab1[n,:]print("the generation is:",i,",the loss is:",loss)#达到最大训练次数,保存此时的参数w1、b1、w2、b2
np.save("w1.npy",w1)
np.save("b1.npy",b1)
np.save("w2.npy",w2)
np.save("b2.npy",b2)
3.4 Nadam测试过程及结果
测试过程只需要利用训练过程生成的相关参数,对测试数据执行一次前向传播过程来获得预测值,之后可使用相关的误差指标对预测值进行评价,详细的测试过程源码见参考源码及数据集。
4、参考源码及数据集
Adamax参考源码及数据集
Nadam参考源码及数据集