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

2023-10-30 20:50

本文主要是介绍NNDL 实验六 卷积神经网络(2)基础算子,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

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

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

5.2.1.1 多通道卷积


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

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

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

import torch
import torch.nn as nnclass Conv2D(nn.Module):def __init__(self, in_channels, out_channels,kernel_size,stride=1, padding=0,):super(Conv2D, self).__init__()# 使用'paddle.create_parameter'创建卷积核# 使用'paddle.ParamAttr'进行参数初始化weight_attr=torch.ones(out_channels,in_channels,kernel_size,kernel_size);bias_attr=torch.ones(out_channels,1);nn.init.constant_(weight_attr,1.0)nn.init.constant_(bias_attr, 0.0)self.weight = nn.Parameter(weight_attr)self.bias = nn.Parameter(bias_attr)# 步长self.stride = stride# 零填充self.padding = padding# 输入通道数self.in_channels = in_channels# 输出通道数self.out_channels = out_channelsdef 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] = Xu, v = weight.shapeoutput_w = (new_X.shape[1] - u) // self.stride+1output_h = (new_X.shape[2] - v) // self.stride+1output = 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 outputdef forward(self, inputs):"""输入:- inputs:输入矩阵,shape=[B, D, M, N]- weights:P组二维卷积核,shape=[P, D, U, V]- bias:P个偏置,shape=[P, 1]"""feature_maps = []# 进行多次多输入通道卷积运算p = 0for w, b in zip(self.weight, self.bias):  # P个(w,b),每次计算一个特征图Zpmulti_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),0) + b  # Zpfeature_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 outinputs = torch.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)# 比较与paddle API运算结果
conv2d_paddle = nn.Conv2d(in_channels=2, out_channels=3,kernel_size=2,)
outputs_paddle = conv2d_paddle(inputs)
# 自定义算子运算结果
print('Conv2D outputs:', outputs)
# paddle API运算结果
print('nn.Conv2D outputs:', outputs_paddle)
inputs shape: torch.Size([1, 2, 3, 3])
Conv2D outputs shape: torch.Size([1, 3, 2, 2])
Conv2D outputs: tensor([[[[20., 28.],[44., 52.]],[[20., 28.],[44., 52.]],[[20., 28.],[44., 52.]]]], grad_fn=<StackBackward>)nn.Conv2D outputs: tensor([[[[-2.0249, -2.6304],[-3.8416, -4.4472]],[[-0.2048, -0.6759],[-1.6180, -2.0890]],[[ 0.8913,  1.2849],[ 2.0720,  2.4656]]]], grad_fn=<ThnnConv2DBackward>)Process finished with exit code 0

pytorch框架中的卷积核参数与自定义中的卷积核参数不同,导致输出结果与自定义算子不同

卷积核参数如下:

Parameter containing:
tensor([[[[ 0.0776, -0.2293],[-0.0970,  0.3162]],[[ 0.0123,  0.0400],[-0.2898,  0.1585]]],[[[ 0.3282,  0.0408],[-0.0139, -0.2561]],[[ 0.0024, -0.0025],[-0.2003, -0.1251]]],[[[ 0.2519, -0.2914],[ 0.1964,  0.2667]],[[-0.0483,  0.0835],[-0.0808,  0.3366]]]], requires_grad=True)Process finished with exit code 0

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

可以想像,随着隐藏层神经元数量的变多以及层数的加深,使用全连接前馈网络处理图像数据时,参数量会急剧增加。

如果使用卷积进行图像处理,当卷积核为3×33×3时,参数量仅为99,相较于全连接前馈网络,参数量少了非常多。

5.2.2 汇聚层算子

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

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

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

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

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

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


class Pool2D(nn.Module):def __init__(self, size=(2, 2), mode='max', stride=1):super(Pool2D, self).__init__()# 汇聚方式self.mode = modeself.h, self.w = sizeself.stride = stridedef forward(self, x):output_w = (x.shape[2] - self.w) // self.stride + 1output_h = (x.shape[3] - self.h) // self.stride + 1output = 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] =\x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h]\.max(dim=2).values.max(dim=2).values# 平均汇聚elif self.mode == 'avg':output[:, :, i, j] = \x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h]\.mean(dim=[2, 3])return outputinputs = torch.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与paddle API运算结果
maxpool2d_paddle = nn.MaxPool2d(kernel_size=(2, 2), stride=2)
outputs_paddle = maxpool2d_paddle(inputs)
# 自定义算子运算结果
print('Maxpool2D outputs:', outputs)
# paddle API运算结果
print('nn.Maxpool2D outputs:', outputs_paddle)# 比较Avgpool2D与paddle API运算结果
avgpool2d_paddle = nn.AvgPool2d(kernel_size=(2, 2), stride=2)
outputs_paddle = avgpool2d_paddle(inputs)
pool2d = Pool2D(mode='avg', stride=2)
outputs = pool2d(inputs)
# 自定义算子运算结果
print('Avgpool2D outputs:', outputs)
# paddle API运算结果
print('nn.Avgpool2D outputs:', outputs_paddle)
input: torch.Size([1, 1, 4, 4]), 
output: torch.Size([1, 1, 2, 2])
Maxpool2D outputs: tensor([[[[ 6.,  8.],[14., 16.]]]])
nn.Maxpool2D outputs: tensor([[[[ 6.,  8.],[14., 16.]]]])
Avgpool2D outputs: tensor([[[[ 3.5000,  5.5000],[11.5000, 13.5000]]]])
nn.Avgpool2D outputs: tensor([[[[ 3.5000,  5.5000],[11.5000, 13.5000]]]])

自定义算子和框架算子参数相同。

汇聚层的参数量和计算量

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

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

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

总结:这次试验体会了自定义算子的流程,了解了池化和卷积操作,了解了框架算子的使用方法,以及与自定义算子的区别。

这篇关于NNDL 实验六 卷积神经网络(2)基础算子的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/310510

相关文章

从基础到高级详解Python数值格式化输出的完全指南

《从基础到高级详解Python数值格式化输出的完全指南》在数据分析、金融计算和科学报告领域,数值格式化是提升可读性和专业性的关键技术,本文将深入解析Python中数值格式化输出的相关方法,感兴趣的小伙... 目录引言:数值格式化的核心价值一、基础格式化方法1.1 三种核心格式化方式对比1.2 基础格式化示例

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

从基础到进阶详解Python条件判断的实用指南

《从基础到进阶详解Python条件判断的实用指南》本文将通过15个实战案例,带你大家掌握条件判断的核心技巧,并从基础语法到高级应用一网打尽,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录​引言:条件判断为何如此重要一、基础语法:三行代码构建决策系统二、多条件分支:elif的魔法三、

Python WebSockets 库从基础到实战使用举例

《PythonWebSockets库从基础到实战使用举例》WebSocket是一种全双工、持久化的网络通信协议,适用于需要低延迟的应用,如实时聊天、股票行情推送、在线协作、多人游戏等,本文给大家介... 目录1. 引言2. 为什么使用 WebSocket?3. 安装 WebSockets 库4. 使用 We

从基础到高阶详解Python多态实战应用指南

《从基础到高阶详解Python多态实战应用指南》这篇文章主要从基础到高阶为大家详细介绍Python中多态的相关应用与技巧,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、多态的本质:python的“鸭子类型”哲学二、多态的三大实战场景场景1:数据处理管道——统一处理不同数据格式

MySQL数据类型与表操作全指南( 从基础到高级实践)

《MySQL数据类型与表操作全指南(从基础到高级实践)》本文详解MySQL数据类型分类(数值、日期/时间、字符串)及表操作(创建、修改、维护),涵盖优化技巧如数据类型选择、备份、分区,强调规范设计与... 目录mysql数据类型详解数值类型日期时间类型字符串类型表操作全解析创建表修改表结构添加列修改列删除列

Python 函数详解:从基础语法到高级使用技巧

《Python函数详解:从基础语法到高级使用技巧》本文基于实例代码,全面讲解Python函数的定义、参数传递、变量作用域及类型标注等知识点,帮助初学者快速掌握函数的使用技巧,感兴趣的朋友跟随小编一起... 目录一、函数的基本概念与作用二、函数的定义与调用1. 无参函数2. 带参函数3. 带返回值的函数4.

python panda库从基础到高级操作分析

《pythonpanda库从基础到高级操作分析》本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据... 目录1. Pandas 概述2. 基本操作:数据读取与查看3. 索引操作:精准定位数据4. Group

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

安装centos8设置基础软件仓库时出错的解决方案

《安装centos8设置基础软件仓库时出错的解决方案》:本文主要介绍安装centos8设置基础软件仓库时出错的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录安装Centos8设置基础软件仓库时出错版本 8版本 8.2.200android4版本 javas