对于刚接触神经网络的人,理解了网络层级之后就是要自己搭建网络,设置参数,这个时候就需要计算参数,特征图尺寸,输出维度等,这个其实看一遍流程,动手测试一下就懂了,所以我也在这里记录一下。
不同的框架,函数可能不一样,参数可能不一样,但是原理是不变的
这里我们把矩阵视作(w,h)
二维卷积
self.conv1 = nn.Conv2d(3, 6, 5) # 定义conv1函数的是图像卷积函数:输入为图像(3个通道),输出为 6张特征图, 卷积核为5x5正方形
如果把参数写完整就是
nn.Conv2d(in_channels=3,out_channels=6, kernel_size=5)
in_channels 输入通道理解的就是你一个图像有几层,如果是灰度图像那么就一层,如果是RGB彩色什么的就是三通道。当我们对一个二维矩阵做卷积 那么使用卷积核做一次就好,但是如果是三个二维矩阵的话,那么我们的卷积核就得做三次
out_channels 而什么是输出通道,我们使用一个卷积核对输入进行卷积会得到一个卷积后的结果,也是一个矩阵。而我们使用多个卷积核对输入进行卷积,那么会得到多个矩阵。如果我们设定输入通道为
out_channels=6
那么我们会对输入使用多个卷积核进行多次的卷积,得到6个输入矩阵。
kernel_size 卷积核
我们可以设置不同的卷积核的尺寸,那么同情况下输出的卷积结果矩阵也会有不同
nn.Conv2d(in_channels=3,out_channels=6, kernel_size=5,stride=2,padding=1)
除了上述三个参数,还有的就是步长和填补
padding则是在原输入矩阵的基础上进行扩充的一个过程,(不同框架略有不同 )
这里padding=1则是在输入矩阵四周扩充1单位的元素
那么计算的时候就变成了从新的位置开始进行卷积
stride表示的步长,因为卷积计算一次之后需要移位,步长不是移动的尺度
可想而知,步长会影响输出的矩阵尺寸
二维卷积维度计算
卷积维度算法
input = input_w × input_h
kernel = kernel_size_w × kernel_size_h
output_w = ( input_w- kernel_size_w + 2 × padding) / stride + 1
意思就是使用(真实的宽度(填充之后的)-核宽)/步长 再加1 简单理解初始位置 加上可以移动的次数不就是输出的维度了吗
input = torch.randn(1,1,5,5)
input
tensor([[[[-0.1454, 0.0959, 0.7894, -1.8632, 0.7346],
[ 1.4939, 0.6125, 0.5970, 1.2868, 0.6414],
[ 0.1438, -1.9135, -0.2211, 0.9153, -0.8114],
[ 1.7509, 1.0152, 0.7613, -0.7284, -0.4400],
[ 0.4867, -0.6124, -0.7566, 0.7052, -0.2713]]]])
m = nn.Conv2d(in_channels=1,out_channels=1, kernel_size=4,stride=1,padding=1)
m(input)
tensor([[[[ 0.0993, -0.1762, 0.2221, 0.3407],
[-0.4635, 0.3792, -0.1610, 0.1519],
[-0.3681, 1.0446, 0.5317, 0.1578],
[-0.3003, -0.0451, -0.2738, -0.2864]]]],
grad_fn=<ThnnConv2DBackward>)
输入为1通道 5*5的 卷积核为4*4 那么计算一下
(原始5 - 核4+padding2)/ 1 + 1 = 4 那么结果是4*4的 并且由于输出通道为1 所以只有一个输出矩阵
将输出通道更改一下为2,可以看到输出的就是2个矩阵
m = nn.Conv2d(in_channels=1,out_channels=2, kernel_size=4,stride=1,padding=1)
m(input)
tensor([[[[ 0.2068, 0.8328, 0.2907, 0.1911],
[ 0.0800, 0.9450, 0.1021, 0.7446],
[-0.5282, 0.0712, 0.0238, -0.2792],
[ 0.7343, 0.8588, -0.4857, -0.3299]],
[[ 0.0505, -0.4451, 0.9784, 0.4165],
[-0.3324, -0.3740, 0.5277, -1.5232],
[ 1.2352, 0.1066, -0.0122, 0.1837],
[-0.0487, -1.0785, 0.0505, 0.5279]]]],
grad_fn=<ThnnConv2DBackward>)
这是方阵的情况 那么使用不是方阵的试一下
input = torch.randn(1,1,5,8)
m = nn.Conv2d(in_channels=1,out_channels=1, kernel_size=(2,4),stride=2,padding=1)
m(input)
tensor([[[[ 0.1220, 0.3527, 0.0259, -0.6563],
[ 0.1079, 0.1251, -0.6078, 0.1546],
[ 0.7988, -0.0212, -0.4859, -0.1893]]]],
grad_fn=<ThnnConv2DBackward>)
输入为5*8 核函数为2*4 填充1 步长2
out_w= (原始8+填充2-核4)/ 步长2 + 1 = 4 (除法是向下省略的,因为卷积移位中位置不够不会继续计算了)
out_h = (原始5+填充2-核2)/ 步长2 + 1 = 3 输出为3*4
池化
池化对应的操作则是减少我们的特征维度,同样的是移位,不过是不重叠的
池化为(n,m)那么输出变为(w/n,h/m)
import torch.nn.functional as F
input
tensor([[[[-0.8740, 0.1030, -0.1662, 1.0354, 0.4021, -1.5810, 1.4051,
-2.0059],
[ 0.6855, -0.0885, -0.5764, 0.8782, 2.0018, 0.4929, 0.1271,
1.1021],
[ 0.1221, 0.6200, -0.2208, -0.7250, 0.9385, -0.7616, 0.4884,
-0.1267],
[ 0.5273, 2.3836, -0.6806, 0.4481, 0.3136, -0.1510, 0.3711,
0.2278],
[ 0.1555, -0.2847, 1.2660, -0.1363, -0.5036, -1.7452, 0.1767,
0.1904]]]])
F.max_pool2d(input,(3,2))
tensor([[[[0.6855, 1.0354, 2.0018, 1.4051]]]])
输入5*8 池化3*2 可见w=5/3=1 而h=8/2=4 所以输出为1*4
可以右池化四次 上下不能了
全连接特征维度计算
input = torch.randn(1,1,7,10)
conv1 = nn.Conv2d(1, 6, 5,padding = 1)
conv2 = nn.Conv2d(6, 16, 3,padding = 1)
m1 = conv1(input)
m2 = conv2(m)
mp = F.max_pool2d(m,(2, 2))
fc1 = nn.Linear(???,120)
经过两个卷积之后,我们需要全连接 此时的特征维度不允许错误,那么我们来计算一下
输入7*10 卷积核为5*5那么输出为
(7-5+2)/1 + 1 = 5 (10-5+2)/1+1 = 8 即5*8
第二个卷积层
(5-3+2)/1+1 = 5 (8-3+2)/1+1= 8 不变
池化
5*8 池化 2*2 那么变为 2*4
全连接
所有输出特征图维度为2*4 一共输出16个特征图
2*4*16 = 128
m1 torch.Size([1, 6, 5, 8])
m2 torch.Size([1, 16, 5, 8])
mp torch.Size([1, 16, 2, 4])
其中的6 16 16 便是通道数 也就是矩阵数目 最后的两个是每个矩阵的尺寸
所以最后的总特征数就是通道数 * 矩阵尺寸的元素数 128
大家注意这个通道数,实际上影响的就是输入输出的特征图的数目,跟输出尺寸是没有关系的,但是在卷积中输入输出一定要对应,输入是多少通道就是多少,不能对应不上。我们这里第一次卷积一个通道生成6个通道,第二层则是6个生成16个通道。
最后得到的则是16张特征图
最前面的1表示的则是样本数,标记一组有多少个样本 该参数在运行过程中是不会变的,也不用我们更改。