携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,papers.nips.cc/paper/2012/…
比赛结果不仅使人们开始挖掘CNN在计算机视觉上的优势,更重要的是重新引起了人们对神经网络的兴趣,引发了人工智能的第三次繁荣浪潮。
深度卷积神经网络的突破出现在2012年。突破可归因于两个关键因素:数据和硬件。
包含许多特征的深度模型需要大量的有标签数据,才能显著优于基于凸优化的传统方法(如线性方法和核方法)。 然而,限于早期计算机有限的存储和90年代有限的研究预算,大部分研究只基于小的公开数据集。例如,不少研究论文基于加州大学欧文分校(UCI)提供的若干个公开数据集,其中许多数据集只有几百至几千张在非自然环境下以低分辨率拍摄的图像。这一状况在2010年前后兴起的大数据浪潮中得到改善。2009年,ImageNet数据集发布,并发起ImageNet挑战赛:要求研究人员从100万个样本中训练模型,以区分1000个不同类别的对象。ImageNet数据集由斯坦福教授李飞飞小组的研究人员开发,利用谷歌图像搜索( Google Image Search ) 对每一类图像进行预筛选,并利用亚马逊众包( Amazon Mechanical Turk ) 来标注每张图片的相关类别。这种规模是前所未有的。这项被称为ImageNet的挑战赛推动了计算机视觉和机器学习研究的发展,挑战研究人员确定哪些模型能够在更大的数据规模下表现最好。
深度学习对计算资源要求很高,训练可能需要数百个迭代轮数,每次迭代都需要通过代价高昂的许多线性代数层传递数据。这也是为什么在20世纪90年代至21世纪初,优化凸目标的简单算法是研究人员的首选。然而,用GPU训练神经网络改变了这一格局。图形处理器(Graphics Processing Unit,GPU)是早年用来加速图形处理,这使电脑游戏玩家受益。GPU可优化高吞吐量的4×4矩阵和向量乘法,从而服务于基本的图形任务。幸运的是,这些数学运算与卷积层的计算惊人地相似。由此,英伟达(NVIDIA)和ATI已经开始为通用计算操作优化gpu,甚至把它们作为通用GPU(general-purpose GPUs,GPGPU)来销售。
那么GPU比CPU强在哪里呢?
中央处理器(Central Processing Unit,CPU)的每个核心都拥有高时钟频率的运行能力,和高达数MB的三级缓存(L3Cache)。 它们非常适合执行各种指令,具有分支预测器、深层流水线和其他使CPU能够运行各种程序的功能。 然而,这种明显的优势也是它的致命弱点:通用核心的制造成本非常高。 它们需要大量的芯片面积、复杂的支持结构(内存接口、内核之间的缓存逻辑、高速互连等等)。 现代笔记本电脑最多有4核,即使是高端服务器也很少超过64核,因为它们的性价比不高。
相比于CPU,GPU由100∼1000个小的处理单元组成(NVIDIA、ATI、ARM和其他芯片供应商之间的细节稍有不同),通常被分成更大的组(NVIDIA称之为warps)。 虽然每个GPU核心都相对较弱,有时甚至以低于1GHz的时钟频率运行,但庞大的核心数量使GPU比CPU快几个数量级。 例如,NVIDIA最近一代的Ampere GPU架构为每个芯片提供了高达312 TFlops的浮点性能,而CPU的浮点性能到目前为止还没有超过1 TFlops。 之所以有如此大的差距,原因其实很简单:首先,功耗往往会随时钟频率呈二次方增长。 对于一个CPU核心,假设它的运行速度比GPU快4倍,你可以使用16个GPU内核取代,那么GPU的综合性能就是CPU的16×1/4=4倍。 其次,GPU内核要简单得多,这使得它们更节能。 此外,深度学习中的许多操作需要相对较高的内存带宽,而GPU拥有10倍于CPU的带宽。
网络
作者在LeNet-5网络基础上,对神经网络的结构进行改进,加深了网络的层数,并使用ImageNet竞赛提供的120 万张带标签图像对神经网络进行训练,包含约6000万个参数。正是由于网络结构的改进和数据量的提升,使得CNN得到了令人满意的结果。(其实从网络的角度看,AlexNet的代码只比LeNet多出几行)
AlexNet由八层组成: AlexNet使用了8层卷积神经网络:五个卷积层、两个全连接隐藏层和一个全连接输出层。 其次,AlexNet使用ReLU而不是sigmoid作为其激活函数。原网络:
当Alex Krizhevsky和Ilya Sutskever实现了可以在GPU硬件上运行的深度卷积神经网络时,一个重大突破出现了。他们意识到卷积神经网络中的计算瓶颈:卷积和矩阵乘法,都是可以在硬件上并行化的操作。 于是,他们使用两个显存为3GB的NVIDIA GTX580 GPU实现了快速卷积运算。他们创新的cuda-convnet几年来它一直是行业标准,并推动了深度学习热潮。
当然,现在可以去除当年需要两个小型GPU同时运算的设计特点,精简版本的AlexNet:
AlexNet在训练时增加了大量的图像数据增强,如翻转、裁切和变色,进一步扩充数据。更大的样本量也有效地减少了过拟合。输入图像经过卷积操作和全连接层的操作,最后输入具有1 000个节点的Softmax 分类器完成图像分类(原论文不是sofwmax是高斯层)。该网络通过使用线性整流函数 ( rectifiled linear unit,ReLU) 作为激活函数。池化方式也调整为MaxPooling(over-lapping,重叠池化技术)。引入局部响应归一化 (local response normalization,LRN)缓解梯度消失问题(后来证明没什么大用)。通过丢弃法( Dropout ) 控制全连接层的模型复杂度(LeNet只使用了权重衰减),防止过拟合。
在AlexNet的第一层,卷积窗口的形状是11×11。 由于ImageNet中大多数图像的宽和高比MNIST图像的多10倍以上,因此,需要一个更大的卷积窗口来捕获目标。 第二层中的卷积窗口形状被缩减为5×5,然后是3×3。 此外,在第一层、第二层和第五层卷积层之后,加入窗口形状为3×3、步幅为2的最大汇聚层。 而且,AlexNet的卷积通道数目是LeNet的10倍。
在最后一个卷积层后有两个全连接层,分别有4096个输出。 这两个巨大的全连接层拥有将近1GB的模型参数。由于早期GPU显存有限,原版的AlexNet采用了双数据流设计,使得每个GPU只负责存储和计算模型的一半参数。幸运的是,现在GPU显存相对充裕,所以我们现在很少需要跨GPU分解模型。
详细变化如下:AlexNet首先用一张227×227×3的图片作为输入,实际上原文中使用的图像是224×224×3,但是如果尝试去推导一下,会发现227×227这个尺寸更好一些。第一层我们使用96个11×11的过滤器,步幅为4,由于步幅是4,因此尺寸缩小到55×55,缩小了4倍左右。然后用一个3×3的过滤器构建最大池化层,f=3,步幅s为2,卷积层尺寸缩小为27×27×96。接着再执行一个5×5的卷积,padding之后,输出是27×27×276。然后再次进行最大池化,尺寸缩小到13×13。再执行一次same卷积,相同的padding,得到的结果是13×13×384,384个过滤器。再做一次same卷积,就像这样。再做一次同样的操作,最后再进行一次最大池化,尺寸缩小到6×6×256。6×6×256等于9216,将其展开为9216个单元,然后是一些全连接层。最后使用softmax函数输出识别的结果,看它究竟是1000个可能的对象中的哪一个。.
在Alex网络的最底层,模型学习到了一些类似于传统滤波器的特征提取。AlexNet的更高层建立在这些底层表示的基础上,以表示更大的特征,如眼睛、鼻子、草叶等等。而更高的层可以检测整个物体,如人、飞机、狗或飞盘。最终的隐藏神经元可以学习图像的综合表示,从而使属于不同类别的数据易于区分。尽管一直有一群执着的研究者不断钻研,试图学习视觉数据的逐级表征,然而很长一段时间里这些尝试都未有突破。
早期的深度卷积神经网络,例如 AlexNet,其结构首先为卷积层,随后为全连接层,最后为 Softmax分类器,网络的参数量很大。在AlexNet之后,改进了的CNN结构不断刷新ImageNet图像分类的记录。如牛津大学的 VGG (Visual geometry group)模型和 Google 公司的 Inception系列模型,在增加 CNN 网络层数的同时设计了精巧丰富的卷积核结构,从而降低参数数量,提高训练速度。
参数
Q:在AlexNet中主要是哪部分占用显存?
A1:AlexNet里面不同层需要的参数大小决定了占用显存的大小
卷积层的参数量=k2 (窗口大小)×ci ×co
第一层卷积层卷积核参数个数:11x11x3x96=34848
汇聚层没有参数所以几乎不占任何显存
第二层卷积层卷积核参数个数:5x5x96x256=614400
第三层卷积层卷积核参数个数:3x3x256x384=884736
第四层卷积层卷积核参数个数:3x3x384x384=1327104
第五层卷积层卷积核参数个数:3x3x384x256=884736
全连接层的参数量= 输入(特征图)×神经元个数×通道数+ 偏置
第一层全连层参数(权重+偏移):6400×4096+4096=26218496
第二层全连层参数(权重+偏移):4096×4096+4096=16781312
第三层全连层参数(权重+偏移):4096×1000+1000=4100096
所以是第一层全连层占用了最多的显存
A2:通过代码查看
import torch
from torch import nn
device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”)
print(“use device:”, device)
print(f”torch MSE:{torch.cuda.memory_allocated()/(1024.01024):5.5}MiB {torch.cuda.memory_allocated()} B”)
X = torch.rand((1024,1024), dtype=torch.float32).to(device) #4M
y = torch.randint(0, 1024, (1024,1)).to(device)
print(f”初始化X,y→torch MSE:{torch.cuda.memory_allocated()/(1024.01024):5.5}MiB {torch.cuda.memory_allocated()} B”)
net = nn.Sequential(
nn.Linear(1024, 1024, bias=False), # 102410244 = 4M
nn.Linear(1024, 1024, bias=False),)
loss = nn.CrossEntropyLoss()
net.to(device)
print(f”载入网络后→torch MSE:{torch.cuda.memory_allocated()/(1024.01024):5.5}MiB {torch.cuda.memory_allocated()} B”)
X1 = X[:256]
y1 = y[:256].squeeze()
print(f”torch MSE:{torch.cuda.memory_allocated()/(1024.01024):5.5}MiB {torch.cuda.memory_allocated()}B”)
net.train()
print(f”训练结束后→torch MSE:{torch.cuda.memory_allocated()/(1024.01024):5.5}MiB {torch.cuda.memory_allocated()}B”)
y_hat = net(X1) # 运行增加占用是最终中间的Tensor占用 22561024sizeof(float32)
print(f”推理y_hat后→torch MSE:{torch.cuda.memory_allocated()/(1024.01024):5.5}MiB {torch.cuda.memory_allocated()}B”)
l = loss(y_hat, y1)
print(f”计算交叉熵损失后→torch MSE:{torch.cuda.memory_allocated()/(1024.01024):5.5}MiB {torch.cuda.memory_allocated()}B”)
l.backward()
print(f”反向传播后→torch MSE:{torch.cuda.memory_allocated()/(1024.0*1024):5.5}MiB {torch.cuda.memory_allocated()}B”)
model_name = ‘AlexNet’
flops, params = get_model_complexity_info(net, (1, 224, 224), as_strings=True,
print_per_layer_stat=True)
print(“%s |%s |%s” % (model_name, flops, params))
Q:在AlexNet中主要是哪部分需要更多的计算?
A:通过以下代码查看和比较各层的计算量和参数量
from ptflops import get_model_complexity_info
from torch import nn
net = nn.Sequential(
nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
……
nn.Linear(4096, 10))
model_name = ‘AlexNet’
flops, params = get_model_complexity_info(net, (1, 224, 224), as_strings=True,
print_per_layer_stat=True)
print(“%s |%s |%s” % (model_name, flops, params))
每个卷积层的计算复杂度应该是O( Nw Nh Kw Kh Ci Co)有六个参数
主要学习资料:
space.bilibili.com/1567748478/…
欢迎大家在评论区留言讨论,共同学习,一起进步