NNDL 实验六 卷积神经网络(2)基础算子

 5.2 卷积神经网络的基础算子

我们先实现卷积网络的两个基础算子卷积层算子汇聚层算子


5.2.1 卷积算子

卷积层是指用卷积操作来实现神经网络中一层。

为了提取不同种类的特征,通常会使用多个卷积核一起进行特征提取。

附上二维卷积提取原理动图----很形象(来源:刘先生TT)

拥有多个通道的卷积,例如处理彩色图像时,分别对R, G, B这3个层处理的3通道卷积

再将三个通道的卷积结果进行合并(一般采用元素相加),得到卷积后的结果

在这里插入图片描述


5.2.1.1 多通道卷积


5.2.1.2 多通道卷积层算子

1. 多通道卷积卷积层的代码实现

2. Pytorch:torch.nn.Conv2d()代码实现

3. 比较自定义算子框架中的算子


1. 多通道卷积卷积层的代码实现

import torch.nn as nn
import torch
class Conv2D(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        super(Conv2D, self).__init__()
        # 创建卷积核
        self.weight = nn.Parameter(torch.ones(size=[out_channels, in_channels, kernel_size,kernel_size]))

        self.bias = nn.Parameter(torch.ones(size=[out_channels,1]))
        self.stride = stride
        self.padding = padding
        # 输入通道数
        self.in_channels = in_channels
        # 输出通道数
        self.out_channels = out_channels

    # 基础卷积运算
    def single_forward(self, X, weight):
        # 零填充
        new_X = torch.zeros([X.shape[0], X.shape[1]+2*self.padding, X.shape[2]+2*self.padding])
        new_X[:, self.padding:X.shape[1]+self.padding, self.padding:X.shape[2]+self.padding] = X
        u, v = weight.shape
        output_w = (new_X.shape[1] - u) // self.stride + 1
        output_h = (new_X.shape[2] - v) // self.stride + 1
        output = torch.zeros([X.shape[0], output_w, output_h])
        for i in range(0, output.shape[1]):
            for j in range(0, output.shape[2]):
                output[:, i, j] = torch.sum(
                    new_X[:, self.stride*i:self.stride*i+u, self.stride*j:self.stride*j+v]*weight,
                    axis=[1,2])
        return output

    def forward(self, inputs):
        """
        输入:
            - inputs:输入矩阵,shape=[B, D, M, N]
            - weights:P组二维卷积核,shape=[P, D, U, V]
            - bias:P个偏置,shape=[P, 1]
        """
        feature_maps = []
        # 进行多次多输入通道卷积运算
        p=0
        for w, b in zip(self.weight, self.bias): # P个(w,b),每次计算一个特征图Zp
            multi_outs = []
            # 循环计算每个输入特征图对应的卷积结果
            for i in range(self.in_channels):
                single = self.single_forward(inputs[:,i,:,:], w[i])
                multi_outs.append(single)
                # print("Conv2D in_channels:",self.in_channels,"i:",i,"single:",single.shape)
            # 将所有卷积结果相加
            feature_map = torch.sum(torch.stack(multi_outs), axis=0) + b #Zp
            feature_maps.append(feature_map)
            # print("Conv2D out_channels:",self.out_channels, "p:",p,"feature_map:",feature_map.shape)
            p+=1
        # 将所有Zp进行堆叠
        out = torch.stack(feature_maps, 1)
        return out

inputs = torch.as_tensor([[[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
               [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]]])
conv2d = Conv2D(in_channels=2, out_channels=3, kernel_size=2)
print("inputs shape:",inputs.shape)
outputs = conv2d(inputs)
print("Conv2D outputs shape:",outputs.shape)

# 比较与torch API运算结果
conv2d_torch = nn.Conv2d(in_channels=2, out_channels=3, kernel_size=2)
conv2d_torch.weight.data = nn.Parameter(torch.ones(size=[3, 2, 2, 2]))
conv2d_torch.bias.data = nn.Parameter(torch.ones(size=[3]))
outputs_torch = conv2d_torch(inputs)
# 自定义算子运算结果
print('Conv2D outputs:', outputs)
# torch API运算结果
print('nn.Conv2D outputs:', outputs_torch)

输出结果:

比较自定义算子和框架中的算子 :

Pytorch框架中,自定义算子和框架中的算子卷积核的参数是不一样的,从而导致两者输出结果不同。


5.2.1.3 卷积算子的参数量和计算量

参数量:

(来源:刘先生TT)

计算量:

(来源:刘先生TT) 

本来不懂,但是刘先生给的公式清晰明了,通俗易懂。


5.2.2 汇聚层算子
汇聚层的作用是进行特征选择,降低特征数量,从而减少参数数量。

由于汇聚之后特征图会变得更小,如果后面连接的是全连接层,可以有效地减小神经元的个数,节省存储空间并提高计算效率。

常用的汇聚方法有两种,分别是:平均汇聚、最大汇聚。

1. 代码实现一个简单的汇聚层。 

2. torch.nn.MaxPool2d();torch.nn.avg_pool2d()代码实现

3. 比较自定义算子和框架中的算子


import torch.nn as nn
import torch
class Pool2D(nn.Module):
    def __init__(self, size=(2, 2), mode='max', stride=1):
        super(Pool2D, self).__init__()
        # 汇聚方式
        self.mode = mode
        self.h, self.w = size
        self.stride = stride

    def forward(self, x):
        output_w = (x.shape[2] - self.w) // self.stride + 1
        output_h = (x.shape[3] - self.h) // self.stride + 1
        output = torch.zeros([x.shape[0], x.shape[1], output_w, output_h])
        # 汇聚
        for i in range(output.shape[2]):
            for j in range(output.shape[3]):
                # 最大汇聚
                if self.mode == 'max':
                    output[:, :, i, j] = torch.max(
                        x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h],
                )
                # 平均汇聚
                elif self.mode == 'avg':
                    output[:, :, i, j] = torch.mean(
                        x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h],
                )

        return output


inputs = torch.as_tensor([[[[1., 2., 3., 4.], [5., 6., 7., 8.], [9., 10., 11., 12.], [13., 14., 15., 16.]]]])
pool2d = Pool2D(stride=2)
outputs = pool2d(inputs)
print("input: {}, \noutput: {}".format(inputs.shape, outputs.shape))

# 比较Maxpool2D与torch API运算结果
maxpool2d_torch = nn.MaxPool2d(kernel_size=(2, 2), stride=2)
outputs_torch = maxpool2d_torch(inputs)
# 自定义算子运算结果
print('Maxpool2D outputs:', outputs)
# torch API运算结果
print('nn.Maxpool2D outputs:', outputs_torch)

# 比较Avgpool2D与torch API运算结果
avgpool2d_torch = nn.AvgPool2d(kernel_size=(2, 2), stride=2)
outputs_torch = avgpool2d_torch(inputs)
pool2d = Pool2D(mode='avg', stride=2)
outputs = pool2d(inputs)
# 自定义算子运算结果
print('Avgpool2D outputs:', outputs)
# torch API运算结果
print('nn.Avgpool2D outputs:', outputs_torch)

输出结果:

比较自定义算子和框架中的算子

两者应该是相同的。


汇聚层的参数量和计算量

由于汇聚层中没有参数,所以参数量为0;

最大汇聚中,没有乘加运算,所以计算量为0,

平均汇聚中,输出特征图上每个点都对应了一次求平均运算。


总结

这次实验我主要是理解了卷积算子的原理和实现步骤,了解了自定义算子和框架中算子的区别,导致最终结果不同的原因等,刘先生TT的博客给了我很大的帮助,取别人之长补自己之短,这样才能更快进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值