专栏:神经网络复现目录
本章介绍的是现代神经网络的结构和复现,包括深度卷积神经网络(AlexNet),VGG,NiN,GoogleNet,残差网络(ResNet),稠密连接网络(DenseNet)。
文章部分文字和代码来自《动手学深度学习》
文章目录
- 使用块的网络(VGG)
- VGG块
- 定义
- 实现
- VGG16
- 模型设计
- 实现
- 利用VGG16进行CIFAR10分类
- 数据集
- 超参数,优化器,损失函数
- 训练
使用块的网络(VGG)
VGG是一种深度卷积神经网络,由牛津大学视觉几何组(Visual Geometry Group)在2014年提出。它是由多个卷积层和池化层组成的深度神经网络,具有很强的图像分类能力,特别是在图像识别领域,取得了很好的成果。
VGG的特点在于,它使用相对较小的卷积核(3x3),但是通过叠加多个卷积层和池化层,增加了网络的深度,从而达到更好的图像分类性能。VGG网络包含了多个版本,以卷积层数目为标志,如VGG16和VGG19等,其中VGG16和VGG19是最著名的两个版本。
VGG网络的设计非常简单和规整,容易理解和实现,因此也成为了很多深度学习新手的入门模型。
下图为VGG的六个版本,比较实用的是VGG16和VGG19,本文以VGG16为例子进行讲解
VGG块
定义
VGG块是VGG网络中的一个基本组成单元,由若干个卷积层和池化层组成,通常用于提取输入图像的特征。每个VGG块都由连续的1或2个卷积层,和一个最大池化层组成。其中,卷积层的卷积核大小都是3x3,而池化层的窗口大小通常是2x2。在每个VGG块中,卷积层的输出通道数都相同,可以通过超参数进行控制。
具体来说,假设一个VGG块由k个卷积层和一个池化层组成,输入为 x x x,则该块的输出可以表示为:
VGG ( x ) = Pool ( conv k ( conv k − 1 ( ⋯ conv 1 ( x ) ) ) ) . \text{VGG}(x) = \text{Pool}(\text{conv}k(\text{conv}{k-1}(\cdots\text{conv}_1(x)))). VGG(x)=Pool(convk(convk−1(⋯conv1(x)))).
其中, conv i ( ⋅ ) \text{conv}_i(\cdot) convi(⋅)表示第 i i i个卷积层, Pool ( ⋅ ) \text{Pool}(\cdot) Pool(⋅)表示池化层。在VGG块中,每个卷积层都会使用ReLU激活函数进行非线性变换,而最大池化层则用于下采样和特征压缩。
在VGG网络中,通常通过叠加多个VGG块来构建网络结构。通过增加VGG块的数量,可以增加网络的深度和宽度,从而提高网络的表达能力和泛化性能。
实现
self.conv1=nn.Sequential(
nn.Conv2d(in_channels=3,out_channels=64,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=64,out_channels=64,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2,stride=2),
)
inplace=True 表示对于输入的张量进行原地操作,即直接对原始的输入张量进行修改,而不是创建一个新的张量。这样做可以节省内存,但会覆盖原始的输入张量,可能会对后续的计算产生影响。因此,当我们需要保留原始的输入张量时,可以将 inplace 参数设置为 False。
VGG16
模型设计
VGG16是一个卷积神经网络模型,包含13个卷积层、5个池化层和3个全连接层,是由牛津大学计算机视觉组(Visual Geometry Group)在2014年提出的模型,具有较好的图像识别表现。
VGG16模型的架构如下:
输入层:输入图像的大小为224x224x3。
VGG块1
卷积层1:使用64个3x3大小的卷积核进行卷积操作,得到64张大小为224x224的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
卷积层2:使用64个3x3大小的卷积核进行卷积操作,得到64张大小为224x224的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
池化层1:使用2x2的最大池化操作,将64张大小为224x224的特征图缩小为64张大小为112x112的特征图。采用SAME填充,步长为2。
VGG块2
卷积层3:使用128个3x3大小的卷积核进行卷积操作,得到128张大小为112x112的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
卷积层4:使用128个3x3大小的卷积核进行卷积操作,得到128张大小为112x112的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
池化层2:使用2x2的最大池化操作,将128张大小为112x112的特征图缩小为128张大小为56x56的特征图。采用SAME填充,步长为2。
VGG块3
卷积层5:使用256个3x3大小的卷积核进行卷积操作,得到256张大小为56x56的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
卷积层6:使用256个3x3大小的卷积核进行卷积操作,得到256张大小为56x56的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
卷积层7:使用256个3x3大小的卷积核进行卷积操作,得到256张大小为56x56的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
池化层3:使用2x2的最大池化操作,将256张大小为56x56的特征图缩小为256张大小为28x28的特征图。采用SAME填充,步长为2。
VGG块4
卷积层8-10:使用512个3x3大小的卷积核进行卷积操作,得到512张大小为28x28的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
池化层4:使用2x2的最大池化操作,将512张大小为28x28的特征图缩小为512张大小为14x14的特征图。采用SAME填充,步长为2。
VGG块5
卷积层11-13:使用512个3x3大小的卷积核进行卷积操作,得到512张大小为14x14的特征图。采用SAME填充,步长为1。然后再通过ReLU非线性激活函数进行激活。
池化层5:使用2x2的最大池化操作,将512张大小为14x14的特征图缩小为512张大小为7x7的特征图。采用SAME填充,步长为2。
全连接层
3个全连接层,第1、2个都有4096个输出通道,第3个全连接层则有1000个输出通道。
实现
class VGG16(nn.Module):
def __init__(self):
super(VGG16,self).__init__()
self.conv1=nn.Sequential(
nn.Conv2d(in_channels=3,out_channels=64,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=64,out_channels=64,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2,stride=2),
)
self.conv2=nn.Sequential(
nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=128,out_channels=128,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2,stride=2),
)
self.conv3=nn.Sequential(
nn.Conv2d(in_channels=128,out_channels=256,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=256,out_channels=256,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=256,out_channels=256,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2,stride=2),
)
self.conv4=nn.Sequential(
nn.Conv2d(in_channels=256,out_channels=512,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=512,out_channels=512,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=512,out_channels=512,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2,stride=2),
)
self.conv5=nn.Sequential(
nn.Conv2d(in_channels=512,out_channels=512,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=512,out_channels=512,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=512,out_channels=512,kernel_size=3,padding=1,stride=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2,stride=2),
)
self.feature=nn.Sequential(
self.conv1,
self.conv2,
self.conv3,
self.conv4,
self.conv5,
)
self.flatten=nn.Flatten()
self.fc=nn.Sequential(
nn.Linear(512*7*7,4096),
nn.ReLU(inplace=True),
nn.Dropout(0.4),
nn.Linear(4096,4096),
nn.ReLU(inplace=True),
nn.Dropout(0.4),
nn.Linear(4096,1000),
#nn.Softmax(10)
)
def forward(self,x):
x=self.feature(x)
# x=self.flatten(x)
x = x.view(x.size(0), -1)
x=self.fc(x)
return x
查看结构
vgg = VGG16()
print(vgg)
x=torch.rand(1,3,224,224)
y=vgg(x)
print(y.shape)
利用VGG16进行CIFAR10分类
import torch.nn as nn
import torch
import torchvision
if(torch.cuda.is_available()):
device = torch.device("cuda")
print("使用GPU训练中:{}".format(torch.cuda.get_device_name()))
else:
device = torch.device("cpu")
print("使用CPU训练")
数据集
# transform的创建(compose方法)
from torchvision import transforms
def get_dataloader_workers(): #@save
"""使用4个进程来读取数据"""
return 4
def load_data_cifar10(batch_size, resize=None): #@save
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.CIFAR10(
root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.CIFAR10(
root="../data", train=False, transform=trans, download=True)
return (torch.utils.data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers()),
torch.utils.data.DataLoader(mnist_test, batch_size, shuffle=False,
num_workers=get_dataloader_workers()))
batch_size=4
train_iter, test_iter = load_data_cifar10(batch_size,resize=224)
超参数,优化器,损失函数
from torch import optim
net=VGG16()
lr=0.001
optimizer=optim.SGD(net.parameters(),lr=lr,momentum=0.9)
loss=nn.CrossEntropyLoss()
epochs=10
训练
def train(net,train_iter,test_iter,num_epochs, lr, device):
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
for epoch in range(num_epochs):
net.train()
train_step = 0
for i, (X, y) in enumerate(train_iter):
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l=loss(y_hat,y)
l.backward()
optimizer.step()
train_step+=1
if(train_step%50==0):#每训练一百组输出一次损失
print("第{}轮的第{}次训练的loss:{}".format((epoch+1),train_step,l.item()))