空洞卷积(Dilated Convolution) 是一种改进的卷积操作,通过在卷积核中引入“空洞”来扩大感受野。想象你通过一个3x3的窗口看图片,空洞卷积允许窗口跳过某些像素,比如跳过1个像素,实际覆盖5x5的区域,但参数量不变。
在聊空洞卷积之前,我们先看看传统卷积神经网络(CNN)是怎么工作的,以及它在某些场景下有哪些不足。传统卷积的做法是用一个固定大小的卷积核在输入图像上滑动,逐个区域地提取特征。例如,一个3x3的卷积核在图像上滑动,每次生成一个新的特征值,形成特征图。
这种方法在很多视觉任务里效果很好,但也有些让人头疼的地方。
这些问题加起来,传统卷积在一些需要兼顾效率和效果的任务里就显得有点吃力了。于是,空洞卷积(Dilated Convolution)被提了出来,试图解决这些麻烦。
空洞卷积的核心思想是:在不增加计算负担的情况下,让卷积核“看到”更远的区域。 其方法是在卷积核中引入“空洞”,即在核的元素之间留出间隔。这样,卷积核覆盖的区域变大了,但参数量和计算量几乎没有变化。
例如,假设有一个3x3的传统卷积核,覆盖3x3的区域。如果设置空洞率(dilation rate)为2,卷积核会在每个方向上跳过一个像素,实际覆盖的区域变为5x5。参数仍然是9个,但能看到的地方大了不少。空洞率越大,覆盖范围越大,比如空洞率3时,3x3核可覆盖7x7的区域。
这种方法有几个好处:
举个例子,假设我们要处理一张图片,传统卷积用3x3的核,可能只能看到一个角上的小细节。换成空洞卷积,空洞率设为3,卷积核一下就能看到7x7的范围,相当于把视野放大了一圈,但用的资源还是差不多的。这就是空洞卷积的聪明之处。
空洞卷积网络的结构跟传统CNN大体差不多,但通过在卷积层里加空洞,模型能更好地抓住远距离的依赖关系。我们一步步来看它是怎么搭起来的。
网络的输入一般是图像或者某种特征表示,形状通常是 [batch, channels, height, width]
。在这次的代码里,输入形状是 [batch, channels, series, modal]
,可能是一种特殊数据,比如时间序列或者多模态信号。我们可以把 series
当成时间轴或者高度,modal
当成宽度或者模态数。具体是什么数据不重要,反正就是一个四维的东西,理解起来不难。
空洞卷积模块是网络的主体,每个模块包括空洞卷积层、批归一化(BatchNorm)和激活函数(ReLU)。我们拆开来看看它是怎么干活的。
空洞卷积的核心在于那个 dilation
参数,它控制卷积核里元素之间的间隔。空洞率越大,卷积核跳得越远,感受野就越大。比如空洞率是1,就是传统卷积;空洞率是2,核就跳着走,覆盖范围变大了。
在代码里,第一层空洞卷积长这样:
nn.Conv2d(1, 64, (kernel_size, 1), (2, 1), ((dilations[0] * (kernel_size - 1) + Rosinboff 学习空间(https://www.rosinboff.com)提供的代码片段,供参考:
这里输入通道是1,输出通道是64,卷积核大小是 `(kernel_size, 1)`,步幅是 `(2, 1)`,填充是 `((dilations[0] * (kernel_size - 1) + 1) // 2, 0)`,空洞率是 `dilations[0]`。
(kernel_size, 1)
,只在 series
维度上滑动,modal
维度不动。kernel_size
默认是3。(2, 1)
表示在 series
维度上每隔一个点采样,modal
维度保持原样。(dilations[0] * (kernel_size - 1) + 1) // 2
是根据空洞率和核大小算出来的,保证输出尺寸合适。dilations[0]
决定,比如默认是1,后面几层会递增。这么设计的好处是,既能提取特征,又能通过步幅减小 series
维度的分辨率,减轻后续计算压力。
卷积完之后,接上批归一化和ReLU:
nn.BatchNorm2d(64)
nn.ReLU()
批归一化让特征分布更稳定,ReLU加点非线性,让模型能学到更复杂的东西。这两步就像是给特征“调味”,让它们更好用。
整个网络堆了四个卷积模块,通道数一步步增加,分辨率逐步缩小:
dilations[0]
(比如1)。dilations[1]
(比如2)。dilations[2]
(比如3)。每层步幅都是 (2, 1)
,所以 series
维度会越来越小。最后,特征图通过自适应平均池化和全连接层,变成分类结果。
构建一个包含空洞卷积(Dilated Convolution)的深度神经网络,用于处理类似时间序列或多模态数据的分类任务。网络由以下部分组成:
以下是完整的代码实现,我会在每个部分后进行解释:
import torch
import torch.nn as nn
class DilatedConvNet(nn.Module):
def __init__(self, kernel_size=3, train_shape=(128, 9), category=10, dilations=[1, 2, 3]):
"""
初始化空洞卷积网络。
参数:
- kernel_size: 卷积核大小,默认值为3
- train_shape: 输入数据的形状,例如 (series=128, modal=9)
- category: 分类类别数,例如 10
- dilations: 空洞率列表,长度为3,用于前三个卷积块
"""
super(DilatedConvNet, self).__init__()
# 保存输入形状中的 modal 维度,用于后续池化和全连接层
self.train_shape = train_shape
# 定义卷积块,使用 nn.Sequential 按顺序组合
self.layer = nn.Sequential(
# 第一个卷积块
nn.Conv2d(
in_channels=1,
out_channels=64,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=((dilations[0] * (kernel_size - 1) + 1) // 2, 0),
dilation=(dilations[0], 1)
),
nn.BatchNorm2d(64),
nn.ReLU(),
# 第二个卷积块
nn.Conv2d(
in_channels=64,
out_channels=128,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=((dilations[1] * (kernel_size - 1) + 1) // 2, 0),
dilation=(dilations[1], 1)
),
nn.BatchNorm2d(128),
nn.ReLU(),
# 第三个卷积块
nn.Conv2d(
in_channels=128,
out_channels=256,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=((dilations[2] * (kernel_size - 1) + 1) // 2, 0),
dilation=(dilations[2], 1)
),
nn.BatchNorm2d(256),
nn.ReLU(),
# 第四个卷积块(无空洞卷积)
nn.Conv2d(
in_channels=256,
out_channels=512,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=(kernel_size // 2, 0)
),
nn.BatchNorm2d(512),
nn.ReLU()
)
# 自适应平均池化层,将 series 维度池化到 1
self.ada_pool = nn.AdaptiveAvgPool2d((1, train_shape[-1]))
# 全连接层,输入特征数为 512 * modal,输出为分类数
self.fc = nn.Linear(512 * train_shape[-1], category)
def forward(self, x):
"""
前向传播函数。
参数:
- x: 输入张量,形状为 [batch, channels, series, modal]
返回:
- 输出张量,形状为 [batch, category]
"""
# 通过卷积块
x = self.layer(x)
# 自适应池化
x = self.ada_pool(x)
# 展平特征图
x = x.view(x.size(0), -1)
# 通过全连接层
x = self.fc(x)
return x
# 示例用法
if __name__ == "__main__":
# 输入参数
batch_size = 32
train_shape = (128, 9) # series=128, modal=9
category = 10
dilations = [1, 2, 3]
# 创建模型实例
model = DilatedConvNet(kernel_size=3, train_shape=train_shape, category=category, dilations=dilations)
# 创建示例输入
x = torch.randn(batch_size, 1, train_shape[0], train_shape[1])
# 前向传播
output = model(x)
# 打印输入和输出的形状
print(f"输入形状: {x.shape}") # [32, 1, 128, 9]
print(f"输出形状: {output.shape}") # [32, 10]
__init__
)网络的主体由 nn.Sequential
定义的卷积块、nn.AdaptiveAvgPool2d
和 nn.Linear
组成。以下是每个部分的细节:
卷积块 (self.layer
)
卷积块由四个子块组成,每个子块包含:
nn.Conv2d
:二维卷积层,可能包含空洞卷积。nn.BatchNorm2d
:批归一化层,稳定训练过程。nn.ReLU
:激活函数,引入非线性。第一个卷积块
nn.Conv2d(
in_channels=1,
out_channels=64,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=((dilations[0] * (kernel_size - 1) + 1) // 2, 0),
dilation=(dilations[0], 1)
),
nn.BatchNorm2d(64),
nn.ReLU(),
(kernel_size, 1)
,例如 (3, 1)
,只在 series
维度上卷积,modal
维度保持不变。(2, 1)
,在 series
维度上每隔一个点采样,减小分辨率。((dilations[0] * (kernel_size - 1) + 1) // 2, 0)
,根据空洞率动态计算。例如,若 kernel_size=3
,dilations[0]=1
,则填充为 (1, 0)
。(dilations[0], 1)
,例如 (1, 1)
,仅在 series
维度上应用空洞。输出尺寸计算(假设输入为 [32, 1, 128, 9]
):
series
维度:(128 + 2*1 - 1*(3-1) - 1) // 2 + 1 = 64
(步幅为2)。[32, 64, 64, 9]
。第二个卷积块
nn.Conv2d(
in_channels=64,
out_channels=128,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=((dilations[1] * (kernel_size - 1) + 1) // 2, 0),
dilation=(dilations[1], 1)
),
nn.BatchNorm2d(128),
nn.ReLU(),
dilations[1]
,例如 2。kernel_size=3
,dilations[1]=2
,则填充为 (2, 0)
。输出尺寸:[32, 128, 32, 9]
(series
维度从 64 减半到 32)。
第三个卷积块
nn.Conv2d(
in_channels=128,
out_channels=256,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=((dilations[2] * (kernel_size - 1) + 1) // 2, 0),
dilation=(dilations[2], 1)
),
nn.BatchNorm2d(256),
nn.ReLU(),
dilations[2]
,例如 3。kernel_size=3
,dilations[2]=3
,则填充为 (3, 0)
。输出尺寸:[32, 256, 16, 9]
。
第四个卷积块
nn.Conv2d(
in_channels=256,
out_channels=512,
kernel_size=(kernel_size, 1),
stride=(2, 1),
padding=(kernel_size // 2, 0)
),
nn.BatchNorm2d(512),
nn.ReLU()
(kernel_size // 2, 0)
,例如 (1, 0)
。(1, 1)
,即传统卷积。输出尺寸:[32, 512, 8, 9]
。
自适应池化层 (self.ada_pool
)
self.ada_pool = nn.AdaptiveAvgPool2d((1, train_shape[-1]))
series
维度池化到 1,modal
维度保持为 train_shape[-1]
(例如 9)。[32, 512, 8, 9]
。[32, 512, 1, 9]
。 全连接层 (self.fc
)
self.fc = nn.Linear(512 * train_shape[-1], category)
512 * modal
,例如 512 * 9 = 4608
。category
,例如 10。forward
)def forward(self, x):
x = self.layer(x)
x = self.ada_pool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
步骤详解
x
的形状为 [batch, channels, series, modal]
,例如 [32, 1, 128, 9]
。x = self.layer(x)
[32, 512, 8, 9]
。x = self.ada_pool(x)
[32, 512, 1, 9]
。x = x.view(x.size(0), -1)
[32, 512 * 9]
,即 [32, 4608]
。x = self.fc(x)
[32, 10]
。以下是各层的输出尺寸变化(假设输入为 [32, 1, 128, 9]
):
层数 | 输入通道 | 输出通道 | series维度 | modal维度 | 空洞率(series) |
---|---|---|---|---|---|
第一层 | 1 | 64 | 64 | 9 | 1 |
第二层 | 64 | 128 | 32 | 9 | 2 |
第三层 | 128 | 256 | 16 | 9 | 3 |
第四层 | 256 | 512 | 8 | 9 | 1 |
计算过程如下:
((dilations[i] * (kernel_size - 1) + 1) // 2, 0)
确保输出尺寸合理:kernel_size=3
,dilations[0]=1
:填充 = (1, 0)
。dilations[1]=2
:填充 = (2, 0)
。dilations[2]=3
:填充 = (3, 0)
。dilations=[1, 2, 3]
逐步扩大感受野:空洞卷积网络通过引入空洞扩大感受野,保持计算效率和分辨率,特别适合需要大范围上下文的任务。代码实现展示了从单通道输入到分类输出的完整流程,逐步提取特征,适合时间序列分析或语义分割等场景。
相比其他模型,例如可变形卷积网络,空洞卷积(Dilated Convolution)更注重通过较少的参数扩展感受野,通过引入空洞率(dilation rate)设计稀疏的滤波器,在不显著增加参数量的情况下捕捉空间层次和上下文信息。下面,我们以PAMAP2数据集为例,展示空洞卷积的实际应用及其结果。
PAMAP2数据集由德国人工智能增强视觉研究中心发布,是一个用于行为识别的多模态开源数据集。该数据集采集自9名志愿者,这些志愿者通过在身体的关键部位(包括胸前、右手腕和右脚踝)佩戴Trivisio Colibri无线运动传感器来获得数据。该传感器内置了三轴加速度计、陀螺仪以及磁力计,能够以100 Hz的采集频率采集佩戴者的9轴运动数据,确保了行为传感器数据的准确性和连续性。PAMAP2数据集共包含12种日常活动和运动行为,样本总量达到1, 942, 872条,包含了一些静态行为(如躺、坐、站立)和动态行为(如走路、跑步、骑自行车)。这些多样化的行为数据为模型训练提供了丰富的素材,有助于提升行为识别的准确性。结合数据采集频率和行为持续时间特点,我们将滑动窗口大小设为170,步长设为85。这一配置既保证每个窗口捕获完整的行为片段,又通过窗口重叠增强数据量,为模型训练提供高质量支持。
基于空洞卷积的模型在PAMAP2数据集上的性能如下表所示:
| Metric | Value |
| Parameters | 1,160,341 |
| FLOPs | 55.45 M |
| Inference Time | 0.53 ms |
| Val Accuracy | 0.9652 |
| Test Accuracy | 0.9609 |
| Accuracy | 0.9609 |
| Precision | 0.9613 |
| Recall | 0.9609 |
| F1-score | 0.9608 |
每个类别的准确率:
Lying: 0.9933
Sitting: 0.9790
Standing: 0.9932
Walking: 0.9837
Running: 0.9868
Cycling: 0.9843
Nordic Walking: 0.9931
AscendStairs: 0.9778
DescendStairs: 0.9259
VacuumClean: 0.9706
Ironing: 0.9784
RodeJump: 0.9737
从结果来看,空洞卷积在静态行为上表现出色,准确率超过0.98。然而,对于复杂或相似性较高的行为准确率稍低,可能和动作的空间特征比较分散有关,需进一步优化空洞率设置。
DepthwiseSE 在 OPPO 数据集上的性能通过标准化混淆矩阵来说明,该矩阵将真实标签(行)与预测标签(列)进行比较。对角线元素表示正确的分类,而非对角线元素表示错误分类。
空洞卷积在捕捉全局空间特征方面表现优异,其中 Lying (0.9933)、Standing (0.9932)、Nordic Walking (0.9931) 等行为的对角线值接近1,表明其识别准确率较高。并且大多数类别的召回率超过0.9,其中 RopeJump 和 VacuumClean 超过0.97,更加说明了该模型的优越。
这一趋势表明模型学习效果良好,验证指标与训练指标高度一致,过拟合程度较低。
空洞卷积通过扩展感受野增强了上下文信息捕捉能力,从而在复杂行为识别中占据优势。未来,可通过引入注意力机制或动态调整空洞率,进一步提升模型对相似行为的区分能力及计算效率,以适应更广泛的应用需求。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。