文章目录
1. 案例分析
在深度学习的过程中我们常常需要去搭建一个网络结构,比如:NN、CNN等,最近在学习的过程中碰到了一个难题,就是别人使用Tensorflow搭建的一个CNN网络,我需要使用Pytorch去搭建这个CNN网络,由于这两种框架一些语法不同导致我不能直接信手拈来,所以必须要手动搭建这个CNN网络,比如这个网络结构,使用的是TF框架写的,那么我们该如何将它转为Pytorch,这也是我写这篇文章的缘由。
conv_model.add(tf.keras.Input(input_shape))
conv_model.add(tf.keras.layers.Conv2D(32, (3, 3), padding='same', data_format=data_format))
conv_model.add(tf.keras.layers.Activation('relu'))
conv_model.add(tf.keras.layers.Conv2D(32, (3, 3), data_format=data_format))
conv_model.add(tf.keras.layers.Activation('relu'))
conv_model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
conv_model.add(tf.keras.layers.Conv2D(64, (3, 3), padding='same',
data_format=data_format))
conv_model.add(tf.keras.layers.Activation('relu'))
conv_model.add(tf.keras.layers.Conv2D(64, (3, 3), data_format=data_format))
conv_model.add(tf.keras.layers.Activation('relu'))
conv_model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
conv_model.add(tf.keras.layers.Dropout(0.0))
conv_model.add(tf.keras.layers.Flatten())
conv_model.add(tf.keras.layers.Dense(512, kernel_regularizer=k_reg))
conv_model.add(tf.keras.layers.Activation('relu'))
conv_model.add(tf.keras.layers.Dropout(0.0))
conv_model.add(tf.keras.layers.Dense(num_classes,kernel_regularizer=k_reg))
然后可视化每一层的结构如下所示:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 32, 32, 32) 896
_________________________________________________________________
activation (Activation) (None, 32, 32, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 30, 30, 32) 9248
_________________________________________________________________
activation_1 (Activation) (None, 30, 30, 32) 0
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 15, 15, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 15, 15, 64) 18496
_________________________________________________________________
activation_2 (Activation) (None, 15, 15, 64) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 13, 13, 64) 36928
_________________________________________________________________
activation_3 (Activation) (None, 13, 13, 64) 0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 6, 6, 64) 0
_________________________________________________________________
dropout (Dropout) (None, 6, 6, 64) 0
_________________________________________________________________
flatten (Flatten) (None, 2304) 0
_________________________________________________________________
dense (Dense) (None, 512) 1180160
_________________________________________________________________
activation_4 (Activation) (None, 512) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 512) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 5130
=================================================================
这里我们大致可以知道在TF中网络结构的连接大致是这样的
conv2d -> activation -> conv2d_1 -> activation_1 -> max_pooling2d -> conv2d_3 -> conv2d_2 -> activation_2 -> conv2d_3 -> activation_3 -> max_pooling2d_1 -> dropout -> fullyconnect -> activation_4 -> dropout_1
-> fullyconnect
2. 转化为pytorch实现网络结构
好了,通过上面的分析,我将TF中的CNN每一层结构解剖出来了,接下来我把它转换为Pytorch来实现,如下所示:
class CNN(nn.Module):
def __init__(self,n_in, n_hidden, n_out):
super(CNN, self).__init__()
self.conv_1 = nn.Sequential(
nn.Conv2d(in_channels=n_in[1],
out_channels=32,
kernel_size=(3,3),
),
nn.ReLU()
) # 输出图片大小为: 32 * 30 * 30
self.max_pool_1 = nn.MaxPool2d(
kernel_size=(2,2),
stride=2,
) # 输出图片大小为: 32 * 15 * 15
self.conv_2 = nn.Sequential(
nn.Conv2d(in_channels=32,
out_channels=64,
kernel_size=(3, 3),
),
nn.ReLU()
) # 输出图片大小为: 64 * 13 * 13
self.max_pool_2 = nn.MaxPool2d(
kernel_size=(2,2),
stride = 2,
) # 输出图片大小为: 64 * 6 * 6
self.dropout = nn.Dropout(0.0)
self. fc1 = nn.Sequential(
nn.Linear(in_features=64 * 6 * 6, out_features=n_hidden), # cifar 10 or cifar 100
nn.ReLU()
)
self.fc2 = nn.Sequential(
nn.Linear(in_features=n_hidden, out_features=n_out)
)
def forward(self, x):
x = self.conv_1(x)
x = self.max_pool_1(x)
x = self.conv_2(x)
x = self.max_pool_2(x)
x = self.dropout(x)
x = x.contiguous().view(x.size(0),-1)
x = self.fc1(x)
x = self.fc2(x)
return x
2.1 推导理论
上面是我通过计算给出的答案,在把这个网络设计出来的过程中我们需要理解2个函数,然后才能计算出通过每一层我们的图片大小以及输入到每一层的通道数。
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
卷积一层的几个参数:
in_channels=3:表示的是输入的通道数,RGB型的通道数是3.
out_channels:表示的是输出的通道数,设定输出通道数(这个是可以根据自己的需要来设置的)
kernel_size=12:表示卷积核的大小是12x12
stride=4:表示的是步长为4
padding=2:表示的是填充值的大小为2
nn.MaxPool2d(kernel_size,stride,padding,dilation,return_indices ,ceil_mode )
我们先来看一下基本参数,一共六个:
kernel_size :表示做最大池化的窗口大小
stride :步长
dilation :控制窗口中元素步幅
return_indices :布尔类型,返回最大值位置索引
ceil_mode :布尔类型,为True,用向上取整的方法,计算输出形状;默认是向下取整。
2.2 池化和卷积计算公式
知道上面的2个函数之后,我们还需要知道图片经过卷积层和池化层图片大小是如何变化的,这时候我们需要套用下面两个公式:
卷积后,池化后尺寸计算公式:
(图像尺寸-卷积核尺寸 + 2*填充值)/步长+1
(图像尺寸-池化窗尺寸 + 2*填充值)/步长+1
换成具体的符号如下所示:
卷积神将网络的计算公式为:
N = (W - F + 2P) / S + 1
其中
N:输出大小
W:输入大小
F:卷积核大小
P:填充值的大小
S:步长大小
2.3 公式推导
有了上面的基础概念之后我们可以进行下一步的操作,计算通过卷积层以及池化层图片的大小以及输入和输出的通道数。
2.3.1 计算第一层
self.conv_1 = nn.Sequential(
nn.Conv2d(in_channels=n_in[1],
out_channels=32,
kernel_size=(3,3), # 如果没有说明默认padding=0,stride=1
),
nn.ReLU()
) # 输出图片大小为: 32 * 30 * 30
self.max_pool_1 = nn.MaxPool2d(
kernel_size=(2,2),
stride=2,
) # 输出图片大小为: 32 * 15 * 15
这里我以cifar10 作为测试用例输入到我们设计的网络中,cifar10 图片大小为3 * 32 * 32 ,所以我们这个 in_channels 输入的就是 3
然后我们就可以计算原图片经过卷积层之后的图片大小如下所示:
N=(W - F + 2P) / S + 1=(32-3 + 2*0)/1 + 1 = 32
所以得到我们第一个输出大小:32 * 30 * 30
这里需要注意的是:out_channels 这个是我们认为设置的,不需要计算
然后我们在计算经过卷积层的图片大小输入到池化层之后的大小。
N=(W - F + 2P) / S + 1=(30-2 + 2*0)/2 + 1 = 15
所以得到我们第一个输出大小:32 * 15 * 15
2.3.2 计算第二层
self.conv_2 = nn.Sequential(
nn.Conv2d(in_channels=32,
out_channels=64,
kernel_size=(3, 3),
),
nn.ReLU()
) # 输出图片大小为: 64 * 13 * 13
self.max_pool_2 = nn.MaxPool2d(
kernel_size=(2,2),
stride = 2,
) # 输出图片大小为: 64 * 6 * 6
图片经过第一层之后的大小为 32 * 15 * 15
然后我们输入到第二层 in_channels 就是第一层传过来的32
按照第一层的计算公式分别可以得到经过第二层的卷积层和池化层的图片大小分别为:64 * 13 * 13 和 64 * 6 * 6
2.3.3 计算第三层
self. fc1 = nn.Sequential(
nn.Linear(in_features=64 * 6 * 6, out_features=n_hidden), # cifar 10 or cifar 100
nn.ReLU()
)
第三层是一个全连接层,其中我们需要了解一下下面这个函数
nn.Linear(in_features= out_features)
in_features:指的是输入的二维张量的大小,即输入的[batch_size, size]中的size。
out_features:指的是输出的二维张量的大小,即输出的二维张量的形状为[batch_size,output_size],当然,它也代表了该全连接层的神经元个数。
所以我们可以知道其实 in_features就是上一层经过卷积和池化的图片形状大小,也就是 64 * 6 * 6
然后 out_features 这个值是我们自己设置的,不需要计算。
注意事项
运行时常见的错误:RuntimeError: mat1 dim 1 must match mat2 dim 0
这个问题的原因是:维度错误 应该是卷积层到全连接层维度有错误,一般主要就是输入到全连接层那个 in_features 计算错误导致的
2.3.4 计算第四层
self.fc2 = nn.Sequential(
nn.Linear(in_features=n_hidden, out_features=n_out)
)
到了这里也就是一个网络的最后一层了, in_features 的值也就是上一层的 n_hidden ,其次就是 out_features 这个值就是我们需要输出的类别,比如cifar 10 我们输出的类别为10 ,cifar 100 我们输出的类别为 100 。