【文献阅读及pytorch实践】AlexNet: ImageNet Classification with Deep Convolutional Neural Networks

这篇文章在读的时候并没有遇到特别多的障碍,当然我很清楚这并不是代表我厉害了一点点…单纯的是这篇文章过于经典,可以说他的出现大大激发了人们研究神经网络的热情。文章中采用架构在现在来看并不复杂的卷积神经网络,并且提出了ReLU和dropout等非常经典有效的方法,这些方法是很basic的方法,但在论文中看到这些眼熟的方法还是非常兴奋的,对Alex及他老师Hinton的崇拜之情又加深了好几分。

第一部分.文献阅读

首先来说一个基础概念,TOP-1和TOP5错误率。
TOP-1错误率就是神经网络给出的最有可能的答案与真正的答案并不一样的错误率;
TOP-5错误率就是神经网络给出的前五个最有可能的答案与真正的答案不一样的错误率。
也就是说TOP-1就是我允许你给一个答案,对了才算对;
TOP-5就是我允许你给五个答案,其中有对的就算对。

1.数据集

ILSVRC
将图片处理为256 * 256,没有进行任何其他的预处理。
大小的处理方法如下:一张矩形图像,等比例缩放到短边长度为256,从图像的中间区域截取256 * 256 的部分。

2.ReLU

ReLU的全称为Rectified Linear Units(修正线性单元)。
更早一些的激活函数是sigmoid和tanh,我们通过观察sigmoid和tanh的函数图可以发现,他们都存在近乎饱和的区域,
在这里插入图片描述
在这里插入图片描述
他们的梯度在远离0的时候都趋于0,而我们知道神经网络在更新weights的时候是要减去梯度 * 学习率的,如果梯度趋于0,也就是说weighs更新的速度会非常非常慢,不利于训练。
但是我们看ReLU的公式和函数图可以发现,
在这里插入图片描述
在这里插入图片描述
至少在ReLU的正半轴,是不存在饱和区域的。而且ReLU的求导要比sigmoid和tanh简单很多。
作者也通过做实验验证了在梯度下降的训练时间方面,饱和非线性比非饱和非线性要慢很多。
在这里插入图片描述
使用ReLUs(实线)的四层卷积神经网络在CIFAR-10上达到25%的训练错误率,比使用tanh神经元的等效网络(虚线)快六倍。每个网络的学习率是独立选择的,以使训练尽可能快。没有采用任何形式的正规化。这里所演示的效果的大小随着网络架构的不同而不同,但是使用ReLUs的网络始终比使用饱和神经元的网络学习速度快几倍。

3.在多个GPU上进行训练

当时的硬件条件跟现在真的没法比,作者那会使用的是GTX 580,并且显存只有3G。
现在的话条件要好很多,TITAN V CEO Edition和GeForce RTX 2080 Ti都很能打,就连我的快淘汰的笔记本都用的GTX 970M,硬件条件着实好了太多太多。
我们从后面的网络架构可以看到,作者将神经网络分成了两部分,在两块GPU上并行计算,加快了速度。

4.局部响应归一化

这个我在看的时候就一脸懵,感觉看公式看起来很费劲。
在这里插入图片描述
他这个定义的归一化实际上就是在厚度上进行归一化。比如我这一层用了10个卷积核来提取特征,我想求第5个卷积核(i=5)在(x,y)的局部响应归一化结果,假设n=4,我就要考虑3至7的卷积核在(x,y)的值,可以理解为用卷积核5的值除以卷积核3-7的总和。只不过他乘了一堆乱七八糟的系数,但实际上原理就是这么简单。
作者把实验中获得的最优系数也写在了文章里,取k=2,n=5,α=10e-4,β=0.75。
但是吴恩达老师在深度学习的课里提到这个方法在后人的测试中发现根本没有啥大作用,所以现在也就没啥人用了。

5.重叠池化

我们非常非常常用的池化是取kernel为2 * 2,stride为2,相当于没有重叠部分。但作者提到经他们实验,采用重叠池化可以轻微的减少过拟合。
AlexNet中采用的参数是stride是2,kernel是3 * 3。

6.数据扩张

在大型网络中,由于参数量过大,所以很容易出现过拟合的现象,故采用数据扩张是非常有必要的。
而且为了加快训练速度,在CPU进行数据的处理,在GPU上训练,各司其职。
在256 * 256的图上随机截取一些227 * 227的图,并且多生成一份他们垂直翻折的版本。而在测试集上,在256 * 256的图上,取四个角以及正中间部位的224 * 224以及他们的垂直翻折版本作为输入,取他们10个的平均值作为最后的结果。
还可以通过某些公式来改变RGB三通道的值来进行数据的扩充,对RGB通道的值进行PCA降维。
在这里插入图片描述
其中,p是特征向量,λ是特征值,α是一个随机数(满足均值为0、方差为1的高斯分布)。

7.随机失活

大规模的神经网络有两个缺点:费时和容易过拟合。
每次做完dropout,相当于从原始的网络中找到一个更瘦的网络:
在这里插入图片描述
因而,对于一个有N个节点的神经网络,有了dropout后,就可以看做是2n个模型的集合了,但此时要训练的参数数目却是不变的,这就解决了费时的问题。
每个神经元有0.5的概率“失活”,也就是不参与此轮的前向传播与反向传播。这种技术减少了神经元之间复杂的相互适应,因为神经元不能依赖于其他神经元的存在。因此,它被迫学习与其他神经元的许多不同随机子集结合使用的更健壮的特征。

总结一下

在这里插入图片描述

小细节都说完了,接下来就是整体网络框架了。

在这里插入图片描述
后面的复现用的将两个分支融合成一个的网络,但是结构是差不多的。
在这里插入图片描述
看图还是挺清楚的,所以直接上代码了!

第二部分.Pytorch实践

首先导入需要用到的库
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2,3"
准备所需的数据集(ImageNet这个数据集过于庞大…建议大家以其他方式搞到这个数据集,我看网上有人说邮寄硬盘给拷的笑死我了233),并进行所需的变换。

其中训练集先等比例缩放,以短边为基准到256;再取中间区域方块256 * 256;接下来随机在图像中采集227 * 227的图像,并随即进行翻折;最后一定不要忘记转成Tensor。
测试集我直接取的验证集,跳过了交叉验证这一步,同理等比例缩放,以短边为基准到256;再取中间区域方块256 * 256;用TenCrop取四角及中间并且多一份镜像翻折;最后转成Tensor。

train_dataset = dsets.ImageNet(root = './ImageNet',
                               split = 'train',
                               transform = transforms.Compose([
                                       transforms.Resize(256),
                                       transforms.CenterCrop(256),
                                       transforms.RandomCrop(227),
                                       transforms.RandomVerticalFlip(p=0.5),
                                       transforms.ToTensor(),
                                       ]),
                               download = True)
test_dataset = dsets.ImageNet(root = './ImageNet',
                              split = 'val',
                              transform = transforms.Compost([
                                      transforms.Resize(256),
                                      transforms.CenterCrop(256),
                                      transforms.TenCrop(227),
                                      transforms.ToTensor(),
                                      ]),
                              download = True)
定义批训练loader

训练时batch size原作者用的128,测试时由于一张图变为10张,所以我的batch size选择10,也就是每次只出一张图的结果。

BATCH_SIZE = 128
train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = BATCH_SIZE,
                                           shuffle = True)
test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = 10,
                                          shuffle = False)
定义网络,具体参数见注释:
第一层(卷积层):卷积→ReLU→池化→局部响应归一化
第二层(卷积层):卷积→ReLU→池化→局部响应归一化
第三层(卷积层):卷积→ReLU
第四层(卷积层):卷积→ReLU
第五层(卷积层):卷积→ReLU→池化
第六层(全连接层):全连接→ReLU→Dropout
第七层(全连接层):全连接→ReLU→Dropout
第八层(全连接层):全连接

第八层我并没有做softmax,因为CrossEntropyLoss函数是log_softmax和nll_loss的结合。

class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 96, 11, stride=4)
        self.conv2 = nn.Conv2d(96, 256, 5, padding=2)
        self.conv3 = nn.Conv2d(256, 384, 3, padding=1)
        self.conv4 = nn.Conv2d(384, 384, 3, padding=1)
        self.conv5 = nn.Conv2d(384, 256, 3, padding=1)
        self.fc1 = nn.Linear(9216, 4096)
        self.fc2 = nn.Linear(4096, 4096)
        self.fc3 = nn.Linear(4096, 1000)
        
    def forward(self, x):
        # 第一层:卷积层
        x = self.conv1(x) # 227 * 227 * 3 -> 55 * 55 * 96
        x = F.relu(x)
        x = F.max_pool2d(x, 3, stride=2) # 55 * 55 * 96 -> 27 * 27 * 96
        x = F.local_response_norm(x, 5, alpha=10e-4, beta=0.75, k=2)
        # 第二层:卷积层
        x = self.conv2(x) # 27 * 27 * 96 -> 27 * 27 * 256
        x = F.relu(x)
        x = F.max_pool2d(x, 3, stride=2) # 27 * 27 * 256 -> 13 * 13 * 256
        x = F.local_response_norm(x, 5, alpha=10e-4, beta=0.75, k=2)
        # 第三层:卷积层
        x = self.conv3(x) # 13 * 13 * 256 -> 13 * 13 * 384
        x = F.relu(x)
        # 第四层:卷积层
        x = self.conv4(x) # 13 * 13 * 384 -> 13 * 13 * 384
        x = F.relu(x)
        # 第五层:卷积层
        x = self.conv5(x) # 13 * 13 * 384 -> 13 * 13 * 256
        x = F.relu(x)
        x = F.max_pool2d(x, 3, stride=2) # 13 * 13 * 256 -> 6 * 6 * 256
        x = x.view(x.shape[0], -1)
        # 第六层:全连接层
        x = self.fc1(x)
        x = F.relu(x)
        x = F.dropout(x, p=0.5)
        # 第七层:全连接层
        x = self.fc2(x)
        x = F.relu(x)
        x = F.dropout(x, p=0.5)
        # 第八层:全连接层
        x = self.fc3(x)
        
        return x
开始训练

这里有点细节:使用随机梯度下降来训练模型,动量系数为 0.9,权值衰减系数为 0.0005;对于每一层,使用相同的学习速率,在训练过程中手动调节:每次当验证误差不再下降时,学习速率减小为原来的十分之一。学习速率初始化为 0.01。

if __name__ == '__main__':
    model = AlexNet()

    LEARNING_RATE = 0.01
    learning_rate = LEARNING_RATE
    EPOCH_NUM = 90
    loss_fn = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=0.005)
    loss_num = None

    for epoch in range(EPOCH_NUM):
        for idx, (data, target) in enumerate(train_loader):
            pred = model(data)
            loss = loss_fn(pred, target)
            
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
        print("epoch: " + str(epoch) + "loss: " + str(loss.item()))
        
        if (loss_num != None):
            if (loss.item() > loss_num):
                learning_rate /= 10
                optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=0.005)
        loss_num = loss.item()   
最后统计测试结果

model.eval()是指网络不加BN和Dropout的,专用于测试;并且测试时不需要再计算梯度,所以要with torch.no_grad()。

model.eval()
correct = 0.
with torch.no_grad():
    for idx, (data, target) in enumerate(test_loader):
        output = model(data)
        result = torch.mean(output, dim=0)
        pred = torch.argmax(result)
        target = torch.mean(target)
        correct += pred.eq(target.view_as(pred)).sum().item()
acc = correct / (len(test_loader.dataset) / 10) * 100.
print("accuracy: " + str(acc))
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值