之前做医学图像分类时,老师有提到3D卷积,当时只是觉得3D卷积就是输入图像的3维的,把卷积核改成三维的就行,没有真正意识到3D卷积与2D卷积中的多通道卷积有什么区别,今天突然想到这个问题又查阅了很多资料才搞清楚里面的区别在哪,决定把自己总结的知识点记下来,以便于之后查看。(以下只是博主自己的理解,如有错误的地方,请一定要指出来,谢谢~)
下文的内容将会涉及到这幅图,来源于Learning Spatiotemporal Features with 3D Convolutional Networks(ICCV 2015):
1 2D卷积
1.1 单通道卷积
以图像为例,单通道是指输入图像的channel为1,如MNIST数据集中的灰度图像。
- 以MNIST数据集中的图像为例,输入图像的shape为(1,1,28,28),第一个‘1’表示batchsize为1,第二个‘1’表示图像channel为1,图像大小为28*28。
- 单通道2D卷积中的卷积核shape为(1,kernel_height,kernel_width),因为卷积核的in_channel与输入图像的channel必须一致,输入图像的channel为1,所以卷积核的in_channel为1。卷积核大小为kernel_height×kernel_width。参数数量为:1×kernel_height×kernel_width×out_channels。
- 单通道2D卷积的过程可以视为上图的中(a)的情况,卷积核在图像中从左向右,从上到下滑动来提取特征。卷积之后的output是2D的。
1.1.1 单通道卷积Pytorch实现
直接使用torchvision.datasets.MNIST()导入MNIST数据集,取其中一张图像为例,使用3×3的卷积核进行卷积:
import torch
import torchvision
import torch.nn as nn
##data是数据集中的一张图片
input_2d=data
print(input_2d.shape)
##out: torch.Size([1, 1, 28, 28])
## '1'是in_channels,‘2’是out_channels
conv_2d_1=nn.Conv2d(1, 2, kernel_size=3,stride=1, padding=0)
output=conv_2d_1(input_2d)
print(output.shape)
##out:torch.Size([1, 2, 26, 26])
print(conv_2d_1.weight.size())
##out:torch.Size([2, 1, 3, 3])
1.2 多通道卷积
与单通道相对应,多通道是指输入图像的channel有多个,常见的是彩色图像,其有RGB三个通道,如CIFAR-10数据集中的图像。
- 以CIFAR-10数据集中的图像为例,输入图像的shape为(1,3,32,32),‘1’表示batchsize,‘3’表示图像channel(R,G,B),图像大小为32*32。
- 多通道2D卷积中的卷积核shape为(3,kernel_height,kernel_width),输入图像的channel决定了卷积核的in_channel。卷积核大小为kernel_height×kernel_width。参数数量为:3×kernel_height×kernel_width×out_channels,且每个通道的卷积核参数不同。
- 多通道2D卷积的过程可以视为上图的中(b)的情况,卷积核在图像中从左向右,从上到下滑动来提取特征,卷积过程与单通道卷积区别不大。卷积之后的output是2D的。
1.2.1 多通道卷积Pytorch实现
直接使用torchvision.datasets.CIFAR10()导入CIFAR10数据集,取其中一张图像为例,使用3×3的卷积核进行卷积:
import torch
import torchvision
import torch.nn as nn
##data是CIFAR10数据集中的一个样本
input_3d=data
print(input_3d.shape)
##out: torch.Size([1, 3, 32, 32])
## '3'是in_channels,‘1’是out_channels
conv_2d_3=nn.Conv2d(3, 1, kernel_size=5,stride=1, padding=0)
output=conv_2d_3(input_3d)
print(output.shape)
##out:torch.Size([1, 1, 28, 28])
print(conv_2d_3.weight.size())
##out:torch.Size([1, 3, 5, 5])
print(conv_2d_3.weight)
##out:将每个通道的卷积核参数打印下来,发现不同通道参数不一样。
#Parameter containing:
#tensor([[[[-0.0600, -0.0141, -0.0144, 0.1019, -0.0315],
# [ 0.0584, 0.0127, -0.0456, -0.0332, -0.0799],
# [ 0.0907, 0.0177, -0.0280, 0.0516, 0.1063],
# [-0.0778, 0.0547, -0.0803, -0.0821, 0.1050],
# [ 0.0043, -0.0023, 0.0605, 0.0147, 0.0778]],
# [[-0.0456, 0.0874, 0.1106, -0.0932, -0.1071],
# [ 0.0710, -0.0980, -0.0349, -0.0049, -0.0561],
# [ 0.0739, -0.0542, -0.0015, 0.0583, 0.0964],
# [ 0.0017, 0.0645, 0.0116, 0.0480, 0.0664],
# [-0.0622, 0.1145, -0.0708, -0.0958, 0.0587]],
# [[ 0.0913, -0.0239, 0.0371, -0.0304, 0.0454],
# [ 0.0646, 0.1053, -0.0504, 0.0908, 0.0729],
# [ 0.0518, 0.0235, -0.0326, -0.0338, -0.0240],
# [-0.0689, -0.0707, 0.0543, 0.1041, -0.0868],
# [-0.0684, -0.0483, -0.0327, -0.0383, -0.0138]]]], requires_grad=True)
2 3D卷积
2.1 3D卷积特点
3D卷积同样有单通道卷积和多通道卷积,与2D卷积类似,这里不再赘述,只强调区别。
- 同样以图像为例,3D卷积在医疗图像中使用较多,如CT,MRI等原始数据往往有多个切片构成。
- 3D卷积中输入图像的shape为(1,channel,depth,height,weight),这里的depth是区别于2D卷积的关键,2D卷积的输入可以看出是3D卷积的特殊情况,即depth为1。
- 3D卷积中卷积核的shape为(input_channels , kernel_depth , kernel_height, kernel_width),与2D卷积相比,3D卷积的卷积核多了kernel_depth这个维度。卷积核大小为kernel_depth×kernel_height×kernel_width。参数数量为:input_channels×kernel_depth×kernel_height×kernel_width×out_channels,且每个通道的卷积核参数不同。
- 3D卷积的过程可以视为上图的中(c)的情况,卷积核除了在3D图像中从左向右,从上到下滑动来提取特征外,还需要往一个额外的depth维度上滑动(可以看出除了在一个平面上往左右,上下滑动外,还要往里走,就是另外一个维度,想象一下,我不太会形容唉),因此3D卷积可以在空间中提取更强的特征信息,3D卷积后的output仍然是3D的。
2.2 3D卷积Pytorch实现
以CT数据集中的一个nii.gz文件为例,就是一个样本,利用SimpleITK中的SimpleITK.GetArrayFromImage()提取nii.gz中的array信息,可以得到该样本的shape为(301, 512, 512),即depth为301,height为512,weight为512。利用2×3×3的卷积核进行卷积:
import torch
import torchvision
import torch.nn as nn
import SimpleITK as sitk
import numpy as np
##这是一个CT数据集中的样本
filename='./coronacases_001.nii.gz'
itkimage = sitk.ReadImage(filename)
numpyImage = sitk.GetArrayFromImage(itkimage)
print(numpyImage.shape)
##out:(301, 512, 512)
##将输入reshape为网络输入数据的格式(batchsize,channel,depth,height,weight)
input_=torch.Tensor(numpyImage.reshape(1,1,301,512,512))
print(input_.shape)
##out:torch.Size([1, 1, 301, 512, 512])
##'1'是in_channels,‘2’是out_channels
##(2,3,3)分别对应kernel_depth,kernel_height,kernel_width
conv_3d=nn.Conv3d(1, 2, kernel_size=(2,3,3),stride=1, padding=0)
output=conv_3d(input_)
print(output.shape)
##out:torch.Size([1, 2, 300, 510, 510])
print(conv_3d.weight.size())
##out:torch.Size([2, 1, 2, 3, 3])
print(conv_3d.weight)
##out:每个通道的卷积核参数不同
#Parameter containing:
#tensor([[[[[-0.2320, -0.1497, -0.0779],
# [-0.0511, -0.1489, -0.0866],
# [ 0.1233, -0.2222, 0.0528]],
# [[-0.0779, 0.2237, -0.0941],
# [ 0.0026, 0.0426, 0.2323],
# [ 0.2230, -0.0586, 0.2127]]]],
# [[[[ 0.0393, -0.0342, -0.1338],
# [ 0.0640, 0.0879, -0.2289],
# [-0.0047, -0.1611, 0.2131]],
# [[ 0.1573, -0.0108, 0.2327],
# [-0.0824, -0.1601, -0.2348],
# [ 0.1938, -0.0731, 0.1490]]]]], requires_grad=True)