??这篇博客总结一下我在学习卷积神经网络(CNN)中遇到的疑惑
??如果模型很大,可能会过拟合,所以我们必须要对模型进行一些限制。因此首先要思考我们如何针对影像问题来做limitation。对一个影响辨识系统而言,最重要的是侦测在这张图片中有没有很重要的patten,例如我们人类看一张鸟类的图片,怎么知道这是一只鸟呢?肯定是先看鸟嘴、鸟爪等部分,这些在影像系统中就是很重要的。 ??基于此,CNN提出了卷积的概念,CNN会设定一个区域叫做 Receptive Field(感受野),每一个 Neuron 都只关心自己的 Receptive Field 所发生的事情就好了,这个 Receptive Field 我们称之为kernal。一般 kernal size 取 3x3 或者5x5。当然,有人会说能不能把一整张图当做一个kernal ,是可以的,但是这样计算量太大了。
- 3x3 或者5x5 的 kernal 会不会太小了,能侦查到patten吗?
??是可以的。只要卷积的层数足够多。 ??因为我们用3x3做kernal的时候,计算出来的第一层卷积是原图中3x3的patten,但是如果我们对第一层的卷积再做一次kernal,那就不止是3x3了(就是原图的5x5的patten了)。因此只要network够深,就没有侦查不到的patten。
??padding: padding(填充)参数的作用是决定在进行卷积或池化操作时,是否对输入的图像矩阵边缘补0 ??stride: 滑动卷积核时的步长stride(例如每次滑动一个或两个) ??kernal: 卷积核,通常为3x3或者5x5 ??filter: 卷积核的数量(神经元的数量)。这个地方怎么理解呢,一个3x3的卷积核有9个参数,这些参数是通过learning出来的,一个卷积核扫过一幅图后,会生成一幅新的图,因为卷积核的参数是不同的,因此生成的图片也是不同的。但是卷积核的作用是侦查patten的对吧,一个图片中不可能只有一个patten吧,比如说一幅图片有100个patten,鸟嘴、鸟爪、牛蹄、猪头等特征都是patten,当将一头猪的图片输入到网络中,猪头这个patten的权重就会非常大,相反鸟嘴、鸟爪、牛蹄的patten权重就很低。所以filter的个数也就代表了神经元的数目。 ??pooling :池化
????整体流程是: ??卷积(Conv2d) -> BN(batch normalization) -> 激励函数(ReLU) -> 池化(MaxPooling) -> ??全连接层(Linear) -> 输出
class CNNnet(torch.nn.Module):
def __init__(self):
super(CNNnet,self).__init__()
self.conv1 = torch.nn.Sequential(
torch.nn.Conv2d(in_channels=1,
out_channels=16,
kernel_size=3,
stride=2,
padding=1),
torch.nn.BatchNorm2d(16),
torch.nn.ReLU()
)
self.conv2 = torch.nn.Sequential(
torch.nn.Conv2d(16,32,3,2,1),
torch.nn.BatchNorm2d(32),
torch.nn.ReLU()
)
self.conv3 = torch.nn.Sequential(
torch.nn.Conv2d(32,64,3,2,1),
torch.nn.BatchNorm2d(64),
torch.nn.ReLU()
)
self.conv4 = torch.nn.Sequential(
torch.nn.Conv2d(64,64,2,2,0),
torch.nn.BatchNorm2d(64),
torch.nn.ReLU()
)
self.linear = torch.nn.Linear(2*2*64,100)
self.linear = torch.nn.Linear(100,10)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.linear(x.view(x.size(0),-1))
x = self.linear(x)
return x
model = CNNnet()
print(model)
??首先图片经过卷积层。卷积层第一个参数是input channel ,如果是RGB图像,就是3维,如果是灰度图就是1维。第二个参数是output channel,也就是filter,设置多少个卷积核,每个卷积核做运算后输出新的“图片”,后续的参数再是卷积核的大小、步长、填充等等。重点要理解这个output channel
??继续来看全连接层,全连接层的输入是什么呢,是卷积层的输出。例如最后一个卷积层定义了64个filter,那我们之前说了,每一个filter做运算后会生成新的图片是吧,所以这个64只是代表了有64个新图片,每个图片是多少尺寸呢?上述程序是2x2的尺寸,因此全连接层是输入就是64x2x2。
??那么问题来了,怎么判断做完卷积后新图片的大小?假设输入的tensor size为C H W,若in_channels=C,out_channels=Cout,kernel_size=k,stride=s,padding=p,那么输出的tensor size是: ??Cout*((H + 2*p - k)/s+1) * ((W + 2*p - k)/s+1)
??这种方法需要手动计算,有时候还可能计算错误,这里再提供一种方法,执行完下述程序后,加上一句 print(x.size()) 将数据打印出来,然后再填到torch.nn.Linear(input,100)里
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
print(x.size())
??最后要注意程序中的这句话,x = self.linear(x.view(x.size(0),-1)) 几乎可以看到每一个CNN有关的代码中,都有维度转换这一句,因为全连接层是输入一个向量,不是输入一个矩阵,所以要把这个卷积后的结果拉直再输入到Linear中。
??推荐三篇博客参考: https://blog.csdn.net/liming89/article/details/113006867 https://blog.csdn.net/jining11/article/details/89114502? https://blog.csdn.net/qq_34714751/article/details/85610966?
|