AIR

AlexNet的成功之处

AlexNet将LeNet的思想发扬光大,把CNN的基本原理应用到了很深很宽的网络中。AlexNet主要使用到的新技术点如下。

(1)成功使用ReLU作为CNN的激活函数,并验证其效果在较深的网络超过了Sigmoid,成功解决了Sigmoid在网络较深时的梯度消失问题。

(2)训练时使用Dropout随机忽略一部分神经元,以避免模型过拟合。在AlexNet中主要是最后几个全连接层使用了Dropout。

(3)在CNN中使用重叠的最大池化。此前CNN中普遍使用平均池化,AlexNet全部使用最大池化,避免平均池化的模糊化效果。并且AlexNet中提出让步长比池化核的尺寸小,这样池化层的输出之间会有重叠和覆盖,提升了特征的丰富性。

(4)提出了LRN层,对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力

AlexNet paper传送门

处理方法 作用
ReLU、多GPU训练 提高训练速度
重叠池化 提高精度、不易过拟合
局部响应归一化 提高精度
Dropout 减少过拟合

激活函数ReLU

一般激活函数有如下一些性质:

  1. 非线性: 当激活函数是线性的,一个两层的神经网络就可以基本上逼近所有的函数。但如果激活函数是恒等激活函数的时候,即f(x)=x,就不满足这个性质,而且如果MLP使用的是恒等激活函数,那么其实整个网络跟单层神经网络是等价的;
  2. 可微性: 当优化方法是基于梯度的时候,就体现了该性质;
  3. 单调性: 当激活函数是单调的时候,单层网络能够保证是凸函数;
  4. f(x)≈x: 当激活函数满足这个性质的时候,如果参数的初始化是随机的较小值,那么神经网络的训练将会很高效;如果不满足这个性质,那么就需要详细地去设置初始值;
  5. 输出值的范围: 当激活函数输出值是有限的时候,基于梯度的优化方法会更加稳定,因为特征的表示受有限权值的影响更显著;当激活函数的输出是无限的时候,模型的训练会更加高效,不过在这种情况小,一般需要更小的Learning Rate。

在深度神经网络中,通常使用一种叫修正线性单元(Rectified linear unit,ReLU)作为神经元的激活函数。ReLU起源于神经科学的研究:2001年,Dayan、Abott从生物学角度模拟出了脑神经元接受信号更精确的激活模型。

sigmoid

sigmoid是通过$f(z)=\frac{1}{1+e^{-z}}$把它输入实数值并将其“挤压”到0到1范围内,适合输出为概率的情况。

对sigmoid求导

但是sigmoid函数的导数在0的时候取到最大值为0.25。易知利用梯度下降算法的时候容易造成梯度消失。

relu

对relu求导

它与sigmoid相比有几大优点:

  • 在正区间内解决了梯度消失的问题
  • 少了次方计算,计算速度加快

Dropout

在机器学习的模型中,如果模型的参数太多,而训练样本又太少,训练出来的模型很容易产生过拟合的现象。在训练神经网络的时候经常会遇到过拟合的问题,过拟合具体表现在:模型在训练数据上损失函数较小,预测准确率较高;但是在测试数据上损失函数比较大,预测准确率较低。

Dropout说的简单一点就是:我们在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。

正常的神经网络先通过前向传播然后把误差通过反向传播更新参数,利用神经网络来学习

而使用了dropout的神经网络就按一定概率删除一部分神经元。

把dropout后的网络通过前向传播,然后把误差通过反向传播,神经网络学习,然后按梯度下降来更新参数;在恢复删除掉的神经元,到隐藏层随机选择一个子集临时删除掉,然后通过前向传播反向传播在用梯度下降算法更新参数不断重复这一过程。

在训练时增加一个一次概率删除

没有dropout的神经网络

有dropout的神经网络

数据扩充

可以通过图像增广实现数据扩充

torchvision.transforms对于多种变换可以使用torchvision.transforms.Compose来合并。

LRN局部响应归一化

在神经生物学有一个概念叫做“侧抑制”(lateral inhibitio),指的是被激活的神经元抑制相邻神经元。归一化(normalization)的目的是“抑制”,局部归一化就是借鉴了“侧抑制”的思想来实现局部抑制,尤其当使用ReLU时这种“侧抑制”很管用,因为ReLU的响应结果是无界的(可以非常大),所以需要归一化。使用局部归一化的方案有助于增加泛化能力。

AlexNet的结构

第一层(卷积层)

该层的处理流程为:卷积–>ReLU–>池化–>归一化

卷积

在本层使用96个步长为4的11×11×3的卷积核进行卷积计算,其大小为:
$$
floor[(\frac{img_size - filter_size+2\times padding}{stride}) +1] = new_feature_size
$$
其中floor表示向下取整,img_size为图像大小,filter_size为核大小,stride为步长,new_feature_size为卷积后的特征图大小,pading为填充数目,这个公式表示图像尺寸减去卷积核尺寸除以步长,再加上被减去的核大小像素对应生成的一个像素,结果就是卷积后特征图的大小。

得到的特征图大小为55x55,由于采用了两个GPU并行运算,因此,网络结构图中上下两部分分别承担了48个卷积核的运算。所以尺寸为2组55×55×48的像素层数据。

激活

卷积后的55×55像素层经过ReLU单元的激活,生成激活层,尺寸仍为2组55×55×48的像素层数据。

池化

激活再经过池化运算,池化运算的尺寸为3×3,步长为2,则池化后图像的尺寸为 (55-3)/2+1=27,即池化后特征图的规模为27×27×96

归一化

池化后再进行归一化处理,归一化运算的尺寸为5×5,归一化后的像素规模不变,仍为27×27×96,这96层像素层被分为两组,每组48层,分别在一个独立的GPU上进行运算。

第二层(卷积层)

该层与第一层类似,处理流程为:卷积–>ReLU–>池化–>归一化

卷积

每一组经过128个5x5x3的卷积核其中padding=2,stride=1,所以得到的特征图为27x27x128,其中每个GPU为27x27x128

激活

然后经过relu激活

池化

每一组经过128个3x3,stride=2的池化,得到2组13x13x128的像素层

归一化

归一化运算的尺度为5×5

第三层(卷积层)

第三层的处理流程为:卷积–>ReLU

卷积

每一组经过192个大小为3x3x256,padding=1,stride=1的卷积核得到2组13×13×192的像素层

激活

通过relu激活

第四层(卷积层)

第四层的处理流程为:卷积–>ReLU

卷积

每一组经过过192个大小为3×3×192,stride=1,padding=1(与第三层不同,第四层的GPU之间没有虚线连接,也即GPU之间没有通信)得到大小为13×13×192的特征图

激活

通过relu激活

第五层(卷积层)

第五层的处理流程为:卷积–>ReLU–>池化

卷积

每一组经过128个3x3,padding=1,stride=1的卷积,得到13×13×128像素层

激活

通过relu激活

池化

2组13×13×128像素层分别在2个不同GPU中进行池化运算处理,池化运算的尺寸为3×3,步长为2,池化后图像的尺寸为 (13-3)/2+1=6,即池化后像素的规模为两组6×6×128的像素层数据,共有6×6×256的像素层数据。

第六层(全连接层)

第六层的处理流程为:卷积(全连接)–>ReLU–>Dropout

卷积全连接

第六层输入数据是第五层的输出,尺寸为6×6×256。本层共有4096个卷积核,每个卷积核的尺寸为6×6×256,由于卷积核的尺寸刚好与待处理特征图(输入)的尺寸相同,即卷积核中的每个系数只与特征图(输入)尺寸的一个像素值相乘,一一对应,因此,该层被称为全连接层。由于卷积核与特征图的尺寸相同,卷积运算后只有一个值,因此,卷积后的像素层尺寸为4096×1×1,即有4096个神经元。

激活

通过relu激活

Dropout

然后再通过Dropout运算,输出4096个结果值。

第七层(全连接层)

第七层的处理流程为:全连接–>ReLU–>Dropout

第六层输出的4096个数据与第七层的4096个神经元进行全连接,然后经ReLU进行处理后生成4096个数据,再经过Dropout处理后输出4096个数据。

第八层(全连接层)

第八层的处理流程为:全连接

第七层输出的4096个数据与第八层的1000个神经元进行全连接,经过训练后输出1000个float型的值,这就是预测结果。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python
# coding: utf-8

# In[ ]:


import time
import torch
from torch import nn, optim
import torchvision


# In[ ]:


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


# In[4]:


device


# In[ ]:


class AlexNet(nn.Module):
def __init__(self):
super(AlexNet,self).__init__()
self.conv=nn.Sequential(
nn.Conv2d(1,96,11,4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3,2), # kernel_size,stride
nn.Conv2d(96,256,5,1,2),
nn.ReLU(),
nn.MaxPool2d(3,2),
nn.Conv2d(256,384,3,1,1),
nn.ReLU(),
nn.Conv2d(384,384,3,1,1),
nn.ReLU(),
nn.Conv2d(384,256,3,1,1),
nn.ReLU(),
nn.MaxPool2d(3,2),
)
self.fc=nn.Sequential(
nn.Linear(256*5*5,4096),
nn.ReLU(),
nn.Dropout(0.5), # 使用dropout防止过拟合
nn.Linear(4096,4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096,10)
)
def forward(self,img):
feature=self.conv(img)
output=self.fc(feature.view(img.shape[0],-1))
return output


# In[6]:


net=AlexNet()
print(net) # 打印神经网络的结构


# In[ ]:


def load_data_fashion_mnist(batch_size, resize=None, root='~/Datasets/FashionMNIST'):
"""Download the fashion mnist dataset and then load into memory."""
trans = []
if resize:
trans.append(torchvision.transforms.Resize(size=resize)) # 更改PIL图像的大小
trans.append(torchvision.transforms.ToTensor()) # 将形状为(HxWxC)PIL的图像转为形状为(CxHxW)的FloatTensor

transform = torchvision.transforms.Compose(trans) # 一起组成一个变换
mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)

train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=4)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)

return train_iter, test_iter


# In[ ]:


def evaluate_accuracy(data_iter,net,device=None):
if device is None and isinstance(net, nn.Module):
device=list(net.parameters())[0].device # 运行设备为net所运行的设备
acc_sum,n=0.0,0
with torch.no_grad(): # 禁用梯度计算
for X,y in data_iter:
if isinstance(net, nn.Module):
net.eval() # 不启用 BatchNormalization 和 Dropout
acc_sum+=(net(X.to(device)).argmax(dim=1)==y.to(device)).float().sum().cpu().item()
net.train() # 启用 BatchNormalization 和 Dropout
else:
if('is training' in net.__code__.co_varnames):
acc_sum+=(net(X, is_training=False).argmax(dim=1)==y).float().sum().item()
else:
acc_sum+=(net(X).argmax(dim=1)==y).float().sum().item()
n+=y.shape[0]
return acc_sum/n


# In[ ]:


def train(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):
net = net.to(device)
print("training on ", device)
loss = torch.nn.CrossEntropyLoss()
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()
for X, y in train_iter:
X = X.to(device)
y = y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.backward() # 把参数通过反向传播来优化
optimizer.step() # 更新参数
train_l_sum += l.cpu().item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item() # softMax:y_hat.argmax(dim=1)
n += y.shape[0]
batch_count += 1
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))


# In[ ]:


batch_size=128
# 如出现“out of memory”的报错信息,可减小batch_size或resize
train_iter,test_iter=load_data_fashion_mnist(batch_size=batch_size,resize=224)


# In[14]:


lr,num_epochs=0.001,5
optimizer=optim.Adam(net.parameters(),lr=lr)
train(net,train_iter,test_iter,batch_size=batch_size,optimizer=optimizer,device=device,num_epochs=num_epochs)

 Comments


Blog content follows the Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) License

Use Material X as theme , total visits times .
载入天数...载入时分秒...