论文介绍
题目:OMNI-SCALE CNNS: A SIMPLE AND EFFECTIVE KER NEL SIZE CONFIGURATION FOR TIME SERIES CLASSIFI CATION
论文地址:
https://arxiv.org/pdf/2002.10061
QQ深度学习交流群:994264161
扫描下方二维码,加入深度学习论文指南星球!
加入即可获得,模块缝合、制作、写作技巧,学会“结构”创新、“创新点”创新,从三区到顶会,小论文大论文,毕业一站式服务
创新点
无需搜索最佳感受野大小:OS-block 提供了一种基于素数的卷积核配置规则,可以自动覆盖所有的感受野大小(Receptive Field Size, RF)。相比传统需要大量搜索感受野大小的方法,这种设计避免了复杂且耗资源的搜索过程。
基于素数设计的内核:OS-block 使用素数作为卷积核的大小,这种设计源于哥德巴赫猜想。通过这种规则,OS-block 能高效覆盖所有时间尺度,从而在多个数据集上获得类似于最佳感受野大小的性能。
跨领域数据的鲁棒性表现:在不同领域的时间序列数据集(如医疗、活动识别、语音和光谱分析)中,OS-block 显示出强大的时间尺度捕获能力,且无需针对每个数据集单独调整超参数。
模型复杂度的高效性:相较于使用偶数或其他数值对来覆盖感受野,OS-block 基于素数的设计可以显著降低模型复杂度,提供更高效的解决方案。
支持多种复杂结构扩展:OS-block 可以轻松与其他深度学习结构结合(如膨胀卷积、注意力机制、Transformer 等),进一步提升性能。
方法
整体架构
这篇论文提出的模型是 OS-CNN,核心组件为 OS-block,通过多层基于素数设计的卷积核,高效覆盖所有可能的感受野大小,从而提取多尺度特征。模型整体结构包括输入层、多个 OS-block、全局平均池化层和全连接层,用于降维和分类。此外,OS-block 可以与其他深度学习结构(如残差连接、Transformer 等)结合,具有很强的灵活性和扩展性,在时间序列分类任务中表现出卓越性能。
输入层:
模型可以处理单变量或多变量时间序列数据。
输入数据根据时间序列变量的数量调整通道数。
OS-block(核心模块):
多卷积核结构:OS-block 包含三层卷积,每层具有多个卷积核,卷积核的大小基于素数设计。
覆盖所有感受野大小:通过素数的组合,OS-block 能够高效覆盖所有可能的感受野大小(RF sizes)。
数学原理支持:OS-block 基于哥德巴赫猜想,利用素数组合和偶数覆盖,确保感受野的完整性和范围的连续性。
全局平均池化层(Global Average Pooling):
用于降维,汇总时间序列的特征。
全连接层(Fully Connected Layer):
用于分类任务,根据输入特征生成最终的分类结果。
可扩展性:
OS-block 能轻松结合其他深度学习结构(如残差连接、Transformer、膨胀卷积等),以增强模型的灵活性和性能。
即插即用模块作用
OS_block 作为一个即插即用模块:
时间序列分类任务:
单变量与多变量时间序列数据(如金融、医疗、物联网传感器数据等)。
特别适合特征提取依赖不同时间尺度的场景,例如健康监测、活动识别、语音分类等。
复杂时间尺度特征的场景:
当数据中存在多种时间尺度或隐藏周期性(如季节性、短期和长期模式)时,OS-block 能够捕获多尺度特征。
无感受野搜索的场景:
在需要避免复杂参数调节或超参数搜索的情况下(如感受野大小、卷积核配置等),OS-block 能自动覆盖所有可能的感受野。
需要模块化扩展的深度学习任务:
可与主流网络(如 Transformer、ResNet、Inception)集成,用于增强模型的时间尺度处理能力。
在具有不同输入特征尺度的模型中,如视频分类(时间维度)、音频分析和其他序列数据建模。
消融实验结果
内容:在多个基准数据集上的性能对比,包括 MEG-TLE、UEA 30、UCR 85 和 UCR 128 数据集。
说明:
OS-CNN 在多个数据集中表现优于其他方法,并且模型参数量显著减少。
结果表明,OS-block 的设计不仅提高了分类准确率,还降低了模型复杂度。
即插即用模块
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import numpy as np
def calculate_mask_index(kernel_length_now, largest_kernel_lenght):
right_zero_mast_length = math.ceil((largest_kernel_lenght - 1) / 2) - math.ceil((kernel_length_now - 1) / 2)
left_zero_mask_length = largest_kernel_lenght - kernel_length_now - right_zero_mast_length
return left_zero_mask_length, left_zero_mask_length + kernel_length_now
def creat_mask(number_of_input_channel, number_of_output_channel, kernel_length_now, largest_kernel_lenght):
ind_left, ind_right = calculate_mask_index(kernel_length_now, largest_kernel_lenght)
mask = np.ones((number_of_input_channel, number_of_output_channel, largest_kernel_lenght))
mask[:, :, 0:ind_left] = 0
mask[:, :, ind_right:] = 0
return mask
def creak_layer_mask(layer_parameter_list):
largest_kernel_lenght = layer_parameter_list[-1][-1]
mask_list = []
init_weight_list = []
bias_list = []
for i in layer_parameter_list:
conv = torch.nn.Conv1d(in_channels=i[0], out_channels=i[1], kernel_size=i[2])
ind_l, ind_r = calculate_mask_index(i[2], largest_kernel_lenght)
big_weight = np.zeros((i[1], i[0], largest_kernel_lenght))
big_weight[:, :, ind_l:ind_r] = conv.weight.detach().numpy()
bias_list.append(conv.bias.detach().numpy())
init_weight_list.append(big_weight)
mask = creat_mask(i[1], i[0], i[2], largest_kernel_lenght)
mask_list.append(mask)
mask = np.concatenate(mask_list, axis=0)
init_weight = np.concatenate(init_weight_list, axis=0)
init_bias = np.concatenate(bias_list, axis=0)
return mask.astype(np.float32), init_weight.astype(np.float32), init_bias.astype(np.float32)
class build_layer_with_layer_parameter(nn.Module):
def __init__(self, layer_parameters, relu_or_not_at_last_layer=True):
super(build_layer_with_layer_parameter, self).__init__()
self.relu_or_not_at_last_layer = relu_or_not_at_last_layer
os_mask, init_weight, init_bias = creak_layer_mask(layer_parameters)
in_channels = os_mask.shape[1]
out_channels = os_mask.shape[0]
max_kernel_size = os_mask.shape[-1]
self.weight_mask = nn.Parameter(torch.from_numpy(os_mask), requires_grad=False)
self.padding = nn.ConstantPad1d((int((max_kernel_size - 1) / 2), int(max_kernel_size / 2)), 0)
self.conv1d = torch.nn.Conv1d(in_channels=in_channels, out_channels=out_channels, kernel_size=max_kernel_size)
self.conv1d.weight = nn.Parameter(torch.from_numpy(init_weight), requires_grad=True)
self.conv1d.bias = nn.Parameter(torch.from_numpy(init_bias), requires_grad=True)
self.bn = nn.BatchNorm1d(num_features=out_channels)
def forward(self, X):
self.conv1d.weight.data = self.conv1d.weight * self.weight_mask
# self.conv1d.weight.data.mul_(self.weight_mask)
result_1 = self.padding(X)
result_2 = self.conv1d(result_1)
result_3 = self.bn(result_2)
if self.relu_or_not_at_last_layer:
result = F.relu(result_3)
return result
else:
return result_3
class OS_block(nn.Module):
def __init__(self, layer_parameter_list, relu_or_not_at_last_layer=True):
super(OS_block, self).__init__()
self.layer_parameter_list = layer_parameter_list
self.layer_list = []
self.relu_or_not_at_last_layer = relu_or_not_at_last_layer
for i in range(len(layer_parameter_list)):
if i != len(layer_parameter_list) - 1:
using_relu = True
else:
using_relu = self.relu_or_not_at_last_layer
layer = build_layer_with_layer_parameter(layer_parameter_list[i], using_relu)
self.layer_list.append(layer)
self.net = nn.Sequential(*self.layer_list)
def forward(self, X):
X = self.net(X)
return X
if __name__ == '__main__':
layer_parameter_list = [
[(16, 32, 3)],
[(32, 16, 5)],
[(16, 16, 7)]
]
input = torch.rand(10, 16, 100)
block = OS_block(layer_parameter_list=layer_parameter_list, relu_or_not_at_last_layer=True)
output = block(input)
print(input.size()) print(output.size())
便捷下载方式
浏览打开网址:https://github.com/ai-dawang/PlugNPlay-Modules
更多分析可见原文