python和numpy纯手写3层神经网络,干货满满
前言
在上一篇转载的文章中,我们已经使用numpy手写了两层神经网络,尽管这篇文章只是增添了一层,但是,还是在实践中给我造成了不少的困难,主要的困难点在于感觉自己理解了BP反向传播,但是真正动手写的时候,还是迷迷糊糊的。所以,这里也给大家提个醒,不要眼高手低,尽量去实践自己学的东西。
在看这篇文章时,建议大家先把上一篇手写2层神经网络的文章看一下:
网络结构
共有三层神经网络结构
- 第一层 输入层,有13个神经元,所需参数是13*13+13=182个
- 第二层 隐藏层,有13个神经元,所需参数是13+1=14个
- 第三层 输出层,有1个神经元 即为输出结果
知识讲解
偏导数
偏导数这个概念咱们在本科的教程中已经学过了,就是这个函数沿该坐标轴正方向的变化率。这个有什么用呢,我们先来看个图:
从图中,我们可以很容易的去找到该图像的最低点,也就是图中大概T点的位置。但是,假如你不知道呢,如何去慢慢的找到这个最低点呢?咱们先来看P0点的切线斜率,也等于该点的导数值,设为α。因为这个切线与x轴的角度小于90°,所以p0点的倒数值大于0.我们有了这个讯息,相当于知道了方向,于是就有了这个公式:x = x-axα a 为步长。由于α>0,所以x的值在慢慢减小,逐渐靠近T。当x在T点时,该点的切线为水平线,即导数为0,所以此时x=x-ax0,x值不再变化,此时x为该函数的最小值的取值点。
上面说的都是x,y二维坐标系,扩展为多维坐标系时,导数的概念就变成了偏导数,但是原理都是一样的,朝着最低点的方向前进。然后上述过程在多维时就变成了梯度下降法。
梯度下降法
这里我们举一个上篇文章中的例子。一个想从山峰走向山底的盲人,他看不见坡谷在哪(无法逆向求解出$Loss&导数为0时的参数值),但可以伸脚探索身边的坡度(当前点的导数值,也称为梯度)。那么,求解Loss函数最小值可以“从当前的参数取值,一步步的按照下坡的方向下降,直到走到最低点”实现。
具体计算
下面我们使用比较简单的三层神经网络模型来做计算:
- 我们先进行前向传播,这里我们没有使用激活函数:
- 我们先对隐藏层进行反向传播:
- 紧接着对输入层进行反向传播:
从上面这几张图的计算过程,我们可以得出一个结论:某一神经元偏导数的值(也有地方叫残差)=之后与它相连线的权值✖该神经元本身的值✖(预测值-准确值)
代码过程
`
import numpy as np
import matplotlib.pyplot as plt
'''
项目介绍:
此项目是使用numpy搭建神经网络模型,预测波士顿房价。
数据介绍:
每一条房屋数据有14个值,前13个值是房屋属性,比如面积,长度等,最后一个是房屋价格,共506条数据。
模型介绍:
在combat1.py文件二层神经网络模型的基础上,此文件将搭建三层神经网络,其中包含输入层(13)、隐藏层(13)、输出层(1)
'''
#数据导入以及处理
def deal_data():
#读取文件数据,此时数据形状是(7084,),即所有数据在一行中
housingdata = np.fromfile('data/housing.data',sep=' ')
#修改数据格式,将每一条房屋数据放在一行中。
housingdata = np.array(housingdata).reshape((-1,14))#此时数据形状为(506,14)
#对数据的前13个属性进行归一化操作,有助于提高模型精准度,这里使用max-min归一化方式。公式为(x-min)/(max-min)
for i in range(13):
Max = np.max(housingdata[:,i])
Min = np.min(housingdata[:,i])
housingdata[:,i]=(housingdata[:,i]-Min)/(Max-Min)
#依据2-8原则,80%的数据作为训练数据,20%数据作为测试数据;此时训练数据是405条,测试数据是101条
Splitdata = round(len(housingdata)*0.8)
Train = housingdata[:Splitdata]#训练数据集
Test = housingdata[Splitdata:]#测试数据集
return Train,Test
#模型设计以及配置
#首先确定有13个权值参数w,并随机初始化
class Model_Config(object):
def __init__(self,firstnetnum,secondnetnum):
np.random.seed(1)
self.w0 = np.random.randn(firstnetnum*secondnetnum,1).reshape(firstnetnum,secondnetnum)
self.w1 = np.random.randn(secondnetnum,1)
self.b0 = np.random.randn(firstnetnum,1).reshape(1,firstnetnum)
self.b1 = np.random.randn(1,1)
#计算预测值
def forward(self,x):
hidden1 = np.dot(x,self.w0)+self.b0
y = np.dot(hidden1,self.w1)+self.b1
return hidden1,y
#设置损失函数,这里使用差平方损失函数计算方式
def loss(self,z,y):
error = z-y
cost = error*error
avg_cost = np.mean(cost)
return avg_cost
#计算梯度
def back(self,x,y):
hidden1,z = self.forward(x)
#hidden层的梯度
gradient_w1 = (z-y)*hidden1
gradient_w1 = np.mean(gradient_w1,axis=0)#这里注意,axis=0必须写上,否则默认将这个数组变成一维的求平均
gradient_w1 = gradient_w1[:,np.newaxis]#
gradient_b1 = (z-y)
gradient_b1 = np.mean(gradient_b1)
gradient_w0 = np.zeros(shape=(13,13))
for i in range(len(x)):
data = x[i,:]
data = data[:,np.newaxis]
# print("data.shape",data.shape)
w1 = self.w1.reshape(1,13)
# print("self.w1.shape",w1.shape)
gradient_w01 = (z-y)[i]*np.dot(data,w1)
# print("gradient_w01.shape:",gradient_w01.shape)
gradient_w0+=gradient_w01
gradient_w0 = gradient_w0/len(x)
w2 = self.w1.reshape(1,13)
gradient_b0 =np.mean((z-y)*w2,axis=0)
return gradient_w1,gradient_b1,gradient_w0,gradient_b0
#输入层的梯度
#(z-y)x*self.w1
# gradient_w0 = np.zeros(shape=(13,13))
# gradient_w01 = gradient_w1.reshape(1,13)
# for i in range(13):
# data = x[:,i]
# data = data[:,np.newaxis]
# gradient = data*gradient_w01
# gradient = np.mean(gradient,axis=0)
# gradient_w0[i,:] = gradient
# gradient_b0 = gradient_b1*self.b0
# return gradient_w1,gradient_b1,gradient_w0,gradient_b0
#使用梯度更新权值参数w
def update(self,gradient_w1,gradient_b1,gradient_w0,gradient_b0,learning_rate):
self.w1 = self.w1-learning_rate*gradient_w1
self.b1 = self.b1-learning_rate*gradient_b1
self.w0 = self.w0-learning_rate*gradient_w0
self.b0 = self.b0-learning_rate*gradient_b0
#开始训练
def train(self,epoch_num,x,y,learning_rate):
#循环迭代
losses=[]
for i in range(epoch_num):
_,z = self.forward(x)
avg_loss = self.loss(z,y)
gradient_w1,gradient_b1,gradient_w0,gradient_b0 = self.back(x,y)
self.update(gradient_w1,gradient_b1,gradient_w0,gradient_b0,learning_rate)
losses.append(avg_loss)
#每进行20此迭代,显示一下当前的损失值
if(i%20==0):
print("iter:{},loss:{}".format(i,avg_loss))
return losses
def showpeocess(loss,epoch_num):
plt.title("The Process Of Train")
plt.plot([i for i in range(epoch_num)],loss)
plt.xlabel("epoch_num")
plt.ylabel("loss")
plt.show()
if __name__ == '__main__':
Train,Test = deal_data()
np.random.shuffle(Train)
#只获取前13个属性的数据
x = Train[:,:-1]
y = Train[:,-1:]
epoch_num = 1000#设置迭代次数
Model = Model_Config(13,13)
losses = Model.train(epoch_num=epoch_num,x=x,y=y,learning_rate=0.001)
showpeocess(loss=losses,epoch_num=epoch_num)
`
闲话
我大概花了一个下午的时间才终于写出来三层的神经网络结构,中间这个反向传播我一直没弄清楚。大家看完理论之后,记住一定要进行实战,手动写一下。
我把这个项目已经上传到GitHub上,大家可以去下载相关数据文件和项目文件。
博主目前正在GitHub上创建针对机器学习和人工智能新手入门项目集合,目的是让初学者延续学习热情,如果你有简单上手的项目,可以博客下面留言,也欢迎各位大佬去GitHub点个⭐,支持一下!