深度学习基本模块:ConvTranspose2D 二维转置卷积层
ConvTranspose2D 是一种专门用于处理二维数据的转置卷积层。它通过反向卷积操作将低维特征图上采样到高维空间,从而恢复输入数据的空间结构。与二维卷积(Conv2D)不同,ConvTranspose2D 在反向传播过程中用于生成更高分辨率的特征图,适合处理图像、音频频谱等数据。
ConvTranspose2D 提供了一种可学习的、自适应的上采样机制,能够根据大量训练数据,学会如何最优地重建和填充时频图中的信息,在音频生成与超分辨率领域有广泛用途。
一、ConvTranspose2D 介绍
1.1 结构
- 输入层:接收二维输入数据,通常为形状为
(batch_size, in_channels, height, width)
的张量。 - 转置卷积层:核心计算层,包含
out_channels
个可学习的卷积核组。每个卷积核的形状为(in_channels, kernel_height, kernel_width)
,负责执行上采样操作,处理所有输入通道并生成一个输出通道。- 权重张量:
(in_channels, out_channels, kernel_height, kernel_width)
。 - 偏置项 :每个输出通道有一个独立的可学习偏置值(标量),因此整个层的偏置项形状为
(out_channels,)
- 权重张量:
- 激活层:通常使用 ReLU 激活函数,引入非线性。
1.2 参数
- in_channels:输入数据的通道数。例如,RGB 图像为 3,灰度图像为 1。
- out_channels:输出数据的通道数,即转置卷积层中卷积核组的数量。这个参数决定了输出特征图的深度。
- kernel_size:卷积核的空间尺寸,可以是单个整数(方形核)或一个元组 (kernel_height, kernel_width)。
- stride:步幅,卷积核在输入特征图上滑动的步长,默认为 1。当 stride>1 时,实现显著上采样效果。
- padding:输入特征图的填充数量。填充会影响输出尺寸的计算,但不会改变上采样的基本比例。
- output_padding:用于微调输出尺寸的额外参数。当 stride>1 时,可能需要在输出添加少量填充来精确控制输出长度。
- dilation:控制卷积核中元素的间距(空洞卷积)。增大 dilation 值可以增加感受野而不增加参数数量。
一个标准的 3x3 卷积核。它就像一个小窗口,每次查看输入图像上 3x3 的相邻像素区域。
dilation 的作用就是拉大这个窗口中原有元素之间的物理距离。它不是扩大窗口本身的大小,而是让窗口的“触角”伸得更远。我们以一个 3x3 的卷积核为例,其权重矩阵如下:
[[w₁₁, w₁₂, w₁₃],[w₂₁, w₂₂, w₂₃],[w₃₁, w₃₂, w₃₃]]
这个核有 9 个参数。dilation 参数控制了这 9 个权重在输入图像上采样的间距。情况 1: dilation = 1 (标准卷积)
•效果:卷积核元素紧密相连,没有间隔。它每次查看输入图像上一个连续的 3x3 区域。
•感受野:3x3 像素。
•计算示意图:卷积核的每个权重直接覆盖输入图像上相邻的 9 个像素。
输入图像感受野 (● 为被计算的像素):
[●, ●, ●]
[●, ●, ●]
[●, ●, ●]
卷积核应用方式:
[w₁₁, w₁₂, w₁₃] [x₁₁, x₁₂, x₁₃]
[w₂₁, w₂₂, w₂₃] * [x₂₁, x₂₂, x₂₃]
[w₃₁, w₃₂, w₃₃] [x₃₁, x₃₂, x₃₃]情况 2: dilation = 2
•效果:在每个卷积核权重之间(行和列方向上)插入 1 个空白像素。这使得卷积核在输入图像上以一个“带洞”的模式进行采样。
•感受野:扩张到 5x5 像素。计算公式:new_kernel_size = dilation * (kernel_size - 1) + 1 -> 2*(3-1)+1=5。
•计算示意图:权重不再与相邻的像素相乘,而是与间隔 1 个像素的像素相乘。参数数量仍然是 9 个。
输入图像感受野 (● 为被计算的像素,_ 为被跳过的像素):
[●, _, ●, _, ●]
[_, _, _, _, _]
[●, _, ●, _, ●]
[_, _, _, _, _]
[●, _, ●, _, ●]
卷积核应用方式:
核权重 w₁₁ 作用在输入 (1,1) -> 输出 (1,1)
核权重 w₁₂ 作用在输入 (1,3) -> 跳过 (1,2)
核权重 w₁₃ 作用在输入 (1,5) -> 跳过 (1,4)
核权重 w₂₁ 作用在输入 (3,1) -> 跳过 (2,1)
... 以此类推等效的卷积核是一个稀疏的 5x5 核,只有 9 个位置有非零值(即原来的权重):
[[w₁₁, 0, w₁₂, 0, w₁₃],[ 0, 0, 0, 0, 0 ],[w₂₁, 0, w₂₂, 0, w₂₃],[ 0, 0, 0, 0, 0 ],[w₃₁, 0, w₃₂, 0, w₃₃]]1.指数级扩大感受野:无需将卷积核增大,仅通过调整 dilation扩大了感受野。
2.极高的参数效率:参数数量恒定为9个。一个标准的 7x7 卷积核需要 49 个参数,是空洞卷积的 5 倍多。空洞卷积用极少的参数代价就获得了巨大的感受野,有效防止了模型过拟合。
3.捕获远程上下文信息:这使得网络在较低的层级就能融合图像中相距较远区域的信息。这对于语义分割、目标检测等任务至关重要,因为判断一个像素属于“天空”还是“道路”,或者判断一个目标是什么,往往需要依赖图像中很大范围的上下文信息。
1.3 输入输出维度、
- 输入数据维度:
(batch_size, in_channels, height, width)
- 输出数据维度:
(batch_size, out_channels, new_height, new_width)
输出尺寸计算公式:
Hout=(Hin−1)×strideh−2×paddingh+dilationh×(kh−1)+output_paddingh+1H_{out} = (H_{in} - 1) \times \text{stride}_h - 2 \times \text{padding}_h + \text{dilation}_h \times (k_h - 1) + \text{output\_padding}_h + 1Hout=(Hin−1)×strideh−2×paddingh+dilationh×(kh−1)+output_paddingh+1
Wout=(Win−1)×stridew−2×paddingw+dilationw×(kw−1)+output_paddingw+1W_{out} = (W_{in} - 1) \times \text{stride}_w - 2 \times \text{padding}_w + \text{dilation}_w \times (k_w - 1) + \text{output\_padding}_w + 1Wout=(Win−1)×stridew−2×paddingw+dilationw×(kw−1)+output_paddingw+1
其中:
- HinH_{in}Hin, WinW_{in}Win:输入特征图的高度和宽度
- HoutH_{out}Hout,WoutW_{out}Wout:输出特征图的高度和宽度
- strideh\text{stride}_hstrideh, stridew\text{stride}_wstridew:高度和宽度方向的步长
- paddingh\text{padding}_hpaddingh,paddingw\text{padding}_wpaddingw:高度和宽度方向应用于输入的填充
- khk_hkh, kwk_wkw:卷积核的高度和宽度
- dilationh\text{dilation}_hdilationh, dilationw\text{dilation}_wdilationw:高度和宽度方向的空洞率
- output_paddingh\text{output\_padding}_houtput_paddingh, output_paddingw\text{output\_padding}_woutput_paddingw:高度和宽度方向的输出填充
1.4 计算过程
ConvTranspose2D(转置卷积)的计算过程核心思想是:通过在输入元素间插入零值并调整填充方式,将上采样问题转化为一个等效的常规卷积操作。
步骤一:输入扩展(Zero Insertion / Interleaving)
- 在输入特征图的每个空间元素之间插入 (stride - 1) 行和列的零值。
- 目的:初步扩大输入的空间尺寸,为后续卷积操作创造空间。
- 效果:将一个尺寸为
(H_in, W_in)
的输入,扩展为一个尺寸约为(H_in * stride, W_in * stride)
的稀疏中间张量。
• 转置卷积层参数: in_channels=1, out_channels=1, kernel_size=3, stride=2, padding=1, output_padding=1, bias=False
• 卷积核权重 W (已旋转180°):
W = [[[w₁, w₂, w₃], [w₄, w₅, w₆], [w₇, w₈, w₉]]]• 输入张量: X,形状 (1, 1, 2, 2) (单通道,2x2)
X = [[[1, 2], [3, 4]]]
• 在原始输入的每个元素间插入零。高度和宽度方向均变为 2 + (2-1) = 3。
• 扩展后中间张量 Z:
[[[1, 0, 2, 0],[0, 0, 0, 0],[3, 0, 4, 0],[0, 0, 0, 0]]]
步骤二:边缘填充(Padding Adjustment)
- 在扩展后的稀疏张量四周添加填充。
- 填充量计算:并非简单的 (kernel_size - padding - 1)。正确的填充量 P 需要根据期望的输出尺寸通过公式反推,通常框架会自动计算。其目的是确保后续卷积操作的输出尺寸符合预期。
- 目的:控制输出特征图的最终尺寸。
• 在 Z 的上下左右各填充 1 行/列的零。
• 填充后张量 Z_padded:
[[[0, 0, 0, 0, 0, 0],[0, 1, 0, 2, 0, 0],[0, 0, 0, 0, 0, 0],[0, 3, 0, 4, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]]]
步骤三:执行常规卷积(Convolution Operation)
- 对经过扩展和填充的中间张量,执行一个步长为 1 的常规二维卷积操作。
- 使用的卷积核:即是转置卷积层本身学习到的权重,其形状为
(in_channels, out_channels, kernel_height, kernel_width)
。 - 目的:利用卷积核将稀疏的、插零后的激活值“聚合”起来,生成密集的、高分辨率的输出特征图。
- 卷积计算公式:
Y(i,j)=∑m=0kh−1∑n=0kw−1Zpadded(i+m,j+n)⋅W(m,n)+bY(i, j) = \sum_{m=0}^{k_h-1} \sum_{n=0}^{k_w-1} Z_{\text{padded}}(i + m, j + n) \cdot W(m, n) + bY(i,j)=m=0∑kh−1n=0∑kw−1Zpadded(i+m,j+n)⋅W(m,n)+b
其中:
- Y(i,j)Y(i, j)Y(i,j)是输出位置(i,j)(i, j)(i,j)的值
- ZpaddedZ_{\text{padded}}Zpadded 是经过扩展和填充的中间张量
- WWW是卷积核权重
- bbb 是偏置项
- khk_hkh, kwk_wkw 是卷积核的高度和宽度
# 卷积核权重 W (3x3):
W = [[w₁₁, w₁₂, w₁₃],[w₂₁, w₂₂, w₂₃],[w₃₁, w₃₂, w₃₃]]# 计算输出特征图 Y 的 (0,0) 位置:
Y(0,0) = (0*w₁₁) + (0*w₁₂) + (0*w₁₃) + (0*w₂₁) + (1*w₂₂) + (0*w₂₃) + (0*w₃₁) + (0*w₃₂) + (0*w₃₃)
代码示例
通过两层ConvTranspose2D处理一段音频频谱,打印每层的输出形状、参数形状,并可视化特征图。
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import librosa
import numpy as np# 定义 ConvTranspose2D 模型
class ConvTranspose2DModel(nn.Module):def __init__(self):super(ConvTranspose2DModel, self).__init__()self.deconv1 = nn.ConvTranspose2d(in_channels=1, out_channels=2, kernel_size=(3, 3), stride=2, padding=1)self.deconv2 = nn.ConvTranspose2d(in_channels=2, out_channels=1, kernel_size=(3, 3), stride=2, padding=1)def forward(self, x):x = self.deconv1(x)x = self.deconv2(x)return x# 1. 读取音频文件并处理
file_path = 'test.wav'
waveform, sample_rate = librosa.load(file_path, sr=16000, mono=True)# 选取 1.5 到 4.5 秒的数据
start_sample = int(1.5 * sample_rate)
end_sample = int(4.5 * sample_rate)
audio_segment = waveform[start_sample:end_sample]# 2. 转换为频谱
n_fft = 512
hop_length = 256
spectrogram = librosa.stft(audio_segment, n_fft=n_fft, hop_length=hop_length)
spectrogram_db = librosa.amplitude_to_db(np.abs(spectrogram))# 将频谱转换为 PyTorch 张量并调整形状
spectrogram_tensor = torch.tensor(spectrogram_db, dtype=torch.float32).unsqueeze(0).unsqueeze(0) # (1, 1, height, width)# 打印原始频谱的维度
print(f"Original spectrogram shape: {spectrogram_tensor.shape}")# 3. 创建模型实例
model = ConvTranspose2DModel()# 打印每一层卷积层的权重形状
print(f"ConvTranspose2D Layer 1 weights shape: {model.deconv1.weight.shape}")
print(f"ConvTranspose2D Layer 1 bias shape: {model.deconv1.bias.shape}")
print(f"ConvTranspose2D Layer 2 weights shape: {model.deconv2.weight.shape}")
print(f"ConvTranspose2D Layer 2 bias shape: {model.deconv2.bias.shape}")# 进行前向传播以获取每一层的输出
output1 = model.deconv1(spectrogram_tensor) # 第一层输出
output2 = model.deconv2(output1) # 第二层输出# 打印每一层的输出形状
print(f"Output shape after ConvTranspose2D Layer 1: {output1.shape}")
print(f"Output shape after ConvTranspose2D Layer 2: {output2.shape}")# 4. 可视化原始频谱
plt.figure(figsize=(12, 6))
plt.imshow(spectrogram_db, aspect='auto', origin='lower', cmap='inferno')
plt.title("Original Spectrogram")
plt.xlabel("Time Frames")
plt.ylabel("Frequency Bins")
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()# 可视化第一层输出的特征图
for i in range(output1.shape[1]): # 遍历每个特征图plt.figure(figsize=(12, 6))plt.imshow(output1[0, i, :, :].detach().numpy(), aspect='auto', origin='lower', cmap='inferno')plt.title(f"Output after ConvTranspose2D Layer 1 - Feature Map {i + 1}")plt.xlabel("Width")plt.ylabel("Height")plt.colorbar()plt.tight_layout()
# 可视化第二层输出的特征图
plt.figure(figsize=(12, 6))
for i in range(output2.shape[1]): # 遍历每个特征图plt.subplot(output2.shape[1], 1, i + 1)plt.imshow(output2[0, i, :, :].detach().numpy(), aspect='auto', origin='lower', cmap='inferno')plt.title(f"Output after ConvTranspose2D Layer 2 - Feature Map {i + 1}")plt.xlabel("Width")plt.ylabel("Height")plt.colorbar()plt.tight_layout()
plt.show()
Original spectrogram shape: torch.Size([1, 1, 257, 188])
ConvTranspose2D Layer 1 weights shape: torch.Size([1, 2, 3, 3])
ConvTranspose2D Layer 1 bias shape: torch.Size([2])
ConvTranspose2D Layer 2 weights shape: torch.Size([2, 1, 3, 3])
ConvTranspose2D Layer 2 bias shape: torch.Size([1])
Output shape after ConvTranspose2D Layer 1: torch.Size([1, 2, 513, 375])
Output shape after ConvTranspose2D Layer 2: torch.Size([1, 1, 1025, 749])