卷积神经网络
绪论
传统神经网络基本全网络采用全连接设计,这在最终结果上会出现过拟合、参数过多难以训练等问题,针对这一问题提出卷积神经网络,其通过卷积核的设计实现局部参数共享,可以有效降低参数数目和过拟合问题
基本结构
卷积
一个卷积核和输入图片的对应位置的卷积计算定义为对应问题数值乘积之和,对应得到一个输出的一个值
计算大小公式为
(
I
n
p
u
t
+
P
a
d
d
i
n
g
∗
2
−
K
e
r
n
e
l
)
/
s
t
r
i
d
e
+
1
(Input+Padding*2-Kernel)/stride+1
(Input+Padding∗2−Kernel)/stride+1
除此以外,还有一些其他配置:
padding:通常是对输入图像进行外部零填充,使输入图像尺寸变大
channel:通道数
grid图像通道数为1;
rgb图像通道数为3;
在网络过程中通道数可能会不断变化,其具体变化过程可以如下理解:
例如对于CIFAR10数据集,其初始输入图片维度为32*32*3,即表示通道数为3,一张图像像素点数目为32*32,对于这样一张图片如果使用5*5*6的卷积核去卷积,其中每一个5*5的卷积核都会和原图片的3个维度上的像素矩阵进行计算得到一个输出特征图,共6个卷积核故最后得到的特征图通道数为6,经过卷积最终的结果是18*18*6
池化
最大值池化:对于filter块中的取最大值为对应结果
平均值池化:对于filter块中的取平均值为对应结果
全连接
一般是最后一个部分,将多维的矩阵变为一个一维竖列矩阵进行分类
常见网络
AlexNet及相关代码
网络结构
AlexNet作为一个具有突破性成就的网络,其上应用了很多技术:
为防止过拟合,使用了Dropout技术,其于在训练过程中随机关闭部分神经元;
使用了非线性激活函数ReLU,在上一篇小结中说过,其相对于Sigmoid激活函数来说收敛速度更快
VGG
网络结构
具体参数分析:
GoogleNet
网络结构:
GoogLeNet提出了一个Inception模块,对于一个输入块采用多个卷积核同时进行卷积计算,增加特征提取的多样性,Inception模块如下图所示
在此基础上,我们使用两个3*3的卷积核来替代一个5*5的卷积和,得到如下图所示的结构:
具体分析如下:
例如对于一个输入大小为n*n的图片,直接使用一个5*5的卷积核得到的结果为
(
n
−
5
)
/
1
+
1
=
n
−
4
(n-5)/1+1=n-4
(n−5)/1+1=n−4
相应参数个数为5*5+5=30
而使用两个3*3的卷积核得到的结果是:
(
n
−
3
)
/
1
+
1
=
n
−
2
(n-3)/1+1=n-2
(n−3)/1+1=n−2
(
n
−
2
−
3
)
+
1
=
n
−
4
(n-2-3)+1=n-4
(n−2−3)+1=n−4
相应参数个数为(3*3+3)*2=24
可以看到后者与前者相比最终得到结果相同,但参数个数更少
ResNet
网络结构:
残差结构:
如原论文所说,这里不再让相应的几层直接去拟合一个想要的底层映射,而是希望去拟合一个残差的映射,即让
F
(
x
)
:
=
H
(
x
)
−
x
F(x):=H(x)-x
F(x):=H(x)−x
结果证明学习残差映射较原本的底层映射更加简单
代码部分
CNN对MINIST数据集进行分类
数据集处理部分:
input_size = 28*28 # MNIST上的图像尺寸是 28x28
output_size = 10 # 类别为 0 到 9 的数字,因此为十类
train_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=True, download=True,transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])),batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=False, transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])),batch_size=1000, shuffle=True)
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))]))
这里是对原数据集中图像的处理,主要是进行了正则化
该部分的几个参数解释如下:
- shuffle表示是否在每一个epoch中重新调整数据集的顺序,在训练集中使用shuffle=True可以使训练时每一个batch中的图像具有随机性,通过将shuffle设为True和False进行实验对比,其中黑色部分表示shuffle部分为True,紫色部分表示shuffle参数为False
Train_Loss
Test_Accuracy
Test_Loss
发现只是训练和测试时的loss差别较大,但对最终的准确率影响不大,主要原因是如果shuffle参数为True,在训练时打乱的数据集更多,整个模型loss变化应该更平稳,因为其在每个steps中都会随机数据,从图中来看也是如此
- transforms中图片标准化中的不同值
transforms.Normalize()函数对图片每一个通道都进行标准化处理,其中第一个值为设置的平均值,第二个为设置的标准差,由于这个为灰度图片,故只有一个维度。这里我们对比分别进行标准化处理和没有进行标准化处理的两个步骤,其中橙色为进行了标准化的部分,紫色为没有进行标准化的部分
Train_Loss
Test_Accuracy
Test_Loss:
可以发现,最终进行了标准化部分的准确率较没有进行的部分高1%左右,同时无论是在训练还是测试时loss都会较没有进行标准化的部分低,说明进行了标准化有利于最终模型的训练
网络代码部分:
class CNN(nn.Module):
def __init__(self, input_size, n_feature, output_size):
super(CNN, self).__init__()
self.n_feature=n_feature
self.cov1=nn.Conv2d(in_channels=1, out_channels=n_feature, kernel_size=5)# note that the picture is grid form, so the in_channel = 1
self.con2=nn.Conv2d(in_channels=n_feature, out_channels=n_feature, kernel_size=5)
self.fc1=nn.Linear(n_feature*4*4, 50)
self.fc2=nn.Linear(50, 10)
def forward(self, x, verbose=False):
x=self.cov1(x)
x=F.relu(x)
x=F.max_pool2d(x, kernel_size=2)#Note for max_pool2d the default value of stride equals to kernel_size
x=self.con2(x)
x=F.relu(x)
x=F.max_pool2d(x, kernel_size=2)
x=x.view(-1, self.n_feature*4*4)
x=self.fc1(x)
x=F.relu(x)
x=self.fc2(x)
x=F.log_softmax(x, dim=1)
return x
CNN对CIFAR10进行分类
具体网络结构和上一个实验相同,在对CIFAR10数据集进行处理时,其结果表现如下:
Test_Accuracy
Test_Loss
Train_Loss
VGG对CIFAR10的分类
网络结构:
class VGG(nn.Module):
def __init__(self):
super(VGG,self).__init__()
self.cfg=[64,'M',128,'M',256,256,'M',512,512,'M',512,512,'M']
self.features = self._make_layers(self.cfg)
self.classifier = nn.Linear(512, 10)
def forward(self,x):
x=self.features(x)
x=x.view(x.size(0),-1)
x=self.classifier(x)
return x
def _make_layers(self, cfg):
layers=[]
in_channel=3
for x in cfg:
if x=='M':
layers+=[nn.MaxPool2d(kernel_size=2,stride=2)]
else:
layers+=[nn.Conv2d(in_channel,x,kernel_size=3,padding=1),nn.BatchNorm2d(x),nn.ReLU(inplace=True)]
in_channel=x
layers+=[nn.AvgPool2d(kernel_size=1,stride=1)]
return nn.Sequential(*layers)
这里最终结果为:
Train_Loss
Test_Accuracy
Test_Loss
对比CNN和VGG对CIFAR10的分类结果,结果如下:
其中左边为VGG,右边为CNN
每周博客问题回答集中展示
-
dataloader里面shuffle取不同值有什么区别
dataloader中存在trainloader和testloader两个部分,对于训练集部分,通过对训练时每一个batch中的图片尽量丰富,可以使训练的结果更加稳壮;而对于测试集部分,使shuffle设True没有意义,因为这时我们将训练得到的结果进行图片识别,识别时的顺序和结果好坏没有关系。至于将训练集部分的shuffle设为True和False的对比实验如下链接所示:
对CNNforMINIST中shuffle取不同值实验结果 -
transform中取不同值有什么区别
transform是对数据集图像进行处理的部分,在本次实验中最为常见的便是transforms.Normalize()函数
对CNNforMINIST中transform取不同值实验结果 -
epoch和batch的区别
epoch是跑完全部一轮训练集的数据,但在通常训练网络时不可能一次性将全部数据全部输入,通常是取一个batch大小的数据作为输入进行一小轮训练。 -
1*1的卷积和FC有什么区别,分别去什么作用
1*1的卷积对输入大小并没有改变,其在神经网络中起到的作用通常是升维或是降维,例如对于一个3维输入,我们如果使用n个1*1的卷积对其进行计算,得到的便是n个原尺寸大小的结果,实现维度变化;而FC通常会在神经网络的最后一部分出现,其将前面已有各卷积层所探测到的特征综合学习,通常我们会将矩阵扁平化处理之后进行全连接分类。 -
residual learning为什么可以提升准确率
residual learning中的残差结构:
如原论文所说,这里不再让相应的几层直接去拟合一个想要的底层映射,而是希望去拟合一个残差的映射,即让
F ( x ) : = H ( x ) − x F(x):=H(x)-x F(x):=H(x)−x
结果证明学习残差映射较原本的底层映射更加简单,相应结果也会更好。同时由于残差结构的存在,使得在反向传播的过程中的梯度消失问题得到缓解,原因是加入残差结构后在梯度计算时对x的部分求导会得到1,这使得其加上前面部分导数结果不会太小,使得梯度可以传播下去 -
代码二中网络和1989年Lecun提出的LeNet有什么区别
LeNet网络结构
CNN网络结构
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1=nn.Conv2d(3, 6, 5)
self.conv2=nn.Conv2d(6,16,5)
self.pool=nn.MaxPool2d(2,2)
self.fc1=nn.Linear(16 * 5 * 5, 120)
self.fc2=nn.Linear(120, 84)
self.fc3=nn.Linear(84, 10)
def forward(self, x):
x=self.pool(F.relu(self.conv1(x)))
x=self.pool(F.relu(self.conv2(x)))
x=x.view(-1, 16*5*5)
x=F.relu(self.fc1(x))
x=F.relu(self.fc2(x))
x=self.fc3(x)
return x
可以看到,具体区别有LeNet-5在激活函数部分使用了tanh激活函数,同时池化层使用了平均池化方法,最后加上 softmax函数;而本实验中使用了ReLU激活函数,池化层使用的时最大池化,没有使用softmax函数。
-
代码二中卷积以后的featrure map尺寸变小如何应用Residual Learning
如果卷积以后的feature map尺寸变小,我们可以通过设置padding来保证卷积尺寸不变 -
有什么方法可以进一步提升准确率
首先进行网络结构上的变化对准确率的提高最为明显,对于CIFAR10数据集,CNN网络的准确 率只能在60%多,而ResNet网络的准确率可以达到90%以上;
同时同一网络结构增加卷积层数可能也会对准确率有所提升;
对数据集进行有效的预处理也可以提升准确率。