目录
使用LRN-Local Response Normalization局部抑制
网络特色
1、使用ReLU作为激活函数代替了传统的Sigmoid和Tanh
ReLU为非饱和函数,论文中验证其效果在较深的网络超过了Sigmoid,成功解决了Sigmoid在网络较深时的梯度弥散问题。
2、在多个GPU上进行模型的训练,不但可以提高模型的训练速度,还能提升数据的使用规模
3、使用LRN对局部的特征进行归一化
结果作为ReLU激活函数的输入能有效降低错误率
4、使用随机丢弃技术(dropout)选择性地忽略训练中的单个神经元
在AlexNet的最后几个全连接层中使用了Dropout来避免模型的过拟合
5、重叠最大池化(overlapping max pooling)
重叠池化,即池化范围z与步长s存在关系z > s(如最大池化下采样中核大小为3 × 3,步距为2),提高了一点点准确度,非常玄学。
最大池化,避免平均池化(average pooling)的平均效应。
后面我会单独分别介绍这5个特点。
网络结构和描述
经典的上下两层结构,目的是用多GPU并行处理。
为了简化网络结构,将作者原论文中的在两个GPU上的并行结构合并,接下来我们对AlexNet的每一层作详细的分析。
1、Conv1: kernels:48×2=96;kernel_size:11;padding:[1, 2] ;stride:4
卷积层1输入的尺寸为224×224,卷积核的数量为96,论文中两片GPU分别计算48个核; 卷积核的大小为 11 × 11 × 3 ;卷积核步距stride = 4;padding=[1, 2]表示在原输入图像上左侧补一列0,右侧2列0,上侧一行0,下侧2行0。
输出feature map的尺寸为:N = (W − F + 2P ) / S + 1 = [ 224 - 11 + (1 + 2)] / 4 + 1 = 55
输出结果Relu后,使用LRN进行局部抑制
2、Maxpool1: kernel_size:3;pading:0;stride:2
卷积层Conv1之后接着进行了局部响应规范化操作( Local Response Normalized),将规范化的结果送入大小为3 × 3 3\times33×3,步距为2的池化核进行最大池化下采样。
输出的feature map尺寸为:N = (W − F + 2P ) / S + 1 = (55 - 3) / 2 + 1 = 27
3、Conv2: kernels:128×2=256; kernel_size:5; padding: [2, 2]; stride:1
卷积层2使用256个卷积核做常规的卷积操作
输出的feature map尺寸为:N = (W − F + 2P ) / S + 1 = (27 - 5 + 4) / 1 + 1 = 27
输出结果Relu后,使用LRN进行局部抑制
4、Maxpool2: kernel_size:3; pading:0; stride:2
与下采样层Conv2类似,在上述卷积层之后接着进行了局部响应规范化操作,然后将结果送入大小为3 × 3 3\times33×3,步距为2的池化核进行最大池化下采样。
输出的feature map尺寸为:N = (W − F + 2P ) / S + 1 = (27 - 3) / 2 + 1 = 13
5、Conv3: kernels:192×2=384; kernel_size:3; padding: [1, 1]; stride:1
与Conv1和Conv2不同,Conv3、Conv4、Conv5后均不接局部响应归一化LRN层**
输出的feature map尺寸为:N = (W − F + 2P ) / S + 1 = (13 - 3 + 2) / 1 + 1 = 13
6、Conv4: kernels:192×2=384; kernel_size:3; padding: [1, 1]; stride:1
输出的feature map尺寸为:N = (W − F + 2P ) / S + 1 = (13 - 3 + 2) / 1 + 1 = 13
7、Conv5: kernels:128×2=256; kernel_size:3; padding: [1, 1]; stride:1
输出的feature map尺寸为:N = (W − F + 2P ) / S + 1 = (13 - 3 + 2) / 1 + 1 = 13
8、Maxpool3: kernel_size:3 padding: 0 stride:2
输出的feature map尺寸为:N = (W − F + 2P ) / S + 1 = (13 - 3) / 2 + 1 = 6
9、全连接层FC1、FC2、FC3
FC1和FC2分别有4096个神经元,FC3输出softmax为1000个(ImageNet数据集分类类别)。
每个全连接层之间都有DropOut。
使用LRN-Local Response Normalization局部抑制
LRN基于生物学“侧抑制”的理论,即被激活的神经元会抑制周围其他神经元。
我们使用局部归一化,可以理解为把局部的神经元放在一个评价体系中,看这些神经元中谁是被激活的、活跃的神经元(Relu输出值高的),这些神经元在局部的评价体系中给的评分就越高。
如下公式:
a(i,x,y)表示输入图像中通道i、坐标为(x,y)的像素通过Relu后的输出值。假如某个像素是正数,a(i,x,y)=像素值。
N:通道总数量
n:选择的局部区域通道数量
累加a(j,x,y):正如前文所说,要选定一个局部区间,对这个局部区间进行归一化,因此肯定得是某一项/局部所有项之和这样的形式。
n,k,α,β分别表示函数中的depth_radius,bias,alpha,beta,其中n,k,α,β都是自定义的,特别注意一下∑叠加的方向是沿着通道方向的。
计算公式
形象化的绘图如下:
注意图中n/2的地方,那是一个沿着channel方向的区间,代表的就是你选择的局部区域,这个区域包含m个通道,m<=N。
如果n=N,这个区域就是0-N,即所有通道。
下面具体的计算方式很形象:
import tensorflow as tf
import numpy as np
x = np.array([i for i in range(1,33)]).reshape([2,2,2,4])
y = tf.nn.lrn(input=x,depth_radius=2,bias=0,alpha=1,beta=1)
with tf.Session() as sess:
print(x)
print('#############')
print(y.eval())
上图中有两个Batch,每组中一列可以看成一个通道,如[1,5,9,13]是第一批、第一个通道:
计算第二批、通道2中的26那个位置的局部归一化输出:
因为代码中指定depth_radius=2,所以表示局部区间是从26这个点所在的通道开始,往前数两个通道,再往后数两个的通道。这个通道区间中对应26位置[(1,0)]的像素值分别是25 26 27 28,再用上面公式计算即可:
26/(0+1*(25^2 + 26^2 + 27^2 + 28^2)) ^1 = 0.00923952。
下面这张图来自于:参考2-CSDN,就当总结一下吧。
全连接层使用DropOut
以前学神经网络的时候我们学过添加正则项避免过拟合,本质是降低网络的学习能力。
对于全连接层来说,可以使用dropout来降低网络的学习能力,进而避免过拟合。
dropout是就是随机使得一些神经元失活:
为了保证期望不变,我们应该在使得一些神经元失活的基础上,提高那些没失活神经元的学习能力:
以概率 p 将原始数据元素变为 0,即丢弃数据;以概率 1-p 将原始数据元素变大
可以看到现在的期望就变成了 E(x') = 0p + (1-p)x/ (1-p) = x,并没有变化。
具体实现:
import torch
from torch import nn
from d2l import torch as d2l
def dropout_layer(X, dropout):
assert 0 <= dropout <= 1
# 在本情况中,所有元素都被丢弃
if dropout == 1:
return torch.zeros_like(X)
# 在本情况中,所有元素都被保留
if dropout == 0:
return X
# torch.rand() 生成 0~1 之间的随机均匀分布, mask 最终生成与 输入 X 同维度 且元素只有 0 或 1 的张量
mask = (torch.rand(X.shape) > dropout).float()
return mask * X / (1.0 - dropout)
使用Relu函数代替Sigmoid和tanh
用Relu的原因
- Relu计算简单
相较于sigmoid来说,没有指数运算,速度快 - sigmoid在深层网络出现梯度为零的情况,relu可以缓解此问题
因为sigmoid的值恒大于0,在深层网络中,正值不断累加为一个很大的值,而sigmoid函数在x很大时的梯度为0,进而无法使用梯度下降法更新此处梯度。而relu的函数值可以取0,在一定程度上缓解了此问题。 - Relu把一些输入以0输出,在一定程度上简化的(稀疏的)特征图,留下了更重要的信息。
使用ReLU激活后,输出的矩阵产生很多0值,而通过Sigmoid激活函数后,得到的矩阵中的元素的值都是处于0到1之间,所以,ReLU的输出要比Sigmoid的稀疏程度高的多,而稀疏程度高,则意味着我们去找这些矩阵所表含的规律时就比较容易。(卷积神经网络基础题——CNN中为什么用ReLU,而不用Sigmoid?_G5Lorenzo的博客-CSDN博客_为什么用relu不用sigmoid)
relu组成非线性的原理
- 叠加操作(串联神经元),表达式为: Relu(13∗Relu(10∗Relu(−2∗x+1,0)+2,0)−2,0)
- 加法操作(并联神经元),表达式为:Relu(−2∗x+1,0)+Relu(3∗t−3,0)+Relu(−2∗t−5,0)+Relu(4∗t−100,0)
可以看到对于stack操作,你无论stack多少次,最终的函数图像都只能是一边是全零,一边是斜率为正或负的线性函数。
但是对于加法操作,可以很大程度上改变图像的样子,我这里做了四个add操作,函数图像就有点像二次函数了。当你add操作足够多,结合stack操作和bias(为了有负值),理论上是能拟合绝大部分在闭区间上的连续函数的。(事实上,Relu函数无法真正意义上拟合所有函数,但对于神经网络来说,已经够用了)
使用Overlapping Pooling(重叠池化)
相对于传统的no-overlapping pooling,采用Overlapping Pooling不仅可以提升预测精度,同时一定程度上可以减缓过拟合。
相比于正常池化(步长s=2,窗口z=2) 重叠池化(步长s=2,窗口z=3) 可以减少top-1, top-5分别为0.4% 和0.3%;重叠池化可以避免过拟合。
Pytorch实现
import torch
import torch.nn as nn
class AlexNet(nn.Module):
def __init__(self, num_classes=1000):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2), # [None, 3, 224, 224] --> [None, 96, 55, 55]
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2), # [None, 96, 55, 55] --> [None, 96, 27, 27]
nn.Conv2d(96, 256, kernel_size=5, padding=2), # [None, 96, 27, 27] --> [None, 256, 27, 27]
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2), # [None, 256, 27, 27] --> [None, 256, 13, 13]
nn.Conv2d(256, 384, kernel_size=3, padding=1), # [None, 256, 27, 27] --> [None, 384, 13, 13]
nn.ReLU(inplace=True),
nn.Conv2d(384, 384, kernel_size=3, padding=1), # [None, 384, 13, 13] --> [None, 384, 13, 13]
nn.ReLU(inplace=True),
nn.Conv2d(384, 256, kernel_size=3, padding=1), # [None, 384, 13, 13] --> [None, 256, 13, 13]
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2) # [None, 256, 13, 13] --> [None, 256, 6, 6]
)
self.classifier = nn.Sequential(
nn.Dropout(p=0.2),
nn.Linear(256 * 6 * 6, 2048),
nn.ReLU(inplace=True),
nn.Dropout(p=0.2),
nn.Linear(2048, 2048),
nn.ReLU(inplace=True),
nn.Linear(2048, num_classes)
)
def forward(self, inputs):
x = self.features(inputs)
x = torch.flatten(x, start_dim=1)
outputs = self.classifier(x)
return outputs