当前位置: 首页 > news >正文

卷积运算结果的非线性处理|结果非负性

引言

前序学习进程中,已经简单学习了全连接层的概念,知晓全连接层先把数据展平,然后使用激活函数运算数据,最后将输出结果直接传递给下一层。
实际上全连接层就是激活函数发挥功能的层,通过激活函数可以引入非线性或者仅保留线性。
在最近的学习中,卷积运算的矩阵扩充和改变卷积核移动步长操作已经相对熟练,为此,我们把上述内容结合一下,对卷积运算后的结果矩阵稍作运算,把负值改为0,正值都保留。

非负值操作

在PyTorch里面,取非负值可以直接调用torch.max()函数,学习的官方链接为:torch.max。

torch.max(input,*,out)原理非常简单,给定输入张量input,直接返回所有输入张量里面的最大值。

维度最值计算

要想实现数据非负的转变,就把最小值设为0.0,比0小的全部都会改换成0.0,比0.0大的都会保持原值。由于我们的计算结果基本都是float.32这样的浮点数,所以把最小值设置为浮点数有利于保持数据稳定。
这里给一个简单示例:

importtorch# 生成5行5列的随机矩阵,符合标准正态分布a=torch.randn(5,5)print('a=',a)# 输出torch.max()函数对比效果print('torch.max(a, 1)=\n',torch.max(a,1))

这里先生成一个5行5列的标准正态分布随机数矩阵,然后进行非负性运算,获得的输出效果为:

根据输出可以看到,每一行的最大值都被挑选出来,并且还多了一行indices用来记录每一行中最大值所在的列位置。
我们最开始可能会以为所有数是和1比大小,但实际上是每一行内部元素自己比大小,1代表列这个维度,这是torch.max(a,1)中1的本意。

元素最值计算

那该如何让元素和1比大小,这就要仔细观察,我们是在张量里面定义数据,1不是张量,所以要对代码稍作改变,先把1转换为张量torch.tensor(1.0):

importtorch# 生成5行5列的随机矩阵,符合标准正态分布a=torch.randn(5,5)print('a=',a)# 输出torch.max()函数对比效果print('torch.max(a, 1)=\n',torch.max(a,1))t=torch.tensor(1.0)print('torch.max(a, torch.tensor(1.0))=\n',torch.max(a,t))

次时代码的输出效果为:

很显然,小于1.0的元素值都被强制赋值1.0,其余元素保持原值

卷积元素那结果非负运算

在上述讨论的基础上,直接进行卷积运算结果的非负运算就比较简单直观。
只需要先定义一个和原始计算结果一样大小的全0矩阵,然后开展非负运算就可以。
核心代码:

# 初始化全0矩阵,使其和卷积计算的结果矩阵大小一致relu_manual=torch.zeros_like(padded_output_cal_tensor)foriinrange(padded_output_cal_tensor.shape[0]):forjinrange(padded_output_cal_tensor.shape[1]):relu_manual[i,j]=torch.max(torch.tensor(0.0),padded_output_cal_tensor[i,j])print('ReLu输出:')print(relu_manual)

此时的运算效果为:

实际上,上述代码是为了逐行展示计算过程,也可以直接对比整个矩阵,让代码更简洁:

# 初始化全0矩阵,使其和卷积计算的结果矩阵大小一致#relu_manual_s=torch.zeros_like(padded_output_cal_tensor)relu_manual_s=torch.max(torch.tensor(0.0),padded_output_cal_tensor)print('ReLu输出:')print(relu_manual_s)

实际上只用了一行代码就完成了对比。
上述代码中的第一行被注释掉是因为确实没有用,进行torch.max()对比时,会自动给relu_manual_s创造一个和padded_output_cal_tensor等大的矩阵。
此时的输出效果和之前一样,这里给出两种代码写法输出的对比效果:

此时的完整代码为:

importtorch# 1. 定义原始输入(3通道5×5)和卷积核1(边缘检测核)input_tensor=torch.tensor([# 输入通道1(R):5×5[[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15],[16,17,18,19,20],[21,22,23,24,25]],# 输入通道2(G):5×5[[26,27,28,29,30],[31,32,33,34,35],[36,37,38,39,40],[41,42,43,44,45],[46,47,48,49,50]],# 输入通道3(B):5×5[[51,52,53,54,55],[56,57,58,59,60],[61,62,63,64,65],[66,67,68,69,70],[71,72,73,74,75]]],dtype=torch.float32)# 形状:(3,5,5)# 输出原始通道的形状input_tensor_channels,input_tensor_h,input_tensor_w=input_tensor.shapeprint('input_tensor_channels=',input_tensor_channels)print('input_tensor_h=',input_tensor_h)print('input_tensor_w=',input_tensor_w)# 卷积核1(边缘检测核):3个子核,每个3×3kernel1=torch.tensor([[[1,0,-1],[1,0,-1],[1,0,-1]],# 子核1(R通道)[[1,0,-1],[1,0,-1],[1,0,-1]],# 子核2(G通道)[[1,0,-1],[1,0,-1],[1,0,-1]]# 子核3(B通道)],dtype=torch.float32)# 形状:(3,3,3)# 输出卷积核的形状kernel1_channels,kernel1_h,kernel1_w=kernel1.shapeprint('kernel1_channels=',kernel1_channels)print('kernel1_h=',kernel1_h)print('kernel1_w=',kernel1_w)# 通过循环自动计算卷积值# 定义步长为1stride=1# 定义卷积运算后的输出矩阵大小out_h=(input_tensor_h-kernel1_h)//stride+1out_w=(input_tensor_w-kernel1_w)//stride+1# 为避免对原始矩阵造成破坏,新定义一个量来完全复制原始矩阵input_cal_tensor=input_tensor# 输出矩阵里面是所有卷积运算的效果,这个矩阵比原始矩阵小,先定义为纯0矩阵output_cal_tensor=torch.zeros((out_h,out_w),dtype=torch.float32)# 循环计算foriinrange(out_h):forjinrange(out_w):# 对原始矩阵取块进行计算,这里定义了取块的起始行in_h_start=i*stride# 取块的末行in_h_end=in_h_start+kernel1_h# 取块的起始列in_w_start=j*stride# 取块的末列in_w_end=in_w_start+kernel1_w# 这是取到的块input_patch=input_cal_tensor[:,in_h_start:in_h_end,in_w_start:in_w_end]# 定义一个空列表output_cal存储数据output_cal=[]forkinrange(input_tensor_channels):# k代表了通道数,因为每个通道都要进行卷积运算,# 因此需要先得到单通道的计算效果,然后把它们储存在列表中output_channel=(input_patch[k]*kernel1[k]).sum()#print('input_patch[',k,']=',input_patch[k])#print('output_patch[', k, ']=', kernel1[k])# 计算结果储存在列表中output_cal.append(output_channel)# 把通道效果叠加后,保存到卷积输出矩阵# 这里有3层,第一层是每一个子卷积核和原始矩阵的每一个通道进行卷积运算# 第二层是将第一层的卷积运算值按照顺序排放在列表output_cal中# 第三层再将列表中获得数据直接叠加,获得当下卷积运算的总值# 综合起来,卷积核和每一通道内的对应块对位相乘,然后全部求和,输出给当前记录卷积值的矩阵# 由于每一个i和j都会对应k个通道,所以使用了3层循环来表达output_cal_tensor[i,j]=sum(output_cal)#print('output_cal_tensor[', i, j, ']=', output_cal_tensor[i, j])print('output=',output_cal_tensor)# 扩充原始矩阵,周围补一圈0padding=1padded_h=input_tensor_h+2*padding padded_w=input_tensor_w+2*padding padded_input_tensor=torch.zeros((input_tensor_channels,padded_h,padded_w),dtype=torch.float32)padded_input_tensor[:,padding:-padding,padding:-padding]=input_tensorprint(padded_input_tensor)# 对扩充后的矩阵卷积运算# 计算卷积矩阵大小padded_out_h=(padded_h-kernel1_h)//stride+1padded_out_w=(padded_w-kernel1_w)//stride+1# 为避免对原始矩阵造成破坏,新定义一个量来完全复制原始矩阵padded_input_cal_tensor=padded_input_tensor# 输出矩阵里面是所有卷积运算的效果,这个矩阵比原始矩阵小,先定义为纯0矩阵padded_output_cal_tensor=torch.zeros((padded_out_h,padded_out_w),dtype=torch.float32)foriinrange(padded_out_h):forjinrange(padded_out_w):# 对原始矩阵取块进行计算,这里定义了取块的起始行in_h_start=i*stride# 取块的末行in_h_end=in_h_start+kernel1_h# 取块的起始列in_w_start=j*stride# 取块的末列in_w_end=in_w_start+kernel1_w# 这是取到的块input_patch=padded_input_cal_tensor[:,in_h_start:in_h_end,in_w_start:in_w_end]# 定义一个空列表output_cal存储数据output_cal=[]forkinrange(input_tensor_channels):# k代表了通道数,因为每个通道都要进行卷积运算,# 因此需要先得到单通道的计算效果,然后把它们储存在列表中output_channel=(input_patch[k]*kernel1[k]).sum()#print('input_patch[',k,']=',input_patch[k])#print('output_patch[', k, ']=', kernel1[k])# 计算结果储存在列表中output_cal.append(output_channel)# 把通道效果叠加后,保存到卷积输出矩阵# 这里有3层,第一层是每一个子卷积核和原始矩阵的每一个通道进行卷积运算# 第二层是将第一层的卷积运算值按照顺序排放在列表output_cal中# 第三层再将列表中获得数据直接叠加,获得当下卷积运算的总值# 综合起来,卷积核和每一通道内的对应块对位相乘,然后全部求和,输出给当前记录卷积值的矩阵# 由于每一个i和j都会对应k个通道,所以使用了3层循环来表达padded_output_cal_tensor[i,j]=sum(output_cal)#print('padded_output_cal_tensor[', i, j, ']=', padded_output_cal_tensor[i, j])print('padded_output=',padded_output_cal_tensor)# 使卷积计算的负值为0,正值不变# 初始化全0矩阵,使其和卷积计算的结果矩阵大小一致relu_manual=torch.zeros_like(padded_output_cal_tensor)foriinrange(padded_output_cal_tensor.shape[0]):forjinrange(padded_output_cal_tensor.shape[1]):relu_manual[i,j]=torch.max(torch.tensor(0.0),padded_output_cal_tensor[i,j])print('ReLu输出:')print(relu_manual)# 简洁代码# 初始化全0矩阵,使其和卷积计算的结果矩阵大小一致#relu_manual_s=torch.zeros_like(padded_output_cal_tensor)relu_manual_s=torch.max(torch.tensor(0.0),padded_output_cal_tensor)print('ReLu输出:')print(relu_manual_s)

细节说明

torch.max()函数包括维度最值和元素最值两种写法,虽然简单,但一定要区分比较的元素属性。

总结

学习了卷积运算结果的非线性处理中的非负性操作。

http://www.jsqmd.com/news/87515/

相关文章:

  • 掌握BOTW存档编辑器:5个实用技巧让海拉鲁冒险更精彩
  • K8s 证书又双叒过期?3招教你平滑轮换记录【转】
  • Step-Audio 2音频大模型:重新定义智能语音交互新纪元
  • ALVR无线串流性能调优终极指南
  • 5步掌握DolphinScheduler分布式工作流调度实战指南
  • FlashAttention突破性指南:如何用IO感知技术实现20倍内存节省
  • MS-SSIM:图像恢复领域的“黄金标准“评价指标
  • HTML5如何结合国密加密实现大文件安全存储?
  • 磁盘调度算法终极指南:Linux IO性能优化完整解决方案
  • 多级缓存设计思路——本地 + 远程的一致性策略、失效风暴与旁路缓存的取舍
  • 网页前端如何配合JSP完成1T文件分块上传?
  • AutoGPT读写分离实现:提升数据库并发能力
  • ExifToolGUI完全攻略:快速上手元数据编辑与GPS定位
  • PKHeX插件完全指南:解锁宝可梦数据管理新维度
  • 学Simulink——机器人力控场景实例:基于Simulink的永磁同步电机重力补偿力矩控制仿真
  • AutoGPT在儿童教育游戏设计中的互动情节生成
  • OpenPLC Editor开源工具在工业自动化领域的应用实践
  • 4大突破:Flash-Attention在AMD GPU上的性能跃迁实战指南
  • NVIDIA开源生态与硬件革新推动AI发展
  • 什么是 Vibe Coding?为什么提出者自己后来放弃了它
  • 湖北中教教育姚利民老师 做武汉学历提升靠谱引路人 - 速递信息
  • 学Simulink——移动机器人导航场景实例:基于Simulink的BLDC阿克曼转向Stanley算法路径跟踪仿真
  • 分治算法精解:归并排序技术的深度剖析与实践指南
  • 新能源行业“抢人战“升级:HR如何避免“招到的人用不上,想用的人招不来“?
  • 2、深入了解 Linux:特性、版本与文件系统
  • 学Simulink——机器人轨迹跟踪场景实例:基于Simulink的永磁同步电机多关节同步轨迹跟踪仿真
  • HeyGem.ai Docker部署完整指南:从环境配置到一键启动全流程
  • MATLAB COCO API实战指南:从数据困惑到精准分析的蜕变之路
  • 2025清障车市场盘点:领先生产厂家综合评测,落地清障车/重型清障车/清障车/3万左右清障车/帕菲特清障车/8吨清障车清障车源头厂家口碑排行 - 品牌推荐师
  • 3、Unix内核、内存寻址与进程管理全解析