在神经网络中,初始化生成的参数在使用时往往难以使网络获得最好的回归效果,因此除了前向传播过程外,需要通过反向传播过程对相关参数进行优化,从而使得回归效果达到最好,而神经网络的训练过程即为前向传播过程与反向传播过程的循环重复。在反向传播过程中,主要需要利用损失函数来获取输出层、隐含层参数的梯度,然后利用此梯度对参数进行更新,从而使损失函数计算结果达到一个较小值。
梯度可以被理解为一个矢量,它表示某个函数在这一点的方向导数在该方向上可以取得最大值,即函数在该点处沿着这个梯度方向变化最快、变化率最大。假设某一连续可微的多元函数为f(x_1,x_2,…,x_n),则在点(x_1,x_2,…,x_n)处,可以将其梯度表示为(∂f/(∂x_1 ),∂f/(∂x_2 ),…,∂f/(∂x_n )),此时函数f(x_1,x_2,…,x_n)在此方向上函数值增长的速度最快,而(-∂f/(∂x_1 ),-∂f/(∂x_2 ),…,-∂f/(∂x_n ))为该处的负梯度,即函数f(x_1,x_2,…,x_n)在此方向上函数值减少的速度最快。
根据上述梯度的定义我们可以将损失函数作为一个连续可微的多元函数,而由于输入数据为固定值,则由输出层的计算公式可以得知不同的参数值可以视作该多元函数上的不同的点,为了使该函数值能够达到目标值(最大或最小),则需要通过梯度值的计算,使得函数在梯度的方向上具有较快的增长(减小)速度。同理,隐含层的参数优化过程也是如此,由损失函数可计算得出隐含层输出数据,而隐含层的梯度值可以通过该数据计算过程,因此隐含层的参数可在此梯度方向上进行变化以获得最优值。
损失函数原理参考:
机器学习基础知识之损失函数
神经网络原理参考:
反向传播神经网络(BPNN)的实现(Python,附源码及数据集)
反向传播过程中最常用的算法即为误差反向传播算法(Error BackPropagation,简称BP),下面以均方误差损失函数作为神经网络的损失函数,对误差反向传播算法的原理进行介绍。
假设神经网络输入层、隐含层、输出层的节点数分别为n, i, m,其隐含层的权值为w,阈值为v,输出层的权值为u,阈值为γ,则其隐含层中第j个节点的输入的计算公式如下:
其中b_j表示隐含层第j个节点的输出。
假设该神经网络最后的输出如下:
其中f为激活函数。
使用均方误差损失函数计算后,获得的均方误差为:
使用神经网络进行预测时要求预测值与目标值之间的误差达到最小,因此上述计算过程需要朝向负梯度的方向进行减小,此减小过程的计算公式如下:
其中μ为给定的学习率,而等号右边的梯度值可以通过以下公式进行计算:
假设输出层使用的激活函数为Sigmod函数,则对激活函数求导后可以求得:
同理,可分别计算得出参数γ_t、w_lj、v_j的更新公式分别如下:
上述计算过程即为误差反向传播算法的主要原理,在实际的应用过程中,无论为哪一种神经网络,首先将输入数据输入神经网络,输入数据依次经过神经网络的隐含层、输出层,在每一层分别经由线性计算与非线性转换最后获得输出层的输出结果,然后使用损失函数计算出误差值,通过使用输出层的误差梯度来对输出层的参数进行调整,最后将误差反向传递至隐含层神经元并根据隐含层的误差梯度来对隐含层的参数进行调整。
需要注意的是,当激活函数使用Sigmod函数或Tanh函数时,由这两个函数的图像可以得知在x取向正负无穷大值,偏导数为趋近于0,则此时对连接权值的调整值也会趋近于0,因此无法进行调整,这就是误差反向传播算法中容易出现的梯度消失问题,因此在训练的过程中需要对学习率进行合理调整以反之梯度消失的现象发生。
激活函数原理参考:
神经网络基础知识之激活函数
以数据预测为例,下面介绍误差反向传播算法的实现过程,将误差反向传播算法应用于普通的三层神经网络即为BP神经网络。
选用某省市的表层土壤重金属元素数据集作为实验数据,该数据集总共96组,随机选择其中的24组作为测试数据集,72组作为训练数据集。选取重金属Ti的含量作为待预测的输出特征,选取重金属Co、Cr、Mg、Pb作为模型的输入特征。
#库的导入
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-x**2)
#输入数据的导入
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])
#数据归一化,将输入数据压缩至0到1之间,便于计算,后续通过反归一化恢复原始值
sampleinminmax = np.array([samplein.min(axis=1).T.tolist()[0],samplein.max(axis=1).T.tolist()[0]]).transpose()
sampleout = np.mat([Ti])
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()
noise = 0.03*np.random.rand(sampleoutnorm.shape[0],sampleoutnorm.shape[1])
sampleoutnorm += noise
maxepochs = 5000 #训练次数
learnrate = 0.001 #学习率
errorfinal = 0.65*10**(-3) #停止训练误差阈值
samnum = 72 #输入数据数量
indim = 4 #输入层节点数
outdim = 1 #输出层节点数
hiddenunitnum = 8 #隐含层节点数
#随机生成隐含层与输出层的权值w和阈值b
scale = np.sqrt(3/((indim+outdim)*0.5)) #最大值最小值范围为-1.44~1.44
w1 = np.random.uniform(low=-scale, high=scale, size=[hiddenunitnum,indim])
b1 = np.random.uniform(low=-scale, high=scale, size=[hiddenunitnum,1])
w2 = np.random.uniform(low=-scale, high=scale, size=[outdim,hiddenunitnum])
b2 = np.random.uniform(low=-scale, high=scale, size=[outdim,1])
#errhistory存储误差
errhistory = np.mat(np.zeros((1,maxepochs)))
#开始训练
for i in range(maxepochs):
print("The iteration is : ", i)
#前向传播,计算隐含层、输出层输出
hiddenout = tanh((np.dot(w1,sampleinnorm).transpose()+b1.transpose())).transpose()
networkout = tanh((np.dot(w2,hiddenout).transpose()+b2.transpose())).transpose()
#计算误差值
err = sampleoutnorm - networkout
loss = np.sum(err**2)/2
print("the loss is :",loss)
errhistory[:,i] = loss
#判断是否停止训练
if loss < errorfinal:
break
#反向传播,利用结果误差进行误差项的计算
delta2 = err*de_tanh(networkout)
delta1 = np.dot(w2.transpose(),delta2)*de_tanh(hiddenout)
#计算输出层两个参数的梯度值
dw2 = np.dot(delta2,hiddenout.transpose())
dw2 = dw2 / samnum
db2 = np.dot(delta2,np.ones((samnum,1)))
db2 = db2 / samnum
#计算隐含层两个参数的梯度值
dw1 = np.dot(delta1,sampleinnorm.transpose())
dw1 = dw1 / samnum
db1 = np.dot(delta1,np.ones((samnum,1)))
db1 = db1/samnum
#利用学习率、梯度值分别对四个参数进行更新,由于前面的梯度值计算过程忽略了一个减号,因此朝向负梯度的方向可以直接进行累加
w2 += learnrate*dw2
b2 += learnrate*db2
w1 += learnrate*dw1
b1 += learnrate*db1
print('更新的权重w1:',w1)
print('更新的偏置b1:',b1)
print('更新的权重w2:',w2)
print('更新的偏置b2:',b2)
print("The loss after iteration is :",loss)
#保存训练结束后的权值、阈值,用于测试
np.save("w1.npy",w1)
np.save("b1.npy",b1)
np.save("w2.npy",w2)
np.save("b2.npy",b2)
测试过程只需要利用训练过程生成的相关参数,对测试数据执行一次前向传播过程来获得预测值,之后可使用相关的误差指标对预测值进行评价,详细的测试过程源码见文章末尾参考源码及数据集。
注:由于每次初始化生成的参数不同,因此对参数设置相同的神经网络进行多次训练和预测,测试结果不会完全一致,此外测试结果的好坏也会受到隐含层节点数、学习率、训练次数等参数的影响。
BP神经网络用于数据预测(Python源码+数据集)