共计 12838 个字符,预计需要花费 33 分钟才能阅读完成。
深度学习根底入门篇[8]::计算机视觉与卷积神经网络、卷积模型 CNN 综述、池化解说、CNN 参数计算
1. 计算机视觉与卷积神经网络
1.1 计算机视觉综述
计算机视觉作为一门让机器学会如何去“看”的学科,具体的说,就是让机器去辨认摄像机拍摄的图片或视频中的物体,检测出物体所在的地位,并对指标物体进行跟踪,从而了解并形容出图片或视频里的场景和故事,以此来模仿人脑视觉零碎。因而,计算机视觉也通常被叫做机器视觉,其目标是建设可能从图像或者视频中“感知”信息的人工零碎。
计算机视觉技术通过几十年的倒退,曾经在交通(车牌辨认、路线违章抓拍)、安防(人脸闸机、小区监控)、金融(刷脸领取、柜台的主动票据辨认)、医疗(医疗影像诊断)、工业生产(产品缺点自动检测)等多个畛域利用,影响或正在扭转人们的日常生活和工业生产方式。将来,随着技术的一直演进,必将涌现出更多的产品和利用,为咱们的生存发明更大的便当和更广大的机会。
图 1 计算机视觉技术在各畛域的利用
1.2 计算机视觉的倒退历程
计算机视觉的倒退历程要从生物视觉讲起。对于生物视觉的起源,目前学术界尚没有造成定论。有研究者认为最早的生物视觉造成于距今约 7 亿年前的水母之中,也有研究者认为生物视觉产生于距今约 5 亿年前寒武纪。寒武纪生物大暴发的起因始终是个未解之谜,不过能够必定的是在寒武纪动物具备了视觉能力,捕食者能够更容易地发现猎物,被捕食者也能够更早的发现天敌的地位。视觉零碎的造成无力地推动了食物链的演变,减速了生物进化过程,是生物发展史上重要的里程碑。通过几亿年的演变,目前人类的视觉零碎曾经具备十分高的复杂度和弱小的性能,人脑中神经元数目达到了 1000 亿个,这些神经元通过网络相互连贯,这样宏大的视觉神经网络使得咱们能够很轻松的察看四周的世界,如 图 2 所示。
图 2 人类视觉感知
对人类来说,辨认猫和狗是件非常容易的事。但对计算机来说,即便是一个精通编程的高手,也很难轻松写出具备通用性的程序(比方:假如程序认为体型大的是狗,体型小的是猫,但因为拍摄角度不同,可能一张图片上猫占据的像素比狗还多)。那么,如何让计算机也能像人一样看懂四周的世界呢?研究者尝试着从不同的角度去解决这个问题,由此也倒退出一系列的子工作,如 图 3 所示。
图 3 计算机视觉子工作示意图
- (a) Image Classification: 图像分类,用于辨认图像中物体的类别(如:bottle、cup、cube)。
- (b) Object Localization: 指标检测,用于检测图像中每个物体的类别,并精确标出它们的地位。
- (c) Semantic Segmentation: 语义宰割,用于标出图像中每个像素点所属的类别,属于同一类别的像素点用一个色彩标识。
- (d) Instance Segmentation: 实例宰割,值得注意的是,指标检测工作只须要标注出物体地位,而实例宰割工作不仅要标注出物体地位,还须要标注出物体的形状轮廓。
这里以图像分类工作为例,为大家介绍计算机视觉技术的倒退历程。在晚期的图像分类工作中,通常是先人工提取图像特色,再用机器学习算法对这些特色进行分类,分类的后果强依赖于特征提取办法,往往只有经验丰富的研究者能力实现,如 图 4 所示。
图 4 晚期的图像分类工作
在这种背景下,基于神经网络的特征提取办法应运而生。Yann LeCun 是最早将卷积神经网络应用到图像识别畛域的,其次要逻辑是应用卷积神经网络提取图像特色,并对图像所属类别进行预测,通过训练数据一直调整网络参数,最终造成一套能主动提取图像特色并对这些特色进行分类的网络,如 图 5 所示。
图 5 晚期的卷积神经网络解决图像工作示意
这一办法在手写数字辨认工作上获得了极大的胜利,但在接下来的工夫里,却没有失去很好的倒退。其次要起因一方面是 数据集不欠缺 ,只能解决简略工作,在大尺寸的数据上容易产生过拟合;另一方面是 硬件瓶颈,网络模型简单时,计算速度会特地慢。
目前,随着互联网技术的不断进步,数据量出现大规模的增长,越来越丰盛的数据集不断涌现。另外,得益于硬件能力的晋升,计算机的算力也越来越弱小。一直有研究者将新的模型和算法利用到计算机视觉畛域。由此催生了越来越丰盛的模型构造和更加精确的精度,同时计算机视觉所解决的问题也越来越丰盛,包含分类、检测、宰割、场景形容、图像生成和格调变换等,甚至还不仅仅局限于 2 维图片,包含视频解决技术和 3D 视觉等。
1.3 卷积神经网络
卷积神经网络是目前计算机视觉中应用最广泛的模型构造。图 6 是一个典型的卷积神经网络构造,多层卷积和池化层组合作用在输出图片上,在网络的最初通常会退出一系列全连贯层,ReLU 激活函数个别加在卷积或者全连贯层的输入上,网络中通常还会退出 Dropout 来避免过拟合。
图 6 卷积神经网络经典构造
- 卷积层:卷积层用于对输出的图像进行特征提取。卷积的计算范畴是在像素点的空间邻域内进行的,因而能够利用输出图像的空间信息。卷积核自身与输出图片大小无关,它代表了对空间邻域内某种特色模式的提取。比方,有些卷积核提取物体边缘特色,有些卷积核提取物体拐角处的特色,图像上不同区域共享同一个卷积核。当输出图片大小不一样时,依然能够应用同一个卷积核进行操作。
- 池化层:池化层通过对卷积层输入的特色图进行约减,实现了下采样。同时对感触域内的特色进行筛选,提取区域内最具代表性的特色,保留特色图中最次要的信息。
- 激活函数:激活函数给神经元引入了非线性因素,对输出信息进行非线性变换,从而使得神经网络能够任意迫近任何非线性函数,而后将变换后的输入信息作为输出信息传给下一层神经元。
- 全连贯层:全连贯层用于对卷积神经网络提取到的特色进行汇总,将多维的特色映射为二维的输入。其中,高维代表样本批次大小,低维代表分类或回归后果。
2. 池化
2.1 根底概念(均匀池化和最大池化)
在图像处理中,因为图像中存在较多冗余信息,可用某一区域子块的统计信息(如最大值或均值等)来刻画该区域中所有像素点出现的空间分布模式,以代替区域子块中所有像素点取值,这就是卷积神经网络中池化 (pooling) 操作。
池化操作对卷积后果特色图进行约减,实现了下采样,同时保留了特色图中次要信息。比方:当辨认一张图像是否是人脸时,咱们须要晓得人脸右边有一只眼睛,左边也有一只眼睛,而不须要晓得眼睛的准确地位,这时候通过池化某一片区域的像素点来失去总体统计特色会显得很有用。
池化的几种常见办法包含:均匀池化、最大池化、K-max 池化。其中均匀池化和最大池化如 图 1 所示,K-max 池化如 图 2 所示。
图 1 均匀池化和最大池化
- 均匀池化: 计算区域子块所蕴含所有像素点的均值,将均值作为均匀池化后果。如 图 1(a),这里应用大小为 $2\times2$ 的池化窗口,每次挪动的步幅为 2,对池化窗口笼罩区域内的像素取平均值,失去相应的输入特色图的像素值。池化窗口的大小也称为池化大小,用 $k_h \times k_w$ 示意。在卷积神经网络中用的比拟多的是窗口大小为 $2 \times 2$,步幅为 2 的池化。
- 最大池化: 从输出特色图的某个区域子块中抉择值最大的像素点作为最大池化后果。如 图 1(b),对池化窗口笼罩区域内的像素取最大值,失去输入特色图的像素值。当池化窗口在图片上滑动时,会失去整张输入特色图。
图 2 K-max 池化
- K-max 池化: 对输出特色图的区域子块中像素点取前 K 个最大值,罕用于自然语言解决中的文本特征提取。如图 2,从蕴含了 4 个取值的每一列中选取前 2 个最大值就失去了 K 最大池化后果。
2.2 池化特点
- 当输出数据做出大量平移时,通过池化后的大多数输入还能放弃不变,因而,池化 对渺小的地位变动具备鲁棒性 。例如 图 3 中,输出矩阵向右平移一个像素值,应用最大池化后,后果与平移前仍旧能放弃不变。
图 3 渺小地位变动时的最大池化后果
- 因为池化之后特色图会变小,如果前面连贯的是全连贯层,能无效的 减小神经元的个数,节俭存储空间并进步计算效率。
2.3 池化中填充的形式
在飞桨中,各种 Pooling API 中的 Padding 参数, 接管值类型都包含 int、list、tuple 和 string。上面用代码和公式来介绍一下这些形式吧。
2.3.1 int 输出
int 输出即接管一个 int 类型的数字 n,对图片的周围包裹 n 行 n 列的 0 来填充图片。如果要放弃图片尺寸不变,n 的值和池化窗口的大小是无关的。如果 $H_{in}, W_{in}$ 为图片输出的大小,$k_h, k_w$ 为池化窗口的大小,$H_{out}, H_{out}$ 为后果图的大小的话,他们之间有着这样的关系。$$H_{out} = \frac{H_{in} + 2*p_h – k_h}{s_h} + 1 \\ W_{out} = \frac{W_{out} + 2*p_w -k_w}{s_w} + 1$ $ 在应用 3×3 的池化窗口且步长为 1 的状况下,还要放弃图片大小不变,则须要应用 padding= 1 的填充。那么,公式就变为了 $ $H_{out} = \frac{6 – 3 + 2*1}{1} + 1 \\ W_{out} = \frac{6 – 3 + 2*1}{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 的后果,能够依据公式填入,$H_{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]
正如咱们下面的公式,$H_{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]
这里咱们带入公式推理一下,$H_{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:$H_{out} = \lceil \frac{H_{in}}{s_h} \rceil$, $W_{out} = \lceil\frac{W_{in}}{s_w}\rceil$
VALID:$H_{out} = \frac{H_{in} – k_h}{s_h} + 1$, $W_{out} = \frac{W_{in} – k_w}{s_w} + 1$
能够看到,VALID 形式就是默认采纳的不填充的形式,与下面不 Padding 的公式一样。而 SAME 则与池化窗口的大小无关,若 $s_h$ 和 $s_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]
代码的后果进去了,咱们来间接带入公式来计算吧,$H_{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 利用示例
与卷积核相似,池化窗口在图片上滑动时,每次挪动的步长称为步幅,当宽和高方向的挪动大小不一样时,别离用 $s_w$ 和 $s_h$ 示意。也能够对须要进行池化的图片进行填充,填充形式与卷积相似,假如在第一行之前填充 $p_{h1}$ 行,在最初一行前面填充 $p_{h2}$ 行。在第一列之前填充 $p_{w1}$ 列,在最初一列之后填充 $p_{w2}$ 列,则池化层的输入特色图大小为:
$$H_{out} = \frac{H + p_{h1} + p_{h2} – k_h}{s_h} + 1$$
$$W_{out} = \frac{W + p_{w1} + p_{w2} – k_w}{s_w} + 1$$
在卷积神经网络中,通常应用 $2\times2$ 大小的池化窗口,步幅也应用 2,填充为 0,则输入特色图的尺寸为:
$$H_{out} = \frac{H}{2}$$
$$W_{out} = \frac{W}{2}$$
通过这种形式的池化,输入特色图的高和宽都减半,但通道数不会扭转。
这里以 图 1 中的 2 个池化运算为例,此时,输出大小是 $4 \times 4$,应用大小为 $2 \times 2$ 的池化窗口进行运算,步幅为 2。此时,输入尺寸的计算形式为:
$$H_{out} = \frac{H + p_{h1} + p_{h2} – k_h}{s_h} + 1=\frac{4 + 0 + 0 – 2}{2} + 1=\frac{4}{2}=2$$
$$W_{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) 中,应用均匀池化进行运算,则输入中的每一个像素均为池化窗口对应的 $2 \times 2$ 区域求均值失去。计算步骤如下:
- 池化窗口的初始地位为左上角,对应粉色区域,此时输入为 $3.5 = \frac{1 + 2 + 5 + 6}{4}$;
- 因为步幅为 2,所以池化窗口向右挪动两个像素,对应蓝色区域,此时输入为 $5.5 = \frac{3 + 4 + 7 + 8}{4}$;
- 遍历完第一行后,再从第三行开始遍历,对应绿色区域,此时输入为 $11.5 = \frac{9 + 10 + 13 + 14}{4}$;
- 池化窗口向右挪动两个像素,对应黄色区域,此时输入为 $13.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 = 76$, 4 对应的是输入的通道数,2 对应的是输出的通道数,两个 3 是卷积核的尺寸,最初的 4 就是 bias 的数量了,值得注意的是,bias 是数量与输入的通道数保持一致。因而,咱们能够得出,一个卷积层的参数量的公式,如下:$ $Param_{conv2d} = C_{in} C_{out} K_h * K_w + C_{out}$ $ 其中,$C_{in} $ 示意输出的通道数,$C_{out} $ 示意输入的通道数,$ K_h$,$ K_w $ 示意卷积核的大小。当然了,有些卷积会将 bias 设置为 False,那么咱们不加最初的 $C_{out}$ 即可。
3.1.2 卷积层 FLOPs 计算
参数量会计算了,那么 FLOPs 其实也是很简略的,就一个公式:$$FLOP_{conv2d} = Param_{conv2d} * M_{outh} * M_{outw}$ $ 这里,$M_{outh}$,$M_{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 的 10$^9$ 倍,咱们的参数量求得的也是 76,那么 FLOPs 呢?咱们来算一下,输出的尺寸为 320 320,卷积核为 3 3,且 padding 为 1,那么图片输出的大小和输入的大小统一,即输入也是 320 * 320,那么依据咱们的公式可得: $76 * 320 * 320 = 7782400$, 与 API 的统一!因而大家计算卷积层的参数和 FLOPs 的时候就能够用下面的公式。
3.2 归一化层
最罕用的归一化层为 BatchNorm2D 啦,咱们这里就用 BatchNorm2D 来做例子介绍。在算参数量和 FLOPs,先看看 BatchNorm 的算法流程吧!
输出为:Values of $x$ over a mini-batch:$B={x_1,…,m}$,
$\quad\quad\quad$Params to be learned: $\beta$, $\gamma$
输入为:{$y_i$=BN$_{\gamma}$,$\beta(x_i)$}
流程如下:
$\quad\quad\quad$$\mu_B\gets$ $\frac{1}{m}\sum_{1}^mx_i$
$\quad\quad\quad\sigma_{B}^2\gets\frac{1}{m}\sum_{1}^m(x_i-\mu_B)^2$
$\quad\quad\quad\hat{x}_i\gets\frac{x_i-\mu_B}{\sqrt{\sigma_{B}^2+\epsilon}}$
$\quad\quad\quad y_i\gets\gamma\hat{x}_i+\beta\equiv BN_{\gamma}$,$\beta(x_i)$
在这个公式中,$B$ 为一个 Batch 的数据,$\beta$ 和 $\gamma$ 为可学习的参数,$\mu$ 和 $\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 $\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 = 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 = 60$, FLOPs : $60 * 28 * 28 = 47040$
features[1] 参数量和 FLOPs 均为 0
features[2] 参数量和 FLOPs 均为 0,输入尺寸变为 14 * 14
features[3] 参数量:$16 * 6 * 5 * 5 + 16 = 2416$, FLOPs : $2416 * 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 = 48120$, FLOPs : $400 * 120 = 48000$(输入尺寸变为[1, 120])
fc[1] 参数量:$120 * 84 + 84 = 10164$, FLOPs : $120 * 84 = 10080$(输入尺寸变为[1, 84])
fc[2] 参数量:$84 * 10 + 10 = 850$, FLOPs : $84 * 10 = 840$(输入尺寸变为[1, 10])。
总参数量为:$60 + 2416 + 48120 + 10164 + 850 = 61610$
总 FLOPs 为:$47040 + 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 只是模型深了点,并没有其余额定的构造。