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

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代码的详细注释

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!下面是一个简单的Python代码实现BP神经网络的示例: ```python import numpy as np class NeuralNetwork: def __init__(self, input_size, hidden_size, output_size): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size # 初始化权重和偏置 self.W1 = np.random.randn(self.input_size, self.hidden_size) self.b1 = np.zeros((1, self.hidden_size)) self.W2 = np.random.randn(self.hidden_size, self.output_size) self.b2 = np.zeros((1, self.output_size)) def forward(self, X): # 前向传播 self.z1 = np.dot(X, self.W1) + self.b1 self.a1 = self.sigmoid(self.z1) self.z2 = np.dot(self.a1, self.W2) + self.b2 self.a2 = self.sigmoid(self.z2) return self.a2 def backward(self, X, y, learning_rate): # 反向传播 m = X.shape # 计算输出层的误差 delta2 = self.a2 - y # 计算隐藏层的误差 delta1 = np.dot(delta2, self.W2.T) * self.sigmoid_derivative(self.a1) # 更新权重和偏置 dW2 = np.dot(self.a1.T, delta2) / m db2 = np.sum(delta2, axis=0) / m dW1 = np.dot(X.T, delta1) / m db1 = np.sum(delta1, axis=0) / m self.W2 -= learning_rate * dW2 self.b2 -= learning_rate * db2 self.W1 -= learning_rate * dW1 self.b1 -= learning_rate * db1 def train(self, X, y, epochs, learning_rate): for i in range(epochs): output = self.forward(X) self.backward(X, y, learning_rate) def predict(self, X): return np.round(self.forward(X)) def sigmoid(self, x): return 1 / (1 + np.exp(-x)) def sigmoid_derivative(self, x): return x * (1 - x) ``` 使用示例: ```python # 创建一个2-3-1的神经网络 nn = NeuralNetwork(2, 3, 1) # 输入数据 X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) y = np.array([, , , ]) # 训练神经网络 nn.train(X, y, epochs=10000, learning_rate=0.1) # 预测结果 print(nn.predict(X)) ``` 这是一个简单的BP神经网络的实现,其中包括前向传播、反向传播、训练和预测等功能。你可以根据自己的需求进行修改和扩展。希望对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值