跟着问题学5——AlexNet网络详解及代码实战

不如语冰 2024-09-30 17:07:03 阅读 71

在LeNet提出后的将近20年里,神经网络一度被其他机器学习方法超越,如支持向量机。虽然LeNet可以在早期的小数据集上取得好的成绩,但是在更大的真实数据集上的表现并不尽如人意。一方面,神经网络计算复杂。虽然20世纪90年代也有过一些针对神经网络的加速硬件,但并没有像之后GPU那样大量普及。因此,训练一个多通道、多层和有大量参数的卷积神经网络在当年很难完成。另一方面,当年研究者还没有大量深入研究参数初始化和非凸优化算法等诸多领域,导致复杂的神经网络的训练通常较困难。

2012年,AlexNet横空出世。这个模型的名字来源于论文第一作者的姓名Alex Krizhevsky [1]。AlexNet使用了8层卷积神经网络,并以很大的优势赢得了ImageNet 2012图像识别挑战赛。它首次证明了学习到的特征可以超越手工设计的特征,从而一举打破计算机视觉研究的前状。

1.Alexnet网络结构

1.1图像维度变化计算

整个AlexNet网络包括输入层×1、卷积层×5、池化层×3、全连接层×3,根据前面所讲的卷积层和池化层图片尺寸变化计算公式,有

第1层输入层: 输入为224×224×3 三通道的图像。

第2层Conv层: 输入为224×224×3,经过96个kernel size为11×11×3的filter, stride = 4,卷积后得到shape为55×55×96的卷积层。

这里有2点值得注意的细节:(224-11)/4 + 1 = 54.25,

(1)按照论文中filter size和stride的设计,输入的图片尺寸应该为227×227×3。

(2)加上padding=2,则(224-11+2*2)/4 + 1 = 55.25,步长会略去小数,得到55.

第3层Max-pooling层: 输入为55×55×96,经Overlapping pooling(重叠池化)pool size = 3,stride = 2后得到尺寸为27×27×96 的池化层

第4层Conv层: 输入尺寸为27×27×96,经256个5×5×96的filter卷积,padding=same得到尺寸为27×27×256的卷积层。

第5层池化层: 输入为27×27×256,,经pool size = 3,stride = 2的重叠池化,得到尺寸为13×13×256的池化层。

第6~8层Conv层: 第6层输入为13×13×256,经384个3×3×256的filter卷积得到13×13×384的卷积层。 第7层输入为13×13×384,经384个3×3×384的filter卷积得到13×13×384的卷积层。 第8层输入为13×13×384,经256个3×3×384的filter卷积得到13×13×256的卷积层。 这里可见,这三层卷积层使用的kernel前两个维度都是3×3,只是通道维不同。

第9层Max-pooling层: 输入尺寸为13×13×256,经pool size = 3,stride = 2的重叠池化得到尺寸为6×6×256的池化层。该层后面还隐藏了flatten操作,通过展平得到6×6×256=9216个参数后与之后的全连接层相连。

第10~12层Dense(全连接)层: 第10~12层神经元个数分别为4096,4096,1000。其中前两层在使用relu后还使用了Dropout对神经元随机失活,最后一层全连接层用softmax输出1000个分类。(分类数量根据具体应用的数量变化,比如数据集中有10个类别,则最后输出10)

1.2 参数计算

根据论文描述,AlexNet中有65万个神经元,包括了6000万个参数。由于池化层不涉及参数计算,我们重点关注5个卷积层和最后3个全连接层。

神经元数量是图像的尺度信息,是计算时占用内存的空间大小(memory);参数数量对于卷积层是卷积核的尺寸,通道数与数量信息,对于全连接层则是前后两层神经元的数量的乘积。

2亮点与贡献

2.1 引入非线性激活函数Relu

AlexNet将sigmoid激活函数改成了更加简单的ReLU激活函数。一方面,ReLU激活函数的计算更简单,例如它并没有sigmoid激活函数中的求幂运算。另一方面,ReLU激活函数在不同的参数初始化方法下使模型更容易训练。这是由于当sigmoid激活函数输出极接近0或1时,这些区域的梯度几乎为0,从而造成反向传播无法继续更新部分模型参数;而ReLU激活函数在正区间的梯度恒为1。因此,若模型参数初始化不当,sigmoid函数可能在正区间得到几乎为0的梯度,从而令模型无法得到有效训练。

论文比较了在CIFAR-10数据集下,一个4层卷积的网络结构,在以tanh和relu作为激活单元时损失和训练轮数的关系,得出结论:使用ReLUs(实线)进行深度卷积神经网络训练的速度比使用tanh(虚线)训练的速度快6倍 。而且防止过拟合的作用比较好。

2.2 多GPU并行训练

论文中展示的多GPU并行训练是在当时GPU的内存较小的情况下,采用的无奈操作,得益于现在GPU内存的高速发展,对于一般的网络模型此类操作如今没有使用的必要了。不过目前来看随着大模型的发展,参数数量越来越多,GPU并行训练还是很有必要的,后续将学习介绍GPU的并行训练。

简单举例来说,池化核大小3 × 3, 扩充边缘padding = 0,步长stride = 2,因此其FeatureMap输出大小为(13-3+0×2+2)/2=6, 即C5输出为6×6×256(此处未将输出分到两个GPU中,若按照论文将分成两组,每组为6×6×128);

2.3 Local Response Normalization LRN

Local Response Normalization(LRN)技术主要是深度学习训练时的一种提高准确度的技术方法。其中caffe、tensorflow等里面是很常见的方法,其跟激活函数是有区别的,LRN一般是在激活、池化后进行的一种处理方法。LRN归一化技术首次在AlexNet模型中提出这个概念。通过实验确实证明它可以提高模型的泛化能力,但是提升的很少,以至于后面不再使用。

论文中提出,采用LRN局部响应正则化可以提高模型的泛化能力,所以在某些层应用了ReLU后,加上了LRN使得top-1和top-5的错误率分别降低了1.4%和1.2%。但是由于实际作用不大(在VGG网络中,作了对比,发现效果并不显著),后面LRN的方式并没有被人们广泛使用

2.4 Overlapping Pooling

论文中的Maxpooling采用的是重叠池化,传统池化时卷积核提取的每一小块特征不会发生重合,譬如kernel size记为k×k,步长stride记为s,当k = s时,就不会发生重合,当k> s时,就会发生重合,即重叠池化。AlexNet训练中的stride为2,kernel size为k = 3,使用的就是重叠池化。 **

以往池化的大小PoolingSize与步长stride一般是相等的,例如:图像大小为256*256,PoolingSize=2×2,stride=2,这样可以使图像或是FeatureMap大小缩小一倍变为128,此时池化过程没有发生层叠。但是AlexNet采用了层叠池化操作,即PoolingSize > stride。这种操作非常像卷积操作,可以使相邻像素间产生信息交互和保留必要的联系。论文中也证明,此操作可以有效防止过拟合的发生。

2.5. 降低过拟合

2.5.1 数据扩增

为了降低过拟合,提高模型的鲁棒性,这里采用了两种Data Augmentation数据扩增方式

1.生成图像平移和水平反射。通过从256×256幅图像中提取随机224×224块图像(及其水平反射),并在这些提取的图像上训练网络。这将训练集的大小增加了2048倍2.改变了训练图像中RGB通道的强度。在整个imagenet训练集中对RGB像素值集执行PCA操作

2.5.2 Dropout

训练采用了0.5丢弃率的传统Dropout,对于使用了Dropout的layer中的每个神经元,训练时都有50%的概率被丢弃。所以每次输入时,神经网络都会对不同的结构进行采样,但是所有这些结构都共享权重。这种技术减少了神经元之间复杂的相互适应,因为神经元不能依赖于其他神经元的存在。因此,它被迫获得更健壮的特征。测试时使用所有的神经元,但将它们的输出乘以0.5。 论文中还提到了:Dropout使收敛所需的迭代次数增加了一倍

2.6.网络层数的增加

与原始的LeNet相比,AlexNet网络结构更深,LeNet为5层,AlexNet为8层。在随后的神经网络发展过程中,AlexNet逐渐让研究人员认识到网络深度对性能的巨大影响。当然,这种思考的重要节点出现在VGG网络,但是很显然从AlexNet为起点就已经开始了这项工作。

3.超参数与代码

训练使用的是小批量随机梯度下降,batch size = 128,动量momentum为0.9,weight decay权重衰减为0.0005;每一层的权重初始化为均值1标准差0.01的正态分布,在第2,4,5卷积层和全连接层中的bias初始化为常数1,其余层则为0.所有层采用了相同的初始化为0.01的学习率,不过可以手动调整。整个训练过程在两台NVIDIA GTX 580 3GB gpu上用了5到6天的时间,120万张图像的训练集,大约90轮迭代。 论文中提到:weight decay对模型的学习很重要。换句话说,这里的重量衰减不仅仅是一个正则化器:它减少了模型的训练误差。

model.py

<code>#此部分代码结合前面的图像维度变化来看,相比于之前介绍的LeNet,特殊之处在于全连接层前加了dropout

import torch

from torch import nn

import torch.nn.functional as F

import random

class AlexNet(nn.Module):

def __init__(self):

super(AlexNet,self).__init__()

self.c1=nn.Conv2d(in_channels=3,out_channels=96,kernel_size=11,stride=4,padding=2)

self.Relu=nn.ReLU()

self.s2=nn.MaxPool2d(kernel_size=3,stride=2)

self.c3=nn.Conv2d(in_channels=96,out_channels=256,kernel_size=5,stride=1, padding=2)

self.s4= nn.MaxPool2d(kernel_size=3, stride=2)

self.c5=nn.Conv2d(in_channels=256,out_channels=384,kernel_size=3,stride=1,padding=1)

self.c6 = nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1,padding=1)

self.c7 = nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3,stride=1, padding=1)

self.s8 = nn.MaxPool2d(kernel_size=3, stride=2)

self.flatten=nn.Flatten()

self.f9=nn.Linear(in_features=6*6*256,out_features=4096)

self.f10=nn.Linear(in_features=4096,out_features=4096)

self.output=nn.Linear(in_features=4096,out_features=7)

def forward(self,x):

x=self.Relu(self.c1(x))

x=self.s2(x)

x=self.Relu(self.c3(x))

x=self.s4(x)

x=self.Relu(self.c5(x))

x=self.Relu(self.c6(x))

x=self.Relu(self.c7(x))

x = self.s8(x)

x=self.flatten(x)

x = F.dropout(x, p=0.5)

x=self.f9(x)

x=F.dropout(x,p=0.5)

x = self.f10(x)

x = F.dropout(x, p=0.5)

output=self.output(x)

return output

if __name__=="__main__":

x=torch.randn([1,3,227,227])

model=AlexNet()

y=model(x)

print(y.size())

train.py

#本次训练代码和前面LeNet的主要区别在于数据集的读取,LeNet练习时直接从下载mnist,本次练习从本地文件夹中使用ImageFolder读取。

<code>#ImageFolder(root, transform``=``None``, target_transform``=``None``, loader``=``default_loader)

#root 指定路径加载图片; transform:对PIL Image进行的转换操作,transform的输入是使用loader读取图片的返回对象

#target_transform:对label的转换 loader:给定路径后如何读取图片,默认读取为RGB格式的PIL Image对象

#label是按照文件夹名顺序排序后存成字典,即{类名:类序号(从0开始)},一般来说最好直接将文件夹命名为从0开始的数字,举例来说,两个类别,

#狗和猫,把狗的图片放到文件夹名为0下;猫的图片放到文件夹名为1的下面。

# 这样会和ImageFolder实际的label一致, 如果不是这种命名规范,建议看看self.class_to_idx属性以了解label和文件夹名的映射关系

import torch

from torch import nn

from models import AlexNet

#from B import C 的原理,有时候可以直接B导入C,有时候需要A.B导入C

from models.AlexNet import *

from torch.optim import lr_scheduler

from torchvision import transforms,datasets

from torchvision.datasets import ImageFolder

from torch.utils.data import DataLoader

import os

import matplotlib.pyplot as plt

#compose的参数为列表[]

train_transform=transforms.Compose([

transforms.Resize([224,224]),

transforms.RandomVerticalFlip(),

transforms.ToTensor(),

#normalize的意义

transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])

])

test_transform=transforms.Compose([

transforms.Resize([224,224]),

transforms.ToTensor(),

transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])

])

#python中\是转义字符,Windows 路径如果只有一个\,会把它识别为转义字符。

#可以用r''把它转为原始字符,也可以用\\,也可以用Linux的路径字符/。

train_dataset=ImageFolder(r"E:\计算机\data\fer2013_数据增强版本\train",train_transform)

test_dataset=ImageFolder(r"E:\计算机\data\fer2013_数据增强版本\test",test_transform)

train_dataloader=DataLoader(train_dataset,batch_size=16,shuffle=True)

test_dataloader=DataLoader(test_dataset,batch_size=16,shuffle=True)

device='cuda' if torch.cuda.is_available() else 'cpu'code>

model=AlexNet().to(device)

loss_fn=nn.CrossEntropyLoss()

#优化器是根据什么优化的,包括网络权重参数,学习率,动量

optimizer=torch.optim.SGD(model.parameters(),lr=0.01,momentum=0.9)

#动态设置学习率变化,参数包括优化器,步长,变化率

lr_scheduler=lr_scheduler.StepLR(optimizer,step_size=10,gamma=0.5)

def train(train_dataloader,model,loss_fn,optimizer):

loss,acc,n=0.0,0.0,0

for batch,(x,y) in enumerate(train_dataloader):

x,y=x.to(device),y.to(device)

#print("x-shape",x.size())

output=model(x)

cur_loss=loss_fn(output,y)

_,pred=torch.max(output,axis=1)

cur_acc=torch.sum(pred==y)/output.shape[0]

#清除过往梯度值

optimizer.zero_grad()

#后向传播

cur_loss.backward()

#优化迭代

optimizer.step()

loss+=cur_loss.item()

acc+=cur_acc.item()

n=n+1

train_loss=loss/n

train_acc=acc/n

print('train_loss==' + str(train_loss))

# 计算训练的准确率

print('train_acc' + str(train_acc))

return train_loss,train_acc

#测试函数里参数无优化器,不需要再训练,只需要测试和验证即可

def test(dataloader,model,loss_fn):

loss,acc,n=0.0,0.0,0

#eval()如果模型中有Batch Normalization和Dropout,则不启用,以防改变权值

model.eval()

#

with torch.no_grad():

for batch,(x,y) in enumerate(dataloader):

x,y=x.to(device),y.to(device)

output=model(x)

cur_loss=loss_fn(output,y)

_,pred=torch.max(output,axis=1)

cur_acc=torch.sum(pred==y)/output.shape[0]

loss += cur_loss.item()

acc += cur_acc.item()

n = n + 1

test_loss=loss/n

test_acc=acc/n

print('test_loss==' + str(test_loss))

# 计算训练的准确率

print('train_acc' + str(test_acc))

return test_loss, test_acc

# 定义画图函数

# 错误率

def matplot_loss(train_loss, test_loss):

# 参数label = ''传入字符串类型的值,也就是图例的名称

plt.plot(train_loss, label='train_loss')code>

plt.plot(test_loss, label='test_loss')code>

# loc代表了图例在整个坐标轴平面中的位置(一般选取'best'这个参数值)

plt.legend(loc='best')code>

plt.xlabel('loss')

plt.ylabel('epoch')

plt.title("训练集和验证集的loss值对比图")

plt.show()

# 准确率

def matplot_acc(train_acc, test_acc):

plt.plot(train_acc, label='train_acc')code>

plt.plot(test_acc, label='test_acc')code>

plt.legend(loc='best')code>

plt.xlabel('acc')

plt.ylabel('epoch')

plt.title("训练集和验证集的acc值对比图")

plt.show()

loss_train=[]

acc_train=[]

loss_test=[]

acc_test=[]

epoch=20

min_acc=0

for t in range(epoch):

lr_scheduler.step()

print(f"epcoh{t+1}\n-------")

train_loss,train_acc=train(train_dataloader,model,loss_fn,optimizer)

test_loss,test_acc=test(test_dataloader,model,loss_fn)

loss_train.append(train_loss)

acc_train.append(train_acc)

loss_test.append(test_loss)

acc_test.append(test_acc)

if test_acc>min_acc:

folder='save_model'code>

if not os.path.exists(folder):

os.mkdir("save_model")

min_acc=test_acc

print(f"save model {t+1}轮")

torch.save(model.state_dict(),'save_model/alexnet-best-model.pth')

if t==epoch-1:

torch.save(model.state_dict(), 'save_model/alexnet-best-model.pth')

matplot_loss(loss_train,loss_test)

matplot_acc(acc_train,acc_test)

print('DONE!')

 

参考资料

《动手学深度学习》 — 动手学深度学习 2.0.0 documentation

Krizhevsky, A., Sutskever, I., & Hinton, G. E. (2012). Imagenet classification with deep convolutional neural networks. In Advances in neural information processing systems (pp. 1097-1105).



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。