文章目录
- 转置卷积的三种等价实现方法:原理、公式与等价性分析
- 数学定义与核心公式
- 方法一:零填充+翻转核卷积(数学定义方法)
- 原理与公式
- 等价性说明
- 方法二:直接位置映射(pytorch框架高效实现)
- 原理与公式
- 等价性说明
- 方法三:矩阵转置法(数学本质实现)
- 原理与公式
- 等价性说明
- 转置卷积三种方法详细计算过程(使用输入X和卷积核K)
- 输入参数
- 方法一:零填充+翻转核卷积(数学定义方法)
- 计算步骤:
- 方法二:直接位置映射(框架高效实现)
- 计算步骤:
- 方法三:矩阵转置法(数学本质实现)
- 计算步骤:
- 三种方法统一结果
- code
转置卷积的三种等价实现方法:原理、公式与等价性分析
数学定义与核心公式
转置卷积的数学本质是卷积运算的伴随算子(adjoint operator)。给定输入 X∈RHin×WinX \in \mathbb{R}^{H_{in} \times W_{in}}X∈RHin×Win 和卷积核 K∈RHk×WkK \in \mathbb{R}^{H_k \times W_k}K∈RHk×Wk,输出 Y∈RHout×WoutY \in \mathbb{R}^{H_{out} \times W_{out}}Y∈RHout×Wout 满足:
Hout=(Hin−1)×stride+Hk−2×paddingH_{out} = (H_{in} - 1) \times \text{stride} + H_k - 2 \times \text{padding}Hout=(Hin−1)×stride+Hk−2×padding
Wout=(Win−1)×stride+Wk−2×paddingW_{out} = (W_{in} - 1) \times \text{stride} + W_k - 2 \times \text{padding}Wout=(Win−1)×stride+Wk−2×padding
三种方法都实现了相同的线性变换:
Y=T(X;K,stride,padding)Y = \mathcal{T}(X; K, \text{stride}, \text{padding})Y=T(X;K,stride,padding)
其中 T\mathcal{T}T 表示转置卷积操作。
方法一:零填充+翻转核卷积(数学定义方法)
原理与公式
-
输入扩展:
Xexpanded[i,j×s]=X[i,j]X_{\text{expanded}}[i, j \times s] = X[i, j]Xexpanded[i,j×s]=X[i,j]
其中 sss 为步长,元素间插入 (s−1)(s-1)(s−1) 个零值 -
边界填充:
Xpadded=pad(Xexpanded,p)X_{\text{padded}} = \text{pad}(X_{\text{expanded}}, p)Xpadded=pad(Xexpanded,p)
p=max(padding,k−1)p = \max(\text{padding}, k-1)p=max(padding,k−1) -
核翻转:
Kflipped[i,j]=K[Hk−1−i,Wk−1−j]K_{\text{flipped}}[i,j] = K[H_k-1-i, W_k-1-j]Kflipped[i,j]=K[Hk−1−i,Wk−1−j] -
互相关运算:
Y[m,n]=∑i=0Hk−1∑j=0Wk−1Xpadded[m+i,n+j]⋅Kflipped[i,j]Y[m,n] = \sum_{i=0}^{H_k-1} \sum_{j=0}^{W_k-1} X_{\text{padded}}[m+i, n+j] \cdot K_{\text{flipped}}[i,j]Y[m,n]=i=0∑Hk−1j=0∑Wk−1Xpadded[m+i,n+j]⋅Kflipped[i,j]
等价性说明
此方法直接实现数学定义:转置卷积 = 翻转核 + 扩展输入 + 互相关
方法二:直接位置映射(pytorch框架高效实现)
原理与公式
-
位置映射:
For each (i,j)∈X:\text{For each } (i,j) \in X:For each (i,j)∈X:
Y[i⋅s+k,j⋅s+l]+=X[i,j]⋅K[k,l]Y[i \cdot s + k, j \cdot s + l] \mathrel{+}= X[i,j] \cdot K[k,l]Y[i⋅s+k,j⋅s+l]+=X[i,j]⋅K[k,l]
k∈[0,Hk−1],l∈[0,Wk−1]k \in [0, H_k-1], l \in [0, W_k-1]k∈[0,Hk−1],l∈[0,Wk−1] -
边界处理:
仅当 i⋅s+k−p≥0i \cdot s + k - p \geq 0i⋅s+k−p≥0 且 j⋅s+l−p<Woutj \cdot s + l - p < W_{out}j⋅s+l−p<Wout 时累加
等价性说明
此方法通过散射(scatter)操作实现:
- 每个输入元素将核权重"放置"到输出空间
- 重叠区域的值自动累加
- 数学上等价于方法一,但避免了显式扩展
方法三:矩阵转置法(数学本质实现)
原理与公式
-
构建卷积矩阵:
定义常规卷积算子 CCC,满足:
vec(Yconv)=C⋅vec(X)\text{vec}(Y_{\text{conv}}) = C \cdot \text{vec}(X)vec(Yconv)=C⋅vec(X) -
转置卷积:
vec(Y)=CT⋅vec(X)\text{vec}(Y) = C^T \cdot \text{vec}(X)vec(Y)=CT⋅vec(X) -
矩阵元素:
C(m,n),(i,j)=K[k,l]if{i=⌊m/s⌋+kj=⌊n/s⌋+lC_{(m,n),(i,j)} = K[k,l] \quad \text{if} \quad \begin{cases} i = \lfloor m/s \rfloor + k \\ j = \lfloor n/s \rfloor + l \end{cases}C(m,n),(i,j)=K[k,l]if{i=⌊m/s⌋+kj=⌊n/s⌋+l
等价性说明
此方法直接实现线性代数的伴随算子定义:
- CCC 是卷积的矩阵表示
- CTC^TCT 是转置卷积的精确数学实现
转置卷积三种方法详细计算过程(使用输入X和卷积核K)
输入参数
- 输入矩阵 X:
[[1, 2],[3, 4]]
- 卷积核 K:
[[5, 6],[7, 8]]
- 步长 stride:2
- 填充 padding:0
- 输出尺寸:4×4(根据公式:
(2-1)×2 + 2 - 0 = 4
)
方法一:零填充+翻转核卷积(数学定义方法)
计算步骤:
-
输入矩阵 X零填充扩展输入:
- 在元素间插入
(stride-1)=1
个零值 - 在行间插入
(stride-1)=1
行零行
[[1, 0, 2],[0, 0, 0],[3, 0, 4]]
- 在元素间插入
-
输入矩阵 X边界填充:
- 添加
(kernel_size-p-1)=1
圈零值
[[0, 0, 0, 0, 0],[0, 1, 0, 2, 0],[0, 0, 0, 0, 0],[0, 3, 0, 4, 0],[0, 0, 0, 0, 0]]
- 添加
-
核翻转:
- 180度旋转卷积核
原始核:[[5, 6],[7, 8]]翻转核:[[8, 7],[6, 5]]
-
互相关计算:
- 使用翻转后的核在填充矩阵上滑动计算
- 位置(0,0):
窗口:[[0,0],[0,1]] 计算:0×8 + 0×7 + 0×6 + 1×5 = 5
- 位置(0,1):
窗口:[[0,0],[1,0]] 计算:0×8 + 0×7 + 1×6 + 0×5 = 6
- 位置(0,2):
窗口:[[0,0],[0,2]] 计算:0×8 + 0×7 + 0×6 + 2×5 = 10
- 位置(0,3):
窗口:[[0,0],[2,0]] 计算:0×8 + 0×7 + 2×6 + 0×5 = 12
- 位置(1,0):
窗口:[[0,1],[0,0]] 计算:0×8 + 1×7 + 0×6 + 0×5 = 7
- 位置(1,1):
窗口:[[1,0],[0,0]] 计算:1×8 + 0×7 + 0×6 + 0×5 = 8
- 位置(1,2):
窗口:[[0,2],[0,0]] 计算:0×8 + 2×7 + 0×6 + 0×5 = 14
- 位置(1,3):
窗口:[[2,0],[0,0]] 计算:2×8 + 0×7 + 0×6 + 0×5 = 16
- 位置(2,0):
窗口:[[0,0],[0,3]] 计算:0×8 + 0×7 + 0×6 + 3×5 = 15
- 位置(2,1):
窗口:[[0,0],[3,0]] 计算:0×8 + 0×7 + 3×6 + 0×5 = 18
- 位置(2,2):
窗口:[[0,0],[0,4]] 计算:0×8 + 0×7 + 0×6 + 4×5 = 20
- 位置(2,3):
窗口:[[0,0],[4,0]] 计算:0×8 + 0×7 + 4×6 + 0×5 = 24
- 位置(3,0):
窗口:[[0,3],[0,0]] 计算:0×8 + 3×7 + 0×6 + 0×5 = 21
- 位置(3,1):
窗口:[[3,0],[0,0]] 计算:3×8 + 0×7 + 0×6 + 0×5 = 24
- 位置(3,2):
窗口:[[0,4],[0,0]] 计算:0×8 + 4×7 + 0×6 + 0×5 = 28
- 位置(3,3):
窗口:[[4,0],[0,0]] 计算:4×8 + 0×7 + 0×6 + 0×5 = 32
-
最终输出:
[[ 5, 6, 10, 12],[ 7, 8, 14, 16],[15, 18, 20, 24],[21, 24, 28, 32]]
方法二:直接位置映射(框架高效实现)
计算步骤:
-
位置映射关系:
- 输入元素位置 → 输出起始位置
(0,0) → (0,0) (0,1) → (0,2) (1,0) → (2,0) (1,1) → (2,2)
-
核权重分配:
- 元素(0,0)=1 的贡献:
[1×5, 1×6] → 位置(0,0)和(0,1) [1×7, 1×8] → 位置(1,0)和(1,1)
- 元素(0,1)=2 的贡献:
[2×5, 2×6] → 位置(0,2)和(0,3) [2×7, 2×8] → 位置(1,2)和(1,3)
- 元素(1,0)=3 的贡献:
[3×5, 3×6] → 位置(2,0)和(2,1) [3×7, 3×8] → 位置(3,0)和(3,1)
- 元素(1,1)=4 的贡献:
[4×5, 4×6] → 位置(2,2)和(2,3) [4×7, 4×8] → 位置(3,2)和(3,3)
- 元素(0,0)=1 的贡献:
-
输出矩阵构建:
(0,0): 1×5 = 5 (0,1): 1×6 = 6 (0,2): 2×5 = 10 (0,3): 2×6 = 12(1,0): 1×7 = 7 (1,1): 1×8 = 8 (1,2): 2×7 = 14 (1,3): 2×8 = 16(2,0): 3×5 = 15 (2,1): 3×6 = 18 (2,2): 4×5 = 20 (2,3): 4×6 = 24(3,0): 3×7 = 21 (3,1): 3×8 = 24 (3,2): 4×7 = 28 (3,3): 4×8 = 32
-
完整输出:
[[ 5, 6, 10, 12],[ 7, 8, 14, 16],[15, 18, 20, 24],[21, 24, 28, 32]]
方法三:矩阵转置法(数学本质实现)
计算步骤:
-
构建卷积矩阵 C:
- 常规卷积:从4×4输入 → 2×2输出
- 矩阵维度:4行(输出元素) × 16列(输入元素)
-
填充卷积矩阵:
- 输出位置(0,0)对应输入位置:
[0,0]:5, [0,1]:6 [1,0]:7, [1,1]:8
- 矩阵行(0,0):
[5,6,0,0,7,8,0,0,0,0,0,0,0,0,0,0]
- 输出位置(0,1)对应输入位置:
[0,2]:5, [0,3]:6 [1,2]:7, [1,3]:8
- 矩阵行(0,1):
[0,0,5,6,0,0,7,8,0,0,0,0,0,0,0,0]
- 输出位置(1,0)对应输入位置:
[2,0]:5, [2,1]:6 [3,0]:7, [3,1]:8
- 矩阵行(1,0):
[0,0,0,0,0,0,0,0,5,6,0,0,7,8,0,0]
- 输出位置(1,1)对应输入位置:
[2,2]:5, [2,3]:6 [3,2]:7, [3,3]:8
- 矩阵行(1,1):
[0,0,0,0,0,0,0,0,0,0,5,6,0,0,7,8]
- 输出位置(0,0)对应输入位置:
-
转置矩阵 CTC^TCT:
- 尺寸:16×4
- 每列对应一个输出位置的影响
-
矩阵乘法:
- 输入展平:
[1,2,3,4]
- 计算:
Y_flat = C^T × [1,2,3,4]^T
- 结果:
[5,6,10,12,7,8,14,16,15,18,20,24,21,24,28,32]
- 输入展平:
-
重塑为4×4矩阵:
[[ 5, 6, 10, 12],[ 7, 8, 14, 16],[15, 18, 20, 24],[21, 24, 28, 32]]
三种方法统一结果
[[ 5, 6, 10, 12],[ 7, 8, 14, 16],[15, 18, 20, 24],[21, 24, 28, 32]]
code
import torch
import torch.nn as nn# 定义输入张量和卷积核
input_tensor = torch.tensor([[[[1, 2],[3, 4]]]], dtype=torch.float32) # 形状(1,1,2,2)
kernel = torch.tensor([[[[5, 6],[7, 8]]]], dtype=torch.float32) # 形状(1,1,2,2)# 方法1:使用nn.ConvTranspose2d模块(推荐)
def method_conv_transpose2d(input, kernel):# 创建转置卷积层conv_trans = nn.ConvTranspose2d(in_channels=1, # 输入通道数out_channels=1, # 输出通道数kernel_size=2, # 卷积核尺寸stride=2, # 步长padding=0, # 输入填充bias=False # 禁用偏置)# 手动注入卷积核权重with torch.no_grad():conv_trans.weight = nn.Parameter(kernel)# 执行计算return conv_trans(input)# 方法2:使用functional.conv_transpose2d函数
def method_functional(input, kernel):return torch.nn.functional.conv_transpose2d(input=input,weight=kernel,bias=None,stride=2,padding=0)# 方法3:手动实现转置卷积(验证原理)
def manual_transposed_conv(input, kernel):"""根据和的数学原理实现:1. 输入插值:步长2需插入1行/列零2. 外部填充:卷积核2x2需在边缘补1圈零3. 用旋转180°的核执行普通卷积(步长1)"""# 输入插值(插入行列零)interpolated = torch.zeros(1, 1, 3, 3)interpolated[0, 0, ::2, ::2] = input.squeeze()# 外部填充(四周各补1圈零)padded = torch.nn.functional.pad(interpolated, [1, 1, 1, 1])# 卷积核旋转180°rotated_kernel = torch.rot90(kernel.squeeze(), 2, dims=(0, 1)).unsqueeze(0).unsqueeze(0)print("旋转后的卷积核:\n", rotated_kernel.squeeze())# 执行普通卷积(步长1,无填充)return torch.nn.functional.conv2d(padded, rotated_kernel, padding=0)# 计算并验证结果
output_module = method_conv_transpose2d(input_tensor, kernel)
output_functional = method_functional(input_tensor, kernel)
output_manual = manual_transposed_conv(input_tensor, kernel)print("nn.ConvTranspose2d 输出:\n", output_module.squeeze())
print("F.conv_transpose2d 输出:\n", output_functional.squeeze())
print("手动实现转置卷积输出:\n", output_manual.squeeze())
结果:
旋转后的卷积核:tensor([[8., 7.],[6., 5.]])
nn.ConvTranspose2d 输出:tensor([[ 5., 6., 10., 12.],[ 7., 8., 14., 16.],[15., 18., 20., 24.],[21., 24., 28., 32.]], grad_fn=<SqueezeBackward0>)
F.conv_transpose2d 输出:tensor([[ 5., 6., 10., 12.],[ 7., 8., 14., 16.],[15., 18., 20., 24.],[21., 24., 28., 32.]])
手动实现转置卷积输出:tensor([[ 5., 6., 10., 12.],[ 7., 8., 14., 16.],[15., 18., 20., 24.],[21., 24., 28., 32.]])
参考:
00
0
1
2
3