一提到反向传播算法,我们就不自觉的想到随机梯度下降、sigmoid激活函数和最让人头大的反向传播法则的推导,即便是把反向传播神经网络的原理学了一遍,也还是一头雾水,在这里推荐一本小编认为把BP神经网络讲的最通透的教材《Python神经网络编程》。下面小编将以代码的方式带着大家一步一步实现BP神经网络,并且还会再用框架的方式实现BP网络,用来和源码作比较,认识一下TensorFlow2.0的强大。
我们首先要理清建立BP神经网络的目的,其次,确定BP神经网络的结构,简单地以一个输入层,一个隐藏层,一个输出层为例,我们来思考一下写代码的思路,然后一步一步实现。
在这里,我们建立BP神经网络的目的是为了做预测,我们这里用700条MG时间序列数据作为训练数据,输入维度是10,输出为1,为了方便,我们设置一个隐藏层,这个隐藏层包含10个节点,所以我们的BP神经网络的结构就是[10,10,1]。接下来,开始思考一下流程:
1、读取数据,将数据分为训练集和测试集,注意这里的数据默认都是np.array()的格式。
2、初始化BP网络,主要是确定网络结构,输入层、隐藏层、输出层个数,以及随机初始化相互连接的权值
3、调用训练方法,传入数据进行训练
前向传播
反向传播
保存模型进行测试
里面还有很多细节,我们边写边完善,整个程序的框架已经很明确了。从第一步开始,读取数据,第一步中需要注意的是,读取的数据最好进行归一化处理,一方面,这样做有助于提高预测精度,另一方面,如果使用sigmoid作为激活函数的话,它的输出是在[0,1]之间的,如果数据不进行归一化,不在[0,1]区间的数是没有办法预测的。这里我们直接调用sklearn中的最大最小归一化的方法就可以,为了方便读者理解代码,我们将import紧挨着代码:
#防止数据用科学计数法输出
np.set_printoptions(suppress=True)
#读取数据
way1 = 'Traindata11.csv'
data = np.array(pd.DataFrame(pd.read_csv(way1)))
# 归一化所有数据
from sklearn.preprocessing import MinMaxScaler
format_minmax = MinMaxScaler()
data = format_minmax.fit_transform(data)
x = data[:,:-1] #需要训练的数据
y = data[:,-1] #label,标签
#初始化BP网络
#训练网络
第二步,我们要初始化网络,确定网络的结构:
def __init__(self, ni, nh, no):
"""
构造神经网络
:param ni:输入单元数量
:param nh:隐藏单元数量
:param no:输出单元数量
"""
self.ni = ni + 1 # +1 是为了偏置节点
self.nh = nh
self.no = no
# 激活值(输出值)
self.ai = [1.0] * self.ni #这里会生成一个1*ni维,数值全为1的列表
self.ah = [1.0] * self.nh
self.ao = [1.0] * self.no
# 权重矩阵
self.wi = np.random.randn(self.ni, self.nh) #输入层到隐藏层
self.wo = np.random.randn(self.nh, self.no) # 隐藏层到输出层
# 记录权重矩阵的上次梯度
self.ci = np.zeros([self.ni, self.nh])
self.co = np.zeros([self.nh, self.no])
第三步开始训练并保存模型:
def train(self,train,lable,max_iterations=1000, N=0.5, M=0.1):
"""
训练
:param train:训练集
:param lable:标签
:param max_iterations:最大迭代次数
:param N:本次学习率
:param M:上次学习率
"""
for i in range(max_iterations): #迭代最大训练次数(epoch)
for j in range(len(train)): #训练集
inputs = train[j] #输入向量
targets = lable[j] #目标值
self.forword_propagation(inputs) #前向传播训练
error = self.back_propagation(targets, N, M) #反向传播训练,传入lable
if i % 50 == 0: #每50次输出一次误差(loss),loss函数为mse
print('Combined error', error)
winame = 'wi.npy'
woname = 'wo.npy'
np.save(winame, self.wi)
np.save(woname, self.wo)
接下来我们就要实现前向传播和反向传播的方法了,我们按照顺序从前向传播开始,前向传播用到了sigmoid作为激活函数,所以我们把sigmoid函数也一并列出:
def sigmoid(self,x):
"""
sigmoid 函数,1/(1+e^-x)
:param x:
:return:
"""
return 1.0 / (1.0 + math.exp(-x))
def forword_propagation(self, inputs):
"""
前向传播进行分类
:param inputs:输入
:return:类别
"""
if len(inputs) != self.ni - 1: #检查输入数据的维度是否和声明的一致
print('输入维度有误,请重新检查输入')
for i in range(self.ni - 1): #按顺序将n维的input映射到n个输入节点上
self.ai[i] = inputs[i]
for j in range(self.nh): #遍历隐藏层节点,每个隐藏层节点的值为n个输入节点*对应权值(输入层和隐藏层)的和
sum = 0.0
for i in range(self.ni):
sum += (self.ai[i] * self.wi[i][j])
self.ah[j] = self.sigmoid(sum) #将节点值经过sigmoid函数激活后,变为[0,1]之间的值
for k in range(self.no): #遍历输出层节点,每个输出层节点的值为n个隐藏层节点*对应权值(隐藏层和输出层)的和
sum = 0.0
for j in range(self.nh):
sum += (self.ah[j] * self.wo[j][k])
self.ao[k] = self.sigmoid(sum) ##将节点值经过sigmoid函数激活后,变为[0,1]之间的值
return self.ao #返回输出层的值
接下来是反向传播,反向传播中需要对sigmoid进行求导,为了方便我们直接写出sigmoid的求导结果,另外这里的误差函数(loss)选择mse:
def dsigmoid(self,y):
"""
sigmoid 函数的导数
:param y:
:return:
"""
return y * (1 - y)
def back_propagation(self, targets, N, M):
"""
反向传播算法
:param targets: 实例的类别
:param N: 本次学习率
:param M: 上次学习率
:return: 最终的误差平方和的一半
"""
# 计算输出层 deltas,求导数
# dE/dw[j][k] = (t[k] - ao[k]) * s'( SUM( w[j][k]*ah[j] ) ) * ah[j]
output_deltas = [0.0] * self.no #初始化输出层列表为0
for k in range(self.no):
error = targets[k] - self.ao[k] #计算输出层误差
output_deltas[k] = error * self.dsigmoid(self.ao[k]) #计算每一个节点对误差的影响大小,占比越大,影响越大
# 根据delta更新隐藏层和输出层之间的权值
for j in range(self.nh):
for k in range(self.no):
# output_deltas[k] * self.ah[j] 才是 dError/dweight[j][k]
change = output_deltas[k] * self.ah[j] #根据比例计算需要调整的值
self.wo[j][k] += N * change + M * self.co[j][k] #调整权值,调整方法有很多,例如随机梯度下降,这里只是简单写了一下
self.co[j][k] = change #保存调整的梯度
# 计算隐藏层 deltas
hidden_deltas = [0.0] * self.nh
for j in range(self.nh):
error = 0.0
for k in range(self.no):
error += output_deltas[k] * self.wo[j][k]
hidden_deltas[j] = error * self.dsigmoid(self.ah[j])
# 更新输入层权值
for i in range(self.ni):
for j in range(self.nh):
change = hidden_deltas[j] * self.ai[i]
# print 'activation',self.ai[i],'synapse',i,j,'change',change
self.wi[i][j] += N * change + M * self.ci[i][j]
self.ci[i][j] = change
# 计算MSE
error = 0.0
for k in range(len(targets)):
error += (targets[k] - self.ao[k]) ** 2
error = error/len(y)
return error
到这里就差不多实现了一个最简单的BP神经网络,不过小编强烈反对用这个代码来进行实战,这个代码只适用于理解BP神经网络。在下一篇中,我将用Tensorflow框架和Pytorch框架来实现一个通用的BP神经网络,并且解答一下为什么要使用激活函数。
不知不觉,2021年到来了,马上天就要亮了,这一篇是小编的第一篇公众号,仅以此铭记2021.1.1日凌晨。祝各位读者,元旦快乐!!!
欢迎关注公众号“NNResearch”
公众号发送“BP神经网络图书”,即可获取电子版图书。
公众号发送“BP源码”,即可获取完整版源码。