手撕神经网络(2)—— 将基本组件搭建成躯干

前言

之前写了篇文章《手撕神经网络(1)——神经网络的基本组件》介绍了手撕神经网络的各个层。但是疫情和学习原因,导致其后续一直没有机会写,这两天难得空闲,终于有机会继续分享。

概要

在文章《手撕神经网络(1)——神经网络的基本组件》一文中,我们将神经网络的各个组件,通过python类的方式定义完毕,而在这篇文章中,我们就来使用这些积木,搭建成我们的神经网络。

网络结构

为双层神经网络,即含有:

  • 一个输入层
  • 一个隐藏层
  • 一个输出层
    我们仍然是使用python类来定义网络的结构

初始化

超参数

显然,对于一个双层神经网络,其有哪些超参数?—— 最基本的就是:各个层的神经元个数,分别设置为:input_size,hidden_size,output_size

网络参数

权重

对于一个双层神经网络,其参数为第一层的权重 w 1 , b 1 w_1,b1 w1,b1,第二层的权重 w 2 , b 2 w_2,b_2 w2,b2。我们将这些参数协程该类的属性:

class TwoLayerNet:
    def __init__(self, input_size,hidden_size,output_size,weight_init_std=0.01):
        # 初始化权重
        self.params={}
        self.params['W1']=weight_init_std*np.random.randn(input_size,hidden_size)
        self.params['b1']=np.zeros(hidden_size)
        self.params['W2']=weight_init_std*np.random.randn(hidden_size,output_size)
        self.params['b2']=np.zeros(output_size)

这里,为了方便对第一层的权重 w 1 , b 1 w_1,b1 w1,b1,第二层的权重 w 2 , b 2 w_2,b_2 w2,b2的管理,我们使用一个容器(字典)来包装它们。

你可能会发现,我们本文开头提到的搭建积木的事,直到现在都没有提起。也没有用到积木。不着急,在使用这些积木搭建我们的模型之前,我们先来想一下我们的神经网络的功能:

  • 预测:通过正向传播,将输入转化为输出。即我们的神经网络类需要有forward方法。
  • 学习:通过反向传播,通过计算实际输出与正确标签之间的误差,计算误差的梯度,在通过梯度下降法完成网络参数的更新。即我们的神经网络类需要有backward方法。
    现在,你可以回到文章《手撕神经网络(1)——神经网络的基本组件》中看一看,我们的积木——也就是每一个层的类,都定义了什么方法?—— 正好是我们整个神经网络所需要的forward方法和backward方法!
    所以,讲到这里,你脑海里一定闪过一个聪明的想法:要实现整个神经网络的正向(反向)传播,只需要用一个for loop遍历所有的层(也就是我们的积木),将上一个层的正向(反向)传播所得到的结果传递给下一层不就行了吗!
    ——是的,这也是链式法则的思想,也是我们接下来要做的事情。但是,先别着急,在做这些之前,我们还需要将这些层(积木)作为属性传递给我们的神经网络类,因为我们无论是在forward方法还是backward方法中,都会用到!
        # 各个层
        self.layers=OrderedDict()
        self.layers['Affine1']=Affine(self.params['W1'],self.params['b1'])
        self.layers['ReLU1']=Relu()
        self.layers['Affine2']=Affine(self.params['W2'],self.params['b2'])
        self.lastLayer=SoftmaxWithLoss()

注意,这里出于同样地目的,我是用了字典这个容器来包装各个层。但是这个字典不是一个普通的字典,而是一个有序字典(普通的哈希表是无序的,这一点你是知道的)。这是因为我们希望各个层在进行正向和反向传播时都能保持前后顺序。

讲到这里,实际上我们的搭积木过程也完成了。—— 你可能会疑惑:嗯?你真的搭积木了吗,我怎么没有发现?——嗯,我真的搭建了,因为搭积木不就是把我们的组件(层)按照一定的顺序组合起来吗。我们的有序字典已经完成了这件事情!

完整初始化代码

from collections import OrderedDict


class TwoLayerNet:
    def __init__(self, input_size,hidden_size,output_size):
        # 初始化权重
        self.params={}
        self.params['W1']=np.random.randn(input_size,hidden_size)
        self.params['b1']=np.zeros(hidden_size)
        self.params['W2']=np.random.randn(hidden_size,output_size)
        self.params['b2']=np.zeros(output_size)

        # 各个层
        self.layers=OrderedDict()
        self.layers['Affine1']=Affine(self.params['W1'],self.params['b1'])
        self.layers['ReLU1']=Relu()
        self.layers['Affine2']=Affine(self.params['W2'],self.params['b2'])
        self.lastLayer=SoftmaxWithLoss()

前向传播

我们的积木已经搭建完成,但是距离最终的任务还很遥远,因为我们现在只是有一个框架,我们所搭建的模型还没有一些实用的功能。神经网络的一个最基本的功能应该是——正向传播。其实现方法之前已经介绍过,通过for loop即可:

    def predict(self,x):
        for layer in self.layers.values():
            x=layer.forward(x)    # 逐层前向传播
        return x

反向传播

想一下,我们反向传播,传播的是什么?——是误差(对参数的梯度),是损失(对参数的梯度)。所以,我们在实现反向传播的方法之前,需要先实现一个误差计算的方法。

    def loss(self,x,t):
        y=self.predict(x)
        return self.lastLayer.forward(y,t)

下面,需要实现反向传播,我们的方法要完成这样的功能:输入数据对 ( X , y ) (X,y) (X,y),计算误差loss并计算loss对网络参数的梯度,然后将梯度返回。

    def backward(self,x,t):
        self.loss(x,t)    # 前向传播
        
        dout=1  # 最后一层的反向输入,为1
        dout=self.lastLayer.backward(dout)
        
        layers=list(self.layers.values())  # 将所有的层按顺序放入列表中
        layers.reverse()  # 反向传播,需要从最后一层开始,一直向前传播
        
        for layer in layers:    # 反向传播
            dout=layer.backward(dout)
            
        # 获取梯度
        grads={}
        grads['W1']=self.layers['Affine1'].dW
        grads['b1']=self.layers['Affine1'].db
        grads['W2']=self.layers['Affine2'].dW
        grads['b2']=self.layers['Affine2'].db
        
        return grads

在上面的代码中,有一点需要注意,那就是反向传播的过程完成以后,各个Affine层的梯度属性自动被更新,我们直接取各Affine个层的梯度属性即可。

其他方法

为了使神经网络的功能更加完善,我们再给神经网络添加一个方法,比如计算精度的方法:

    def accuracy(self,x,t):
        y=self.predict(x)
        y=np.argmax(y,axis=1)
        if t.ndim != 1:
            t=np.argmax(t,axis=1)
        accuracy=np.sum(y==t)/float(x.shape[0])
        return accuracy

到目前为止,整个神经网络的搭建已经完成! 下一篇文章中,我将给出训练的过程。

代码

https://github.com/HanggeAi/numpy-neural-network

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经典卷积神经网络是指使用numpy纯写的卷积神经网络代码,该代码可以帮助理解卷积神经网络的原理。它不使用任何神经网络框架,适合那些有意愿深入理解卷积神经网络底层的人群。这个的代码相对简单,但是通过研究它,可以充分理解卷积神经网络的工作原理。 卷积神经网络(CNN)是一种常用于图像处理和识别的深度学习模型。它通过卷积层、池化层和全连接层等组,实现了对图像特征的提取和分类。在卷积神经网络中,卷积层通过滤波器(卷积核)对输入图像进行卷积操作,以提取图像的局部特征。池化层则通过降采样的方式,减少特征图的尺寸,同时保留重要的特征信息。全连接层将特征图转化为一维向量,并通过神经网络的计算得出最终的分类结果。 通过经典卷积神经网络的代码,我们可以更加深入地了解卷积神经网络的计算过程。该代码中的全连接层实际上就是指上述提到的全连接神经网络,它将最后一次卷积操作的输出作为输入,并通过神经网络的计算产生最终的输出结果。 总之,经典卷积神经网络可以帮助我们更好地理解卷积神经网络的原理和计算过程。通过研究这个代码,我们可以深入了解卷积操作、池化操作和全连接操作在卷积神经网络中的应用,从而更好地应用和设计卷积神经网络模型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值