news 2026/6/10 14:04:35

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

作者头像

张小明

前端开发工程师

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/6/10 14:01:50

44、Perl与Python编程入门指南

Perl与Python编程入门指南 1. Perl编程基础 Perl在解析各种程序的输出方面表现出色,很多人会用awk和sed等工具来完成这类任务,但Perl提供了更丰富的功能。以下是一个简单的例子,展示如何使用Perl筛选出大于10KB的文件: $ ls -la | perl -nae ‘print “$F[8] is $F[4]\…

作者头像 李华
网站建设 2026/6/10 14:26:05

49、Mono开发与Linux安全防护全解析

Mono开发与Linux安全防护全解析 1. Mono库的使用 Ubuntu系统预装了一些基于Mono构建的程序,如Tomboy和Beagle,同时还附带了许多支持.NET的库。Mono的优势在于能让开发者轻松利用这些库进行开发,只需使用 using 语句导入即可开始编程。下面将通过两个实例展示如何构建更复…

作者头像 李华
网站建设 2026/6/10 14:29:18

Cookie Monster插件:Cookie Clicker游戏加速助手完整教程

Cookie Monster是专为Cookie Clicker游戏设计的一款实用增强插件,通过智能数据分析、实时收益追踪和精准时机提醒,帮助玩家轻松构建饼干帝国。本教程将从零开始带你掌握这款工具的使用技巧。 【免费下载链接】CookieMonster Addon for Cookie Clicker th…

作者头像 李华
网站建设 2026/6/10 14:39:50

62、Ubuntu系统使用与管理全解析

Ubuntu系统使用与管理全解析 1. 系统基础操作 在Ubuntu系统中,有许多基础操作是日常使用和管理的关键。例如,通过 ls 命令可以列出当前目录的文件,操作步骤为在终端输入 ls ,即可查看当前目录下的文件列表。若要切换目录,可使用 cd 命令,如 cd /home 能进入 /…

作者头像 李华
网站建设 2026/6/10 14:46:22

磁盘调度算法终极指南:Linux IO性能优化完整解决方案

磁盘调度算法终极指南:Linux IO性能优化完整解决方案 【免费下载链接】linux-tutorial :penguin: Linux教程,主要内容:Linux 命令、Linux 系统运维、软件运维、精选常用Shell脚本 项目地址: https://gitcode.com/GitHub_Trending/lin/linux…

作者头像 李华
网站建设 2026/6/9 21:21:57

200MB实现企业级AI:EmbeddingGemma开启本地智能新篇章

导语 【免费下载链接】embeddinggemma-300m-qat-q8_0-unquantized 项目地址: https://ai.gitcode.com/hf_mirrors/unsloth/embeddinggemma-300m-qat-q8_0-unquantized Google推出的300M参数嵌入模型EmbeddingGemma,以量化后不足200MB的内存占用实现了5亿参数…

作者头像 李华