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

这里写目录标题

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

二级目录

三级目录

神经网络的基本理解

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

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

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

神经网络公式

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

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

神经网络结构如上图所示,下图是上图的公式理解,模型参数的设定,关键在于层层神经元的个数,也就是说上层的输入维度需与下一层的输入维度相同。
例:输入样本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便是第一层网络对原始数据特征提取后得到的结果。

; 前向传播

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

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

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

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

第三层网络公式如下(第二层隐含层→输出层):

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

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

激活函数

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

误差求解

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

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

; 反向传播

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

(1)求解dw_out;

python和numpy纯手写BP神经神经网络
通过链式法则,将复合函数进行分解进行逐步求导。

python和numpy纯手写BP神经神经网络
h2-(5,N)

python和numpy纯手写BP神经神经网络
注意:1. 这里用的激活函数是sigmoid函数,不同的激活函数,在该项中求得的结果是不一样的;2. h3矩阵与(1-h3)矩阵相乘,而不是点乘,这里是对矩阵中的每个元素进行求导,因此出来的结果是 逐元素相乘。在求导过程中,有的是点乘,有的是相乘。

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

python和numpy纯手写BP神经神经网络
h2是输出层的输入值,(h3-y) (h3(1-h3))可以看做误差,所以dwout其实就是等于输入的转置点乘误差。(计算的时候,先计算误差,误差中的乘法运算是相乘。)
python和numpy纯手写BP神经神经网络

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

求解db_out;

python和numpy纯手写BP神经神经网络
db_out的输入只是单位矩阵I,其实就是将误差的所有行相加。
db就是误差的求和。

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

(2)求解dw_2;

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

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

(3)求解dw_1;

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

参数更新

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

w_out=w_out-a _dw_out
b_out=b_out-a_db_out
w_2=w_2-a _dw_2
b_2=b_2-a_db_2
w_1=w_1-a _dw_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)

        z_2=np.dot(h_1,self.w_2)+self.b_2
        h_2=self.sigmoid(z_2)

        z_3=np.dot(h_2,self.w_out)+self.b_out
        h_3=self.sigmoid(z_3)

        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)

        z_2=np.dot(h_1,self.w_2)+self.b_2
        h_2=self.sigmoid(z_2)

        z_3=np.dot(h_2,self.w_out)+self.b_out
        h_3=self.sigmoid(z_3)

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

Original: https://blog.csdn.net/Callmebig/article/details/122919657
Author: 皮皮王波什
Title: python和numpy纯手写BP神经神经网络

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/759428/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球