百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

神经网络篇——从代码出发理解BP神经网络

csdh11 2025-02-09 11:57 20 浏览

一提到反向传播算法,我们就不自觉的想到随机梯度下降、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、调用训练方法,传入数据进行训练

    1. 前向传播
    2. 反向传播
    3. 保存模型进行测试

里面还有很多细节,我们边写边完善,整个程序的框架已经很明确了。从第一步开始,读取数据,第一步中需要注意的是,读取的数据最好进行归一化处理,一方面,这样做有助于提高预测精度,另一方面,如果使用sigmoid作为激活函数的话,它的输出是在[0,1]之间的,如果数据不进行归一化,不在[0,1]区间的数是没有办法预测的。这里我们直接调用sklearn中的最大最小归一化的方法就可以,为了方便读者理解代码,我们将import紧挨着代码:


  1. #防止数据用科学计数法输出np.set_printoptions(suppress=True)#读取数据way1 = 'Traindata11.csv'data = np.array(pd.DataFrame(pd.read_csv(way1)))# 归一化所有数据from sklearn.preprocessing import MinMaxScalerformat_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日凌晨。祝各位读者,元旦快乐!!!

公众号“神经网络研究所”

公众号发送“BP神经网络图书”,即可获取电子版图书。

公众号发送“BP源码”,即可获取完整版源码。

相关推荐

探索Java项目中日志系统最佳实践:从入门到精通

探索Java项目中日志系统最佳实践:从入门到精通在现代软件开发中,日志系统如同一位默默无闻却至关重要的管家,它记录了程序运行中的各种事件,为我们排查问题、监控性能和优化系统提供了宝贵的依据。在Java...

用了这么多年的java日志框架,你真的弄懂了吗?

在项目开发过程中,有一个必不可少的环节就是记录日志,相信只要是个程序员都用过,可是咱们自问下,用了这么多年的日志框架,你确定自己真弄懂了日志框架的来龙去脉嘛?下面笔者就详细聊聊java中常用日志框架的...

物理老师教你学Java语言(中篇)(物理专业学编程)

第四章物质的基本结构——类与对象...

一文搞定!Spring Boot3 定时任务操作全攻略

各位互联网大厂的后端开发小伙伴们,在使用SpringBoot3开发项目时,你是否遇到过定时任务实现的难题呢?比如任务调度时间不准确,代码报错却找不到方向,是不是特别头疼?如今,随着互联网业务规模...

你还不懂java的日志系统吗 ?(java的日志类)

一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...

谈谈枚举的新用法--java(java枚举的作用与好处)

问题的由来前段时间改游戏buff功能,干了一件愚蠢的事情,那就是把枚举和运算集合在一起,然后运行一段时间后buff就出现各种问题,我当时懵逼了!事情是这样的,做过游戏的都知道,buff,需要分类型,且...

你还不懂java的日志系统吗(javaw 日志)

一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...

Java 8之后的那些新特性(三):Java System Logger

去年12月份log4j日志框架的一个漏洞,给Java整个行业造成了非常大的影响。这个事情也顺带把log4j这个日志框架推到了争议的最前线。在Java领域,log4j可能相对比较流行。而在log4j之外...

Java开发中的日志管理:让程序“开口说话”

Java开发中的日志管理:让程序“开口说话”日志是程序员的朋友,也是程序的“嘴巴”。它能让程序在运行过程中“开口说话”,告诉我们它的状态、行为以及遇到的问题。在Java开发中,良好的日志管理不仅能帮助...

吊打面试官(十二)--Java语言中ArrayList类一文全掌握

导读...

OS X 效率启动器 Alfred 详解与使用技巧

问:为什么要在Mac上使用效率启动器类应用?答:在非特殊专业用户的环境下,(每天)用户一般可以在系统中进行上百次操作,可以是点击,也可以是拖拽,但这些只是过程,而我们的真正目的是想获得结果,也就是...

Java中 高级的异常处理(java中异常处理的两种方式)

介绍异常处理是软件开发的一个关键方面,尤其是在Java中,这种语言以其稳健性和平台独立性而闻名。正确的异常处理不仅可以防止应用程序崩溃,还有助于调试并向用户提供有意义的反馈。...

【性能调优】全方位教你定位慢SQL,方法介绍下!

1.使用数据库自带工具...

全面了解mysql锁机制(InnoDB)与问题排查

MySQL/InnoDB的加锁,一直是一个常见的话题。例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?下面是不同锁等级的区别表级锁:开销小,加锁快;不会出现死锁;锁定粒度...

看懂这篇文章,你就懂了数据库死锁产生的场景和解决方法

一、什么是死锁加锁(Locking)是数据库在并发访问时保证数据一致性和完整性的主要机制。任何事务都需要获得相应对象上的锁才能访问数据,读取数据的事务通常只需要获得读锁(共享锁),修改数据的事务需要获...