基于pytorch模型剪枝的实现(极大的减少模型计算参数加快模型运行速度)(代码片段)

帅的发光发亮 帅的发光发亮     2022-12-08     176

关键词:

深度模型剪枝实现以及一些网络优化技巧

模型剪枝:Learning Efficient Convolutional Networks Through Network Slimming (ICCV 2017).

论文参考:Learning Efficient Convolutional Networks Through Network Slimming (ICCV 2017).
1. 概述

导读:这篇文章是一篇关于CNN网络剪枝的文章,文章里面提出通过BatchNorm层的
      scaling参数确定重要的channel,排除不重要的channel,从而达到网络瘦身的目的。
      此外文章还引入了L1范数,通过L1范数约束的稀疏特性使得BN的scaling参数趋于0,
      从而帮助确定非重要的channel,并按照给定的阈值剪裁掉。

文章中对于剪裁的流程可以归纳为下图1所示,通过BN的scaling参数确定非重要的channel并剪裁掉,之后再根据需要是否再进行迭代剪裁。

2. 剪裁方法
文章中将正则化引入到网络的损失函数中,其总的定义如下:

这里引入scaling参数有如下需要注意的事项:

1)scaling参数是加载BN层上的,若是对于没有BN的CNN网络来说加入scaling会增加卷积的权值参数,带来不了稀疏的结构;
2)在BN层的前面添加scaling参数会被BN给补偿回来;
3)加在BN层的后面就相当于是两个Scaling参数进行运算了,也是达到不了稀疏的目的的;
这篇文章的剪裁方法是迭代进行的,其剪裁的流程图见下图所示:


3.裁剪后的网络参数情况

基于论文的代码复现以及拓展:

基于Learning Efficient Convolutional Networks Through Network Slimming论文。实现的代码及其拓展的github地址为:https://github.com/Culturenotes/Network-Slimming

目前加入了Resnet和Densenet的网络模型剪枝的实现,后续将会添加EfficientNet、MobileNet、Xception、shuffleNet的模型剪枝实现。如果感兴趣可以和我一起改进并且实现他们。

在网络上中加入其它优化方法

最强深度学习优化器Ranger

Ranger=RAdam+LookAhead。强强结合,性能更优速度更快!

RAdam可以说是优化者在培训开始时建立的最佳基础。RAdam利用动态整流器根据方差调整Adam的自适应动量,并有效地提供自动预热,根据当前数据集定制,以确保扎实的训练开始。

LookAhead受到深度神经网络损失表面理解的最新进展的启发,并在整个训练期间提供了稳健和稳定探索的突破。“减少了对广泛超参数调整的需求”,同时实现“以最小的计算开销实现不同深度学习任务的更快收敛”。

因此,两者都在深度学习优化的不同方面提供了突破,并且这种组合具有高度协同性,可能为您的深度学习结果提供最佳的两种改进。因此,对更加稳定和强大的优化方法的追求仍在继续,通过结合两项最新突破(RAdam + LookAhead),Ranger的整合有望为深度学习提供又一步。
代码实现如下:

import math
import torch
from torch.optim.optimizer import Optimizer

def centralized_gradient(x, use_gc=True, gc_conv_only=False):
    if use_gc:
        if gc_conv_only:
            if len(list(x.size())) > 3:
                x.add_(-x.mean(dim=tuple(range(1, len(list(x.size())))), keepdim=True))
        else:
            if len(list(x.size())) > 1:
                x.add_(-x.mean(dim=tuple(range(1, len(list(x.size())))), keepdim=True))
    return x


class Ranger(Optimizer):

    def __init__(self, params, lr=1e-3,                       # lr
                 alpha=0.5, k=6, N_sma_threshhold=5,           # Ranger options
                 betas=(.95, 0.999), eps=1e-5, weight_decay=1e-5,  # Adam options
                 use_gc=True, gc_conv_only=False, gc_loc=True
                 ):

        # parameter checks
        if not 0.0 <= alpha <= 1.0:
            raise ValueError(f'Invalid slow update rate: alpha')
        if not 1 <= k:
            raise ValueError(f'Invalid lookahead steps: k')
        if not lr > 0:
            raise ValueError(f'Invalid Learning Rate: lr')
        if not eps > 0:
            raise ValueError(f'Invalid eps: eps')

        # prep defaults and init torch.optim base
        defaults = dict(lr=lr, alpha=alpha, k=k, step_counter=0, betas=betas,
                        N_sma_threshhold=N_sma_threshhold, eps=eps, weight_decay=weight_decay)
        super().__init__(params, defaults)

        # adjustable threshold
        self.N_sma_threshhold = N_sma_threshhold

        # look ahead params

        self.alpha = alpha
        self.k = k

        # radam buffer for state
        self.radam_buffer = [[None, None, None] for ind in range(10)]

        # gc on or off
        self.gc_loc = gc_loc
        self.use_gc = use_gc
        self.gc_conv_only = gc_conv_only
        # level of gradient centralization
        #self.gc_gradient_threshold = 3 if gc_conv_only else 1

        print(
            f"Ranger optimizer loaded. \\nGradient Centralization usage = self.use_gc")
        if (self.use_gc and self.gc_conv_only == False):
            print(f"GC applied to both conv and fc layers")
        elif (self.use_gc and self.gc_conv_only == True):
            print(f"GC applied to conv layers only")

    def __setstate__(self, state):
        print("set state called")
        super(Ranger, self).__setstate__(state)

    def step(self, closure=None):
        loss = None
        for group in self.param_groups:

            for p in group['params']:
                if p.grad is None:
                    continue
                grad = p.grad.data.float()

                if grad.is_sparse:
                    raise RuntimeError(
                        'Ranger optimizer does not support sparse gradients')

                p_data_fp32 = p.data.float()

                state = self.state[p]  # get state dict for this param

                if len(state) == 0:  
                    state['step'] = 0
                    state['exp_avg'] = torch.zeros_like(p_data_fp32)
                    state['exp_avg_sq'] = torch.zeros_like(p_data_fp32)

                    # look ahead weight storage now in state dict
                    state['slow_buffer'] = torch.empty_like(p.data)
                    state['slow_buffer'].copy_(p.data)

                else:
                    state['exp_avg'] = state['exp_avg'].type_as(p_data_fp32)
                    state['exp_avg_sq'] = state['exp_avg_sq'].type_as(
                        p_data_fp32)

                # begin computations
                exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
                beta1, beta2 = group['betas']

                # GC operation for Conv layers and FC layers
                # if grad.dim() > self.gc_gradient_threshold:
                #    grad.add_(-grad.mean(dim=tuple(range(1, grad.dim())), keepdim=True))
                if self.gc_loc:
                    grad = centralized_gradient(grad, use_gc=self.use_gc, gc_conv_only=self.gc_conv_only)

                state['step'] += 1

                # compute variance mov avg
                exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)

                # compute mean moving avg
                exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)

                buffered = self.radam_buffer[int(state['step'] % 10)]

                if state['step'] == buffered[0]:
                    N_sma, step_size = buffered[1], buffered[2]
                else:
                    buffered[0] = state['step']
                    beta2_t = beta2 ** state['step']
                    N_sma_max = 2 / (1 - beta2) - 1
                    N_sma = N_sma_max - 2 * \\
                        state['step'] * beta2_t / (1 - beta2_t)
                    buffered[1] = N_sma
                    if N_sma > self.N_sma_threshhold:
                        step_size = math.sqrt((1 - beta2_t) * (N_sma - 4) / (N_sma_max - 4) * (
                            N_sma - 2) / N_sma * N_sma_max / (N_sma_max - 2)) / (1 - beta1 ** state['step'])
                    else:
                        step_size = 1.0 / (1 - beta1 ** state['step'])
                    buffered[2] = step_size

                # apply lr
                if N_sma > self.N_sma_threshhold:
                    denom = exp_avg_sq.sqrt().add_(group['eps'])
                    G_grad = exp_avg / denom
                else:
                    G_grad = exp_avg

                if group['weight_decay'] != 0:
                    G_grad.add_(p_data_fp32, alpha=group['weight_decay'])
                # GC operation
                if self.gc_loc == False:
                    G_grad = centralized_gradient(G_grad, use_gc=self.use_gc, gc_conv_only=self.gc_conv_only)
                p_data_fp32.add_(G_grad, alpha=-step_size * group['lr'])
                p.data.copy_(p_data_fp32)

                # integrated look ahead...
                # we do it at the param level instead of group level
                if state['step'] % group['k'] == 0:
                    # get access to slow param tensor
                    slow_p = state['slow_buffer']
                    # (fast weights - slow weights) * alpha
                    slow_p.add_(p.data - slow_p, alpha=self.alpha)
                    # copy interpolated weights to RAdam param tensor
                    p.data.copy_(slow_p)
        return loss

官方源码请参考:https://github.com/lessw2020/Ranger-Deep-Learning-Optimizer
RAdam论文参考:
RAdam(Liu,Jiang,He,Chen,Liu,Gao,Han) - “ 关于自适应学习率和超越的变化 ”
LookAhead论文参考:
LookAhead(Zhang,Lucas,Hinton,Ba) - “ Lookahead Optimizer:k向前迈进,后退一步

warm up与consine learning rate

warm up最早来自于这篇文章:
https://arxiv.org/pdf/1706.02677.pdf
根据这篇论文所描述的,我们一般只在前5个epoch使用warm up。

consine learning rate来自于这篇文章:https://arxiv.org/pdf/1812.01187.pdf
通常情况下,把warm up和consine learning rate一起使用会达到更好的效果。

为什么使用warmup?

1、 理性分析
因为模型的weights是随机初始化的,可以理解为训练之初模型对数据的“理解程度”为0(即:没有任何先验知识),在第一个epoches中,每个batch的数据对模型来说都是新的,模型会根据输入的数据进行快速调参,此时如果采用较大的学习率的话,有很大的可能使模型对于数据“过拟合”(“学偏”),后续需要更多的轮次才能“拉回来”;
当模型训练一段时间之后(如:10epoches或10000steps),模型对数据具有一定的先验知识,此时使用较大的学习率模型就不容易学“偏”,可以使用较大的学习率加速模型收敛;
当模型使用较大的学习率训练一段时间之后,模型的分布相对比较稳定,此时不宜从数据中再学到新特点,如果仍使用较大的学习率会破坏模型的稳定性,而使用小学习率更容易获取local optima。
2 、感性分析
刚开始模型对数据完全不了解,这个时候步子太大,容易扯着dan,此时需要使用小学习率摸着石头过河;
对数据了解了一段时间之后,可以使用大学习率朝着目标大步向前;
快接近目标时,使用小学习率进行探索,此时步子太大,容易错过最近点

代码实现**:

    # MultiStepLR without warm up
    scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=args.milestones, gamma=0.1)

    # warm_up_with_multistep_lr
    warm_up_with_multistep_lr = lambda epoch: epoch / args.warm_up_epochs if epoch <= args.warm_up_epochs else 0.1**len([m for m in args.milestones if m <= epoch])
    scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=warm_up_with_multistep_lr)

    # warm_up_with_cosine_lr
    warm_up_with_cosine_lr = lambda epoch: ((epoch+1) / args.warm_up_epochs) if (epoch+1) <= args.warm_up_epochs else 0.5 * (
                math.cos((epoch - args.warm_up_epochs) / (args.epochs - args.warm_up_epochs) * math.pi) + 1)

    scheduler = torch.optim.lr_scheduler.LambdaLR( optimizer, lr_lambda=warm_up_with_cosine_lr)

上面的三段代码分别是不使用warm up+multistep learning rate 衰减、使用warm up+multistep learning rate 衰减、使用warm up+consine learning rate衰减。代码均使用pytorch中的lr_scheduler.LambdaLR自定义学习率衰减器。

label smooth

对于分类任务,通常我们使用cross entropy损失函数进行训练,即:

criterion = nn.CrossEntropyLoss().cuda()

在这篇文章:https://arxiv.org/pdf/1812.01187.pdf 中给出了带label smooth的cross entropy函数计算公式。我们在pytorch中实现了它,代码如下:

class LabelSmoothCELoss(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, pred, label, smoothing=0.1):
        pred = F.softmax(pred, dim=1)
        one_hot_label = F.one_hot(label, pred.size(1)).float()
        smoothed_one_hot_label = (
            1.0 - smoothing) * one_hot_label + smoothing / pred.size(1)
        loss = (-torch.log(pred)) * smoothed_one_hot_label
        loss = loss.sum(axis=1, keepdim=False)
        loss = loss.mean()
        return loss

使用时,只需要:

criterion = LabelSmoothCELoss().cuda()

替换掉原来的cross entropy损失函数即可。

apex混合精度训练

apex是NVIDIA发布的开源混合精度训练工具库。该工具库提供了AMP(自动混合精度)和FP16_Optimizer两种不同的库。AMP提供自动混合精度训练支持,该库会自动检查执行的操作,对于FP16安全的操作在训练中Cast到FP16精度,反之则选择FP32精度。FP16_Optimizer提供的是高阶版混合精度训练,其提供了更多详细的实现细节,对于整个网络完全采用FP16精度训练。
这里只介绍如何用apex实现最简单的自动混合精度训练支持。
首先安装apex工具库。如果你是Python3.7环境,使用下列命令安装:

git clone https://github.com/NVIDIA/apex
cd apex
pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./

如果你是Python3.6环境,则使用下列命令安装:

git clone https://github.com/NVIDIA/apex
cd apex
pip install -v --no-cache-dir ./

安装完成后,在train.py中进行下列修改:

from apex import amp
......
......
# 定义好Model和optimizer后,增加下面这行代码:
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
# 反传梯度原本的代码是loss.backward(),改为下面两行代码:
with amp.scale_loss(loss, optimizer) as scaled_loss:
    scaled_loss.backward()

这样在训练时就会使用apex自动混合精度训练了。使用apex后,对于同一个训练任务,可有效降低显存占用25-30%。在同样batchsize的情况下,使用apex会比不使用apex的情况下训练速度会稍稍变慢,但由于使用apex后,显存占用减少,因此可以继续扩大batchsize,这样相比原来的Batchsize训练速度还是要快很多。最后,在同样的batchsize情况下,使用apex训练出的模型性能和未使用apex时基本一致。

梯度累加

当我们的显卡资源有限时,训练时往往跑不起大的batchsize。梯度累加可以用较少的显卡模拟较大batchsize情况下的训练。具体来说,我们每次用小的batchsize计算出各个变量的梯度,然后把梯度累加起来,最后当小的batchsize累加和等于我们设定的大batchsize时再更新梯度。注意这个模拟较大Batchsize是近似的,因为bn层仍然按照小的batchsize来更新。
在train.py中,进行下列修改,就可以实现梯度累加:

inputs, labels = inputs.cuda(), labels.cuda()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss = loss / args.accumulation_steps
loss.backward()

if iter_index % args.accumulation_steps == 0:
	optimizer.step()
	optimizer.zero_grad()

其中,loss = loss / args.accumulation_steps根据累加次数把loss做除法,使得在不修改学习率的情况下最终累加的反传梯度数量级与原来一致(由于我们在累加的过程中考虑了多次小batchsize产生的loss,但又除以了累加次数,所以在loss.backward()多次累加后就近似相当于大batchsize计算出的loss产生的梯度)。
最后,我们经过累加的梯度次数后才使用optimizer.step()更新一次网络参数,然后使用optimizer.zero_grad()将梯度清零。
需要注意的是,由于batchsize仍然按照真实的小batchsize更新,因此真实batchsize不宜设置的太小,否则误差会比较大。对于分类任务,一般真实batchsize不宜小于32。

其他网络部署的方式:

下一篇,将讲解使用TensoRrt的c++接口使用,比较网络剪枝之后使用trt加速,和未进行网络剪枝trt加速,以及原生pytorch部署后运行的速度对比。

基于pytorch的yolo稀疏训练与模型剪枝

基于pytorch的yolo稀疏训练与模型剪枝数据集+代码下载地址:下载地址参数数量模型体积Flops前向推断耗时(2070TI)mAPBaseline(416)61.5M246.4MB32.8B15.0ms0.7692Prune(416)10.9M43.6MB9.6B7.7ms0.7722Finetune(416)同上同上同上同上0.7750加入稀疏正 查看详情

基于pytorch的模型剪枝+模型量化+bn合并+trt部署(cifar数据)

相关代码下载地址:下载地址压缩量化:High-Bit(>2b):QAT,PTQ,QAFT;Low-Bit(≤2b)/TernaryandBinary:QAT剪枝:正常、规整和分组卷积结构剪枝针对特征(A)二值量化的BN融合(训练量化后,BN参数—>conv的偏置b)High-Bit量化的BN融合(训练量化中... 查看详情

基于pytorch的模型剪枝+模型量化+bn合并+trt部署(cifar数据)

1)量化:High-Bit(>2b):QAT,PTQ,QAFT;Low-Bit(≤2b)/TernaryandBinary:QAT2)剪枝:正常、规整和分组卷积结构剪枝3)针对特征(A)二值量化的BN融合(训练量化后,BN参数—>conv的偏置b)4)High-Bit量化的BN融合(训练量化中,先融合再量化,融合... 查看详情

基于pytorch的模型剪枝+模型量化+bn合并+trt部署(cifar数据)

1)量化:High-Bit(>2b):QAT,PTQ,QAFT;Low-Bit(≤2b)/TernaryandBinary:QAT2)剪枝:正常、规整和分组卷积结构剪枝3)针对特征(A)二值量化的BN融合(训练量化后,BN参数—>conv的偏置b)4)High-Bit量化的BN融合(训练量化中,先融合再量化,融合... 查看详情

基于yolo的手部检测和计数实现(课程设计,训练测试+模型剪枝+模型压缩)

用YOLOv3模型在一个开源的人手检测数据集上做人手检测,并在此基础上做模型剪枝。对于该数据集,对YOLOv3进行channelpruning之后,模型的参数量、模型大小减少80%,FLOPs降低70%,前向推断的速度可以达到原来的200%,同时可以保持m... 查看详情

基于pytorch的模型剪枝+模型量化+bn合并+trt部署(cifar数据)

相关代码下载地址:下载地址压缩量化:High-Bit(>2b):QAT,PTQ,QAFT;Low-Bit(≤2b)/TernaryandBinary:QAT剪枝:正常、规整和分组卷积结构剪枝针对特征(A)二值量化的BN融合(训练量化后,BN参数—>conv的偏置b)High-Bit量化的BN融合(训练量化中... 查看详情

基于多种模型剪枝方法(l1-normslimmingautoslim)的模型轻量化和模型压缩实现

基于多种模型剪枝方法(L1-norm、Slimming、AutoSlim)的模型轻量化实现支持:VGG、MobileNet、Resnet、ShuffleNet等模型。代码下载地址:下载BackBonePrunerPruneRatioOriginal/Pruned/FinetunedAccuracyFLOPs(M)Params(M)MobileV2L1-Norm0.60.937/0.100/0.844313.5->225.52.... 查看详情

实践pytorch中的模型剪枝方法(代码片段)

...差的模型压缩技术。本文分享自华为云社区《模型压缩-pytorch中的模型剪枝方法实践》,作者:嵌入式视觉。一,剪枝分类所谓模型剪枝,其实是一种从神经网络中移除"不必要"权重或偏差(weigths/biasÿ... 查看详情

模型压缩-剪枝算法详解(代码片段)

一,前言学术界的SOTA模型在落地部署到工业界应用到过程中,通常是要面临着低延迟(Latency)、高吞吐(Throughpout)、高效率(Efficiency)挑战的。而模型压缩算法可以将一个庞大而复杂的预训练模型转化为一个精简的小模型,... 查看详情

如何实现神经网络剪枝?

...】:2019-10-1110:24:41【问题描述】:我在keras中训练了一个模型,我正在考虑修剪我的全连接网络。我对如何修剪图层有点迷茫。“学习权重和连接以提高效率”的作者神经网络,说他​​们为层的阈值权重添加了一个掩码。我可... 查看详情

模型压缩:剪枝算法

...络模型推理的时候就不需要这么多参数。而剪枝算法正是基于过参数化的理论基础而提出的。剪枝算法核心思想就是减少网络模型中参数量和计算量,同时尽量保证模型的性能不受影响。那在AI框架中,实际上剪枝主要作用在右... 查看详情

「深度学习一遍过」必修18:基于pytorch的语义分割模型实现(代码片段)

...型基础上实现膨胀卷积 1自定义5层普通卷积 模型结构 pytorch代码 fromtor 查看详情

Pytorch LSTM 模型的损失没有减少

】PytorchLSTM模型的损失没有减少【英文标题】:PytorchLSTMmodel\'slossnotdecreasing【发布时间】:2021-07-2522:58:37【问题描述】:我正在编写一个利用Pytorch中LSTM构建的程序,但是损失总是在一些数字附近,并且没有显着减少。我的模型... 查看详情

Pytorch Global Pruning 不会减少模型的大小

】PytorchGlobalPruning不会减少模型的大小【英文标题】:PytorchGlobalPruningisnotreducingthesizeofthemodel【发布时间】:2021-04-2520:24:26【问题描述】:我正在尝试通过全局修剪来修剪我的深度学习模型。原始的UnPruned模型约为77.5MB。但是修剪... 查看详情

ai常用框架和工具丨13.pytorch实现基于cnn的手写数字识别

代码实例,PyTorch实现基于CNN的手写数字识别,希望对您有所帮助。文章目录环境说明一、模型训练1.1导入相关依赖1.2选择使用的硬件1.3超参数配置1.4准备训练集和测试集1.5模型创建1.6模型评估指标1.7模型训练1.8模型测试1.9模型... 查看详情

ai常用框架和工具丨13.pytorch实现基于cnn的手写数字识别

代码实例,PyTorch实现基于CNN的手写数字识别,希望对您有所帮助。文章目录环境说明一、模型训练1.1导入相关依赖1.2选择使用的硬件1.3超参数配置1.4准备训练集和测试集1.5模型创建1.6模型评估指标1.7模型训练1.8模型测试1.9模型... 查看详情

ai常用框架和工具丨13.pytorch实现基于cnn的手写数字识别

代码实例,PyTorch实现基于CNN的手写数字识别,希望对您有所帮助。文章目录环境说明一、模型训练1.1导入相关依赖1.2选择使用的硬件1.3超参数配置1.4准备训练集和测试集1.5模型创建1.6模型评估指标1.7模型训练1.8模型测试1.9模型... 查看详情

pytorch实现基于charrnn的文本分类与生成(代码片段)

Pytorch实现基于CharRNN的文本分类与生成标签:deep-learningpytorchnlp1简介本篇主要介绍使用pytorch实现基于CharRNN来进行文本分类与内容生成所需要的相关知识,并最终给出完整的实现代码。2相关API的说明pytorch框架中每种网络... 查看详情