1. CNN简介
本文主要介绍利用pytorch对CNN的计算有一个直观的认识,在此认为你已经对CNN有了一些理解,但是对如何计算还有一些迷糊。
卷积神经网络的结构一般用在图像处理领域,优化了全连接神经网络的参数过多问题。CNN的结构图如下所示,一个卷积神经网络由若干卷积层、Pooling层、全连接层组成。常用的架构模式为:
其中Pooling层不一定要有,参考如下的架构图。关于CNN的细节不做太多介绍,具体可以参考这篇文章。
2. pytorch计算卷积CNN
2.1 pytorch的cnn实现函数介绍
首先介绍一下pytorch实现cnn的函数
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
- in_channels, out_channels为输入输出信号的通道;
- kernel_size为卷积核的大小,可以为int or (height, width),int时表示卷积核是高宽相等的,为touple表示卷积核的高度和宽度不相等;
- stride 同样可以为int or touple,参考kernel_size,卷积步长高度和宽度两个方向不等时候,采用touple;
- padding int or (height, width) 输入的每一条边补充0的层数;
- dilation(int or tuple, `optional``) – 卷积核元素之间的间距,默认为1,为2时候,卷积核元素之间的间距拉大,相当于放大了卷积核看到的区域;
- bias bias为True,添加偏置;
- groups(int, optional) – 从输入通道到输出通道的阻塞连接数
举个例子:
import torch as t
from torch import nn
m = nn.Conv2d(in_channels=8,out_channels=3,kernel_size= 3)
input = t.randn(10, 8,5,5 )
output = m(input)
output.shape
>>>
torch.Size([10, 2, 3, 3])
这里的input为batch10,深度为8,高度和宽度都为5的三维矩阵(在此解释一下,明明是3维矩阵,却为啥是conv2d,二维的呢,个人理解是,这里的2d是针对有高度和宽度,卷积核在这两个方向上移动,所以虽然输入的是三维矩阵,但是只在两个方向上移动,所以是Conv2d)。通过卷积核为355的卷积核后,output的高度和宽度为3(5-3+1)。
卷积变换的高度和宽度的计算公式为:
其中为卷积变换之后的高度,为卷积核的宽度,为补0的圈数。
2.2 理解卷积的计算方法
卷积神经网络是如何计算的呢,下面是一张非常经典的图,input是一个深度为3的7*7的数据,2channel的filter,filter的深度要和input的深度 一致,因此CNN输出矩阵的大小和filter有如下规范:
- filter 的深度要和input的深度一致;
- 卷积神经网络的输出的channel是由filter的channel数决定的;
- 输出的高度和宽度由卷积核的高度和宽度、卷积的步长straddle、补零padding的圈数决定的;
卷积的计算方法可以参考下方的动态图,画的非常清晰,filter和input做element-wise的相乘,不同层之间进行求和,最后加上bias,得到output。
接下来,我们再继续使用pytorch实践一下计算过程:
import torch.nn.functional as f
input = t.randn(1,3,3,3) # 输入为batch为2,深度为2的3*3矩阵
filter1 = t.randn(2,3,2,2) # 卷积核即filter为(3,2,2,2),即channel为3,卷积核的深度为2,高宽为2*2
o1 = f.conv2d(input, filter1,stride=1) #因此输出的维度为(2,3,2,2)
input
>>>
Out[18]:
tensor([[[[-0.1759, -0.3417, 1.4123],
[-1.6696, 0.9701, -2.3805],
[-0.7241, -0.2209, -0.8992]],
[[-1.3623, 0.8210, 0.7222],
[ 0.5904, 0.0083, -1.5792],
[ 0.3217, -0.8068, 0.7589]],
[[-0.5402, 1.2805, 0.7455],
[-0.8920, -1.0816, 1.1012],
[-0.0124, -1.6899, 1.2731]]]])
filter1
>>>
tensor([[[[-1.8587e+00, 1.2517e+00],
[-6.4202e-01, 5.6213e-01]],
[[-2.1839e-01, -1.9386e-01],
[ 7.2792e-01, -1.9432e+00]],
[[-1.7671e+00, -1.4224e+00],
[-1.5365e+00, -1.1106e+00]]],
[[[ 1.8957e+00, 5.6250e-01],
[-1.9311e+00, -8.1737e-02]],
[[ 7.7985e-01, -1.3875e-01],
[-8.8998e-01, -1.1328e+00]],
[[ 5.6669e-01, -1.1629e+00],
[ 3.2056e-01, -7.1626e-04]]]])
o1
>>>
Out[19]:
tensor([[[[ 3.7735, 0.3129],
[11.3403, -5.3765]],
[[-1.1724, 0.3006],
[ 0.6336, -1.3521]]]])
如上可以看到第一个元素o1[0,0,0,0]是3.7735,接下来我们人肉计算一下第一个元素:
import torch as t
t.sum(input[0,:,0:2,0:2]*filter1[0,:,:,:])
>>>
out[20]
tensor(3.7735)
结果和利用nn.functional.Conv2d计算出的结果一致。
nn.Conv2d和nn.functional.Conv2很相似,但是在构建模型的时候一般采用nn.Conv2d来实现。