卷积的基本概念
什么是卷积
卷积是深度学习中的一个重要概念,特别是在计算机视觉任务中广泛应用。
卷积操作是通过一个卷积核(也称为滤波器)在输入数据上滑动,进行局部特征提取的过程。PyTorch提供了强大的卷积功能支持。
卷积计算的基础知识
原来卷积是这么计算的_卷积计算-CSDN博客,个人认为这篇文章是非常优秀的,形象解释了卷积的基本概念和计算过程。
PyTorch中的卷积层:
torch.nn.Conv1d: 一维卷积
torch.nn.Conv2d: 二维卷积
torch.nn.Conv3d: 三维卷积
主要参数:
in_channels: 输入的通道数
out_channels: 输出的通道数
kernel_size: 卷积核的大小
stride: 卷积步长
padding: 输入填充
dilation: 卷积核元素之间的间距
groups: 控制输入和输出之间的连接
bias: 是否添加偏置
一个简单的一维卷积DEMO:
import torch
import torch.nn as nn
# 定义一个1维Tensor,并调整其形状为(批次大小, 通道数, 序列长度)
input_tensor = torch.tensor([[[1.0, 2.0, 3.0, 4.0, 5.0]]])
# 定义一个简单的卷积层
conv_layer = nn.Conv1d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1)
# 应用卷积操作
output_tensor = conv_layer(input_tensor)
print(output_tensor)
输出结果:
tensor([[[1.6147, 2.1414, 2.6681, 3.1949, 1.7116]]],
grad_fn=<SqueezeBackward1>)
多运行两次:
tensor([[[-0.6965, -0.2377, 0.2211, 0.6798, 2.4817]]],
grad_fn=<SqueezeBackward1>)
tensor([[[1.2291, 2.5095, 3.7900, 5.0704, 3.9702]]],
grad_fn=<SqueezeBackward1>)
可以看到,每次运行的输出结果并不相同,原因是如果不指定卷积核和偏置值,它就用随机数的卷积核和偏置值来进行卷积操作。在PyTorch的实际应用中,卷积核(也就是卷积层的权重)以及偏置值是可以学习的,也就是说,权重值和偏置值是在训练过程中通过优化算法自动调整的。
我们可以通过下面的方法查看卷积层的权重和偏置:
import torch
import torch.nn as nn
# 定义一个1维Tensor,并调整其形状为(批次大小, 通道数, 序列长度)
input_tensor = torch.tensor([[[1.0, 2.0, 3.0, 4.0, 5.0]]])
# 定义一个简单的卷积层
conv_layer = nn.Conv1d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1)
# 应用卷积操作
output_tensor = conv_layer(input_tensor)
print(f'卷积核的权重{conv_layer.weight.data}') # 打印权重
print(f'偏置值{conv_layer.bias.data}') # 打印偏置值
print(output_tensor)
输出结果:
卷积核的权重tensor([[[ 0.0500, 0.1506, -0.2071]]])
偏置值tensor([-0.3645])
tensor([[[-0.6281, -0.6346, -0.6410, -0.6475, 0.5884]]],
grad_fn=<SqueezeBackward1>)
多运行两次,也可以看出每次的卷积核与偏置值并不相同。
第二次运行的结果:
卷积核的权重tensor([[[ 0.3540, -0.2518, -0.4792]]])
偏置值tensor([0.4238])
tensor([[[-0.7864, -1.1633, -1.5403, -1.9173, 0.5807]]],
grad_fn=<SqueezeBackward1>)
第三次运行的结果:
卷积核的权重tensor([[[-0.3216, -0.0135, -0.4280]]])
偏置值tensor([-0.0388])
tensor([[[-0.9082, -1.6713, -2.4343, -3.1973, -1.3925]]],
grad_fn=<SqueezeBackward1>)
总结:卷积核和偏置在不指定的情况下是随机数,在实际应用中,训练过程中可以通过优化算法自动调整。另外也可以手动指定卷积核和偏置,用来进行一些有目的的特征提取。
在下面的代码中,我们指定了卷积核和偏置:
import torch
import torch.nn as nn
# 定义一个1维Tensor,并调整其形状为(批次大小, 通道数, 序列长度)
input_tensor = torch.tensor([[[1.0, 2.0, 3.0, 4.0, 5.0]]])
# 定义一个简单的卷积层
conv_layer = nn.Conv1d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0, bias=True)
# 设置卷积核的权重
kernel = torch.tensor([[[1.0, 2.0, 3.0]]])
conv_layer.weight.data = kernel
# 设置卷积核的偏置
bias = torch.tensor([0.333])
conv_layer.bias.data = bias
# 应用卷积操作
output_tensor = conv_layer(input_tensor)
print(f'卷积核的权重:{conv_layer.weight.data}')
print(f'卷积核的偏置:{conv_layer.bias.data}')
print(f'卷积操作后的输出:{output_tensor}')
输出结果:
卷积核的权重:tensor([[[1., 2., 3.]]])
卷积核的偏置:tensor([0.3330])
卷积操作后的输出:tensor([[[14.3330, 20.3330, 26.3330]]], grad_fn=<SqueezeBackward1>)
从图像卷积的demo大致了解卷积的作用
新建一个图像文件,命名为demo_texture.png,保存在python脚本同文件夹下,放大后如下图 :
这是一个20*20像素的图,每个方格是一个像素点,1:1显示是这个样子的:
看得出,它是一个具有典型的均匀纹理特征的图片,在下面的代码中,我们通过定义一个卷积核来对它的纹理特征进行提取。
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
# 加载图像
image = Image.open('demo_texture.png') # 替换为你的图像文件路径
# 转换为Tensor
transform = transforms.ToTensor()
image_tensor = transform(image).unsqueeze(0) # 增加一个batch维度
# 定义一个卷积层
conv_layer = nn.Conv2d(in_channels=3, out_channels=1, kernel_size=3, stride=3, padding=0)
# 自定义卷积核
kernel = torch.tensor([
[[0.0, 0.0, 0.0],
[0.0, 0.3, 0.0],
[0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0],
[0.0, 0.3, 0.0],
[0.0, 0.0, 0.0]],
[[0.0, 0.0, 0.0],
[0.0, 0.3, 0.0],
[0.0, 0.0, 0.0]]
], dtype=torch.float32)
# 重新调整权重形状
conv_layer.weight.data = kernel.view(1, 3, 3, 3) # 用于卷积操作的卷积核形状应为 (out_channels, in_channels, kernel_height, kernel_width)
# 应用卷积
output = conv_layer(image_tensor)
# 转换Tensor到PIL图像
transform_to_pil = transforms.ToPILImage()
# 移除batch维度并转换
output_image = transform_to_pil(output.squeeze(0).detach().clamp(0, 1))
'''
在 PyTorch 中,detach () 函数用于从计算图中分离出一个 Tensor,返回一个新的 Tensor,这个新的 Tensor
不再与计算图有任何关系,不再参与梯度计算。常用于将需要计算梯度的 Tensor 与不需要计算梯度的 Tensor 分离开来,
以便在反向传播时只对需要计算梯度的 Tensor 进行计算。
squeeze (0)” 通常指的是对某个数据结构或数组执行 “挤压” 操作,参数 “0” 可能表示沿着某个特定的维度进行操作。
更细致地说,“squeeze” 操作一般用于去除数组中大小为 1 的维度,而 “0” 可能表示要处理的是第一个维度。但具体的
含义还需要根据其所在的编程环境和上下文来确定。
“clamp (0, 1)” 通常表示一种限制或约束操作。它的意思是将某个值限定在 0 到 1 的范围内。具体来说,如果给定的
值小于 0 ,则返回 0 ;如果给定的值大于 1 ,则返回 1 ;如果给定的值在 0 到 1 之间,则返回该值本身。这种操作
常用于确保数值不会超出特定的范围,以保证程序的正确性和稳定性。
'''
# 显示卷积后的图像
output_image.show()
运行结果的显示:
放大之后的:
可以看到,卷积之后的图像尺寸只有6*6像素,但是保持了原图像的纹理特征。
通过定义不同形状和权重的卷积核就可以在卷积操作之后,提取不同类型的图像特征,比如纹理、轮廓等等。