0%

torch | 认识 CNN

从代码进阶到原理。一步步深入 torchCNN 世界。


环境介绍


  • python3.6
  • pytorch 1.3.1
    • 不同的版本差距非常大,要注意版本

相关资料


讲述 CNN 原理

pytorch 的代码案例


从案例入手


常规 CNN 网络结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

def __init__(self):
super(Net, self).__init__()
# 输入图像channel:1;输出channel:6;5x5卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

def forward(self, x):
# 2x2 Max pooling
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 如果是方阵,则可以只使用一个数字进行定义
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x

def num_flat_features(self, x):
size = x.size()[1:] # 除去批处理维度的其他所有维度
num_features = 1
for s in size:
num_features *= s
return num_features


net = Net()
print(net)

网络结构图

我们解析这个图「MNIST」,以及中间数据的变化。

首先,输入的数据维度为 N * 1 * 32 * 32 其中 N 代表的是 batch_size1 代表的通道为 1「灰度图」, 32 * 32 代表一张图片的矩阵大小。第一个卷积层是 nn.Conv2d(1, 6, 5) 代表的是,输出的通道是 6 ,卷积核的大小是 5 * 5。所以,卷积之后图片的大小是 32 - 5 + 1 = 28 ,也就是数据变成了 N * 6 * 28 * 28 。最大池化为 2 * 2,经过最大池化的维度是 28 / 2 = 14 此时,数据变成了 N * 6 * 14 * 14 ,然后,第二个卷积层 nn.Conv2d(6, 16, 5) ,输出的通道是 16,卷积核的大小是 5 * 5 ,所以,卷积后的图片大小变成了 14 - 5 + 1 = 10,数据变成了 N * 16 * 10 * 10,然后最大池化是 2 * 2 ,也就是,数据维度变成了 10 / 2 = 5 ,最后数据变成了 N * 16 * 5 * 5

此时,来到了链接层,首先把数据变成 1 维的。变成一维后,使用链接层

1
2
3
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

最后的结果是十分类。

非常规 CNN 网络结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ConvAutoEncoder(nn.Module):
def __init__(self):
super(ConvAutoEncoder, self).__init__()

# Zero padding is almost the same as average padding in this case
# Input = b, 1, 4, 300
self.encoder = nn.Sequential(
nn.Conv2d(1, 8, (4, 7), stride=1, padding=(0, 3)), # b, 8, 1, 300
nn.Tanh(),
nn.MaxPool2d((1, 2), stride=2), # b, 8, 1, 150
nn.Conv2d(8, 4, 3, stride=1, padding=1), # b, 4, 1, 150
nn.Tanh(),
nn.MaxPool2d((1, 2), stride=2) # b, 4, 1, 75
)
self.decoder = nn.Sequential(
nn.ConvTranspose2d(4, 8, 3, stride=2, padding=1, output_padding=(0, 1)), # b, 8, 1, 150
nn.Tanh(),
nn.ConvTranspose2d(8, 8, 3, stride=2, padding=1, output_padding=(0, 1)), # b, 8, 1, 300
nn.Tanh(),
nn.ConvTranspose2d(8, 1, 3, stride=1, padding=1), # b, 1, 1, 300
)

def forward(self, x):
x = self.encoder(x)
x = self.decoder(x)
return x

这是一个简单的 U-Net 网络结构,我们只看前面的 encoder 环节。

首先输入的数据是 N * 1 * 4 * 300,第一个卷积层是 nn.Conv2d(1, 8, (4, 7), stride=1, padding=(0, 3)),首先,先给 input 增加 padding。根据代码,是在上下两侧分别增加 3 列,所以,原始数据变成了 N * 1 * 4 * 306,卷积核的大小是 4 * 7,步长为 1,所以,经过卷积的维度变成了 306 - 7 + 1 = 300,输出维度为 8,最后,输出的数据为 N * 8 * 1 * 300,然后最大池化 2 * 2,数据变成了 300 / 2 = 150,即数据为 N * 8 * 1 * 150,接着,第二个卷积层是 nn.Conv2d(8, 4, 3, stride=1, padding=1),首先是增加 padding,在上下左右分别增加 1 ,所以,输入的数据变成了 N * 8 * 3 * 152,其卷积核是 3 * 3 ,所以,其维度是 152 - 3 + 1 = 150,输出维度是 4,最后,数据变成了 N * 4 * 1 * 150,最大池化是 2 * 2,所以,最后数据变成了 N * 4 * 1 * 75

关于使用的方法,可以查看

请我喝杯咖啡吧~