深度学习基础入门篇[8]::核算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数核算

1.核算机视觉与卷积神经网络

1.1核算机视觉综述

核算机视觉作为一门让机器学会怎么去“看”的学科,详细的说,便是让机器去辨认摄像机拍摄的图片或视频中的物体,检测出物体所在的方位,并对方针物体进行盯梢,然后理解并描绘出图片或视频里的场景和故事,以此来模拟人脑视觉体系。因而,核算机视觉也通常被叫做机器视觉,其目的是建立能够从图画或许视频中“感知”信息的人工体系。

核算机视觉技能经过几十年的开展,现已在交通(车牌辨认、路途违章抓拍)、安防(人脸闸机、小区监控)、金融(刷脸支付、货台的主动收据辨认)、医疗(医疗印象确诊)、工业生产(产品缺点主动检测)等多个范畴运用,影响或正在改变人们的日常日子和工业生产方法。未来,跟着技能的不断演进,必将涌现出更多的产品和运用,为咱们的日子发明更大的便利和更宽广的机会。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图1 核算机视觉技能在各范畴的运用

1.2 核算机视觉的开展进程

核算机视觉的开展进程要从生物视觉讲起。关于生物视觉的起源,现在学术界尚没有构成结论。有研究者以为最早的生物视觉构成于距今约7亿年前的水母之中,也有研究者以为生物视觉产生于距今约5亿年前寒武纪。寒武纪生物大迸发的原因一直是个未解之谜,不过能够肯定的是在寒武纪动物具有了视觉才能,捕食者能够更容易地发现猎物,被捕食者也能够更早的发现天敌的方位。视觉体系的构成有力地推动了食物链的演化,加快了生物进化进程,是生物开展史上重要的里程碑。经过几亿年的演化,现在人类的视觉体系现已具备十分高的杂乱度和强壮的功能,人脑中神经元数目达到了1000亿个,这些神经元经过网络相互衔接,这样巨大的视觉神经网络使得咱们能够很轻松的调查周围的世界,如 图2 所示。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图2 人类视觉感知

对人类来说,辨认猫和狗是件十分容易的事。但对核算机来说,即使是一个通晓编程的高手,也很难轻松写出具有通用性的程序(比如:假定程序以为体型大的是狗,体型小的是猫,但由于拍摄角度不同,可能一张图片上猫占有的像素比狗还多)。那么,怎么让核算机也能像人相同看懂周围的世界呢?研究者尝试着从不同的角度去解决这个问题,由此也开展出一系列的子使命,如 图3 所示。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图3 核算机视觉子使命暗示图

  • (a) Image Classification: 图画分类,用于辨认图画中物体的类别(如:bottle、cup、cube)。

  • (b) Object Localization: 方针检测,用于检测图画中每个物体的类别,并精确标出它们的方位。

  • (c) Semantic Segmentation: 语义切割,用于标出图画中每个像素点所属的类别,归于同一类其他像素点用一个颜色标识。

  • (d) Instance Segmentation: 实例切割,值得留意的是,方针检测使命只需求标示出物体方位,而实例切割使命不仅要标示出物体方位,还需求标示出物体的外形概括。

这儿以图画分类使命为例,为咱们介绍核算机视觉技能的开展进程。在早期的图画分类使命中,通常是先人工提取图画特征,再用机器学习算法对这些特征进行分类,分类的成果强依赖于特征提取办法,往往只要经验丰富的研究者才能完成,如 图4 所示。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图4 早期的图画分类使命

在这种背景下,基于神经网络的特征提取办法应运而生。Yann LeCun是最早将卷积神经网络运用到图画辨认范畴的,其首要逻辑是运用卷积神经网络提取图画特征,并对图画所属类别进行猜测,经过训练数据不断调整网络参数,终究构成一套能主动提取图画特征并对这些特征进行分类的网络,如 图5 所示。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图5 早期的卷积神经网络处理图画使命暗示

这一办法在手写数字辨认使命上取得了极大的成功,但在接下来的时间里,却没有得到很好的开展。其首要原因一方面是数据集不完善,只能处理简略使命,在大尺度的数据上容易发生过拟合;另一方面是硬件瓶颈网络模型杂乱时,核算速度会特别慢。

现在,跟着互联网技能的不断进步,数据量出现大规模的增加,越来越丰富的数据集不断涌现。另外,得益于硬件才能的提升,核算机的算力也越来越强壮。不断有研究者将新的模型和算法运用到核算机视觉范畴。由此催生了越来越丰富的模型结构和愈加精确的精度,一起核算机视觉所处理的问题也越来越丰富,包括分类、检测、切割、场景描绘、图画生成和风格改换等,乃至还不只是局限于2维图片,包括视频处理技能和3D视觉等。

1.3 卷积神经网络

卷积神经网络是现在核算机视觉中运用最普遍的模型结构。图6 是一个典型的卷积神经网络结构,多层卷积和池化层组合作用在输入图片上,在网络的终究通常会加入一系列全衔接层,ReLU激活函数一般加在卷积或许全衔接层的输出上,网络中通常还会加入Dropout来避免过拟合。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图6 卷积神经网络经典结构

  • 卷积层:卷积层用于对输入的图画进行特征提取。卷积的核算规模是在像素点的空间邻域内进行的,因而能够利用输入图画的空间信息。卷积核本身与输入图片巨细无关,它代表了对空间邻域内某种特征形式的提取。比如,有些卷积核提取物体边缘特征,有些卷积核提取物体角落处的特征,图画上不同区域同享同一个卷积核。当输入图片巨细不相同时,仍然能够运用同一个卷积核进行操作。

  • 池化层:池化层经过对卷积层输出的特征图进行约减,完成了下采样。一起对感触域内的特征进行筛选,提取区域内最具代表性的特征,保留特征图中最首要的信息。

  • 激活函数:激活函数给神经元引入了非线性要素,对输入信息进行非线性改换,然后使得神经网络能够恣意迫临任何非线性函数,然后将改换后的输出信息作为输入信息传给下一层神经元。

  • 全衔接层:全衔接层用于对卷积神经网络提取到的特征进行汇总,将多维的特征映射为二维的输出。其间,高维代表样本批次巨细,低维代表分类或回归成果。

2. 池化

2.1 基础概念(均匀池化和最大池化)

在图画处理中,由于图画中存在较多冗余信息,可用某一区域子块的核算信息(如最大值或均值等)来描写该区域中一切像素点出现的空间分布形式,以替代区域子块中一切像素点取值,这便是卷积神经网络中池化(pooling)操作。

池化操作对卷积成果特征图进行约减,完成了下采样,一起保留了特征图中首要信息。比如:当辨认一张图画是否是人脸时,咱们需求知道人脸左面有一只眼睛,右边也有一只眼睛,而不需求知道眼睛的精确方位,这时分经过池化某一片区域的像素点来得到总体核算特征会显得很有用。

池化的几种常见办法包括:均匀池化、最大池化、K-max池化。其间均匀池化和最大池化如 图1 所示,K-max池化如 图2 所示。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图1 均匀池化和最大池化

  • 均匀池化: 核算区域子块所包括一切像素点的均值,将均值作为均匀池化成果。如 图1(a),这儿运用巨细为222\times2的池化窗口,每次移动的步幅为2,对池化窗口掩盖区域内的像素取均匀值,得到相应的输出特征图的像素值。池化窗口的巨细也称为池化巨细,用khkwk_h \times k_w表明。在卷积神经网络中用的比较多的是窗口巨细为222 \times 2,步幅为2的池化。

  • 最大池化: 从输入特征图的某个区域子块中挑选值最大的像素点作为最大池化成果。如 图1(b),对池化窗口掩盖区域内的像素取最大值,得到输出特征图的像素值。当池化窗口在图片上滑动时,会得到整张输出特征图。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图2 K-max池化

  • K-max池化: 对输入特征图的区域子块中像素点取前K个最大值,常用于自然语言处理中的文本特征提取。如图2,从包括了4个取值的每一列中选取前2个最大值就得到了K最大池化成果。

2.2池化特点

  1. 当输入数据做出少量平移时,经过池化后的大多数输出还能坚持不变,因而,池化对细小的方位变化具有鲁棒性。例如 图3 中,输入矩阵向右平移一个像素值,运用最大池化后,成果与平移前仍旧能坚持不变。

深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算

图3 细小方位变化时的最大池化成果

  1. 由于池化之后特征图会变小,假设后边衔接的是全衔接层,能有效的减小神经元的个数,节约存储空间并进步核算效率

2.3 池化中填充的方法

在飞桨中,各种Pooling API中的Padding参数, 接纳值类型都包括int、list、tuple和string。下面用代码和公式来介绍一下这些方法吧。

2.3.1 int输入

int输入即接纳一个int类型的数字n,对图片的四周包裹n行n列的0来填充图片。假设要坚持图片尺度不变,n的值和池化窗口的巨细是有关的。假设 Hin,WinH_{in}, W_{in} 为图片输入的巨细,kh,kwk_h, k_w 为池化窗口的巨细,Hout,HoutH_{out}, H_{out} 为成果图的巨细的话,他们之间有着这样的关系。 $$H_{out} = \frac{H_{in} + 2p_h – k_h}{s_h} + 1 \ W_{out} = \frac{W_{out} + 2p_w -k_w}{s_w} + 1在运用33的池化窗口且步长为1的情况下,还要坚持图片巨细不变,则需求运用padding=1的填充。 那么,公式就变为了H_{out} = \frac{6 – 3 + 21}{1} + 1 \ W_{out} = \frac{6 – 3 + 21}{1} + 1另外,在stride不为1且不能被整除的情况下,整体成果向下取整。 关于Padding和K的公式如下Padding = \frac{(k-1)}{2} \quad \quad (k % 2 != 0)

关于上面的讲解,下面用飞桨的API来看看吧。

import paddle # No padding
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=3, stride=1, padding=0)
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 4, 4]

这是池化中不padding,stride为1的成果,能够依据公式填入,Hout=Wout=(6+0−3)+1=4H_{out} = W_{out} = (6 + 0 – 3) + 1 = 4,因而池化后的成果为4。假设填充呢?

import paddle # Padding 1
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=3, stride=1, padding=1)
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 6, 6]

正如咱们上面的公式,Hout=Wout=(6+2−3)+1=6H_{out} = W_{out} = (6 + 2 – 3) + 1 = 6, 填充为1的时分图画坚持为原巨细。

2.3.2 list和tuple输入

由于图画有宽和高,所以list和tuple的长度应该为2,里边的两个值别离对应了高和宽,核算方法和上面int输入的相同,独自核算。一般用作输入图片宽高不共同,或许池化窗口巨细不一的情况。咱们直接用飞桨的API来看看吧。

import paddle # No padding and different kernel size
x = paddle.rand((1, 1, 12, 6)) # 12为高H, 6为宽W
avgpool = paddle.nn.AvgPool2D(kernel_size=(3, 5), stride=1, padding=0)
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 12, 6] shape of result: [1, 1, 10, 2]

这儿咱们带入公式推理一下,Hout=12−3+1=10,Wout=6−5+1=2H_{out} = 12 – 3 + 1 = 10, W_{out} = 6 – 5 + 1 = 2.与成果相符。下面是有填充的情况,且3的滑动窗口巨细咱们需求填充1,5的话则需求填充2了。下面来看看吧。

import paddle # No padding and different kernel size
x = paddle.rand((1, 1, 12, 6)) # 12为高H, 6为宽W
avgpool = paddle.nn.AvgPool2D(kernel_size=(3, 5), stride=1, padding=(1, 2))
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 12, 6] shape of result: [1, 1, 12, 6]

这儿的成果与咱们的期望共同,咱们试着带入公式算一下吧。

2.3.3 string输入

string输入有两个值,一个是SAME,一个是VALID。这两个的核算公式如下:

SAME:Hout=⌈Hinsh⌉H_{out} = \lceil \frac{H_{in}}{s_h} \rceil, Wout=⌈Winsw⌉W_{out} = \lceil\frac{W_{in}}{s_w}\rceil

VALID:Hout=Hin−khsh+1H_{out} = \frac{H_{in} – k_h}{s_h} + 1, Wout=Win−kwsw+1W_{out} = \frac{W_{in} – k_w}{s_w} + 1

能够看到,VALID方法便是默认采用的不填充的方法,与上面不Padding的公式相同。而SAME则与池化窗口的巨细无关,若shs_hsws_w为1,不管池化窗口的巨细,输出的特征图的巨细都与原图坚持共同。当恣意一个大于1时,假设能整除,输出的尺度便是整除的成果,假设不能整除,则经过padding的方法继续向上取整。理论过于难懂,咱们直接用飞桨的API来看看吧。

import paddle # Padding SAME kernel_size 2
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=2, padding='SAME')
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 3, 3]

代码的成果出来了,咱们来直接带入公式来核算吧,Hout=6/2=3,Wout=6/2=3H_{out} = 6/2 = 3, W_{out} = 6/2 = 3,成果共同。

import paddle # Padding SAME kernel_size 1
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=1, padding='SAME')
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 6, 6]

这个呢,就和咱们上面说的共同。下面来看看VALID填充方法吧。

import paddle # Padding VALID
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=2, padding='VALID')
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 3, 3]

这便是VALID的填充方法的成果啦。咱们自己按照公式算算,看看你的答案和程序输出的对不对哦。

2.4 运用示例

与卷积核相似,池化窗口在图片上滑动时,每次移动的步长称为步幅,当宽和高方向的移动巨细不相同时,别离用sws_wshs_h表明。也能够对需求进行池化的图片进行填充,填充方法与卷积相似,假定在榜首行之前填充ph1p_{h1}行,在终究一行后边填充ph2p_{h2}行。在榜首列之前填充pw1p_{w1}列,在终究一列之后填充pw2p_{w2}列,则池化层的输出特征图巨细为:

Hout=H+ph1+ph2−khsh+1H_{out} = \frac{H + p_{h1} + p_{h2} – k_h}{s_h} + 1

Wout=W+pw1+pw2−kwsw+1W_{out} = \frac{W + p_{w1} + p_{w2} – k_w}{s_w} + 1

在卷积神经网络中,通常运用222\times2巨细的池化窗口,步幅也运用2,填充为0,则输出特征图的尺度为:

Hout=H2H_{out} = \frac{H}{2}

Wout=W2W_{out} = \frac{W}{2}

经过这种方法的池化,输出特征图的高和宽都折半,但通道数不会改变。

这儿以 图1 中的2个池化运算为例,此刻,输入巨细是444 \times 4 ,运用巨细为222 \times 2 的池化窗口进行运算,步幅为2。此刻,输出尺度的核算方法为:

Hout=H+ph1+ph2−khsh+1=4+0+0−22+1=42=2H_{out} = \frac{H + p_{h1} + p_{h2} – k_h}{s_h} + 1=\frac{4 + 0 + 0 – 2}{2} + 1=\frac{4}{2}=2

Wout=W+pw1+pw2−kwsw+1=4+0+0−22+1=42=2W_{out} = \frac{W + p_{w1} + p_{w2} – k_w}{s_w} + 1=\frac{4 + 0 + 0 – 2}{2} + 1=\frac{4}{2}=2

图1(a) 中,运用均匀池化进行运算,则输出中的每一个像素均为池化窗口对应的 222 \times 2 区域求均值得到。核算进程如下:

  1. 池化窗口的初始方位为左上角,对应粉色区域,此刻输出为 3.5=1+2+5+643.5 = \frac{1 + 2 + 5 + 6}{4}

  2. 由于步幅为2,所以池化窗口向右移动两个像素,对应蓝色区域,此刻输出为 5.5=3+4+7+845.5 = \frac{3 + 4 + 7 + 8}{4}

  3. 遍历完榜首行后,再从第三行开始遍历,对应绿色区域,此刻输出为 11.5=9+10+13+14411.5 = \frac{9 + 10 + 13 + 14}{4}

  4. 池化窗口向右移动两个像素,对应黄色区域,此刻输出为 13.5=11+12+15+16413.5 = \frac{11 + 12 + 15 + 16}{4}

图1(b) 中,运用最大池化进行运算,将上述进程的求均值改为求最大值即为终究成果。

3.CNN中模型的参数量与FLOPs核算

一个卷积神经网络的基本构成一般有卷积层、归一化层、激活层和线性层。这儿咱们就经过逐渐核算这些层来核算一个CNN模型所需求的参数量和FLOPs吧. 另外,FLOPs的全程为floating point operations的缩写(小写s表复数),意指浮点运算数,理解为核算量。能够用来衡量算法/模型的杂乱度。

3.1 卷积层

卷积层,最常用的是2D卷积,因而咱们以飞桨中的Conv2D来表明。

3.1.1卷积层参数量核算

Conv2D的参数量核算较为简略,先看下列的代码,假设界说一个Conv2D,卷积层中的参数会随机初始化,假设打印其shape,就能够知道一个Conv2D里大致包括的参数量了,Conv2D的参数包括两部分,一个是用于卷积的weight,一个是用于调理网络拟合输入特征的bias。如下

import paddle
import numpy as np
cv2d   = paddle.nn.Conv2D(in_channels=2, out_channels=4, kernel_size=(3, 3), stride=1, padding=(1, 1))
params = cv2d.parameters()
print("shape of weight: ", np.array(params[0]).shape)
print("shape of bias: ", np.array(params[1]).shape)
shape of weight:  (4, 2, 3, 3)
shape of bias:  (4,)

这儿解释一下上面的代码,咱们先界说了一个卷积层cv2d,然后输出了这个卷积层的参数的形状,参数包括两部分,别离是weight和bias,这两部分相加才是整个卷积的参数量。因而,能够看到,咱们界说的cv2d的参数量为:4∗2∗3∗3+4=764*2*3*3+4 = 76, 4对应的是输出的通道数,2对应的是输入的通道数,两个3是卷积核的尺度,终究的4便是bias的数量了, 值得留意的是, bias是数量与输出的通道数坚持共同。因而,咱们能够得出,一个卷积层的参数量的公式,如下: Param_{conv2d} = C_{in} * C_{out} * K_h * K_w + C_{out}其间,CinC_{in} 表明输入的通道数,CoutC_{out} 表明输出的通道数,Kh K_h,KwK_w表明卷积核的巨细。当然了,有些卷积会将bias设置为False,那么咱们不加终究的CoutC_{out}即可。

3.1.2 卷积层FLOPs核算

参数量会核算了,那么FLOPs其实也是很简略的,就一个公式: $$FLOP_{conv2d} = Param_{conv2d} * M_{outh} * M_{outw}这儿,MouthM_{outh}MoutwM_{outw} 为输出的特征图的高和宽,而不是输入的,这儿需求留意一下。

3.1.3 卷积层参数核算示例

Paddle有提供核算FLOPs和参数量的API,paddle.flops, 这儿咱们用咱们的办法和这个API的办法来测一下,看看一不共同吧。代码如下:

import paddle
from paddle import nn
from paddle.nn import functional as F
class TestNet(nn.Layer):
    def __init__(self):
        super(TestNet, self).__init__()
        self.conv2d = nn.Conv2D(in_channels=2, out_channels=4, kernel_size=(3, 3), stride=1, padding=1)
    def forward(self, x):
        x = self.conv2d(x)
        return x
if __name__ == "__main__":
    net = TestNet()
    paddle.flops(net, input_size=[1, 2, 320, 320])
Total GFlops: 0.00778     Total Params: 76.00

API得出的参数量为76,GFLOPs为0.00778,这儿的GFLOPs便是FLOPs的109^9倍,咱们的参数量求得的也是76,那么FLOPs呢?咱们来算一下,输入的尺度为320 * 320, 卷积核为3 * 3, 且padding为1,那么图片输入的巨细和输出的巨细共同,即输出也是320 * 320, 那么依据咱们的公式可得: 76∗320∗320=778240076 * 320 * 320 = 7782400, 与API的共同!因而咱们核算卷积层的参数和FLOPs的时分就能够用上面的公式。

3.2 归一化层

最常用的归一化层为BatchNorm2D啦,咱们这儿就用BatchNorm2D来做比如介绍。在算参数量和FLOPs,先看看BatchNorm的算法流程吧!

输入为:Values of xx over a mini-batch:B=x1,…,mB={x_1,…,m},

\quad\quad\quadParams to be learned: \beta, \gamma

输出为:{yiy_i=BN_{\gamma},(xi)\beta(x_i)}

流程如下:

\quad\quad\quad$$\mu_B\gets 1m∑1mxi\frac{1}{m}\sum_{1}^mx_i

B2←1m∑1m(xi−B)2\quad\quad\quad\sigma_{B}^2\gets\frac{1}{m}\sum_{1}^m(x_i-\mu_B)^2

xi←xi−BB2+\quad\quad\quad\hat{x}_i\gets\frac{x_i-\mu_B}{\sqrt{\sigma_{B}^2+\epsilon}}

yi←xi+≡BN\quad\quad\quad y_i\gets\gamma\hat{x}_i+\beta\equiv BN_{\gamma},(xi)\beta(x_i)

在这个公式中,BB 为一个Batch的数据,\beta\gamma 为可学习的参数,\mu2\sigma^2 为均值和方差,由输入的数据的值求得。该算法先求出整体数据集的均值和方差,然后依据第三行的更新公式求出新的x,终究依据可学习的\beta\gamma调整数据。第三行中的 \epsilon 在飞桨中默以为 1e-5, 用于处理除法中的极点情况。

3.2.1 归一化层参数量核算

由于归一化层较为简略,这儿直接写出公式: $$Param_{bn2d} = 4 * C_{out} 其间4表明四个参数值,每个特征图对应一组四个元素的参数组合;

beta_initializer \beta 权重的初始值设定项。

gamma_initializer \gamma 伽马权重的初始值设定项。

moving_mean_initializer \mu 移动均值的初始值设定项。

moving_variance_initializer 2\sigma^2 移动方差的初始值设定项。

3.2.2 归一化层FLOPs核算

由于只要两个能够学习的权重,\beta\gamma,所以FLOPs只需求2乘以输出通道数和输入的尺度即可。 归一化的FLOPs核算公式则为: FLOP_{bn2d} = 2 * C_{out} * M_{outh} * M_{outw} 与1.3相似,欢迎咱们运用上面的代码进行验证。

3.3 线性层


线性层也是常用的分类层了,咱们以飞桨的Linear为例来介绍。

3.3.1线性层参数量核算

其实线性层是比较简略的,它便是相当于卷积核为1的卷积层,线性层的每一个参数与对应的数据进行矩阵相乘,再加上偏置项bias,线性层没有相似于卷积层的“卷”的操作的,所以核算公式如下: $$Param_{linear} = C_{in} * C_{out} + C_{out}。咱们这儿打印一下线性层参数的形状看看。

import paddle
import numpy as np
linear = paddle.nn.Linear(in_features=2, out_features=4)
params = linear.parameters()
print("shape of weight: ", np.array(params[0]).shape)
print("shape of bias: ", np.array(params[1]).shape)
shape of weight:  (2, 4)
shape of bias:  (4,)

能够看到,线性层相较于卷积层仍是简略的,这儿咱们直接核算这个界说的线性层的参数量为 2∗4+4=122 * 4 + 4 = 12。详细对不对,咱们在下面的实例演示中检查。

3.3.2 线性层FLOPs核算

与卷积层不同的是,线性层没有”卷“的进程,所以线性层的FLOPs核算公式为: FLOP_{linear} = C_{in} * C_{out}$$

3.4 实例演示


这儿咱们就以LeNet为比如,核算出LeNet的一切参数量和核算量。LeNet的结构如下。输入的图片巨细为28 * 28

LeNet(
  (features): Sequential(
    (0): Conv2D(1, 6, kernel_size=[3, 3], padding=1, data_format=NCHW)
    (1): ReLU()
    (2): MaxPool2D(kernel_size=2, stride=2, padding=0)
    (3): Conv2D(6, 16, kernel_size=[5, 5], data_format=NCHW)
    (4): ReLU()
    (5): MaxPool2D(kernel_size=2, stride=2, padding=0)
  )
  (fc): Sequential(
    (0): Linear(in_features=400, out_features=120, dtype=float32)
    (1): Linear(in_features=120, out_features=84, dtype=float32)
    (2): Linear(in_features=84, out_features=10, dtype=float32)
  )
)
咱们先来手动算一下参数量和FLOPs。

features[0] 参数量: 6∗1∗3∗3+6=606 * 1 * 3 * 3 + 6 = 60, FLOPs : 60∗28∗28=4704060 * 28 * 28 = 47040

features[1] 参数量和FLOPs均为0

features[2] 参数量和FLOPs均为0, 输出尺度变为14 * 14

features[3] 参数量: 16∗6∗5∗5+16=241616 * 6 * 5 * 5 + 16 = 2416, FLOPs : 2416∗10∗10=2416002416 * 10 * 10 = 241600, 需求留意的是,这个卷积没有padding,所以输出特征图巨细变为 10 * 10

features[4] 参数量和FLOPs均为0

features[5] 参数量和FLOPs均为0,输出尺度变为5 * 5, 然后整个被拉伸为[1, 400]的尺度,其间400为5 * 5 * 16。

fc[0] 参数量: 400∗120+120=48120400 * 120 + 120 = 48120, FLOPs : 400∗120=48000400 * 120 = 48000 (输出尺度变为[1, 120])

fc[1] 参数量: 120∗84+84=10164120 * 84 + 84 = 10164, FLOPs : 120∗84=10080120 * 84 = 10080 (输出尺度变为[1, 84])

fc[2] 参数量: 84∗10+10=85084 * 10 + 10 = 850, FLOPs : 84∗10=84084 * 10 = 840 (输出尺度变为[1, 10])。

总参数量为: 60+2416+48120+10164+850=6161060 + 2416 + 48120 + 10164 + 850 = 61610

总FLOPs为:47040+241600+48000+10080+840=34756047040 + 241600 + 48000 + 10080 + 840 = 347560

下面咱们用代码验证以下:

from paddle.vision.models import LeNet
net = LeNet()
print(net)
paddle.flops(net, input_size=[1, 1, 28, 28], print_detail=True)
+--------------+-----------------+-----------------+--------+--------+
|  Layer Name  |   Input Shape   |   Output Shape  | Params | Flops  |
+--------------+-----------------+-----------------+--------+--------+
|   conv2d_0   |  [1, 1, 28, 28] |  [1, 6, 28, 28] |   60   | 47040  |
|   re_lu_0    |  [1, 6, 28, 28] |  [1, 6, 28, 28] |   0    |   0    |
| max_pool2d_0 |  [1, 6, 28, 28] |  [1, 6, 14, 14] |   0    |   0    |
|   conv2d_1   |  [1, 6, 14, 14] | [1, 16, 10, 10] |  2416  | 241600 |
|   re_lu_1    | [1, 16, 10, 10] | [1, 16, 10, 10] |   0    |   0    |
| max_pool2d_1 | [1, 16, 10, 10] |  [1, 16, 5, 5]  |   0    |   0    |
|   linear_0   |     [1, 400]    |     [1, 120]    | 48120  | 48000  |
|   linear_1   |     [1, 120]    |     [1, 84]     | 10164  | 10080  |
|   linear_2   |     [1, 84]     |     [1, 10]     |  850   |  840   |
+--------------+-----------------+-----------------+--------+--------+
Total GFlops: 0.00034756     Total Params: 61610.00

能够看到,与咱们的核算是共同的,咱们能够自己把VGG-16的模型算一下参数量FLOPs,相较于LeNet, VGG-16只是模型深了点,并没有其余额定的结构。