实现卷积的几种代码方式

article/2025/10/7 12:01:53

目录

摘要

卷积(convolution)

1、pytorch实现

2、对input展开矩阵相乘

3、对kernel展开以及矩阵相乘

转置卷积

1、API实现

2、对kernel矩阵转置+矩阵相乘

总结

摘要

卷积的基本元素有着input size、kernel size、stride、padding、group以及dilation等等。在卷积中有着卷积(convolution)和转置卷积(transpose convolution)。其中卷积常常用于局部建模和下采样,而转置卷积则多用于上采用。本次学习针对二者的具体代码展开,并分别对官方api和手动复现进行学习比对。

卷积(convolution)

1、pytorch实现

在pytorch中有两个实现卷积的方法。一种是以类的形式,另外一种是以函数的进行进行调用。二者区别则为,以函数的进行调用无需手动化实例kernel,若以类的形式进行调用的话,则需自己手动将相关张量实例化。

首先对一些张量进行初始化,在二维的卷积中,input_size一搬是四维的张量。

import torch
import torch.nn as nn
import torch.nn.functional as F
import math
​
in_channels = 1 #输入的通道数
out_channels = 1 #输出的通道数
kernel_size = 3 #卷积核大小
batch_size = 1  #样本的数目
bias = False
input_size = [batch_size, in_channels, 4, 4]

通过类进行实现。首先实例化二维卷积对象,其次生成输入、调用正态分布随机函数,最后将将input_feature_map作为conv_layer的输入得到output_feature_map。

conv_layer = torch.nn.Conv2d(in_channels,out_channels,kernel_size,bias=bias)#实例化二维卷积的对象
input_feature_map = torch.randn(input_size) #生成输入,调用正态分布的随机函数
output_feature_map = conv_layer(input_feature_map) #将input_feature_map作为conv_layer的输入

通过函数进行实现,直接传入input,和kernel张量。

output_feature_map1 = F.conv2d(input_feature_map, conv_layer.weight)

最后来看二者的结果是否相同。

print(output_feature_map)
print(output_feature_map1)
print(torch.allclose(output_feature_map,output_feature_map1))

经验证二者结果是相同的。

2、对input展开矩阵相乘

将每次滑动相乘区域的input拉直,然后将这些向量拼凑成一个矩阵,之后和kernel矩阵进行矩阵相乘。在这里即可以手动写,也可以通过调用torch.Unfold完成。

先对一些张量进行初始化。

input = torch.randn(5,5)   #卷积输入特征图
kernel = torch.randn(3,3)   #卷积核
bias = torch.randn(1)       #卷积偏置,默认输出通道数目等于一,长度为1的随机量

step1: 用原始的矩阵运算来实现二维卷积,先不考虑batchsize维度和channels维度。pytorch中的维度是反过来的,从里到外,即从左到右,从上到下进行填充。

def matrix_multiplication_for_conv2d(input, kernel, bias=0, stride=1, padding=0):    if padding > 0:       input = F.pad(input, (padding, padding, padding, padding)) #对input进行填充操作  input_h, input_w = input.shape  kernel_h, kernel_w = kernel.shape   output_h = (math.floor((input_h - kernel_h)/stride) + 1)    #卷积输出的高度    output_w = (math.floor((input_w - kernel_w)/stride) + 1)    #卷积输出的宽度    output = torch.zeros(output_h, output_w)    #初始化输出矩阵    for i in range(0, input_h-kernel_h+1,stride):     #对高度进行遍历     for j in range(0, input_w-kernel_w+1,stride):     #对宽度进行遍历                  region=input[i:i+kernel_h, j:j+kernel_w]    #取出被核滑动到的区域            output[int(i/stride), int(j/stride)] = torch.sum(region * kernel) +bias     #点乘,并赋值给输出位置的元素  return output

step2: 用原始的矩阵运算来实现二维卷积,先不考虑batchsize维度和channels维度,flatten input版本。

def matrix_multiplication_for_conv2d_flatten(input, kernel, bias=0, stride=1, padding=0):if padding>0:input = F.pad(input,(padding, padding, padding, padding))input_h, input_w = input.shapekernel_h, kernel_w = kernel.shapeoutput_h = (math.floor((input_h-kernel_h)/stride)+1)    #卷积输出的高度output_w = (math.floor((input_w-kernel_w)/stride)+1)    #卷积输出的高度output = torch.zeros(output_h,output_w)     #初始化输出矩阵region_matrix = torch.zeros(output.numel(), kernel.numel())  #存储着所有的拉平后特征区域kernel_matrix = kernel.reshape((kernel.numel(), 1))     #kernel的列向量(矩阵)形式row_index = 0for i in range(0,input_h-kernel_h+1,stride):    #对高度维进行遍历for j in range(0,input_w-kernel_w+1,stride):    #对宽度维进行遍历region = input[i:i+kernel_h,j:j+kernel_w]   #取出被核滑动到的区域region_vector = torch.flatten(region)region_matrix[row_index] = region_vectorrow_index +=1output_matrix = region_matrix @ kernel_matrixoutput=output_matrix.reshape(output_h, output_w) +biasreturn  output

对三者的结果进行验证。

#矩阵运算实现卷积的结果
mat_mul_conv_output = matrix_multiplication_for_conv2d(input, kernel, bias=bias, padding=1, stride=2)
​
#调用PyTorch API卷积的结果
pytorch_api_conv_output = F.conv2d(input.reshape((1,1,input.shape[0],input.shape[1])),\                                   kernel.reshape((1,1,kernel.shape[0],kernel.shape[1])),\                                   padding=1,\                                   bias=bias, stride=2).squeeze(0).squeeze(0)
​
#矩阵运算实现卷积的结果,flatten input版本
mat_mul_conv_output_flatten = matrix_multiplication_for_conv2d_flatten(input, kernel, bias=bias, padding=1, stride=2)
​
#验证flatten版本卷积、非flatten版本卷积与PyTorch官方的卷积结果
print(mat_mul_conv_output_flatten)
print(mat_mul_conv_output)
print(pytorch_api_conv_output)
flag1 = torch.allclose(mat_mul_conv_output_flatten, pytorch_api_conv_output)
flag2 = torch.allclose(mat_mul_conv_output, pytorch_api_conv_output)
flag3 = torch.allclose(mat_mul_conv_output_flatten,mat_mul_conv_output)
​
print(flag1)
print(flag2)
print(flag3)
验证结果为三者一致。

step3: 用原始的矩阵运算来实现二维卷积,考虑batchsize维度和channels维度

def matrix_multiplication_for_conv2d_full(input, kernel, bias=0, stride=1, padding=0):#input、kernel都是思维张量if padding > 0:input = F.pad(input, (padding, padding, padding, padding, 0, 0, 0, 0))bs ,in_channel, input_h ,input_w = input.shapeout_channel, in_channel, kernel_h ,kernel_w = kernel.shapeif bias is None:bias = torch.zeros(out_channel)output_h = (math.floor((input_h - kernel_h) / stride) + 1)  # 卷积输出的高度output_w = (math.floor((input_w - kernel_w) / stride) + 1)  # 卷积输出的宽度output = torch.zeros(bs, out_channel, output_h, output_w)   #初始化输出矩阵for ind in range(bs):for oc in range(out_channel):for ic in range(in_channel):for i in range(0, input_h-kernel_h+1,stride):   #对高度进行遍历for j in range(0, input_w-kernel_w+1,stride):   #对宽度进行遍历region = input[ind, ic, i:i+kernel_h,j:j+kernel_w]  #取出被核滑动到的区域output[ind, oc, int(i/stride), int(j/stride)] +=torch.sum(region * kernel[oc, ic])   #点乘,并赋值给输出位置的元素output[ind,oc] +=bias[oc]return output

验证matrix_multiplication_for_conv2d_full与pytorch官方API是否一致

input = torch.randn(2, 2, 5, 5)
kernel = torch.randn(3, 2, 3, 3)
bias = torch.randn(3)pytorch_conv2d_api_output = F.conv2d(input, kernel ,bias=bias, padding=1, stride=2)
mm_conv2d_full_output = matrix_multiplication_for_conv2d_full(input, kernel ,bias=bias, padding=1, stride=2)
flag = torch.allclose(pytorch_conv2d_api_output,mm_conv2d_full_output)print("all close:", flag)

结果为二者一致

 

3、对kernel展开以及矩阵相乘

将每一步滑动相乘看作是把kernel填充到跟input一样大小的矩阵,然后将这个新的矩阵拉直,之后将每一步拉直后的向量堆叠起来构成一个kernel矩阵,再用这个kernel矩阵和input矩阵进行矩阵相乘。

def get_kernel_matrix(kernel, input_size):       #基于kenerl和输入特征图的大小来得到填充拉直后的kernel堆叠后的矩阵    kernel_h, kernel_w = kernel.shape   input_h, input_w = input_size    num_out_feat_map = (input_h-kernel_h + 1) * (input_w-kernel_w + 1)  result = torch.zeros((num_out_feat_map, input_h*input_w))   #初始化结果矩阵,输出特征图元素个数*输入特征图元素个数    count = 0    for i in range(0,input_h-kernel_h+1, 1):      for j in range(0,input_w-kernel_w+1, 1):          padded_kernel = F.pad(kernel, (j,input_w-kernel_w-j, i, input_h-kernel_h-i))    #填充成跟输入特征图一样大小           result[count] = padded_kernel.flatten()        count += 1   return result

result是基于kenerl和输入特征图的大小来得到填充拉直后的kernel堆叠后的矩阵

对结果和官方api进行验证。

kernel = torch.randn(3,3)
input = torch.randn(4,4)
kernel_matrix = get_kernel_matrix(kernel, input.shape)  #4*16mm_conv2d_full_output = kernel_matrix @ input.reshape((-1, 1))  #通过矩阵乘积来计算卷积
pytorch_conv2d_output = F.conv2d(input.unsqueeze(0).unsqueeze(0), kernel.unsqueeze(0).unsqueeze(0))print(mm_conv2d_full_output.reshape((2,2)))
print(pytorch_conv2d_output)   #2*2

可见二者是一致的。

 

转置卷积

将kernel矩阵转置再和卷积的输出进行相乘,即实现了上采样效果。同样的在转置卷积中,可以通过调用api和手写完成。

1、API实现

kernel = torch.randn(3,3)
input = torch.randn(4,4)pytorch_transposed_conv2d_output = F.conv_transpose2d(pytorch_conv2d_output, kernel.unsqueeze(0).unsqueeze(0))

2、对kernel矩阵转置+矩阵相乘

转置就是将kernel_matrix矩阵的负一维和负二维交换一下再与mm_conv2d_full_output矩阵相乘得出,即反向运算。

mm_conv2d_full_output = kernel_matrix @ input.reshape((-1, 1))
mm_transposed_conv2d_output = kernel_matrix.transpose(-1, -2) @ mm_conv2d_full_output  

对于手算和调用api结果进行验证,结论一致。

print(mm_transposed_conv2d_output.reshape((4,4)))
print(pytorch_transposed_conv2d_output)

 

 

总结

在对卷积的相关代码进行学习后,对于卷积的原理认识更加深刻了,在当前的大部分程序中,对于卷积大部分都是调用官方api,但通过手写卷积代码可以加深对其的理解,,手写框架代码也尤为重要。通过上手,对于PyTorch的许多方法不是很熟悉,因此下周准备对PyTorch的相关知识进行系统的学习。


http://chatgpt.dhexx.cn/article/UAl7yv7V.shtml

相关文章

卷积卷积神经网络

文章目录 一、关于卷积(convolution)的直观感受二、卷积在不同领域的应用三、卷积神经网络(CNN)的诞生四、卷积神经网络(CNN)(1)为什么需要卷积层(2)池化&…

MATLAB (n,k,m)卷积码原理及仿真代码(你值得拥有)

卷积码原理介绍 1.基本概念 首先卷积码是一种纠错码,让我们先从大格局出发,去认识卷积码。如图1所示我是先从通信原理书上了解了卷积码的概念,再结合网上部分资料,勉强搞懂,感觉主要需要掌握卷积码编码器、状态图、网…

通信原理学习笔记4:信道编码、分组码、卷积码、现代信道编码(Turbo码、LDPC码、Polar码)

信道编码 / 前向纠错码FEC 思想是在数据中增加冗余信息,即校验码元 / 监督码元,从而检错、纠错 信道编码的优劣评判 首先,最基本的是要追求低差错率 实现纠错很简单,只要多添加冗余信息就好;但实际中,我…

韩信点兵算法:

韩信点兵问题:韩信点兵不足百人,3人一行排列多一人,7人一行排列少两人,5人一行正好, 输出韩信究竟点了多少兵。 使用 math 类的DivRem 方法进行运算。 static void Main(string[] args){///韩信点兵不足百人&#xff…

韩信点兵

韩信点兵&#xff1a; 韩信带1500名兵士打仗&#xff0c;战死四五百人&#xff0c;站3人一排&#xff0c;多出2人&#xff1b;站5人一排&#xff0c;多出4人&#xff1b;站7人一排&#xff0c;多出6人。韩信马上说出人数&#xff1a;1049。 代码实现&#xff1a; <span styl…

韩信点兵(python)

韩信点兵 全部士兵按每行8人站立&#xff0c;剩余7人 全部士兵按每行7人站立&#xff0c;剩余6人 问题&#xff1a;已知每一营士兵人数在1000~2000之间&#xff0c;如何利用循环判断表示出代码逻辑 for num in range (1000,2000):if num % 87 and num %76 and num%65\and num%5…

经典算法--韩信点兵

韩信点兵是一道古代的数学题&#xff0c;题意&#xff1a;韩信点兵不足百人&#xff0c;三人一排多1人&#xff0c;七人一排少2人&#xff0c;五人一排正好。问韩信带兵多少&#xff1f; /*** 韩信点兵&#xff1a;* 韩信带兵不足百人&#xff0c;3人一排多1人&#xff0c;7人一…

枚举算法:韩信点兵。

韩信点兵。韩信在点兵的时候&#xff0c;为了知道有多少名士兵&#xff0c;同时又能保住军事机密&#xff0c;便让士兵排队报数。 按从1至5报数&#xff0c;最末一个士兵报的数为1。 再按从1至6报数&#xff0c;最末一个士兵报的数为5。 再按1至7报数&#xff0c;最末一个士兵报…

java工作流activity_activity 工作流学习(一)

启动流程实例 什么是流程实例?根据一个流程定义具体的一次执行过程就是一个流程实例,一个流程定义对应多个流程实例(一对多关系) 为了演示:在流程图中指定办理人是谁,现在是写死的,表示只能张三能提交请假申请。后面会讲解如何动态指定。 //根据流程定义的Id启动一个流程实…

工作流:一文让你学会使用flowable工作流

1.请假流程图 下图是 一个请假申请的简单流程图 &#xff08;1&#xff09;申请人通过发起流程进行请假申请&#xff0c;给经理发送一个待审批事项&#xff1b; &#xff08;2&#xff09;经理在待办列表选择事项&#xff0c;进行审批&#xff0c;approved同意或者rejected驳回…

jeesite工作流使用

问题&#xff1a;jeesite工作流如何使用&#xff1f; 背景&#xff1a;公司没人熟悉工作流&#xff0c;现在要上线办公系统&#xff0c;请假&#xff0c;加班&#xff0c;报销&#xff0c;预审批&#xff0c;用印&#xff0c;付款等工作流要写&#xff0c;之前有简单版本&…

工作流的大致开发流程

前段时间公司在做一个oa的项目&#xff0c;用到了flowable工作流&#xff0c;刚开始的时候还在纠结于是用activity还是flowable&#xff0c;后来查了相关资料发现flowable的作者之前就是开发activity的作者&#xff0c;只不过后来自己出去又搞了一套就叫做flowable&#xff0c;…

flowable工作流所有业务概念

1.什么是工作流审批 根据本人的理解&#xff0c;就是审批流程管理。 2.什么是flowable 1.官方解释 官方解释如下&#xff1a; Flowable 项目提供了一套核心的开源业务流程引擎&#xff0c;这些引擎紧凑且高效。它们为开发人员、系统管理员和业务用户提供工作流和业务流程管…

微服务与工作流

本文主要想谈一谈工作流在微服务系统中的使用以及工作流能够为微服务系统带来的好处。 通过查找资料可得&#xff0c;微服务的编排主要分为两种形式&#xff0c;一种是“choreography”&#xff0c;有人将其翻译成微服务的编排&#xff1b;另一种是“orchestration”,有人将其翻…

Camunda工作流引擎入门

文档集合 1、camunda文档&#xff1a;https://docs.camunda.org/get-started/quick-start/ 2、camunda资源下载&#xff1a;https://camunda.com/download/ 3、camunda示例github仓库&#xff1a;https://github.com/camunda/camunda-bpm-examples 4、camunda 代码仓库&…

工作流设计详解

工作流 概念&#xff1a; workflow流程性通知和审批控制&#xff0c;业务流程中、发送、提供附加信息或进行附加业务处理&#xff0c;两个或两个以上的人为共同目标&#xff0c;连续以并行或串行的方式完成某一业务。 工作流 设计&#xff1a; 按照业务规划流程图&#xff0…

什么是工作流?为什么程序员要用它?

每一个程序员&#xff0c;在接触到工作流的时候&#xff0c;都会有这么一个疑问——我用一般的方法可以实现&#xff0c;为什么还要用工作流&#xff1f; 我曾经也问过这个问题&#xff0c;不过现在稍微有点明白了。别着急要答案&#xff0c;看过下面的例子&#xff0c;或许你…

什么是工作流

什么是工作流&#xff1f; 工作流是从英文单词work flow中直译过来的。最直白的意思就是日常工作中相对固定的流程计算机化。 在此列举两个工作流简例&#xff1a; 客户到银行开户的工作流&#xff1a; 客户索取开户资料单——资料填写——营业员核对个人证件——营业员核对帐款…

什么是工作流?如何利用工作流引擎实现业务流程

工作流引擎是用来实现工作流的一种组件化工具&#xff0c;它是一整套解决方案&#xff0c;比如说一般工作流引擎包含这些功能&#xff1a;流程节点管理、流向管理等&#xff0c;是为了减小开发成本而推出的。因为在软件开发过程中&#xff0c;如果是从零开始实现工作流&#xf…

什么是工作流,flowable 与 Activiti对比

工作流 什么是工作流工作流是复杂版本的状态机Java工作流开源框架工作流对比Activiti设计器 Flowable兼容性 Camunda设计器兼容性&#xff1a;小结&#xff1a; 社区活跃度FlowableActivitiCamunda 总结 什么是工作流 工作流&#xff0c;是指“业务​过程的部分或整体在​计算…