文章目录
一、理论基础
反向传播神经网络(BPNN)是1986年由Rumelhart和McClelland为首的科学家提出的概念,是一种按照误差逆向传播算法训练的多层前馈神经网络,最常见结构为3层。
数据在神经网络中的训练过程可分为前向传播过程与反向传播过程。通过前向传递过程将数据输入网络,数据依次通过隐含层与输出层并进行相关计算,得到输出值与目标值之间的误差;然后在反向进行传递过程进行神经网络权值、阈值的调整,重复此过程,使得神经网络的输出结果不断逼近真实值。
常见的BP神经网络为3层,输入层、隐含层、输出层。其中输入层传入数据,然后在隐含层与输出层分别与权值、阈值进行计算、从而实现非线性变换,最后在输出层与目标值进行比较获取误差。
1、前向传播
前向传播过程中隐含层与输出层的输出公式及误差公式计算如下:
其中W与b为隐含层与输出层的权值和阈值,通过初始化生成,x为隐含层与输出层的输入数据,g为激活函数,h为输出,E为误差,y_i为目标值,y ̂_i为实际值。
参数初始化方法参考:
神经网络基础知识之参数初始化
2、反向传播
反向传播过程中输出层的误差项计算公式如下:
其中V与b_2为输出层的权值和阈值,E为损失函数。隐含层的误差项的计算公式可以此类推进行计算。
隐含层与输出层的权值和阈值的更新计算公式如下:
损失函数原理参考:
机器学习基础知识之损失函数
反向传播原理参考:
神经网络之反向传播算法(梯度、误差反向传播算法BP)
3、激活函数
在前向传播过程中,输入数据除了和隐含层、输出层的权值、阈值进行计算外,还会使用激活函数g对计算结果进行非线性计算。激活函数作为人工神经网络中神经元的核心,其作用在于将非线性因素引入神经元,它在输入传递至输出的过程中进行函数转换,以此将无限范围内的输入非线性变换为有限范围内的输出,一旦人工神经网络缺少激活函数,那么它每一层的数据传递过程就变成了单纯的矩阵计算过程,无论数据传递了多少层,最后的输出都是输入的线性组合。
常见的激活函数如下:
1、sigmod
Sigmod函数是一种常见的S型函数,它能够将输入变量映射到0到1之间,其公式如下:
2、Tanh
Tanh函数是一种双曲正切函数,它是由双曲正弦函数与双曲余弦函数推导而来,同样将输入处理成0到1之间,与sigmod不同的是它的输出是零中心的,其公式如下:
3、Relu
Relu函数将输入和零进行比较,输出较大值,其公式如下:
4、Leaky Relu
Leaky Relu函数和Relu函数不同的是,当输入小于零时,将输入与常量gamma进行计算作为输出,其公式如下:
激活函数对比及适用场景参考:
神经网络基础知识之激活函数
4、神经网络结构
BP神经网络的输入层和输出层层数通常需要根据实际问题进行确定,而隐含层的层数即节点数的确定,通常没有一个确定的方法,一般通过设置不同的节点数然后比较其网络训练结果来选择最优数量,而节点数的范围可通过以下公式进行确定:
其中h为隐含层单元数,n为输入层单元数,m为输出层单元数,a为1到10之间的常数。
二、BP神经网络的实现
以数据预测为例,下面介绍BP神经网络的实现过程。
选用某省市的表层土壤重金属元素数据集作为实验数据,该数据集总共96组,随机选择其中的24组作为测试数据集,72组作为训练数据集。选取重金属Ti的含量作为待预测的输出特征,选取重金属Co、Cr、Mg、Pb作为模型的输入特征。
1、训练过程(BPNN.py)
#库的导入
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)
2、测试过程(test.py)
#库的导入
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))
#输入数据的导入,用于测试数据的归一化与返归一化
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])
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()
#导入训练的权值、阈值
w1=np.load('w1.npy')
w2=np.load('w2.npy')
b1=np.load('b1.npy')
b2=np.load('b2.npy')
#测试数据的导入
df = pd.read_csv("test.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)
input=np.mat([Co,Cr,Mg,Pb])
#测试数据数量
testnum = 24
#测试数据中输入数据的归一化
inputnorm=(2*(np.array(input.T)-sampleinminmax.transpose()[0])/(sampleinminmax.transpose()[1]-sampleinminmax.transpose()[0])-1).transpose()
#隐含层、输出层的计算
hiddenout = tanh((np.dot(w1,inputnorm).transpose()+b1.transpose())).transpose()
networkout = tanh((np.dot(w2,hiddenout).transpose()+b2.transpose())).transpose()
#对输出结果进行反归一化
diff = sampleoutminmax[:,1]-sampleoutminmax[:,0]
networkout2 = (networkout+1)/2
networkout2 = networkout2*diff+sampleoutminmax[0][0]
output1=networkout2.flatten()
output1=output1.tolist()
for i in range(testnum):
output1[i] = float('%.2f'%output1[i])
print("the prediction is:",output1)
#将输出结果与真实值进行对比,计算误差
output=Ti
rmse = (np.sum(np.square(output-output1))/len(output))**0.5
mae = np.sum(np.abs(output-output1))/len(output)
average_loss1=np.sum(np.abs((output-output1)/output))/len(output)
mape="%.2f%%"%(average_loss1*100)
f1 = 0
for m in range(testnum):
f1 = f1 + np.abs(output[m]-output1[m])/((np.abs(output[m])+np.abs(output1[m]))/2)
f2 = f1 / testnum
smape="%.2f%%"%(f2*100)
print("the MAE is :",mae)
print("the RMSE is :",rmse)
print("the MAPE is :",mape)
print("the SMAPE is :",smape)
#计算预测值与真实值误差与真实值之比的分布
A=0
B=0
C=0
D=0
E=0
for m in range(testnum):
y1 = np.abs(output[m]-output1[m])/np.abs(output[m])
if y1 <= 0.1:
A = A + 1
elif y1 > 0.1 and y1 <= 0.2:
B = B + 1
elif y1 > 0.2 and y1 <= 0.3:
C = C + 1
elif y1 > 0.3 and y1 <= 0.4:
D = D + 1
else:
E = E + 1
print("Ratio <= 0.1 :",A)
print("0.1< Ratio <= 0.2 :",B)
print("0.2< Ratio <= 0.3 :",C)
print("0.3< Ratio <= 0.4 :",D)
print("Ratio > 0.4 :",E)
3、测试结果
注:由于每次初始化生成的参数不同,因此对参数设置相同的神经网络进行多次训练和预测,测试结果不会完全一致,此外测试结果的好坏也会受到隐含层节点数、学习率、训练次数等参数的影响。
4、参考源码及实验数据集