目录
- 参考资料
- 一、前言
- 二、论文相关细节
- 三、网络结构
- 三、参数数量与神经元个数分析
- 四、减少过拟合的方法
- 五、训练细节
- 六、总结
- 七、论文复现
- 1. 数据集
- 2.具体代码
- 3.训练出现的问题
- (1)jupyter notebook中的num_workers大于0卡住:
- (2)什么时候用model.train(),什么时候用model.eval():
- (3)损失torch.nn.CrossEntropyLoss()与F.cross_entropy()效果一样:
- (4)查看GPU利用率:
- (5)训练集batch和测试集batch
- (6)学习率learning rate如何调整?
- (7)如果用交叉熵损失,输出层的Linear后不应该再跟softmax:
- (8)Pytorch中transforms.Normalize解析:
- (9)trainloader和testloader解包时写法不同:
- (10)Pytorch中a += b 和 a = a+b的区别:
- (11)Dropout层:
- (12)模型精度一直停留在50%左右:
参考资料
论文地址:ImageNet Classifification with Deep Convolutional Neural Networks
参考博客:
一、前言
AlexNet共包含5层卷积层和三层全连接层,层数比LeNet多了不少,但卷积神经网络总的流程并没有变化,只是在深度上加了不少。 网络大约有6千万个参数,其中3千万全用在第一个全连接层上了。
ILSVRC使用ImageNet的一个子集,在1000个类别中的每个类别中大约有1000个图像。总共大约有120万张训练图像、50000张验证图像和150000张测试图像。
-
输入:224 * 224 * 3大小的RGB图像(数据集里图像是256 * 256大小)
-
输出:1000个类别。
二、论文相关细节
1. ReLU激活函数
使用ReLU的深度卷积神经网络训练速度比使用tanh单元的等效网络快几倍。
ReLU函数是一个分段线性函数,小于等于0则输出0;大于0的则恒等输出。反向传播中,ReLU有输出的部分,导数始终为1,而且ReLU会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
2. 多GPU并行计算
受当时硬件水平限制,AlexNet训练作者使用了双GPU,这是工程上的一种创新做法。双GPU网络的训练时间比单GPU网络更少,分别将top-1和top-5错误率分别降低了1.7%和1.2%。
3. LRN局部响应归一化
对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。LRN一般是在激活、池化后进行的一种处理方法,但是效果一般,现在基本不用。
4. 重叠池化
一般的池化(Pooling)是不重叠的,池化区域的窗口大小与步长相同。在AlexNet中使用的池化(Pooling)却是可重叠(Overlapping)的,也就是说,在池化的时候,每次移动的步长小于池化的窗口长度。
相比于正常池化(步长s=2,窗口z=2),重叠池化(步长s=2,窗口z=3) 可以减少top-1, top-5分别为0.4% 和0.3%,重叠池化提升了特征的丰富性,避免过拟合。
三、网络结构
AlexNet整体的网络结构包括:1个输入层(input layer)、5个卷积层(C1、C2、C3、C4、C5)、2个全连接层(FC6、FC7)和1个输出层(output layer)。
1.输入层(Input layer)
原论文中,AlexNet的输入图像尺寸是224 * 224 * 3,但是实际图像尺寸为227 * 227 * 3。
2.卷积层(C1)
该层的处理流程是:卷积–>ReLU–>池化–>局部响应归一化(LRN)。
(1)卷积:输入是227 * 227 * 3,使用96个11 * 11 * 3的卷积核进行卷积,padding=0,stride=4,根据公式:(input_size + 2 * padding - kernel_size) / stride + 1=(227+2*0-11)/4+1=55,得到输出是55 * 55 * 96。
(2)ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
(3)池化:使用3 * 3,stride=2的池化单元进行最大池化操作(max pooling)。注意这里使用的是重叠池化,即stride小于池化单元的边长。根据公式:(55+2*0-3)/2+1=27,每组得到的输出为27 * 27 * 96。
(4)LRN:局部响应归一化的输出仍然是27 * 27 * 96。将其分成两组,每组大小是27 * 27 * 48,分别位于单个GPU上。
3.卷积层(C2)
该层的处理流程是:卷积–>ReLU–>池化–>局部响应归一化(LRN)。
(1)卷积:两组输入均是27 * 27 * 48,各组分别使用128个5 * 5 * 48的卷积核进行卷积,padding=2,stride=1,根据公式:(input_size + 2 * padding - kernel_size) / stride + 1=(27+2 * 2-5)/1+1=27,得到每组输出是27 * 27 * 128。
(2)ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
(3)池化:使用3 * 3,stride=2的池化单元进行最大池化操作(max pooling)。注意这里使用的是重叠池化,即stride小于池化单元的边长。根据公式:(27+2 * 0-3)/2+1=13,每组得到的输出为13 * 13 * 128。
(4)局部响应归一化:使用参数k=2,n=5,α=0.0001,β=0.75进行归一化。每组输出仍然是13 * 13 * 128。
4.卷积层(C3)
该层的处理流程是: 卷积–>ReLU
(1)卷积:输入是13 * 13 * 256,使用384个3 * 3 * 256的卷积核进行卷积,padding=1,stride=1,根据公式:(input_size + 2 * padding - kernel_size) / stride + 1=(13+2*1-3)/1+1=13,得到输出是13 * 13 * 384。
(2)ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。将输出其分成两组,每组FeatureMap大小是13 * 13 * 192,分别位于单个GPU上。
5.卷积层(C4)
该层的处理流程是:卷积–>ReLU
(1)卷积:两组输入均是13 * 13 * 192,各组分别使用192个3 * 3 * 192的卷积核进行卷积,padding=1,stride=1,根据公式:(input_size + 2 * padding - kernel_size) / stride + 1=(13+2 * 1-3)/1+1=13,得到每组FeatureMap输出是13 * 13 * 192。
(2)ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
6.卷积层(C5)
该层的处理流程是:卷积–>ReLU–>池化
(1)卷积:两组输入均是13 * 13 * 192,各组分别使用128个3 * 3 * 192的卷积核进行卷积,padding=1,stride=1,根据公式:(input_size + 2 * padding - kernel_size) / stride + 1=(13+2 * 1-3)/1+1=13,得到每组FeatureMap输出是13 * 13 * 128。
(2)ReLU:将卷积层输出的FeatureMap输入到ReLU函数中。
(3)池化:使用3 * 3,stride=2的池化单元进行最大池化操作(max pooling)。注意这里使用的是重叠池化,即stride小于池化单元的边长。根据公式:(13+2 * 0-3)/2+1=6,每组得到的输出为6 * 6 * 128。
7.全连接层(FC6)
该层的流程为:(卷积)全连接 -->ReLU -->Dropout (卷积)
(1)全连接:输入为6 * 6 * 256,使用4096个6 * 6 * 256的卷积核进行卷积,由于卷积核尺寸与输入的尺寸完全相同,即卷积核中的每个系数只与输入尺寸的一个像素值相乘一一对应,根据公式:(input_size + 2 * padding - kernel_size) / stride + 1=(6+2 * 0 - 6)/1+1=1,得到输出是1 * 1 * 4096。即有4096个神经元,该层被称为全连接层。
(2)ReLU:这4096个神经元的运算结果通过ReLU激活函数中。
(3)Dropout:随机的断开全连接层某些神经元的连接,通过不激活某些神经元的方式防止过拟合。4096个神经元也被均分到两块GPU上进行运算。
8.全连接层(FC7)
该层的流程为:(卷积)全连接 -->ReLU -->Dropout
(1)全连接:输入为4096个神经元,输出也是4096个神经元(作者设定的)。
(2)ReLU:这4096个神经元的运算结果通过ReLU激活函数中。
(3)Dropout:随机的断开全连接层某些神经元的连接,通过不激活某些神经元的方式防止过拟合。
9.输出层(Output layer)
该层的流程为:(卷积)全连接 -->Softmax
(1)全连接:输入为4096个神经元,输出是1000个神经元。这1000个神经元即对应1000个检测类别。
(2)Softmax:这1000个神经元的运算结果通过Softmax函数中,输出1000个类别对应的预测概率值。
三、参数数量与神经元个数分析
- AlexNet神经元数量
- AlexNet参数数量
四、减少过拟合的方法
(1)数据增强:
- 图像先从256 * 256 resize到224 * 224 ,然后利用图像平移、水平反射、随机裁剪;
- 改变训练图像中RGB通道的强度,在整个ImageNet训练集中对RGB像素值集执行PCA,然后对主成分做一个(0, 0.1)的高斯扰动,也就是对颜色、光照作变换,结果使错误率又下降了1%。
(2)Dropout(暂退法):
-
在训练时,丢弃概率p设置为0.5,将需要丢弃的隐藏神经元的输出设置为0,使其不参与正向传递,也不参与反向传播,因此,每次输入时,神经网络都会对不同的结构进行采样,但所有这些结构都具有相同的权重。该技术减少了神经元的复杂协同适应,因为神经元不能依赖于特定其他神经元的存在。因此,它被迫学习与其他神经元的许多不同随机子集结合使用的更稳健的特征。
-
在测试时,我们使用所有神经元,但将它们的输出乘以0.5,这是一个合理的近似值,用于获取由指数多个丢失网络产生的预测分布的几何平均值。
五、训练细节
- 训练时采用动量法+权重衰减,动量因子设为0.9,权重衰减系数为0.0005。
- 当验证集上的错误率不再随着当前学习率的提高而提高时,将学习率除以10。学习率初始化为0.01,在终止前降低3倍。
六、总结
- 首次利用 GPU 进行网络加速训练(在两块GPU上训练)。
- 使用了 ReLU 激活函数,而不是传统的 Sigmoid 激活函数以及 Tanh 激活函数。
- 使用了 LRN 局部响应归一化,对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。
- 在全连接层的前两层中使用了 Dropout 随机失活神经元操作,以减少过拟合。
- 使用了重叠的最大池化,步长stride比池化的核的尺寸小,这样池化层的输出之间有重叠,提升了特征的丰富性,减少过拟合。
七、论文复现
多GPU训练版本:
单GPU训练版本:
1. 数据集
在论文中AlexNet作者使用的是ILSVRC 2012比赛数据集,该数据集非常大(有138G),下载、训练都很消耗时间,我们在复现的时候就不用这个数据集了。由于MNIST、CIFAR10、CIFAR100这些数据集图片尺寸都较小,不符合AlexNet网络输入尺寸227x227的要求,因此我们改用kaggle比赛经典的“猫狗大战”数据集了。
该数据集包含的训练集总共25000张图片,猫狗各12500张,带标签;测试集总共12500张,不带标签。我们仅使用带标签的25000张图片,分别拿出2500张猫和狗的图片作为模型的验证集。我们按照以下目录层级结构,将数据集图片放好。
数据集下载地址:提取码:cdue
2.具体代码
网络结构主体部分:
# 单GPU训练
class AlexNet(nn.Module):
"""
Neural network model consisting of layers propsed by AlexNet paper.
"""
def __init__(self, num_classes=2):
"""
Define and allocate layers for this neural net.
Args:
num_classes (int): number of classes to predict with this model
"""
super().__init__()
# input size should be : (b x 3 x 227 x 227)
# The image in the original paper states that width and height are 224 pixels, but
# the dimensions after first convolution layer do not lead to 55 x 55.
self.Features = nn.Sequential(
# 卷积层C1
nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4), # (b x 96 x 55 x 55)
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2), # (b x 96 x 27 x 27)
nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2), # section 3.3
# 卷积层C2
nn.Conv2d(96, 256, 5, padding=2), # (b x 256 x 27 x 27)
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2), # (b x 256 x 13 x 13)
nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
# 卷积层C3
nn.Conv2d(256, 384, 3, padding=1), # (b x 384 x 13 x 13)
nn.ReLU(),
# 卷积层C4
nn.Conv2d(384, 384, 3, padding=1), # (b x 384 x 13 x 13)
nn.ReLU(),
# 卷积层C5
nn.Conv2d(384, 256, 3, padding=1), # (b x 256 x 13 x 13)
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2), # (b x 256 x 6 x 6)
)
# classifier is just a name for linear layers
self.classifier = nn.Sequential(
# 全连接层FC6
nn.Linear(in_features=(256 * 6 * 6), out_features=500),
nn.ReLU(),
# https://blog.csdn.net/hxxjxw/article/details/120637104
nn.Dropout(p=0.5, inplace=False), # 这里设成True会报错
# 全连接层FC7
nn.Linear(in_features=500, out_features=50),
nn.ReLU(),
# 个人理解:当网络比较浅时不应该加过多的dropout
# 会使得一半的神经元失效,导致网络能力随机性太大。
# 靠近输出层的dropout层不一定需要加,这里神经元连接数相对较少
# 加了的话会损失一半有效信息(高层语义特征)
nn.Dropout(p=0.5, inplace=False),
# 输出层OutLayer
nn.Linear(in_features=50, out_features=num_classes),
)
def forward(self, x):
"""
Pass the input through the net.
Args:
x (Tensor): input tensor
Returns:
output (Tensor): output tensor
"""
x = self.Features(x)
x = x.view(-1, 256 * 6 * 6) # reduce the dimensions for linear layer input
return self.classifier(x)
# 测试模型各层输出维度
def test_size(self, input):
# 测试卷积层
for layer in self.Features:
input=layer(input)
print(layer.__class__.__name__,'output shape:\t',input.shape)
input = input.view(-1, 256 * 6 * 6)
# 测试线性层
for layer in self.classifier:
input=layer(input)
print(layer.__class__.__name__,'output shape:\t',input.shape)
# 将定义好的网络结构搭载到GPU/CPU
# 创建模型,部署gpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = AlexNet().to(device)
3.训练出现的问题
(1)jupyter notebook中的num_workers大于0卡住:
在jupyter notebook里只能设置0,如果使用单独的py文件写,可以设置>0的数,使用.py分文件编程更流畅!
(2)什么时候用model.train(),什么时候用model.eval():
注意,在模型中有BN层或者dropout层时,在训练阶段和测试阶段必须显式指定train()和eval()。
(3)损失torch.nn.CrossEntropyLoss()与F.cross_entropy()效果一样:
损失CrossEntropyLoss()与cross_entropy()
(4)查看GPU利用率:
cmd界面下运行nvidia-smi -l
参考:GPU利用率问题
(5)训练集batch和测试集batch
- 训练集的batch size通常较大,测试集的通常较小;
- 一般设置为2的整数倍,如16、32、64等;
(6)学习率learning rate如何调整?
- 二分类问题中,SGD的性能优于Adam法;
- Batch越大,学习率也应该越大;
(7)如果用交叉熵损失,输出层的Linear后不应该再跟softmax:
使用交叉熵(CrossEntropyLoss)做多分类问题最后一层是否使用softmax
(8)Pytorch中transforms.Normalize解析:
归一化transforms.Normalize的真正计算过程
(9)trainloader和testloader解包时写法不同:
其实没有太大区别,只是因为训练时可能需要用到这个batch_idx,去输出打印一定数量batch下的loss
训练集的trainloader需要写成for batch_idx, data in enumerate(train_loader):
测试集的testloader直接写成for data, label in testloader:
(10)Pytorch中a += b 和 a = a+b的区别:
Numpy & Pytorch 中的 + 和 += 的区别
- a+=b 是深拷贝,直接对原来的a操作,不开辟新的内存地址空间;
- a=a+b是浅拷贝,重新开辟内存地址给a;
(11)Dropout层:
- 当网络不是很深时,加入dropout层会使得模型拟合能力不足,导致模型学不到东西;
- 靠近输出层的dropout层不一定需要加,如果神经元连接数相对较少,加了的话会损失一半有效信息(高层语义特征),导致模型学不到东西;如果神经元数多,可以加上dropout;
(12)模型精度一直停留在50%左右:
- 把优化器Adam换成SGD;
- 去掉Dropout层;
- 调整学习率和Batch,一般Batch小,模型精度越高,收敛所需epoch次数越小,当Batch调小后,需要把学习率也调小;
- 需要多几次epoch迭代,模型才能找到收敛的方向;