美文网首页深度学习-推荐系统-CV-NLP
numpy 实现反向传播学习笔记

numpy 实现反向传播学习笔记

作者: 开飞机的乔巴 | 来源:发表于2019-12-18 15:12 被阅读0次

    本博客内容来源于网络以及其他书籍,结合自己学习的心得进行重编辑,因为看了很多文章不便一一标注引用,如图片文字等侵权,请告知删除。

    传统2D计算机视觉学习笔记目录------->传送门
    传统3D计算机视觉学习笔记目录------->传送门
    深度学习学习笔记目录 ------------------->传送门

    本文简介

    本文的主要目的就是描述出怎么使用numpy实现一个简单的神经网络,通过反向传播完成训练的过程,正如题目一样。当然我们不会像成熟的深度学习框架一样内部实现自动求导,那就太麻烦了。通过自己手写这么一份代码,可以让自己加深深度神经网络到底是怎么运作的,以达到我们的目的,而不再是完完全全的黑箱了。

    目前网上有很多相关的文章,我自己也通过那些文章得到很多的认识再最初学习的时候,但是总是感觉有一些不足,比如为了追求代码简洁,而失去了结构性,而我们使用的pytorch或者tensorflow有很好的面型对象的结构。所以本文实现的代码更注重结构性,和可拓展性,可以在此基础上在实现其他的一些简单的层。那么开始吧


    图文无关

    分步实现思路

    首先我们知道神经网络是有一些layer(层)组成的的,我们目前主要关注隐藏层,因为神经网络的主要计算是在隐藏层。这些层分别可以进行前向推导,反向传播,参数更新,所以我们先写这些层的基类,为方便调试,我们在初始化类时,要给层一个名字。

    class BaseLayer:
        def __init__(self,name):
            self.name = name
        def forward(self, input):            #前向推导
            pass
        def backward(self,grad):             #反向传播
            pass
        def update(self):                    #参数更新
            pass
    

    接着我们要实现全连接层,激活函数,以及损失函数。激活函数我们实现简单的sigmoid激活函数,损失函数我们实现带有softmax的CrossEntropyLoss。有关简单的激活函数和损失函数我会在其他文章详细描述。

    我们先实现sigmoid激活函数,由于sigmoid 中我们不需要更新任何的参数,所以不用重载参数更新函数。

    class SigmoidLayer(BaseLayer):
        def __init__(self, name):
            super(SigmoidLayer,self).__init__(name)
        def forward(self,input):
            self.output = 1/(1+np.exp(-input))
            return self.output
        def backward(self,grad):
            grad = grad * self.output*(1-self.output)
            return grad
    

    然后我们实现全连接层,在此我们将学习率简化为1,初始参数设置为正太分布随机参数,优化器也是最简单的批量梯度下降(BGD)

    class LinearLayer(BaseLayer):
        def __init__(self,name,input_channels,output_channels):
            super(LinearLayer,self).__init__(name)
            self.weight = np.random.randn( input_channels,output_channels )
            self.bias = np.random.randn(1,output_channels)
        def forward(self,input):
            self.input = input
            self.output = np.dot(self.input,self.weight)+ self.bias          # y = wx +b
            return self.output
        def backward(self,grad):
            self.batch_size = grad.shape[0]
            self.grad_w = np.dot(self.input.T,grad )/self.batch_size     # δw = δg * x
            self.grad_b = np.sum( grad , axis=0,keepdims= True )/self.batch_size
            grad = np.dot(grad,self.weight.T)
            return grad
        def update(self):
            self.weight -= self.grad_w
            self.bias -= self.grad_b
    

    然后我们来实现损失函数,以及softmax,我们可以将softmax的反向传播与CrossEntropy反向传播一起执行,可以简化整个过程。

    class SoftMaxLayer(BaseLayer):
        def __init__(self, name):
            super(SoftMaxLayer,self).__init__(name)
        def forward(self,input):
            vec_max = np.max( input,axis=1 )[np.newaxis,:].T
            input -= vec_max
            exp = np.exp(input)
            output = exp / (np.sum(exp,axis=1)[np.newaxis,:].T)
            return output
    
    class SMCrossEntropyLossLayer(BaseLayer):
        def __init__(self, name):
            super(SMCrossEntropyLossLayer,self).__init__(name)
        def forward(self,pred,real):
            self.softmax_p = SoftMaxLayer("softmax").forward(pred)
            self.real = real
            loss = 0
            for i in range(self.real.shape[0]):
                loss += -np.log( self.softmax_p[i,real[i]] )
            loss /= self.real.shape[0]
            return loss
        def backward(self):
            for i in range(self.real.shape[0]):
                self.softmax_p[i,self.real[i]] -= 1
            self.softmax_p = self.softmax_p / self.real.shape[0]
            return self.softmax_p
    

    现在我们将神经网络的基本的几个层实现完了,现在我们要将这些隐层组建成一个网络。我们实现一个基本的网络框架,然后再通过新的子类继承基类,只需要该变隐层结构就可以了。由于准备训练一个mnist手写数字数据,所以第一层的输入的维度是784。

    class NetBase:
        def __init__(self):
            self.layers = []
            
        def forward(self,input):
            for layer in self.layers:
                input = layer.forward(input)
            pred = SoftMaxLayer("softmax").forward(input)
            return input,pred
        def backward(self,grad):
            for layer in  reversed(self.layers):
                grad = layer.backward(grad)
                layer.update()
    
    class SimpleNet(NetBase):
        def __init__(self):
            super(SimpleNet,self).__init__()
            self.layers = [
                LinearLayer(name="full1",input_channels= 784, output_channels= 512),
                SigmoidLayer(name="relu1"),
                LinearLayer(name="full2",input_channels=512,output_channels=128),
                SigmoidLayer(name="sigmoid2"),
                LinearLayer(name="full3",input_channels=128,output_channels=10)
            ]
    

    整体代码

    现在我们将网络结构的代码以及训练代码放到一起。

    #BaseNet.py
    import numpy as np
    class BaseLayer:
        def __init__(self,name):
            self.name = name
        def forward(self, input):
            pass
        def backward(self,grad):
            pass
        def update(self):
            pass
    
    class SigmoidLayer(BaseLayer):
        def __init__(self, name):
            super(SigmoidLayer,self).__init__(name)
        def forward(self,input):
            self.output = 1/(1+np.exp(-input))
            return self.output
        def backward(self,grad):
            grad = grad * self.output*(1-self.output)
            return grad
    
    class LinearLayer(BaseLayer):
        def __init__(self,name,input_channels,output_channels):
            super(LinearLayer,self).__init__(name)
            self.weight = np.random.randn( input_channels,output_channels )
            self.bias = np.random.randn(1,output_channels)
        def forward(self,input):
            self.input = input
            self.output = np.dot(self.input,self.weight)+ self.bias
            return self.output
        def backward(self,grad):
            self.batch_size = grad.shape[0]
            self.grad_w = np.dot(self.input.T,grad )/self.batch_size 
            self.grad_b = np.sum( grad , axis=0,keepdims= True )/self.batch_size
            grad = np.dot(grad,self.weight.T)
            return grad
        def update(self):
            self.weight -= self.grad_w
            self.bias -= self.grad_b
    
    class SoftMaxLayer(BaseLayer):
        def __init__(self, name):
            super(SoftMaxLayer,self).__init__(name)
        def forward(self,input):
            vec_max = np.max( input,axis=1 )[np.newaxis,:].T
            input -= vec_max
            exp = np.exp(input)
            output = exp / (np.sum(exp,axis=1)[np.newaxis,:].T)
            return output
    
    class SMCrossEntropyLossLayer(BaseLayer):
        def __init__(self, name):
            super(SMCrossEntropyLossLayer,self).__init__(name)
        def forward(self,pred,real):
            self.softmax_p = SoftMaxLayer("softmax").forward(pred)
            self.real = real
            loss = 0
            for i in range(self.real.shape[0]):
                loss += -np.log( self.softmax_p[i,real[i]] )
            loss /= self.real.shape[0]
            return loss
        def backward(self):
            for i in range(self.real.shape[0]):
                self.softmax_p[i,self.real[i]] -= 1
            self.softmax_p = self.softmax_p / self.real.shape[0]
            return self.softmax_p
    
    class NetBase:
        def __init__(self):
            self.layers = []
            
        def forward(self,input):
            for layer in self.layers:
                input = layer.forward(input)
            pred = SoftMaxLayer("softmax").forward(input)
            return input,pred
        def backward(self,grad):
            for layer in  reversed(self.layers):
                grad = layer.backward(grad)
                layer.update()
    
    class SimpleNet(NetBase):
        def __init__(self):
            super(SimpleNet,self).__init__()
            self.layers = [
                LinearLayer(name="full1",input_channels= 784, output_channels= 512),
                SigmoidLayer(name="relu1"),
                LinearLayer(name="full2",input_channels=512,output_channels=128),
                SigmoidLayer(name="sigmoid2"),
                LinearLayer(name="full3",input_channels=128,output_channels=10)
            ]
    

    训练部分代码,由于numpy没有使用gpu来进行训练,训练整体还是比较慢的,所以我们只训练了 前100个数据,通过观察loss 就可以验证我们的网络是否进行工作。

    #train.py
    import BaseNet
    import numpy as np
    import matplotlib.pyplot as plt
    import os
    
    training_set_inputs  = []
    training_set_outputs   = []
    
    def read_mnist(mnist_image_file, mnist_label_file):
        if 'train' in os.path.basename(mnist_image_file):
            num_file = 60000
        else:
            num_file = 10000
        with open(mnist_image_file, 'rb') as f1:
            image_file = f1.read()
        with open(mnist_label_file, 'rb') as f2:
            label_file = f2.read()
        image_file = image_file[16:]
        label_file = label_file[8:]
        for i in range(num_file):
            label = int(label_file[i])
            image_list = [int(item) for item in image_file[i*784:i*784+784]]
            image_np = np.array(image_list, dtype=np.uint8).reshape(28*28)
            training_set_outputs.append([label])
            training_set_inputs.append( image_np )
    
    train_image_file = '/home/eric/data/mnist/train-images-idx3-ubyte'
    train_label_file = '/home/eric/data/mnist/train-labels-idx1-ubyte'
    read_mnist(train_image_file, train_label_file)
    training_set_inputs = np.array( training_set_inputs )
    training_set_outputs = np.array( training_set_outputs )
    
    training_set_inputs = training_set_inputs[:100,:]
    training_set_outputs = training_set_outputs[:100,:]
    
    net  = BaseNet.SimpleNet()
    loss = BaseNet.SMCrossEntropyLossLayer("loss")
    
    x = []
    y=[]
    for i in range(10000):
        input = training_set_inputs
        output,pred = net.forward(input)
        loss_value = np.squeeze(loss.forward(output,training_set_outputs))
        print(i,loss_value,np.sum( (np.equal(pred.argmax(axis = 1),training_set_outputs.T)))/ training_set_outputs.shape[0] )
        x.append(i)
        y.append(loss_value)
    
        delta = loss.backward()
        net.backward(delta)
    
    plt.plot(x,y,'r--')
    plt.title('loss')
    plt.show()
    

    总结

    写完这篇文章,才发现代码太多,没有太多的文字叙述,感觉要是一点点解释,怕是累死我,估计没有人像我这么笨吧。自己认为学习的过程还是需要自己用手就敲一遍,观察一下每个状态的输出,才能更好的理解。虽然代码很多但是其实也可以压缩成十几行,但是对初学者就太不友好了。


    重要的事情说三遍:

    如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )

    如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )

    如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )

    传统2D计算机视觉学习笔记目录------->传送门
    传统3D计算机视觉学习笔记目录------->传送门
    深度学习学习笔记目录 ------------------->传送门

    任何人或团体、机构全部转载或者部分转载、摘录,请保留本博客链接或标注来源。博客地址:开飞机的乔巴

    作者简介:开飞机的乔巴(WeChat:zhangzheng-thu),现主要从事机器人抓取视觉系统以及三维重建等3D视觉相关方面,另外对slam以及深度学习技术也颇感兴趣,欢迎加我微信或留言交流相关工作。

    相关文章

      网友评论

        本文标题:numpy 实现反向传播学习笔记

        本文链接:https://www.haomeiwen.com/subject/jzcunctx.html