当前位置: 首页 > article >正文

CNN中模型的参数量与FLOPs计算

在这里插入图片描述

CNN中模型的参数量与FLOPs计算

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

1. 卷积层

卷积层,最常用的是2D卷积,因此我们以飞桨中的Conv2D来表示。

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*3+4 = 76 4233+4=76, 4对应的是输出的通道数,2对应的是输入的通道数,两个3是卷积核的尺寸,最后的4就是bias的数量了, 值得注意的是, bias是数量与输出的通道数保持一致。因此,我们可以得出,一个卷积层的参数量的公式,如下:
P a r a m c o n v 2 d = C i n ∗ C o u t ∗ K h ∗ K w + C o u t Param_{conv2d} = C_{in} * C_{out} * K_h * K_w + C_{out} Paramconv2d=CinCoutKhKw+Cout
其中, C i n C_{in} Cin 表示输入的通道数, C o u t C_{out} Cout 表示输出的通道数, K h K_h Kh, K w K_w Kw 表示卷积核的大小。当然了,有些卷积会将bias设置为False,那么我们不加最后的 C o u t C_{out} Cout即可。

1.2 卷积层FLOPs计算

参数量会计算了,那么FLOPs其实也是很简单的,就一个公式:
F L O P c o n v 2 d = P a r a m c o n v 2 d ∗ M o u t h ∗ M o u t w FLOP_{conv2d} = Param_{conv2d} * M_{outh} * M_{outw} FLOPconv2d=Paramconv2dMouthMoutw
这里, M o u t h M_{outh} Mouth M o u t w M_{outw} Moutw 为输出的特征图的高和宽,而不是输入的,这里需要注意一下。

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 ^9 9倍,我们的参数量求得的也是76,那么FLOPs呢?我们来算一下,输入的尺寸为320 * 320, 卷积核为3 * 3, 且padding为1,那么图片输入的大小和输出的大小一致,即输出也是320 * 320, 那么根据我们的公式可得: 76 ∗ 320 ∗ 320 = 7782400 76 * 320 * 320 = 7782400 76320320=7782400, 与API的一致!因此大家计算卷积层的参数和FLOPs的时候就可以用上面的公式。

2. 归一化层

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

输入为:Values of x x x over a mini-batch: B = x 1 , . . . , m B={x_1,...,m} B=x1,...,m,

\quad\quad\quad Params to be learned: β \beta β, γ \gamma γ

输出为:{ y i y_i yi=BN γ _{\gamma} γ, β ( x i ) \beta(x_i) β(xi)}

流程如下:

\quad\quad\quad μ B ← \mu_B\gets μB 1 m ∑ 1 m x i \frac{1}{m}\sum_{1}^mx_i m11mxi

σ B 2 ← 1 m ∑ 1 m ( x i − μ B ) 2 \quad\quad\quad\sigma_{B}^2\gets\frac{1}{m}\sum_{1}^m(x_i-\mu_B)^2 σB2m11m(xiμB)2

x ^ i ← x i − μ B σ B 2 + ϵ \quad\quad\quad\hat{x}_i\gets\frac{x_i-\mu_B}{\sqrt{\sigma_{B}^2+\epsilon}} x^iσB2+ϵ xiμB

y i ← γ x ^ i + β ≡ B N γ \quad\quad\quad y_i\gets\gamma\hat{x}_i+\beta\equiv BN_{\gamma} yiγx^i+βBNγ, β ( x i ) \beta(x_i) β(xi)

在这个公式中, B B B 为一个Batch的数据, β \beta β γ \gamma γ 为可学习的参数, μ \mu μ σ 2 \sigma^2 σ2 为均值和方差,由输入的数据的值求得。该算法先求出整体数据集的均值和方差,然后根据第三行的更新公式求出新的x,最后根据可学习的 β \beta β γ \gamma γ调整数据。第三行中的 ϵ \epsilon ϵ 在飞桨中默认为 1e-5, 用于处理除法中的极端情况。

2.1 归一化层参数量计算

由于归一化层较为简单,这里直接写出公式:
P a r a m b n 2 d = 4 ∗ C o u t Param_{bn2d} = 4 * C_{out} Parambn2d=4Cout
其中4表示四个参数值,每个特征图对应一组四个元素的参数组合;

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

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

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

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

2.2 归一化层FLOPs计算

因为只有两个可以学习的权重, β \beta β γ \gamma γ,所以FLOPs只需要2乘以输出通道数和输入的尺寸即可。
归一化的FLOPs计算公式则为:
F L O P b n 2 d = 2 ∗ C o u t ∗ M o u t h ∗ M o u t w FLOP_{bn2d} = 2 * C_{out} * M_{outh} * M_{outw} FLOPbn2d=2CoutMouthMoutw
与1.3相似,欢迎大家使用上面的代码进行验证。

3. 线性层

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

3.1 线性层参数量计算

其实线性层是比较简单的,它就是相当于卷积核为1的卷积层,线性层的每一个参数与对应的数据进行矩阵相乘,再加上偏置项bias,线性层没有类似于卷积层的“卷”的操作的,所以计算公式如下:
P a r a m l i n e a r = C i n ∗ C o u t + C o u t Param_{linear} = C_{in} * C_{out} + C_{out} Paramlinear=CinCout+Cout。我们这里打印一下线性层参数的形状看看。

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 2 * 4 + 4 = 12 24+4=12。具体对不对,我们在下面的实例演示中检查。

3.2 线性层FLOPs计算

与卷积层不同的是,线性层没有”卷“的过程,所以线性层的FLOPs计算公式为:
F L O P l i n e a r = C i n ∗ C o u t FLOP_{linear} = C_{in} * C_{out} FLOPlinear=CinCout

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 60 + 2416 + 48120 + 10164 + 850 = 61610 60+2416+48120+10164+850=61610

总FLOPs为: 47040 + 241600 + 48000 + 10080 + 840 = 347560 47040 + 241600 + 48000 + 10080 + 840 = 347560 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只是模型深了点,并没有其余额外的结构。


http://www.kler.cn/a/502757.html

相关文章:

  • linux进程
  • 【机器学习案列】学生抑郁可视化及预测分析
  • 任务调度系统Quartz.net详解2-Scheduler、Calendar及Listener
  • 二分查找算法——山脉数组的峰顶索引
  • 开放词汇检测新晋SOTA:DOSOD实时检测算法详解
  • Java中的Push方法:实现与应用探讨
  • Spring MVC数据绑定POJO类型
  • 【动态规划-矩阵】6.最大正方形
  • Linux 子系统 Ubuntu 安装MySQL 8
  • 【Apache Paimon】-- 为什么选择将 Spark 与 Paimon 集成,解决什么问题?
  • 国产linux系统(银河麒麟,统信uos)使用 PageOffice 实现后台生成单个PDF文档
  • 虚假星标:GitHub上的“刷星”乱象与应对之道
  • 如何解决HTML和CSS相关情况下会导致页面布局不稳定?
  • ImportError: attempted relative import with no known parent package 报错的解决!
  • 2025年,华为认证HCIA、HCIP、HCIE 该如何选择?
  • 任务调度系统Quartz.net详解1-基本流程及Core表达式
  • 验证码的设置
  • Linux离线部署ELK
  • 【漫话机器学习系列】045.特征向量(Eigenvector)
  • 微信小程序开发设置支持scss文件
  • js:正则表达式
  • 每日学习30分轻松掌握CursorAI:Cursor隐私与安全设置
  • Django Admin 中实现 ECS 服务重启的细粒度权限控制
  • 面试加分项:Android Framework PMS 全面概述和知识要点
  • TaskBuilder前端页面JS脚本编辑
  • 【练习】力扣 热题100 两数之和