深度学习/联邦学习笔记(六)
卷积神经及相关案例+pytorch
卷积神经网络不同于一般的全连接神经网络,卷积神经网络是一个3D容量的神经元,即神经元是以三个维度来排列的:宽度、高度和深度
卷积神经网络中的主要层结构有三个:卷积层、池化层和全连接层,通过堆叠这些层结构形成了一个完整的卷积神经网络结构。卷积神经网络将原始图片转化成最后的类别得分,其中一些层包含参数,一些层没有包含参数,比如卷积层和全连接层拥有参数,而激活层和池化层不含参数。
构造一个简单的多层卷积神经网络
卷积层
nn. Conv2d()就是PyTorch中的卷积模块了,里面常用的参数有5个,分别是in_channels, out_ channels, kernel size, stride, padding, 除此之外还有参数dilation, groups, bias。 下 面来解释每个参数的含义。
in_channels对应的是输人数据体的深度; out_channels 表示输出数据体的深度; kernel_ size表示滤波器(卷积核)的大小,可以使用一个数字来表示高和宽相同的卷积核,比如kernel_ size=3, 也可以使用不同的数字来表示高和宽不同的卷积核,比如kernel_ size=(3, 2); stride表示滑动的步长; padding=0 表示四周不进行零填充,而padding=1表示四周进行1个像素点的零填充; bias是一一个布尔值,默认bias=True,表示使用偏置; groups表示输出数据体深度上和输人数据体深度上的联系,默认groups=1, 也就是所有的输出和输人都是相关联的,如果groups=2, 这表示输人的深度被分割成两份,输出的深度也被分割成两份,它们之间分别对应起来,所以要求输出和输人都必须要能被groups整除; dilation 表示卷积对于输人数据体的空间间隔,默认dilation1
池化层
nn.MaxPool2d()表示网络中的最大值池化,其中的参数有kernel_size、stride、padding. dilation. return_sindices. ceil_mode.下面解释一下它们各自的含义。
- kernel_ size, stride, padding,dilation之前卷积层已经介绍过了,是相同的含义;
- retun_indices表示是否返回最大值所处的下标默认return_indices=False
- ceil_mode表示使用一些方格代替层结构,默认ceil_mode=False, 一般都不会设置这些参数。
- nn. AvgPool2d()表示均值池化,里面的参数和nn.MaxPool2d() 类似,但多一个参数 count_ include_pad,这个参数表示计算均值的时候是否包含零填充,默认count_ include_pad=True。
一般使用较多的就是nn.MaxPool2d() 和nn .AvgPool2d(),另外PyTorch还提供了一些别的池化层,如nn. LPPcol2d()、nn .AdaptiveMaxPool2d()等
全连接层和激活函数上一章已经介绍过, 接下来构造一个简单的多层卷积神经网络。
from torch import nn
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__() #b,3,32,32
#将卷积层、激活层和池化层组合在一起构成了一个层结构,定义了3个这样的层结构
layer1 = nn.Sequential() #nn.Sequential()是将网络的层组合在一起
layer1.add_module('conv1',nn.Conv2d(3,32,3,1,padding=1)) #nn.Conv2d()是pytorch中的卷积模块
#b,32,32,32
layer1.add_module('relu1', nn.ReLU(True)) #激活层
layer1.add_module('pool1',nn.MaxPool2d(2,2)) #b, 32, 16, 16
self.layerl = layer1
layer2 = nn.Sequential()
layer2.add_module('conv2', nn.Conv2d(32, 64, 3, 1, padding=1))
#b,64,16,16
layer2 .add_module('relu2', nn.ReLU(True))
layer2.add_module('pool2', nn.MaxPool2d(2, 2)) #b, 64,8, 8
self.layer2 = layer2
layer3 = nn. Sequential()
layer3.add_module('conv3', nn.Conv2d(64, 128, 3, 1, padding=1))
#b, 128, 8, 8
layer3.add_module('relu3', nn.ReLU(True))
layer3.add_module('pool3', nn.MaxPool2d(2, 2)) #b, 128,4, 4
self.layer3 = layer3
#定义全连接层,输出10
layer4 = nn. Sequential()
layer4.add_module('fcl', nn.Linear(2048, 512))
layer4 .add_module('fc_relul', nn.ReLU(True))
layer4 .add_module('fc2', nn.Linear(512, 64))
layer4 .add_module('fc_relu2', nn.ReLU(True))
layer4 .add_module('fc3', nn.Linear(64, 10))
self.layer4. layer4
def forward(self, x):
conv1 = self.layerl(x)
conv2 = self.layer2(conv1)
conv3 = self.layer3(conv2)
fc_input = conv3 .view(conv3.size(0), -1)
fc_out = self.layer4(fc_input)
return fc_out
model = SimpleCNN()
print(model)
使用print(model)可以查看网络中都定义了哪些层结构,上面代码运行结果如下:
SimpleCNN(
(layerl): Sequential(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU(inplace=True)
(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(layer2): Sequential(
(conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU(inplace=True)
(pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(layer3): Sequential(
(conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu3): ReLU(inplace=True)
(pool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(layer4): Sequential(
(fcl): Linear(in_features=2048, out_features=512, bias=True)
(fc_relul): ReLU(inplace=True)
(fc2): Linear(in_features=512, out_features=64, bias=True)
(fc_relu2): ReLU(inplace=True)
(fc3): Linear(in_features=64, out_features=10, bias=True)
)
)
Process finished with exit code 0
提取层结构:
假设想要提取前面两层
new_model = nn.Sequential(*list(model.children())[:2])
print(new_model)
结果如下:
Sequential(
(0): Sequential(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU(inplace=True)
(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(1): Sequential(
(conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU(inplace=True)
(pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
)
Process finished with exit code 0
提取参数和自定义初始化
有时候提取出的层结构并不够,还需要对里面的参数进行初始化,那么如何提取出网络的参数并对其初始化呢?
nn.Module里面有两个特别重要的关于参数的属性,分别是named_parameters()和parameters() 。named_parameters()是给出网络层的名字和参数的迭代器,parameters()会给出一个网络的全部参数的迭代器。
想要得到每一层参数的名字:
for param in model.named_parameters():
print(param[0])
结果:
layerl.conv1.weight
layerl.conv1.bias
layer2.conv2.weight
layer2.conv2.bias
layer3.conv3.weight
layer3.conv3.bias
layer4.fcl.weight
layer4.fcl.bias
layer4.fc2.weight
layer4.fc2.bias
layer4.fc3.weight
layer4.fc3.bias
Process finished with exit code 0
如何对权重做初始化呢?非常简单,因为权重是一个variable, 所以只需要取出其中的data属性,然后对它进行所需要的处理就可以了。
for m in model.modules():
if isinstance(m,nn.Conv2d): #isinstance()可以判断这个模型是不是所需的类型实例
init.normal(m.weight.data)
init.xavier_normal(m.weight.data)
init.kaiming_normal(m.weight.data)
m.bias.data.fill_(0)
elif isinstance(m,nn.Linear):
m.weight.data.normal_()
卷积神经网络案例
LeNet
LeNet的网络结构图
由上图可见,LeNet一共有7层,其中2层卷积层和2层池化层交替出现,最后输出3层全连接层得到整体的结果
from torch import nn
class Lenet(nn.Module):
def __init__(self):
super(Lenet,self).__init__()
layer1 = nn.Sequential()
layer1.add_module('conv1', nn.Conv2d(1, 6, 3, padding=1))
layer1.add_module('pooll', nn.MaxPool2d(2, 2))
self.layer1 = layer1
layer2 = nn. Sequential()
layer2 .add_module('conv2', nn.Conv2d(6, 16, 5))
layer2 .add_module('poo2', nn.MaxPool2d(2, 2))
self .layer2 = layer2
layer3 = nn.Sequential()
layer3.add_module('fcl', nn.Linear(400, 120))
layer3 .add_module('fc2', nn.Linear(120, 84))
layer3 .add_module('fc3', nn.Linear(84, 10))
self.layer3 = layer3
def forward(self,x):
x = self.layerl(x)
x = self.layer2(x)
x = x.view(x.size(0),-1)
x = self.layer3(x)
return x
这样就实现了LeNet网络,可以发现网络的层数很浅,也没有添加激活层。
AlexNet
AlexNet网络结构图(关于该结构的详解链接:https://www.jianshu.com/p/00a53eb5f4b3)
AlexNet网络相对于LeNet,层数更深,同时第一次引入了激活层ReLU,在全连接层引入了Dropout层防止过拟合
from torch import nn
class AlexNet (nn.Module):
def __init__(self, num_classes):
super(AlexNet,self).__init__()
self.features = nn.Sequential( #nn.Sequential()是将网络的层组合在一起
nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(64, 192, kernel_eize=5, padding=2),
nn. ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, Btride=2),
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn. ReLU(inplace=True),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn. ReLU(inplace=True),
nn. Conv2d(256, 256, kernel_size=3, padding=1),
nn. ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2), )
self.classifier = nn.Sequential(
nn.Dropout(),
nn.Linear(256*6*6, 4096),
nn.ReLU(inplace=True),
nn. Dropoutl(),
nn. Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Linear(4096, num_classes), )
def forward(self,x):
x . self.features(x)
x- x.view(x.size(0), 256*6* 6)
x . self.classifier(x)
return x
VGGNet
VGGNet使用了更小的滤波器和更深的结构,AlexNet只有8层网络,而VGGNet有16-19层网络,也不像AlexNet使用11x11那么大的滤波器,它只使用3x3的卷积滤波器和2x2的大池化层
VGGNet之所以使用很多小的滤波器,是因为层叠很多小的滤波器和感受也和一个大的感受野是相同的,还能减少参数,同时有更深的网络结构
from torch import nn
class VGG (nn .Module):
def __init__(self, num_classes):
super(VGG, self).__init__()
self.features = nn.Sequential(
nn. Conv2d(3, 64, kernel_size=3, padding=1),
nn.ReLU(True),
nn. Conv2d(64, 64, kernel_size=3, padding=1),
nn. ReLU(True),
nn.MaxPool2d(kernel_size=2,stride=2),
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn. ReLU(True),
nn.Conv2d(128, 128, kernel_eize=3,padding=1),
nn. ReLU(True),
nn.MaxPool2d(kernel_size=2, atride=2),
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn. ReLU(True),
nn.Conv2d(256, 256, kermel_siz=3, padding=1),
nn. ReLU(True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn. ReLU(True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.ReLU(True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn. ReLU(True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(True),
nn .MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn. ReLU(True),
nn.MaxP0ol2d(kernel_size=2, stride=2),)
self .classifier = nn. Sequential(
nn. Linear(512 * 7.7, 4096),
nn. ReLU(True),
nn. Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096, num_classes), )
self._initialize_weights()
def forward(self, x):
x = self. features(x)
x = x.view(x.nize(0), -1)
x = self.claseifier(x)
可以看出,VVG只是对网络层进行不断的堆叠,增加深度确实可以一定程度改善模型效果
实现MNIST手写数字分类
下面写一个多层卷积神经网络,使它在MNIST数据集上达到99%的验证集准确率
from torch import nn
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
#有4层卷积,2层赤化,卷积之后使用批标准化加快收敛速度,使用ReLU激活函数增加费线性,最后使用全连接层输出分类得分
self.layer1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3),# b, 16, 26, 26
nn.BatchNorm2d(16),
nn.ReLU(inplace=True))
self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=3),# b, 32, 24, 24
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2))#b, 32, 12, 12
self.layer3 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3), #b, 64, 10, 10
nn.BatchNorm2d(64),
nn.ReLU(inplace=True))
self.layer4 = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3), #b, 128, 8, 8
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2)#b, 128, 4, 4
)
self.fc = nn.Sequential(
nn.Linear(128*4*4, 1024),
nn.ReLU(inplace=True),
nn.Linear(1024, 128),
nn.ReLU(inplace=True),
nn.Linear(128, 10))
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x