网络模型的参数量和FLOPs的计算 Pytorch

目录

1、torchstat 

2、thop

3、fvcore 

4、flops_counter

5、自定义统计函数


FLOPS和FLOPs的区别:

  • FLOPS:注意全大写,是floating point operations per second的缩写,意指每秒浮点运算次数,理解为计算速度。是一个衡量硬件性能的指标。
  • FLOPs:注意s小写,是floating point operations的缩写(s表复数),意指浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度。

在介绍torchstat包和thop包之前,先总结一下:

  • torchstat包可以统计卷积神经网络和全连接神经网络的参数和计算量。
  • thop包可以统计统计卷积神经网络、全连接神经网络以及循环神经网络的参数和计算量,程序示例等详见下文。

1、torchstat 

pip install torchstat -i https://pypi.tuna.tsinghua.edu.cn/simple

在实际操作中,我们可以调用torchstat包,帮助我们统计模型的parameters和FLOPs。如果不修改这个包里面的一些代码,那么这个包只适用于输入为3通道的图像的模型。

import torch
import torch.nn as nn
from torchstat import stat
 
 
class Simple(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 16, 3, 1, padding=1, bias=False)
        self.conv2 = nn.Conv2d(16, 32, 3, 1, padding=1, bias=False)
 
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        return x
 
 
model = Simple()
stat(model, (3, 244, 244))   # 统计模型的参数量和FLOPs,(3,244,244)是输入图像的size

 如果把torchstat包中的一行程序进行一点点改动,那么这个包可以用来统计全连接神经网络的参数量和计算量。当然手动计算全连接神经网络的参数量和计算量也很快 =_= 。进入torchstat源代码之后,如下图所示,注释掉圈红的地方,就可以用torchstat包统计全连接神经网络的参数量和计算量了。

2、thop

pip install thop -i https://pypi.tuna.tsinghua.edu.cn/simple
import torch
import torch.nn as nn
from thop import profile
 
class Simple(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(10, 10)
 
    def forward(self, x):
        x = self.fc1(x)
        return x
 
net = Simple()
input = torch.randn(1, 10)  # batchsize=1, 输入向量长度为10
macs, params = profile(net, inputs=(input, ))
print(' FLOPs: ', macs*2)   # 一般来讲,FLOPs是macs的两倍
print('params: ', params)

3、fvcore 

pip install fvcore -i https://pypi.tuna.tsinghua.edu.cn/simple

用它比较好

import torch
from torchvision.models import resnet50
from fvcore.nn import FlopCountAnalysis, parameter_count_table

# 创建resnet50网络
model = resnet50(num_classes=1000)

# 创建输入网络的tensor
tensor = (torch.rand(1, 3, 224, 224),)

# 分析FLOPs
flops = FlopCountAnalysis(model, tensor)
print("FLOPs: ", flops.total())

# 分析parameters
print(parameter_count_table(model))

 终端输出结果如下,FLOPs为4089184256,模型参数数量约为25.6M(这里的参数数量和我自己计算的有些出入,主要是在BN模块中,这里只计算了beta和gamma两个训练参数,没有统计moving_mean和moving_var两个参数),具体可以看下我在官方提的issue。
通过终端打印的信息我们可以发现在计算FLOPs时并没有包含BN层,池化层还有普通的add操作(我发现计算FLOPs时并没有统一的规定,在github上看的计算FLOPs项目基本每个都不同,但计算出来的结果大同小异)。

注意:在使用fvcore模块计算模型的flops时,遇到了问题,记录一下解决方案。首先是在jit_analysis.py的589行出错。经过调试发现,op_counts.values()的类型是int32,但是计算要求的类型只能是int、float、np.float64和np.int64,因此需要手动进行强制转换。修改如下:

4、flops_counter

pip install ptflops -i https://pypi.tuna.tsinghua.edu.cn/simple

用它也很好,结果和fvcore一样

from ptflops import get_model_complexity_info

macs, params = get_model_complexity_info(model, (112, 9, 9), as_strings=True,
                                         print_per_layer_stat=True, verbose=True)
print('{:<30}  {:<8}'.format('Computational complexity: ', macs))
print('{:<30}  {:<8}'.format('Number of parameters: ', params))

5、自定义统计函数

import torch
import numpy as np

def calc_flops(model, input):
    def conv_hook(self, input, output):
        batch_size, input_channels, input_height, input_width = input[0].size()
        output_channels, output_height, output_width = output[0].size()

        kernel_ops = self.kernel_size[0] * self.kernel_size[1] * (self.in_channels / self.groups) * (
            2 if multiply_adds else 1)
        bias_ops = 1 if self.bias is not None else 0

        params = output_channels * (kernel_ops + bias_ops)
        flops = batch_size * params * output_height * output_width

        list_conv.append(flops)

    def linear_hook(self, input, output):
        batch_size = input[0].size(0) if input[0].dim() == 2 else 1
        num_steps = input[0].size(0)
        weight_ops = self.weight.nelement() * (2 if multiply_adds else 1)
        bias_ops = self.bias.nelement() if self.bias is not None else 0

        flops = batch_size * (weight_ops + bias_ops)
        flops *= num_steps
        list_linear.append(flops)

    def fsmn_hook(self, input, output):
        batch_size = input[0].size(0) if input[0].dim() == 2 else 1

        weight_ops = self.filter.nelement() * (2 if multiply_adds else 1)
        num_steps = input[0].size(0)
        flops = num_steps * weight_ops
        flops *= batch_size
        list_fsmn.append(flops)

    def gru_cell(input_size, hidden_size, bias=True):
        total_ops = 0
        # r = \sigma(W_{ir} x + b_{ir} + W_{hr} h + b_{hr}) \\
        # z = \sigma(W_{iz} x + b_{iz} + W_{hz} h + b_{hz}) \\
        state_ops = (hidden_size + input_size) * hidden_size + hidden_size
        if bias:
            state_ops += hidden_size * 2
        total_ops += state_ops * 2

        # n = \tanh(W_{in} x + b_{in} + r * (W_{hn} h + b_{hn})) \\
        total_ops += (hidden_size + input_size) * hidden_size + hidden_size
        if bias:
            total_ops += hidden_size * 2
        # r hadamard : r * (~)
        total_ops += hidden_size

        # h' = (1 - z) * n + z * h
        # hadamard hadamard add
        total_ops += hidden_size * 3

        return total_ops

    def gru_hook(self, input, output):

        batch_size = input[0].size(0) if input[0].dim() == 2 else 1
        if self.batch_first:
            batch_size = input[0].size(0)
            num_steps = input[0].size(1)
        else:
            batch_size = input[0].size(1)
            num_steps = input[0].size(0)
        total_ops = 0
        bias = self.bias
        input_size = self.input_size
        hidden_size = self.hidden_size
        num_layers = self.num_layers
        total_ops = 0
        total_ops += gru_cell(input_size, hidden_size, bias)
        for i in range(num_layers - 1):
            total_ops += gru_cell(hidden_size, hidden_size, bias)
        total_ops *= batch_size
        total_ops *= num_steps

        list_lstm.append(total_ops)

    def lstm_cell(input_size, hidden_size, bias):
        total_ops = 0
        state_ops = (input_size + hidden_size) * hidden_size + hidden_size
        if bias:
            state_ops += hidden_size * 2
        total_ops += state_ops * 4
        total_ops += hidden_size * 3
        total_ops += hidden_size
        return total_ops

    def lstm_hook(self, input, output):

        batch_size = input[0].size(0) if input[0].dim() == 2 else 1
        if self.batch_first:
            batch_size = input[0].size(0)
            num_steps = input[0].size(1)
        else:
            batch_size = input[0].size(1)
            num_steps = input[0].size(0)
        total_ops = 0
        bias = self.bias
        input_size = self.input_size
        hidden_size = self.hidden_size
        num_layers = self.num_layers
        total_ops = 0
        total_ops += lstm_cell(input_size, hidden_size, bias)
        for i in range(num_layers - 1):
            total_ops += lstm_cell(hidden_size, hidden_size, bias)
        total_ops *= batch_size
        total_ops *= num_steps

        list_lstm.append(total_ops)

    def bn_hook(self, input, output):
        list_bn.append(input[0].nelement())

    def relu_hook(self, input, output):
        list_relu.append(input[0].nelement())

    def pooling_hook(self, input, output):
        batch_size, input_channels, input_height, input_width = input[0].size()
        output_channels, output_height, output_width = output[0].size()

        kernel_ops = self.kernel_size * self.kernel_size
        bias_ops = 0
        params = output_channels * (kernel_ops + bias_ops)
        flops = batch_size * params * output_height * output_width

        list_pooling.append(flops)

    def foo(net):
        childrens = list(net.children())
        if not childrens:
            print(net)
            if isinstance(net, torch.nn.Conv2d) or isinstance(net, torch.nn.ConvTranspose2d):
                net.register_forward_hook(conv_hook)
                # print('conv_hook_ready')
            if isinstance(net, torch.nn.Linear):
                net.register_forward_hook(linear_hook)
                # print('linear_hook_ready')
            if isinstance(net, torch.nn.BatchNorm2d):
                net.register_forward_hook(bn_hook)
                # print('batch_norm_hook_ready')
            if isinstance(net, torch.nn.ReLU) or isinstance(net, torch.nn.PReLU):
                net.register_forward_hook(relu_hook)
                # print('relu_hook_ready')
            if isinstance(net, torch.nn.MaxPool2d) or isinstance(net, torch.nn.AvgPool2d):
                net.register_forward_hook(pooling_hook)
                # print('pooling_hook_ready')
            if isinstance(net, torch.nn.LSTM):
                net.register_forward_hook(lstm_hook)
                # print('lstm_hook_ready')
            if isinstance(net, torch.nn.GRU):
                net.register_forward_hook(gru_hook)

            # if isinstance(net, FSMNZQ):
            #     net.register_forward_hook(fsmn_hook)
                # print('fsmn_hook_ready')
            return
        for c in childrens:
            foo(c)

    multiply_adds = False
    list_conv, list_bn, list_relu, list_linear, list_pooling, list_lstm, list_fsmn = [], [], [], [], [], [], []
    foo(model)

    _ = model(input)

    total_flops = (sum(list_conv) + sum(list_linear) + sum(list_bn) + sum(list_relu) + sum(list_pooling) + sum(
        list_lstm) + sum(list_fsmn))
    fsmn_flops = (sum(list_fsmn) + sum(list_linear))
    lstm_flops = sum(list_lstm)

    model_parameters = filter(lambda p: p.requires_grad, model.parameters())
    params = sum([np.prod(p.size()) for p in model_parameters])
    print('The network has {} params.'.format(params))

    print(total_flops, fsmn_flops, lstm_flops)
    print('  + Number of FLOPs: %.2f M' % (total_flops / 1000 ** 2))
    return total_flops

if __name__ == '__main__':
    from torchvision.models import resnet18

    model = resnet18(num_classes=1000)
    imput_size = torch.rand((1,3,224,224))
    calc_flops(model, imput_size)

  • 17
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
唐朔飞计算机组成原理1-10章答案 第一章 计算机系统概论 1. 什么是计算机系统、计算机硬件和计算机软件?硬件和软件哪个更重要? 解:P3 计算机系统:由计算机硬件系统和软件系统组成的综合体。 计算机硬件:指计算机中的电子线路和物理装置。 计算机软件:计算机运行所需的程序及相关资料。 硬件和软件在计算机系统中相互依存,缺一不可,因此同样重要。 5. 冯•诺依曼计算机的特点是什么? 解:冯•诺依曼计算机的特点是:P8 计算机由运算器、控制器、存储器、输入设备、输出设备五大部件组成; 指令和数据以同同等地位存放于存储器内,并可以按地址访问; 指令和数据均用二进制表示; 指令由操作码、地址码两大部分组成,操作码用来表示操作的性质,地址码用来表示操作数在存储器中的位置; 指令在存储器中顺序存放,通常自动顺序取出执行; 机器以运算器为中心(原始冯•诺依曼机)。 7. 解释下列概念: 主机、CPU、主存、存储单元、存储元件、存储基元、存储元、存储字、存储字长、存储容量、机器字长、指令字长。 解:P9-10  主机:是计算机硬件的主体部分,由CPU和主存储器MM合成为主机。  CPU:中央处理器,是计算机硬件的核心部件,由运算器和控制器组成;(早期的运算器和控制器不在同一芯片上,现在的CPU内除含有运算器和控制器外还集成了CACHE)。  主存:计算机中存放正在运行的程序和数据的存储器,为计算机的主要工作存储器,可随机存取;由存储体、各种逻辑部件及控制电路组成。  存储单元:可存放一个机器字并具有特定存储地址的存储单位。  存储元件:存储一位二进制信息的物理元件,是存储器中最小的存储单位,又叫存储基元或存储元,不能单独存取。  存储字:一个存储单元所存二进制代码的逻辑单位。  存储字长:一个存储单元所存二进制代码的位数。  存储容量:存储器中可存二进制代码的总量;(通常主、辅存容量分开描述)。  机器字长:指CPU一次能处理的二进制数据的位数,通常与CPU的寄存器位数有关。  指令字长:一条指令的二进制代码位数。 8. 解释下列英文缩写的中文含义: CPU、PC、IR、CU、ALU、ACC、MQ、X、MAR、MDR、I/O、MIPS、CPI、FLOPS 解:全面的回答应分英文全称、中文名、功能三部分。 CPU:Central Processing Unit,中央处理机(器),是计算机硬件的核心部件,主要由运算器和控制器组成。 PC:Program Counter,程序计数器,其功能是存放当前欲执行指令的地址,并可自动计数形成下一条指令地址。 IR:Instruction Register,指令寄存器,其功能是存放当前正在执行的指令。 CU:Control Unit,控制单元(部件),为控制器的核心部件,其功能是产生微操作命令序列。 ALU:Arithmetic Logic Unit,算术逻辑运算单元,为运算器的核心部件,其功能是进行算术、逻辑运算。 ACC:Accumulator,累加器,是运算器中既能存放运算前的操作数,又能存放运算结果的寄存器。 MQ:Multiplier-Quotient Register,乘商寄存器,乘法运算时存放乘数、除法时存放商的寄存器。 X:此字母没有专指的缩写含义,可以用作任一部件名,在此表示操作数寄存器,即运算器中工作寄存器之一,用来存放操作数; MAR:Memory Address Register,存储器地址寄存器,在主存中用来存放欲访问的存储单元的地址。 MDR:Memory Data Register,存储器数据缓冲寄存器,在主存中用来存放从某单元读出、或要写入某存储单元的数据。 I/O:Input/Output equipment,输入/输出设备,为输入设备和输出设备的总称,用于计算机内部和外界信息的转换与传送。 MIPS:Million Instruction Per Second,每秒执行百万条指令数,为计算机运算速度指标的一种计量单位。 9. 画出主机框图,分别以存数指令“STA M”和加法指令“ADD M”(M均为主存地址)为例,在图中按序标出完成该指令(包括取指令阶段)的信息流程(如→①)。假设主存容量为256M*32位,在指令字长、存储字长、机器字长相等的条件下,指出图中各寄存器的位数。 解:主机框图如P13图1.11所示。 (1)STA M指令:PC→MAR,MAR→MM,MM→MDR,MDR→IR, OP(IR) →CU,Ad(IR) →MAR,ACC→MDR,MAR→MM,WR (2)ADD M指令:PC→MAR,MAR→MM,MM→MDR,MDR→IR, OP(IR) →CU,Ad(IR) →MAR,RD,MM→MDR,MDR→X,ADD,ALU→ACC,ACC→MDR,WR 假设主存容量256M*32位,在指令字长、存储字长、机器字长相等的条件下,ACC、X、IR、MDR寄存器均为32位,PC和MAR寄存器均为28位。 10. 指令和数据都存于存储器中,计算机如何区分它们? 解:计算机区分指令和数据有以下2种方法: 通过不同的时间段来区分指令和数据,即在取指令阶段(或取指微程序)取出的为指令,在执行指令阶段(或相应微程序)取出的即为数据。 通过地址来源区分,由PC提供存储单元地址的取出的是指令,由指令地址码部分提供存储单元地址的取出的是操作数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清纯世纪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值