python和numpy纯手写BP神经神经网络

本文深入浅出地介绍了神经网络的基本结构和工作原理,包括正向传播、激活函数、误差求解和反向传播。通过实例详细解析了BP神经网络的数学公式和Python实现,涵盖了从初始化参数到前向传播、反向传播和参数更新的完整过程。此外,还提供了完整的神经网络Python代码示例,帮助读者理解并动手实践神经网络的构建和训练。
摘要由CSDN通过智能技术生成

python和numpy纯手写BP神经神经网络

二级目录

三级目录

神经网络的基本理解

神经网络分为三层,输入层,隐含层以及输出层。隐含层可以为多层神经网络。输入层为输入样本的数据维度(N*D),输出层为样本标签y,y可以为离散值(分类任务)也可以为连续值(回归任务)。

个人理解,神经网络的训练过程主要有四个步骤,包括:
(1)正向传播:通过多层线性网络结构(w_1,w_2,…w_n),将输入样本进行特征信息提取,降低原始数据的维度。
(2)激活函数:将每层网络线性求解的结果,进行非线性变换,实现特征信息的非线性转换。常见的激活函数:simgoid函数,relu函数,tanh函数等。
(3)误差求解:将模型求出的预测值,与真实值进行误差求解,得到模型预测值与真实值之间的差异,用于后续的反向传播。
(4)反向传播。根据误差值,调整模型参数w,使得模型的预测值接近于模型的真实值。

神经网络的主要原理就是通过不断调整模型参数,使得模型的预测值接近于真实值。

神经网络公式

图1神经网络图结构

神经网络公式结构

神经网络结构如上图所示,下图是上图的公式理解,模型参数的设定,关键在于层层神经元的个数,也就是说上层的输入维度需与下一层的输入维度相同。
例:输入样本X为N个样本,3维特征,X-[N,3],第一层隐含层的w_1-[3,d],d为经过第一层网络后,对原始数据进行升维或者降维(特征提取),设w_1-[3,4],b_1-[1,4],进过第一层网络得到z_1,z_1-[N,4],通过激活函数,得到h_1,h_1便是第一层网络对原始数据特征提取后得到的结果。

前向传播

第一层网络公式如下(输入层→第一层隐含层):
在这里插入图片描述f()为激活函数

第二层网络公式如下(第一层隐含层→第二层隐含层):

在这里插入图片描述

第三层网络公式如下(第二层隐含层→输出层):
在这里插入图片描述

最后输出的是h3,(N,10),N是样本个数,10是类别种类个数。

激活函数

前向传播中的f()就是激活函数

误差求解

计算模型输出值与真实值之间的误差。误差一般都是经过绝对值或者平方的数,以得的一个始终大于0得的数,以无限接近于0,作为预测精准度。本文以一个L2-LOSS为例:
在这里插入图片描述

反向传播

反向传播是神经网络中的关键一步,如何将误差层层方向传递到各个网络层中的w是关键。求解dw_out,dw_2.dw_1.

(1)求解dw_out;
在这里插入图片描述
通过链式法则,将复合函数进行分解进行逐步求导。

在这里插入图片描述
h2-(5,N)

在这里插入图片描述
注意:1. 这里用的激活函数是sigmoid函数,不同的激活函数,在该项中求得的结果是不一样的;2. h3矩阵与(1-h3)矩阵相乘,而不是点乘,这里是对矩阵中的每个元素进行求导,因此出来的结果是逐元素相乘。在求导过程中,有的是点乘,有的是相乘。

在这里插入图片描述

在这里插入图片描述
h2是输出层的输入值,(h3-y)(h3(1-h3))可以看做误差,所以dwout其实就是等于输入的转置点乘误差。(计算的时候,先计算误差,误差中的乘法运算是相乘。)
在这里插入图片描述

在这里插入图片描述

求解db_out;
在这里插入图片描述
db_out的输入只是单位矩阵I,其实就是将误差的所有行相加。
db就是误差的求和。

需要改变的梯度方向,其实就是输入的转置·误差

(2)求解dw_2;

在这里插入图片描述
将上一层的误差error_1通过上一层的参数w进行传递,并再乘以该层的激活函数的求导,便是这一层的误差。
这里w的转置,是为了与前面的维度相匹配,其实求导出来的是w还是w的转置,这个是根据分子布局还是分母分局来确定的,w和w的装置在这里其实是一样的,只是元素排列的方式不同。(个人理解)

在这里插入图片描述

(3)求解dw_1;
在这里插入图片描述
在这里插入图片描述

参数更新

求出dw,db只是说明在某点参数w,b下降最快的方向,并不能确定下降多少距离。因此引入一个学习率a,用于表示下降的距离。学习率a不能过大,因为过大的话,有可能下降距离过大,导致错过最小极值点。

w_out=w_out-adw_out
b_out=b_out-a
db_out
w_2=w_2-adw_2
b_2=b_2-a
db_2
w_1=w_1-adw_1
b_1=b_1-a
db_1

dw只是个变化方向,通过a来赋予距离,在于原始的w进行向量的加减,便可以达到新w在某一方向变化的距离。

神经网络python代码

初始化模型参数w,b。利用np.random.randn随机生成0-1之间的数

def __init__(self,input_size,label_size):
        np.random.seed(6)
        self.w_1=np.random.randn(input_size,4)
        self.b_1=np.random.randn(1,4)
        self.w_2=np.random.randn(4,5)
        self.b_2=np.random.randn(1,5)
        self.w_out=np.random.randn(5,label_size)
        self.b_out=np.random.randn(1,label_size)

定义前向传播

def feed_forward(self,x):
        z_1=np.dot(x,self.w_1)+self.b_1
        h_1=self.relu(z_1)
        ##print(h_1.shape)
        z_2=np.dot(h_1,self.w_2)+self.b_2
        h_2=self.sigmoid(z_2)
        ##print(h_2.shape)
        z_3=np.dot(h_2,self.w_out)+self.b_out
        h_3=self.sigmoid(z_3)
        ##print(h_3.shape)
        return h_1,h_2,h_3

得到每一层神经网络的输出,并通过激活函数对输出进行非线性转换。

def back(self,x,y):
        h_1,h_2,h_3=self.feed_forward(x)
        error,loss=self.loss_l2(h_3,y)
        error_tans_out=np.multiply(error,self.de_sigmoid(h_3))
        dw_out=np.dot(h_2.T,error_tans_out)
        db_out=np.sum(error_tans_out,axis=0,keepdims=True)
        
        error_trans_2=np.multiply(np.dot(error_tans_out,w_out.T),self.de_sigmoid(h_2))
        
        dw_2=np.dot(h_1.T,error_trans_2)
        db_2=np.sum(error_trans_2,axis=0,keepdims=True)
        
        error_trans_3=np.multiply(np.dot(error_trans_2,w_2.T),self.relu(h_1))
        dw_1=np.dot(x.T,error_trans_3)
        db_1=np.sum(error_trans_3,axis=0,keepdims=True)
        return dw_out,db_out,dw_2,db_2,dw_1,db_1

在反向传播中,一定要搞清楚点乘和叉乘。

参数更新:

def update_weight(self,w_1,b_1,w_2,b_2,w_out,b_out,learn_rate):
        self.w_1=self.w_1-learn_rate*dw_1
        self.b_1=self.b_1-learn_rate*db_1
        self.w_2=self.w_2-learn_rate*dw_2
        self.b_2=self.b_2-learn_rate*db_2
        self.w_out=self.w_out-learn_rate*dw_out
        self.b_out=self.b_out-learn_rate*db_out

损失函数:

 def loss_l2(self,h,y):
        error=h-y
        error_2=0.5*(error*error)
        return error,np.sum(error_2)

激活函数与激活函数的求导

 def relu(self,x):
        z = np.maximum(x, 0)
        return z
    def tanh(self,x):
        return np.tanh(x)
    def sigmoid(self,x):
        ex=np.exp(x)
        return ex/(ex+1)
    
    def de_relu(self,z,h):
        z[z <= 0] = 0
        z[z > 0] = 1.0
        return z
    def de_sigmoid(self,h):
        return h*(1-h)
    

训练过程

def train(self,epoch_num,x,y,learning_rate):
        losses=[]
        for i in range(epoch_num):
            h_1,h_2,h_3=self.feed_forward(x)
            error,error_2=self.loss_l2(h_3,y)
            dw_out,db_out,dw_2,db_2,dw_1,db_1=self.back(x,y)
            self.update_weight(dw_1,db_1,dw_2,db_2,dw_out,db_out,learning_rate)
            losses.append(error_2)
            if(i%20==0):
                print("iter:{},loss:{}".format(i,error_2))
        
        return losses

所有代码:

class BPNN(object):
    def __init__(self,input_size,label_size):
        np.random.seed(6)
        self.w_1=np.random.randn(input_size,10)
        self.b_1=np.random.randn(1,10)
        self.w_2=np.random.randn(10,5)
        self.b_2=np.random.randn(1,5)
        self.w_out=np.random.randn(5,label_size)
        self.b_out=np.random.randn(1,label_size)
    def feed_forward(self,x):
        z_1=np.dot(x,self.w_1)+self.b_1
        h_1=self.relu(z_1)
        ##print(h_1.shape)
        z_2=np.dot(h_1,self.w_2)+self.b_2
        h_2=self.sigmoid(z_2)
        ##print(h_2.shape)
        z_3=np.dot(h_2,self.w_out)+self.b_out
        h_3=self.sigmoid(z_3)
        ##print(h_3.shape)
        return h_1,h_2,h_3
    
    
    def back(self,x,y):
        h_1,h_2,h_3=self.feed_forward(x)
        error,loss=self.loss_l2(h_3,y)
        error_tans_out=np.multiply(error,self.de_sigmoid(h_3))
        dw_out=np.dot(h_2.T,error_tans_out)
        db_out=np.sum(error_tans_out,axis=0,keepdims=True)
        
        error_trans_2=np.multiply(np.dot(error_tans_out,w_out.T),self.de_sigmoid(h_2))
        
        dw_2=np.dot(h_1.T,error_trans_2)
        db_2=np.sum(error_trans_2,axis=0,keepdims=True)
        
        error_trans_3=np.multiply(np.dot(error_trans_2,w_2.T),self.relu(h_1))
        dw_1=np.dot(x.T,error_trans_3)
        db_1=np.sum(error_trans_3,axis=0,keepdims=True)
        return dw_out,db_out,dw_2,db_2,dw_1,db_1
    
    
    def update_weight(self,w_1,b_1,w_2,b_2,w_out,b_out,learn_rate):
        self.w_1=self.w_1-learn_rate*dw_1
        self.b_1=self.b_1-learn_rate*db_1
        self.w_2=self.w_2-learn_rate*dw_2
        self.b_2=self.b_2-learn_rate*db_2
        self.w_out=self.w_out-learn_rate*dw_out
        self.b_out=self.b_out-learn_rate*db_out
    
    
    def loss_l2(self,h,y):
        error=h-y
        error_2=0.5*(error*error)
        return error,np.sum(error_2)
        
        
    def relu(self,x):
        z = np.maximum(x, 0)
        return z
    def tanh(self,x):
        return np.tanh(x)
    def sigmoid(self,x):
        ex=np.exp(x)
        return ex/(ex+1)
    
    def de_relu(self,z,h):
        z[z <= 0] = 0
        z[z > 0] = 1.0
        return z
    def de_sigmoid(self,h):
        return h*(1-h)
    
    def train(self,epoch_num,x,y,learning_rate):
        losses=[]
        for i in range(epoch_num):
            h_1,h_2,h_3=self.feed_forward(x)
            error,error_2=self.loss_l2(h_3,y)
            dw_out,db_out,dw_2,db_2,dw_1,db_1=self.back(x,y)
            self.update_weight(dw_1,db_1,dw_2,db_2,dw_out,db_out,learning_rate)
            losses.append(error_2)
            if(i%20==0):
                print("iter:{},loss:{}".format(i,error_2))
        
        return losses

if __name__ == '__main__':
    n_samples=10
    n_feature=5
    x=np.random.randn(n_samples,n_feature)
    y=np.array([1,2,2,3,2,3,1,3,1,3])
    y=y-1
    out_size=len(np.unique(y))
    y_1=np.zeros(shape=(n_samples,out_size))
    for i in range(len(y)):
        y_1[i][y[i]]=1
    
    epoch_num = 10000
    model=BPNN(5,3)
    losses = model.train(epoch_num=epoch_num,x=x,y=y_1,learning_rate=0.01)

关键点:
参数梯度变化的方向(dw)=输入的转置·误差
误差就是将上一层的误差通过上一层的w进行传递,在叉乘该层激活函数的导数。

参考文献:
王木头学科学:添加链接描述(非常宝藏的up主,通俗易懂的讲解原理)
鲁东大学课件:添加链接描述

python和numpy纯手写3层神经网络,干货满满:

BP神经网络以及在手写数字分类中python代码的详细注释

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值