news 2026/8/31 4:21:19

深度学习26转置卷积

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度学习26转置卷积

1. 转置卷积

转置卷积通俗讲解

先记住一句话:普通卷积把图片变小;转置卷积(也叫反卷积)把图片变大,它不是普通卷积求逆,只是把输出尺寸还原回去

1. 普通卷积可以等价变成矩阵乘法

  • \(Y=X \star W\):X是输入图片,W是卷积核,普通卷积做完,图片高宽会缩小:\((h,w)\rightarrow(h',w')\)。
  • 我们可以把图片X拉成一长条向量\(X'\),输出图片Y也拉成长条向量\(Y'\)。
  • 然后构造一个大矩阵V,卷积操作完全等价做矩阵乘法:\(Y'=V X'\)

简单说:卷积不用滑动窗口,直接乘大矩阵 V,也能得到一模一样的结果。 假设:输入向量\(X'\)长度是n,输出向量\(Y'\)长度是m,那矩阵V的形状就是 \(\boldsymbol{m \times n}\)。 输入 n 个元素,输出 m 个元素,\(m<n\),所以输出图片尺寸变小。

2. 什么是转置 \(V^T\)

矩阵V是\(m\times n\),它的转置\(V^T\)就是把行和列互换,变成 \(\boldsymbol{n\times m}\)。

  • 原来矩阵 V:n 个输入 → m 个输出(n 大 m 小,图片缩小)
  • 转置后的矩阵\(V^T\):m 个输入 → n 个输出(m 小 n 大,图片放大!)

于是就有:转置卷积等价 \(Y'=V^T X'\)

3. 尺寸关系(最核心)

  1. 普通卷积:输入图片尺寸\((h,w)\) → 输出\((h',w')\),图片变小
  2. 同样的卷积核、同样 padding、步长这些超参数,换成转置卷积:输入\((h',w')\) → 输出变回\((h,w)\),图片被放大回去。

⚠️重要误区:

❗转置卷积不能复原原始像素数值!只是把图片高宽尺寸还原,不是数学上把图片完完全全变回去。 不是普通卷积的逆运算,只是借用矩阵 V 的转置,实现放大图片。

举个生活化小例子

想象普通卷积:一张大照片,经过卷积,压缩成一张小缩略图。 这个压缩过程,可以写成矩阵 V 乘法。

转置卷积就是拿这个 V 做转置,拿小缩略图作为输入,输出一张大尺寸图片。

注意:输出的大图不是原来那张原图,只是尺寸和原图一样大,像素是网络重新算出来的,常用于 GAN、Unet 分割网络里,把小特征图放大。

对应图里的 n、m

  • V:\(m \times n\),普通卷积:输入向量长度 n,输出 m,\(m<n\),图变小
  • \(V^T\):\(n \times m\),转置卷积:输入向量长度 m,输出 n,(n>m),图变大

一句话总结

普通卷积 = 矩阵 V,图片缩小; 转置卷积 = 矩阵 V 的转置V^T,用一样的超参数,把特征图尺寸再放大回去。

2. 转置卷积

import torch from torch import nn from d2l import torch as d2l
# 实现基本的转置卷积运算 # 定义转置卷积运算 def trans_conv(X, K): # 获取卷积核的宽度和高度 h, w = K.shape # 卷积核的宽、高 # 创建一个新的张量Y,其尺寸为输入X的尺寸加上卷积核K的尺寸减去1。在常规卷积中,输出尺寸通常是输入尺寸减去卷积核尺寸加1 Y = torch.zeros((X.shape[0] + h -1, X.shape[1] + w - 1)) # 正常的卷积后尺寸为(X.shape[0] - h + 1, X.shape[1] - w + 1) # 遍历输入张量X的每一行 for i in range(X.shape[0]): # 遍历输入张量X的每一列 for j in range(X.shape[1]): # 对于输入X的每一个元素,我们将其与卷积核K进行元素级别的乘法,然后将结果加到输出张量Y的相应位置上 Y[i:i + h, j:j + w] += X[i, j] * K # 按元素乘法,加回到自己矩阵 # 返回转置卷积的结果 return Y
# 验证上述实现输出 # 定义输入张量X,这是一个2x2的矩阵 X = torch.tensor([[0.0,1.0],[2.0,3.0]]) # 定义卷积核K,也是一个2x2的矩阵 K = torch.tensor([[0.0,1.0],[2.0,3.0]]) # 调用上面定义的trans_conv函数,对输入张量X和卷积核K进行转置卷积操作,并打印结果 trans_conv(X,K)
tensor([[ 0., 0., 1.], [ 0., 4., 6.], [ 4., 12., 9.]])
# 使用高级API获得相同的结果 # 将输入张量X和卷积核K进行形状变换,原来是2x2的二维张量,现在变成了1x1x2x2的四维张量 # 第一个1表示批量大小,第二个1表示通道数,2x2是卷积核的高和宽 X, K = X.reshape(1,1,2,2), K.reshape(1,1,2,2) # 创建一个转置卷积层对象tconv,其中输入通道数为1,输出通道数为1,卷积核的大小为2,没有偏置项 tconv = nn.ConvTranspose2d(1,1,kernel_size=2,bias=False) # 输入通道数为1,输出通道数为1 # 将创建的转置卷积层对象tconv的权重设置为我们的卷积核K tconv.weight.data = K # 使用创建的转置卷积层tconv对输入张量X进行转置卷积操作,并返回结果 tconv(X)
tensor([[[[ 0., 0., 1.], [ 0., 4., 6.], [ 4., 12., 9.]]]], grad_fn=<SlowConvTranspose2DBackward0>)
# 填充、步幅和多通道 # 填充在输出上,padding=1,之前输出3x3,现在上下左右都填充了1,那就剩下中心那个元素了 # 填充为1,就是把输出最外面的一圈当作填充 # 创建一个转置卷积层对象tconv,其中输入通道数为1,输出通道数为1,卷积核的大小为2,没有偏置项,同时设置填充大小为1 # 填充(padding)操作在输出上执行,原本输出为3x3,由于填充了大小为1的边框,结果就只剩下中心的元素 # 所以填充大小为1就相当于将输出矩阵最外面的一圈当作填充并剔除 tconv = nn.ConvTranspose2d(1,1,kernel_size=2,padding=1,bias=False) # 将创建的转置卷积层对象tconv的权重设置为我们的卷积核K tconv.weight.data = K # 使用创建的转置卷积层tconv对输入张量X进行转置卷积操作,并返回结果 tconv(X)
tensor([[[[4.]]]], grad_fn=<SlowConvTranspose2DBackward0>)
# 创建一个转置卷积层对象tconv,其中输入通道数为1,输出通道数为1,卷积核的大小为2,步幅(stride)为2,没有偏置项 # 步幅为2表示在进行卷积时,每次移动2个单位,相较于步幅为1,这样会使得输出尺寸增大 tconv = nn.ConvTranspose2d(1,1,kernel_size=2,stride=2,bias=False) # 将创建的转置卷积层对象tconv的权重设置为我们的卷积核K tconv.weight.data = K # 使用创建的转置卷积层tconv对输入张量X进行转置卷积操作,并返回结果 tconv(X)
tensor([[[[0., 0., 0., 1.], [0., 0., 2., 3.], [0., 2., 0., 3.], [4., 6., 6., 9.]]]], grad_fn=<SlowConvTranspose2DBackward0>)
# 多通道 # 创建一个四维张量X,批量大小为1,通道数为10,高和宽都为16 X = torch.rand(size=(1,10,16,16)) # 创建一个二维卷积层对象conv,其中输入通道数为10,输出通道数为20,卷积核大小为5,填充为2,步幅为3 # 这会将输入的10个通道的图像转换为20个通道的特征图 conv = nn.Conv2d(10,20,kernel_size=5,padding=2,stride=3) # 创建一个转置卷积层对象tconv,其中输入通道数为20,输出通道数为10,卷积核大小为5,填充为2,步幅为3 # 这会将输入的20个通道的特征图转换回10个通道的图像 tconv = nn.ConvTranspose2d(20,10,kernel_size=5,padding=2,stride=3) # 首先对输入张量X进行卷积操作,然后再对卷积的结果进行转置卷积操作 # 然后检查这个结果的形状是否和原始输入张量X的形状相同 # 如果相同,说明转置卷积操作成功地还原了原始输入的形状 tconv(conv(X)).shape == X.shape
True
# 与矩阵变换的联系 # 创建一个一维张量,其中包含从0.0到8.0的连续数字 # 然后将这个一维张量重塑为3x3的二维张量 X = torch.arange(9.0).reshape(3,3) # 创建一个2x2的卷积核K,其中包含四个元素:1.0,2.0,3.0,4.0 K = torch.tensor([[1.0,2.0],[3.0,4.0]]) # 使用自定义的二维卷积函数corr2d对输入张量X和卷积核K进行卷积操作 # corr2d函数需要在引入d2l(深度学习库)之后才能使用 Y = d2l.corr2d(X,K) # 卷积 # 打印卷积操作的结果 Y
tensor([[27., 37.], [57., 67.]])
# 定义一个函数kernel2matrix,用于将给定的卷积核K转换为一个稀疏矩阵W def kernel2matrix(K): # 创建长度为5的零向量k和4x9的零矩阵W k, W = torch.zeros(5), torch.zeros((4,9)) # 打印初始状态的k print(k) # 打印初始状态的W print(W) # 打印输入的卷积核K print(K) # 将卷积核K的元素填充到向量k中的适当位置,形成一个稀疏向量 k[:2], k[3:5] = K[0, :], K[1,:] # 打印填充后的向量k print(k) # 将稀疏向量k填充到矩阵W中的适当位置,形成一个稀疏矩阵 W[0,:5], W[1,1:6], W[2,3:8], W[3,4:] = k, k, k, k # 返回转换后的稀疏矩阵W return W
tensor([0., 0., 0., 0., 0.])
# 每一行向量表示在一个位置的卷积操作,0填充表示卷积核未覆盖到的区域。 # 输入大小为 3 * 3 的图片,拉长一维向量后变成 1 * 9 的向量 # 输入大小为 3 * 3 的图片,卷积核为 2 * 2,则输出图片为 2 * 2,拉长后变为 4 * 1 的向量 # kernel2matrix函数将卷积核改为稀疏矩阵C后矩阵情况 # 使用kernel2matrix函数将卷积核K转换为一个稀疏矩阵W # 这个矩阵的每一行表示在一个特定位置进行的卷积操作,其中的0表示卷积核没有覆盖的区域 # 如果输入是一个3x3的图像,并被拉平为一个1x9的向量 # 而卷积核是2x2的,那么输出图像的大小为2x2,拉平后变为一个4x1的向量 # kernel2matrix函数实际上就是在构建这种转换关系 W = kernel2matrix(K) W
tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0.]]) tensor([[1., 2.], [3., 4.]]) tensor([1., 2., 0., 3., 4.])
tensor([[1., 2., 0., 3., 4., 0., 0., 0., 0.], [0., 1., 2., 0., 3., 4., 0., 0., 0.], [0., 0., 0., 1., 2., 0., 3., 4., 0.], [0., 0., 0., 0., 1., 2., 0., 3., 4.]])
# 打印输入张量X的内容 print(X) # 使用reshape函数将输入张量X拉平为一个一维向量,并打印结果 # 这是为了将X与稀疏矩阵W进行矩阵乘法操作 print(X.reshape(-1)) # 判断卷积操作的结果Y是否等于稀疏矩阵W与拉平的输入张量X的矩阵乘法的结果,并将结果重塑为2x2的形状 # 这是一种检查卷积操作是否等价于某种矩阵变换的方式 Y == torch.matmul(W, X.reshape(-1)).reshape(2,2)
tensor([[0., 1., 2.], [3., 4., 5.], [6., 7., 8.]]) tensor([0., 1., 2., 3., 4., 5., 6., 7., 8.])
tensor([[True, True], [True, True]])
# 使用自定义的转置卷积函数trans_conv对卷积操作的结果Y和卷积核K进行转置卷积操作 Z = trans_conv(Y, K) # 判断转置卷积操作的结果Z是否等于稀疏矩阵W的转置与拉平的卷积结果Y的矩阵乘法的结果,并将结果重塑为3x3的形状 # 这是一种检查转置卷积操作是否等价于某种特定的矩阵变换的方式 # 注意这里得到的结果并不是原图像,尽管它们的尺寸是一样的 Z == torch.matmul(W.T, Y.reshape(-1)).reshape(3,3) # 由卷积后的图像乘以转置卷积后,得到的并不是原图像,而是尺寸一样
tensor([[True, True, True], [True, True, True], [True, True, True]])

1. 转置卷积是一种卷积

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

微信小程序蓝牙开发实战:BLE通信完整流程与踩坑指南

简介&#xff1a;这是一份面向微信小程序开发者的学习型蓝牙通信实战Demo&#xff0c;聚焦BLE设备交互核心流程&#xff0c;解决初学者在小程序蓝牙API调用中常见的搜索失败、连接不稳定、特征值读写异常等典型问题。资源共17个文件&#xff0c;包含4个JS逻辑文件&#xff08;实…

作者头像 李华
网站建设 2026/8/31 4:17:58

做市场分析PPT,这三个平台我反复用了大半年

做市场分析的PPT&#xff0c;数据整理和逻辑梳理本身已经够耗时了&#xff0c;如果还要从零搭建结构、调排版配色&#xff0c;整个流程会变得非常低效。过去一段时间&#xff0c;我陆续试过不少PPT模板平台&#xff0c;目前固定在用的有三个&#xff0c;分享一点真实体验。一、…

作者头像 李华
网站建设 2026/8/31 4:17:22

pdf图纸怎么转换成cad格式?我整理了五个实用工具的完整操作记录

使用背景与需求分析 上周接到一个朋友的求助&#xff0c;说客户发来一套厂房平面图&#xff0c;全是PDF格式。他需要把里面的墙体、门窗、尺寸标注在CAD里重新整理一遍&#xff0c;看看能不能加个设备区。结果用CAD打开PDF一看&#xff0c;全都是图片&#xff0c;看得见摸不着…

作者头像 李华
网站建设 2026/8/31 4:14:45

三极管伏安特性曲线全攻略:看懂三大区域,设计电路不再靠猜

这段时间在折腾硬件电路&#xff0c;绕来绕去都绕不过一个关键器件&#xff1a;三极管。很多初学者会有这种感觉&#xff1a;看电路图时觉得三极管能放大、能开关&#xff0c;可真到自己搭电路&#xff0c;选电阻、定静态工作点、判断饱和还是放大&#xff0c;总是靠查手册猜数…

作者头像 李华
网站建设 2026/8/31 4:14:01

HyperMesh二次开发入门:用Tcl脚本实现网格检查与批量处理

HyperMesh 二次开发最常被误解的地方在于&#xff1a;很多人以为必须懂 C 或 Python 才能做&#xff0c;实际上 HyperMesh 的主流脚本环境是 Tcl/Tk&#xff0c;配合 HM 命令&#xff0c;一个几百行的脚本就能把网格质量批量检查、材料自动创建、模型清理和导出这些重复操作&am…

作者头像 李华
网站建设 2026/8/31 4:12:52

SpringBoot性能优化清单:让接口响应时间降一半

接口慢成狗的时候&#xff0c;没人关心你的业务逻辑有多精妙。用户按下按钮&#xff0c;光标旋转三秒&#xff0c;交易失败&#xff0c;投诉工单飞向客服——这背后往往不是硬件不行&#xff0c;而是你的SpringBoot应用在看不见的地方做着大量无用功。性能问题不是玄学&#xf…

作者头像 李华