面试宝典笔记:卷积计算过程中的FLOPs


模型的运算次数,可用 FLOPs衡量,也就是浮点运算次数(FLoating-point OPerations),表征的是模型的时间复杂度。模型空间复杂度通过Parameters反映,即模型的参数量。
最早是在2017年ICLR会议上由英伟达公司提出FLOPs的计算:从这张图中可以看出,随着模型层数不断加深,相应的flops增大,时间复杂度提高,但模型的验证集的错误率在不断减小。因此我们希望在模型加深获得更高精度的前提下尽可能减少模型的时间复杂度,加快模型的训练和预测时间。

单个卷积核的时间复杂度

Flops~O(M * M * K * K * Cin * Cout)
M为每个卷积核输出特征图(Feature Map)的长宽,输出特征图尺寸由输入特征图尺寸X,卷积核尺寸K,填充层Padding,步长stride四个参数决定,公式表示为:

K为每个卷积核(kernel size)的大小
IC为输入通道数,OC为输出通道数

注1:为了简化表达式中的变量个数,这里统一假设输入和卷积核的形状都是正方形。
注2:严格来讲每层应该还包含 1 个 bias参数,为了简洁就省略了。

模型整体时间复杂度


D为模型全部卷积层数
依据层内相乘,层间相加的准则,将其累计起来就是整个模型所有卷积层的时间复杂度。
除此之外。模型的其它层结构(激活函数层,上下采样,batch normalization,池化层等)同样具有时间复杂度,只不过整体来看卷积层所占比重最大。

常用计算FLOPs的工具

1 stat打印

import torch
import torch.nn as nn
from torchstat import stat

class Net(nn.Module):
 def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 2, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn=nn.BatchNorm2d(2)
        self.activation=nn.ReLU()
    def forward(self, x):
       x = self.conv1(x)
       x = self.bn(x)
       x = self.activation(x)

net = Net()
stat(net, (3, 500, 500))

stat打印的结果如下:

以下为对该模型的卷积层得到的数据进行计算:
【params】
网络主要的参数量
7 * 7 * 3 * 2=294(W * H * C_in * C_out),因为这里偏置设为False,所有不加上偏置的参数量
【memory】
节点推理时候的缓存占用(从个人使用情况来看,当模型设置的batch过大时经常会弹出CUDA out of memory,应该就是说的这个模型整个训练过程所需的缓存占用超出了你的计算机的最大缓存空间)
【Flops】Floating point operations
网络完成的浮点运算。 (7 * 7 * 3) * (250 * 250 * 2) = 18375000 ~= 18.38 MFlops
【MAdd】
一个MAdd算2个operations
网络完成的乘加操作的数量。一次乘加=一次乘法+一次加法,所以可以粗略的认为:MAdd=2 * Flops,((输出一个元素所经历的乘法次数)+(输出一个元素所经历的加法的个数)) * (输出总共的元素的个数)
((7 * 7 * 3)+(7 * 7 * 3-1)) * (250 * 250 * 2) = 36625000 ~= 36.62 MMAdd
【MemRead】
网络运行时,从内存中读取的大小 = 输入的特征图大小 + 网络参数的大小
((500 * 500 * 3) + (7 * 7 * 3 * 2)) * 4 = 3001176.0
这里乘以4,是因为假设这里的数是float32的,一个float32=4*byte
【MemWrite】
网络运行时,写入到内存中的大小 = 输出的特征图大小
250 * 250 * 2 * 4 = 500000
【MemR+W】
MemR+W = MemRead + MemWrite。在这里等于 3001176.0+500000 = 3501176.0

2 profile打印

import torch
import torch.nn as nn
# from torchstat import stat
from thop import profile

class Net(nn.Module):
   def __init__(self):
       super(Net, self).__init__()
       self.conv1 = nn.Conv2d(3, 2, kernel_size=7, stride=2, padding=3, bias=False)
   def forward(self, x):
       x = self.conv1(x)

# net = Net()
# stat(net, (3, 500, 500))

input = torch.randn(1, 3, 500, 500)
flops, params = profile(net, inputs=(input,))
print('FLOPs = ' + str(flops/1000**3) + 'G')
print('Params = ' + str(params/1000**2) + 'M')

模型最终输出两个值,分别是FLOPs和Params。

空间复杂度的计算在这里就不一起讨论了,不过常用的另外一款计算模型空间复杂度的工具是summary。它与前面的工具一样都会展示模型全部的层结构,且结构看上去更清晰明了,没那么复杂繁琐,然后就是展示模型的参数量。

import torch
from torchsummary import summary

from nets.yolo4_tiny import YoloBody

if __name__ == "__main__":
    # 需要使用device来指定网络在GPU还是CPU运行
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model = YoloBody(3, 4, 1).to(device)
    summary(model, input_size=(3, 416, 416))

截取部分
比如这里调用了YOLOv4-tiny的网络结构,就清晰地显示了每一层的分布以及特征图尺寸,通道数,参数量。

参考资料:
https://zhuanlan.zhihu.com/p/31575074
https://blog.csdn.net/magic_ll/article/details/122133532

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2022年5月11日
下一篇 2022年5月11日

相关推荐

此站出售,如需请站内私信或者邮箱!