【深度学习】Pytorch 系列教程(六):PyTorch数据结构:2、张量的数学运算(4):一维卷积及其数学原理(步长stride、零填充pad;宽卷积、窄卷积、等宽卷积;卷积运算与互相关运算)

一、前言

  卷积运算是一种在信号处理、图像处理和神经网络等领域中广泛应用的数学运算。在图像处理和神经网络中,卷积运算可以用来提取特征、模糊图像、边缘检测等。在信号处理中,卷积运算可以用来实现滤波器等操作。
  本文将介绍一维卷积运算,包括步长、零填充;宽卷积、窄卷积、等宽卷积;卷积运算与互相关运算等及其PyTorch实现。

二、实验环境

  本系列实验使用如下环境

conda create -n DL python==3.11
conda activate DL
conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia

三、PyTorch数据结构

1、Tensor(张量)

  Tensor(张量)是PyTorch中用于表示多维数据的主要数据结构,类似于多维数组,可以存储和操作数字数据。

1. 维度(Dimensions)

  Tensor(张量)的维度(Dimensions)是指张量的轴数或阶数。在PyTorch中,可以使用size()方法获取张量的维度信息,使用dim()方法获取张量的轴数。

在这里插入图片描述

2. 数据类型(Data Types)

  PyTorch中的张量可以具有不同的数据类型:

  • torch.float32或torch.float:32位浮点数张量。
  • torch.float64或torch.double:64位浮点数张量。
  • torch.float16或torch.half:16位浮点数张量。
  • torch.int8:8位整数张量。
  • torch.int16或torch.short:16位整数张量。
  • torch.int32或torch.int:32位整数张量。
  • torch.int64或torch.long:64位整数张量。
  • torch.bool:布尔张量,存储True或False。

【深度学习】Pytorch 系列教程(一):PyTorch数据结构:1、Tensor(张量)及其维度(Dimensions)、数据类型(Data Types)

3. GPU加速(GPU Acceleration)

【深度学习】Pytorch 系列教程(二):PyTorch数据结构:1、Tensor(张量): GPU加速(GPU Acceleration)

2、张量的数学运算

  PyTorch提供了丰富的操作函数,用于对Tensor进行各种操作,如数学运算、统计计算、张量变形、索引和切片等。这些操作函数能够高效地利用GPU进行并行计算,加速模型训练过程。

1. 向量运算

【深度学习】Pytorch 系列教程(三):PyTorch数据结构:2、张量的数学运算(1):向量运算(加减乘除、数乘、内积、外积、范数、广播机制)

2. 矩阵运算

【深度学习】Pytorch 系列教程(四):PyTorch数据结构:2、张量的数学运算(2):矩阵运算及其数学原理(基础运算、转置、行列式、迹、伴随矩阵、逆、特征值和特征向量)

3. 向量范数、矩阵范数、与谱半径详解

【深度学习】Pytorch 系列教程(五):PyTorch数据结构:2、张量的数学运算(3):向量范数(0、1、2、p、无穷)、矩阵范数(弗罗贝尼乌斯、列和、行和、谱范数、核范数)与谱半径详解

4. 一维卷积运算

  卷积运算是一种在信号处理、图像处理和神经网络等领域中广泛应用的数学运算。在图像处理和神经网络中,卷积运算可以用来提取特征、模糊图像、边缘检测等。在信号处理中,卷积运算可以用来实现滤波器等操作。

a. 数学原理

  在离散的情况下,给定两个函数 f ( n ) f(n) f(n) g ( n ) g(n) g(n),它们的卷积运算定义为:
( f ∗ g ) ( n ) = ∑ i ( f ( i ) ⋅ g ( n − i ) ) (f * g)(n) = \sum_{i} (f(i) \cdot g(n-i)) (fg)(n)=i(f(i)g(ni))这里的 ∗ * 代表卷积运算, n n n 代表离散的变量。具体地, f ( n ) f(n) f(n) g ( n ) g(n) g(n)的卷积运算 ( f ∗ g ) ( n ) (f * g)(n) (fg)(n)表示在 n n n位置上的加权求和,其中每个加权项是 f ( i ) f(i) f(i) g ( n − i ) g(n-i) g(ni)的乘积, i i i是自由变量。

  在连续的情况下,给定两个函数 f ( x ) f(x) f(x) g ( x ) g(x) g(x),它们的卷积运算定义为:
( f ∗ g ) ( x ) = ∫ f ( u ) ⋅ g ( x − u ) d u (f * g)(x) = \int f(u) \cdot g(x-u) du (fg)(x)=f(u)g(xu)du这里的 x x x代表连续的变量。具体地, f ( x ) f(x) f(x) g ( x ) g(x) g(x)的卷积运算 ( f ∗ g ) ( x ) (f * g)(x) (fg)(x)表示在 x x x位置上的加权积分,其中每个加权项是 f ( u ) f(u) f(u) g ( x − u ) g(x-u) g(xu)的乘积, u u u是自由变量。

  在图像处理和神经网络中卷积运算通常是在离散的情况下进行的。卷积运算的主要特点是具有移动不变性和线性性质,这使得它在信号处理和图像处理中具有广泛的应用。

b. 一维卷积

  基于卷积的定义,给定长度为 M M M 的输入信号 x x x 和长度为 N N N 的卷积核(或滤波器) h h h,一维离散卷积操作可以表示为:
( x ∗ h ) [ n ] = ∑ k = 0 N − 1 x [ k ] ⋅ h [ n − k ] (x * h)[n] = \sum_{k=0}^{N-1} x[k] \cdot h[n-k] (xh)[n]=k=0N1x[k]h[nk]其中, x [ k ] x[k] x[k] 表示输入信号的第 k k k 个元素, h [ n − k ] h[n-k] h[nk] 表示卷积核的第 n − k n-k nk 个元素。
  计算步骤:

  1. 对输入信号x和卷积核h进行对齐,即将卷积核h翻转180度
  2. 将卷积核h从输入信号x的左端开始,依次与输入信号进行元素级乘法并累加求和,得到卷积输出的第n个元素;

  在深度学习中,一维卷积常被用于处理时序数据,例如语音识别、文本分类等任务。同时,一维卷积操作也是构建一维卷积神经网络(1D-CNN) 的基础,通过多层一维卷积层和池化层的堆叠,可以提取输入时序数据中的特征。

步长

  步长(stride)是指卷积核在进行卷积操作时在输入信号上滑动的步长大小。在一维卷积中,如果步长为1,则卷积核每次只移动一个元素进行卷积操作;如果步长为2,则卷积核每次移动两个元素进行卷积操作,以此类推。调整步长可以影响输出信号的长度,以及最终卷积后的特征提取效果。较大的步长可以减少输出信号的长度,同时减少特征提取时的重叠部分,而较小的步长则可以保留更多输入信号的信息

零填充

  零填充(zero-padding)是一种在进行卷积操作时在输入信号的两侧(或者一个侧)填充零值的技术。填充的目的是为了调整卷积操作后输出信号的长度,以及保持输入输出信号的对齐性。

宽卷积、窄卷积、等宽卷积

  卷积的结果按输出长度不同可以分为三类(输入向量的长度M, 卷积核的长度K):

  • 窄卷积(Narrow Convolution)
    • 步长 𝑇 = 1,两端不补零 𝑃 = 0
    • 卷积后输出长度为 𝑀 − 𝐾 +1
  • 宽卷积(Wide Convolution)
    • 步长 𝑇 = 1,两端补零 𝑃 = 𝐾 -1
    • 卷积后输出长度为 𝑀 + 𝐾 - 1
  • 等宽卷积(Same Convolution)
    • 步长 𝑇 = 1,两端补零 𝑃 = (𝐾 -1) / 2
    • 卷积后输出长度为 𝑀
  • 注意:
    • 在早期的文献中,卷积一般默认为窄卷积;而目前的文献中,一般默认为等宽卷积。

在这里插入图片描述

c. nn.Conv1d函数

  在PyTorch中,使用nn.Conv1d计算一维卷积,其期望的输入是一个三维张量,具有以下维度:[batch_size, in_channels, input_length]。同样,卷积核也需要转换成合适的维度。一般来说,卷积操作期望输入信号和卷积核的维度满足这种规范。

  • batch_size表示一次输入的样本数量,通常情况下使用1。
  • in_channels表示输入信号的通道数。
  • input_length表示输入信号的长度。

因此需要使用.view(1, 1, -1)将输入信号和卷积核转换成了这种期望的维度格式。

  • 第一个参数1表示batch size为1。
  • 第二个参数1表示通道数为1。
  • 第三个参数-1表示PyTorch会根据张量的总元素数自动计算最后一个维度的大小,这里用于自动计算输入信号的长度
import torch
import torch.nn as nn


input_signal = torch.tensor([1, 1, 2, -1, 1, -3, 1], dtype=torch.float)
# conv_kernel = torch.tensor([1/3, 1/3, 1/3])
conv_kernel = torch.tensor([-1, 0, 1], dtype=torch.float)
conv_kernel = torch.flip(conv_kernel, [0])
print(f"原始维度:{conv_kernel.size()}")
print(f"原始轴数:{conv_kernel.dim()}")

# 将输入信号和卷积核转换成合适的维度
input_signal = input_signal.view(1, 1, -1)
conv_kernel = conv_kernel.view(1, 1, -1)
print(f"转换后维度:{conv_kernel.size()}")
print(f"转换后轴数:{conv_kernel.dim()}")
# conv_kernel = torch.flip(conv_kernel, [0])
# print(f"conv_kernel:{conv_kernel}")

M = input_signal.size(-1)
K = conv_kernel.size(-1)
# 创建宽、窄、等宽卷积层
conv1d_wide = nn.Conv1d(in_channels=1, out_channels=1, kernel_size=K, padding=K - 1, bias=False)
conv1d_narrow = nn.Conv1d(in_channels=1, out_channels=1, kernel_size=K, bias=False)
conv1d_same = nn.Conv1d(in_channels=1, out_channels=1, kernel_size=K, padding=int((K - 1) / 2), bias=False)
conv1d_stride2 = nn.Conv1d(in_channels=1, out_channels=1, kernel_size=K, stride=2, bias=False)

# 将卷积核加载到卷积层中
conv1d_wide.weight.data = conv_kernel
conv1d_narrow.weight.data = conv_kernel
conv1d_same.weight.data = conv_kernel
conv1d_stride2.weight.data = conv_kernel

output_wide = conv1d_wide(input_signal)
print(f"宽卷积:\n\t{output_wide}")
output_same = conv1d_same(input_signal)
print(f"等宽卷积:\n\t{output_same}")
output_narrow = conv1d_narrow(input_signal)
print(f"窄卷积:\n\t{output_narrow}")
output_stride2 = conv1d_stride2(input_signal)
print(f"步长=2窄卷积:\n\t{output_stride2}")

在这里插入图片描述

d. 互相关

在这里插入图片描述
  注意:在神经网络中使用卷积是为了进行特征抽取,卷积核是否进行翻转和其特征抽取的能力无关(互相关和卷积的区别也可以理解为图像是否进行翻转)。特别是当卷积核是可学习的参数时,卷积和互相关在能力上是等价的。因此,为了实现上(或描述上)的方便起见,我们用互相关来代替卷积。事实上,很多深度学习工具中卷积操作其实都是互相关操作,如上图PyTorch计算结果即为互相关。

  翻转卷积核,计算结果与前面手动计算结果相同:

conv_kernel = torch.flip(conv_kernel, [0])

在这里插入图片描述

d. torch.nn.functional.conv1d
import torch
import torch.nn.functional as F

input_signal = torch.tensor([1, 1, 2, -1, 1, -3, 1], dtype=torch.float)
# conv_kernel = torch.tensor([1/3, 1/3, 1/3])
conv_kernel = torch.tensor([-1, 0, 1], dtype=torch.float)
input_signal = input_signal.view(1, 1, -1)
conv_kernel = conv_kernel.view(1, 1, -1)
K = conv_kernel.size(-1)
print("………………………………………………………………………………互相关………………………………………………………………………………")
output_wide = F.conv1d(input_signal, conv_kernel, padding=K - 1)
output_narrow = F.conv1d(input_signal, conv_kernel, )
output_same = F.conv1d(input_signal, conv_kernel, padding=int((K - 1) / 2))
print(f"宽卷积:\n\t{output_wide}")
print(f"等宽卷积:\n\t{output_same}")
print(f"窄卷积:\n\t{output_narrow}")

print("………………………………………………………………………………卷积………………………………………………………………………………")
conv_kernel = torch.flip(conv_kernel, [2])
output_wide = F.conv1d(input_signal, conv_kernel, padding=K - 1)
output_narrow = F.conv1d(input_signal, conv_kernel, )
output_same = F.conv1d(input_signal, conv_kernel, padding=int((K - 1) / 2))

print(f"宽卷积:\n\t{output_wide}")
print(f"等宽卷积:\n\t{output_same}")
print(f"窄卷积:\n\t{output_narrow}")

在这里插入图片描述
注意:卷积与互相关不是相反数关系,上述卷积核-1, 0, 1特殊~
在这里插入图片描述

e. torch.nn.functional.pad
import torch


input_signal = torch.tensor([1, 1, 2, -1, 1, -3, 1], dtype=torch.float)
conv_kernel = torch.tensor([-1, 0, 1], dtype=torch.float)

# 反转卷积核~定义
conv_kernel_flipped = torch.flip(conv_kernel, [0])
K = conv_kernel.numel()
print(f"K:{K}")
# 零填充输入信号
padded_input = torch.nn.functional.pad(input_signal, (K - 1, K - 1), 'constant', 0)
print(f"输入信号:{padded_input}")
# 执行卷积运算
output1 = torch.nn.functional.conv1d(padded_input.view(1, 1, -1), conv_kernel_flipped.view(1, 1, -1))
# 输出结果
print(f"卷积运算:{output1}")
# 互相关运算
output2 = torch.nn.functional.conv1d(padded_input.view(1, 1, -1), conv_kernel.view(1, 1, -1))
# 输出结果
print(f"互相关运算:{output2}")

  • 45
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QomolangmaH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值