CNN 纯numpy 实现 +完整的梯度回传

动机

虽然主流的框架搭建简单得神经网络已经很方便了,但是总觉得用别人的黑箱没办法理解精髓,为了更深刻的理解CNN结构和梯度下降机制,自己花了两周时间只用python 和 numpy 实现了一个简单的CNN网络, 包括完整得梯度回传和更新, 数据加载部分用了pytorch的接口,真的很方便。

总结

网络可以自己定义一个类调用底层模块进行设计,并且按和前向计算相反的顺序调用对应层的梯度回传函数,就可以实现梯度下降,我写了一个简单的两层卷积层加两层全连接层的网络用于训练minist,可以参考。

整个实现过程最难的就是卷积层批量数据+多通道+padding 的梯度回传实现,参考了很多博客,自己推导了好几天才理清楚,整个过程下来,对于梯度回传和CNN网络的理解又更加深刻了。

代码还有很多可以优化的地方,比如没办法支持resnet式的结构,代码效率不高,因为大三课比较多,加上其它一堆事情要做,所以没办法对代码进行进一步的优化,还请大家多多包涵。 目前训练四个epoch准确率能达到98% 左右。

在这里插入图片描述
nn.py

import numpy as np
import torch
import torch.nn.functional as F
import torchvision.datasets as datasets #加载数据
import torchvision.transforms as transforms #数据增强
import matplotlib.pyplot as plt
from tensorboardX import  SummaryWriter
class Conv_2D():
    def __init__(self, input_dim, output_dim, ksize=3,
                 stride=1, padding=(0,0), dilataion=None):
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.ksize = ksize
        self.stride = stride
        self.padding = padding #(1,2) 左边 和 上边 填充一列 右边和下边填充2列
        self.dilatation = dilataion
        self.output_h = None
        self.output_w = None

        self.patial_w = None
        # 产生服从正态分布的多维随机随机矩阵作为初始卷积核
        # OCHW
        # self.conv_kernel = np.random.randn(self.output_dim, self.input_dim, self.kernelsize, self.kernelsize)  # O*I*k*k
        self.grad = np.zeros((self.output_dim, self.ksize, self.ksize, self.input_dim), dtype=np.float64)
        # 产生服从正态分布的多维随机随机矩阵作为初始卷积核
        self.input = None
        # OCh,w
        self.weights = np.random.normal(scale=0.1,
            size= (output_dim, input_dim, ksize, ksize))
        self.weights.dtype =np.float64
        self.bias = np.random.normal(scale=0.1,size = output_dim)
        self.bias.dtype = np.float64

        self.weights_grad = np.zeros(self.weights.shape)  # 回传到权重的梯度
        self.bias_grad = np.zeros(self.bias.shape)  # 回传到bias的梯度
        self.Jacobi = None  # 反传到输入的梯度

    def forward(self, input):
        '''

        :param input: (N,C,H,W)
        :return:
        '''
        assert len(np.shape(input)) == 4
        input = np.pad(input, ((0, 0), (0, 0), (self.padding[0], self.padding[1]),
                               (self.padding[0], self.padding[1])), mode='constant', constant_values=0)
        self.input = input

        self.Jacobi = np.zeros(input.shape)
        N, C, H, W = input.shape


        # 输出大小
        self.output_h = (H - self.ksize) / self.stride + 1
        self.output_w = (W - self.ksize ) / self.stride + 1

        # 检查是否是整数
        assert self.output_h % 1 == 0
        assert self.output_w % 1 == 0
        self.output_h = int(self.output_h)
        self.output_w = int(self.output_w)

        imgcol = self.im2col(input, self.ksize, self.stride)  # (N*X,C*H*W)


        output = np.dot(imgcol,
                        self.weights.reshape(self.output_dim, -1).transpose(1, 0))  # (N*output_h*output_w,output_dim)


        output += self.bias
        output = output.reshape(N, self.output_w * self.output_h, self.output_dim). \
            transpose(0, 2, 1).reshape(N, int(self.output_dim), int(self.output_h), int(self.output_w))

        return output

    def backward(self, last_layer_delta,lr):
        '''
        计算传递到上一层的梯度
        计算到weights 和bias 的梯度 并更新参数
        :param last_layer_delta: 输出层的梯度 (N,output_dim,output_h,output_w)
        :return:
        '''
        def judge_h(x):
            if x % 1 == 0 and x <= self.output_h-1 and x >= 0:
                return int(x)
            else:
                return -1
        def judge_w(x):
            if x % 1 == 0 and x <= self.output_w - 1 and x >= 0:
                return int(x)
            else:
                return -1
        # 根据推到出的公司 找出索引 与卷积权重相乘
        for i in range(self.Jacobi.shape[2]):  # 遍历输入的高
            for j in range(self.Jacobi.shape[3]):  # W
                mask = np.zeros((self.input.shape[0], self.output_dim,
                                 self.ksize, self.ksize))  # (N,O,k,k)
                index_h = [(i - k) / self.stride for k in range(self.ksize)]
                index_w = [(j - k) / self.stride for k in range(self.ksize)]
                index_h_ = list(map(judge_h, index_h))
                index_w_ = list(map(judge_w, index_w))

                for m in range(self.ksize):
                    for n in range(self.ksize):
                        if index_h_[m] != -1 and index_w_[n] != -1:
                            mask[:, :, m, n] = last_layer_delta[:, :, index_h_[m], index_w_[n]]  # (N,O,1,1)
                        else:
                            continue
                mask = mask.reshape(self.input.shape[0], 1, self.output_dim, self.ksize, self.ksize)
        
  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值