news 2026/9/2 4:29:29

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
卷积运算结果的非线性处理|结果非负性

引言

前序学习进程中,已经简单学习了全连接层的概念,知晓全连接层先把数据展平,然后使用激活函数运算数据,最后将输出结果直接传递给下一层。
实际上全连接层就是激活函数发挥功能的层,通过激活函数可以引入非线性或者仅保留线性。
在最近的学习中,卷积运算的矩阵扩充和改变卷积核移动步长操作已经相对熟练,为此,我们把上述内容结合一下,对卷积运算后的结果矩阵稍作运算,把负值改为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()函数包括维度最值和元素最值两种写法,虽然简单,但一定要区分比较的元素属性。

总结

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 4:18:24

智能家居通知系统终极指南:从零开始搭建完美提醒机制

智能家居通知系统终极指南:从零开始搭建完美提醒机制 【免费下载链接】home-assistant.io :blue_book: Home Assistant User documentation 项目地址: https://gitcode.com/GitHub_Trending/ho/home-assistant.io 你是否曾经因为错过智能设备的重要状态而懊恼…

作者头像 李华
网站建设 2026/9/2 2:42:20

45、Python与PHP编程入门指南

Python与PHP编程入门指南 1. Python编程基础 在Python中,我们可以使用 break 和 continue 关键字来控制循环。 break 用于退出循环并继续后续处理,而 continue 则用于跳过当前循环迭代,直接进入下一次迭代。 1.1 Python函数 与PHP等语言不同,Python在执行代码时…

作者头像 李华
网站建设 2026/9/1 23:37:57

50、保障Linux系统安全的综合指南

保障Linux系统安全的综合指南 在当今数字化时代,保障系统安全至关重要。本文将为你介绍一系列保障Linux系统安全的方法和工具,包括端口扫描、无线网络安全、密码设置、文件完整性检查、病毒防护、防火墙配置以及灾难恢复计划等。 端口扫描工具Nmap Nessus在许多现代发行版…

作者头像 李华
网站建设 2026/9/1 22:11:30

大龄剩女的误区:偶像剧与真实爱情的距离

在繁华的都市里,白领女孩林珊常常在夜深人静时翻看剧集,眼里映着那些高大帅气、事业有成的男主。她心里默默设定:如果没有如此完美的伴侣,自己的人生就不完整。于是,周末的相亲、社交网络的“潜在男友”筛选&#xff0…

作者头像 李华
网站建设 2026/9/2 23:08:29

基于无线互联的电源健康监测与控制系统设计(论文+源码)

系统以STM32单片机作为数据采集以及处理的中心,通过HLW8032电能模块实现电器的电压、电流、功率等电能参数的检测,检测数据能通过OLED12864液晶实时显示,便于用户实时监测电器工作状态。用户可以根据实际需求通过按键对检测参数的阈值进行设定…

作者头像 李华
网站建设 2026/9/2 8:45:44

56、Linux内核管理全解析

Linux内核管理全解析 1. 内核版本号解析 在Linux系统中,为了对内核进行有效跟踪和管理,会为每个内核分配版本号。这些版本号通常是顺序的,但它们并不具备绝对的比较意义,不能简单地认为版本号高的内核就一定比低版本的更先进、功能更多或更稳定。例如,版本8的应用不一定…

作者头像 李华