- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
计算机视觉作为一门让机器学会如何去“看”的学科,具体的说,就是让机器去识别摄像机拍摄的图片或视频中的物体,检测出物体所在的位置,并对目标物体进行跟踪,从而理解并描述出图片或视频里的场景和故事,以此来模拟人脑视觉系统。因此,计算机视觉也通常被叫做机器视觉,其目的是建立能够从图像或者视频中“感知”信息的人工系统.
计算机视觉技术经过几十年的发展,已经在交通(车牌识别、道路违章抓拍)、安防(人脸闸机、小区监控)、金融(刷脸支付、柜台的自动票据识别)、医疗(医疗影像诊断)、工业生产(产品缺陷自动检测)等多个领域应用,影响或正在改变人们的日常生活和工业生产方式。未来,随着技术的不断演进,必将涌现出更多的产品和应用,为我们的生活创造更大的便利和更广阔的机会.
图1 计算机视觉技术在各领域的应用 。
计算机视觉的发展历程要从生物视觉讲起。对于生物视觉的起源,目前学术界尚没有形成定论。有研究者认为最早的生物视觉形成于距今约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视觉等.
卷积神经网络是目前计算机视觉中使用最普遍的模型结构。 图6 是一个典型的卷积神经网络结构,多层卷积和池化层组合作用在输入图片上,在网络的最后通常会加入一系列全连接层,ReLU激活函数一般加在卷积或者全连接层的输出上,网络中通常还会加入Dropout来防止过拟合.
图6 卷积神经网络经典结构 。
卷积层:卷积层用于对输入的图像进行特征提取。卷积的计算范围是在像素点的空间邻域内进行的,因此可以利用输入图像的空间信息。卷积核本身与输入图片大小无关,它代表了对空间邻域内某种特征模式的提取。比如,有些卷积核提取物体边缘特征,有些卷积核提取物体拐角处的特征,图像上不同区域共享同一个卷积核。当输入图片大小不一样时,仍然可以使用同一个卷积核进行操作.
池化层:池化层通过对卷积层输出的特征图进行约减,实现了下采样。同时对感受域内的特征进行筛选,提取区域内最具代表性的特征,保留特征图中最主要的信息.
激活函数:激活函数给神经元引入了非线性因素,对输入信息进行非线性变换,从而使得神经网络可以任意逼近任何非线性函数,然后将变换后的输出信息作为输入信息传给下一层神经元.
全连接层:全连接层用于对卷积神经网络提取到的特征进行汇总,将多维的特征映射为二维的输出。其中,高维代表样本批次大小,低维代表分类或回归结果.
在图像处理中,由于图像中存在较多冗余信息,可用某一区域子块的统计信息(如最大值或均值等)来刻画该区域中所有像素点呈现的空间分布模式,以替代区域子块中所有像素点取值,这就是卷积神经网络中池化(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池化 。
图3 微小位置变化时的最大池化结果 。
在飞桨中,各种Pooling API中的Padding参数, 接收值类型都包括int、list、tuple和string。下面用代码和公式来介绍一下这些方式吧.
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的时候图像保持为原大小.
因为图像有宽和高,所以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]
这里的结果与我们的期望一致,大家试着带入公式算一下吧.
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的填充方式的结果啦。大家自己按照公式算算,看看你的答案和程序输出的对不对哦.
与卷积核类似,池化窗口在图片上滑动时,每次移动的步长称为步幅,当宽和高方向的移动大小不一样时,分别用$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) 中,使用最大池化进行运算,将上述过程的求均值改为求最大值即为最终结果.
一个卷积神经网络的基本构成一般有卷积层、归一化层、激活层和线性层。这里我们就通过逐步计算这些层来计算一个CNN模型所需要的参数量和FLOPs吧. 另外,FLOPs的全程为floating point operations的缩写(小写s表复数),意指浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度.
卷积层,最常用的是2D卷积,因此我们以飞桨中的Conv2D来表示.
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}$即可.
参数量会计算了,那么FLOPs其实也是很简单的,就一个公式: $$FLOP_{conv2d} = Param_{conv2d} * M_{outh} * M_{outw}$ $这里,$M_{outh}$,$M_{outw}$ 为输出的特征图的高和宽,而不是输入的,这里需要注意一下.
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的时候就可以用上面的公式.
最常用的归一化层为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 ^2+\epsilon}}$ 。
$\quad\quad\quad y_i\gets\gamma\hat{x} i+\beta\equiv BN $,$\beta(x_i)$ 。
在这个公式中,$B$ 为一个Batch的数据,$\beta$ 和 $\gamma$ 为可学习的参数,$\mu$ 和 $\sigma^2$ 为均值和方差,由输入的数据的值求得。该算法先求出整体数据集的均值和方差,然后根据第三行的更新公式求出新的x,最后根据可学习的$\beta$ 和 $\gamma$调整数据。第三行中的 $\epsilon$ 在飞桨中默认为 1e-5, 用于处理除法中的极端情况.
由于归一化层较为简单,这里直接写出公式: $$Param_{bn2d} = 4 * C_{out}$ $ 其中4表示四个参数值,每个特征图对应一组四个元素的参数组合; 。
beta_initializer $\beta$ 权重的初始值设定项.
gamma_initializer $\gamma$ 伽马权重的初始值设定项.
moving_mean_initializer $\mu$ 移动均值的初始值设定项.
moving_variance_initializer $\sigma^2$ 移动方差的初始值设定项.
因为只有两个可以学习的权重,$\beta$ 和 $\gamma$,所以FLOPs只需要2乘以输出通道数和输入的尺寸即可。 归一化的FLOPs计算公式则为: $ $FLOP_{bn2d} = 2 * C_{out} * M_{outh} * M_{outw}$ $ 与1.3相似,欢迎大家使用上面的代码进行验证.
线性层也是常用的分类层了,我们以飞桨的Linear为例来介绍.
其实线性层是比较简单的,它就是相当于卷积核为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$。具体对不对,我们在下面的实例演示中检查.
与卷积层不同的是,线性层没有”卷“的过程,所以线性层的FLOPs计算公式为: $ $FLOP_{linear} = C_{in} * C_{out}$$ 。
这里我们就以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只是模型深了点,并没有其余额外的结构.
最后此篇关于深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算的文章就讲到这里了,如果你想了解更多关于深度学习基础入门篇[8]::计算机视觉与卷积神经网络、卷积模型CNN综述、池化讲解、CNN参数计算的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
当我尝试以非整数的步长(例如,每帧 0.5 像素)在屏幕上移动图形对象时,这会导致移动不稳定和“滞后”;因为对象只会每两帧移动 1 个像素。 我理解为什么会发生这种情况,因为对象的 x/y 值必须是整
市面上有大量的家谱应用程序,但出于某种原因,我找不到一个示例来说明如何为 Android 应用程序创建一个。我是否使用 Canvas ,是否有图表库? 我的基本要求是画一个三层的树(节点)图/图表,其
[ {name: 'John'}, {name: 'Plasmody'}, {name: 'Kugelschreiber'}, {name: 'Sarrah'}, ] 如果我在 J并做
我试图定位所有没有 www 的链接。在数据库中。 https://launchhousing.org.au 并替换为 https://www.launchhousing.org.au 我使用了“搜索和
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 6年前关闭。 Improve this qu
我需要排除具有以下模式的文件: ProjectFoo.Data[0-9]{14}.lgp 如何将 RegEx 用于 (Visual)SVN 忽略列表? 最佳答案 subversion 忽略列表不支持正
我正在寻找在处理中创建该项目的方法,但是,我发现该术语有点困难。我不确定如何调用在整个歌曲中线条永久保持的效果来“绘制”音乐数据。 对于我可以查看哪些教程或某人的回答,我将不胜感激。 我的目标是创建尽
我正在尝试为 android 制作游戏。我目前已将所有美术资源加载到 drawables 文件夹中,但我的问题是如何实际引用特定资源来渲染它? 我知道每个文件都有一个唯一的@id,我可能必须在onDr
Closed. This question is off-topic。它当前不接受答案。
只是一个简单的问题。 有一个简单的可视化工具可以生成iOS/QuartzCore的源代码吗? 例如,我会制作一个带有路径和a的CAKeyframeAnimation(例如CGPathMoveToPoi
编辑 3:我想这已经解决了。我刚刚启用了古腾堡编辑器并发现了它的“经典编辑器”部分,即代码编辑器。我唯一需要习惯的是我无法轻易修改的编辑器行高,这还不错。这对我有用,它超过了修改 functions.
我想在具有背景 slider 的可视 Composer 行内创建一个下拉菜单,最重要的是我要切换的链接。我在编辑自定义 css 时面临的问题是链接没有设置为 bottom:0;已设置position:
我正在学习 C++,并且了解一点 Visual Basic 和 Delphi。 但我想知道,有没有像 Delphi 这样的程序,但适用于 C++。您可以将按钮拖到窗体上,双击它,就像在 Delphi
我正在努力使用 pygame 初始化 OpenGL 显示。和pyopengl . import pygame pygame.init() pygame.display.set_mode((1920,
不确定我做错了什么。我创建了一个主题,除了我在可视化编辑器中创建帖子外,一切都很好。对我来说,这很好,但大多数用户不了解 HTML,因此无法真正进入并编辑代码。 在元素检查器(Chrome)中,文章是
我正在编写一个 C# 程序,它接受一堆参数并对数据点进行一些转换,然后将它们绘制到屏幕上。 在我的一个表单上,我有一堆文本框,我都想执行相同的 KeyPress 事件。在我只做一个 switch 语句
我正在创建 UML 事件图,我需要使用发送和接受信号,但我似乎找不到它。我试图用谷歌搜索它,但我似乎找不到任何东西。有谁知道我在哪里可以找到它们,或者它们在 Visio 中不存在? 最佳答案 想知道为
是 Haskell for Visual Studio 2005兼容VS2008 SP1 ? 最佳答案 您最初问题的答案是否定的。visual haskell 的代码是用 Haskell 编写的,并通
我正在使用 Visual Composer 开发我的 WordPress 网站。 我需要包含一个可分页的容器,但如果它可以像幻灯片一样就更好了。 This is my pageable contain
有哪些 Web 应用程序可以让我直观地(通过单击)使用任何 REST API 并生成一些代码(以任何语言)来捕捉我所描述的视觉内容? 与 Swagger 或 Google API Playground
我是一名优秀的程序员,十分优秀!