1. 前言

之前讲过关于模型剪枝的文章深度学习算法优化系列七 | ICCV 2017的一篇模型剪枝论文,也是2019年众多开源剪枝项目的理论基础 并分析过如何利用这个通道剪枝算法对常见的分类模型如VGG16/ResNet/DenseNet进行剪枝,见深度学习算法优化系列八 | VGG,ResNet,DenseNe模型剪枝代码实战 ,感兴趣的可以去看看。这篇推文主要是介绍一下如何将这个通道剪枝算法应用到YOLOV3上,参考的Github工程地址为:https://github.com/Lam1360/YOLOv3-model-pruning

2. 项目整体把握

这个YOLOV3的剪枝工程是基于U版的YOLOV3的,也就是说我们可以直接将U版训练的YOLOV3模型加载到这里进行剪枝。另外还在工程下的models.py中也实现了对DarkNet模型的加载和保存(无论是官方的DarkNet还是AlexeyAB的DarkNet),对应着models.pyDarknet类的load_darknet_weightssave_darknet_weights函数,这里可以简单看一下:

def load_darknet_weights(self, weights_path):"""解析和存储在'weights_path'路径的DarkNet模型"""# 打开权重文件with open(weights_path, "rb") as f:header = np.fromfile(f, dtype=np.int32, count=5)  # 前5行是头部的标题值self.header_info = header  # 保存权重时需要写入标题值self.seen = header[3]  # 训练的时候每次训练几张图片weights = np.fromfile(f, dtype=np.float32)  # 剩下的是权重# 确定加载骨干网络的截断节点cutoff = Noneif "darknet53.conv.74" in weights_path:cutoff = 75ptr = 0# 遍历整个模型(Pytorch下的)for i, (module_def, module) in enumerate(zip(self.module_defs, self.module_list)):if i == cutoff:breakif module_def["type"] == "convolutional":conv_layer = module[0]if module_def["batch_normalize"]:# Load BN bias, weights, running mean and running variancebn_layer = module[1]num_b = bn_layer.bias.numel()  # Number of biases# Biasbn_b = torch.from_numpy(weights[ptr : ptr + num_b]).view_as(bn_layer.bias)bn_layer.bias.data.copy_(bn_b)ptr += num_b# Weightbn_w = torch.from_numpy(weights[ptr : ptr + num_b]).view_as(bn_layer.weight)bn_layer.weight.data.copy_(bn_w)ptr += num_b# Running Meanbn_rm = torch.from_numpy(weights[ptr : ptr + num_b]).view_as(bn_layer.running_mean)bn_layer.running_mean.data.copy_(bn_rm)ptr += num_b# Running Varbn_rv = torch.from_numpy(weights[ptr : ptr + num_b]).view_as(bn_layer.running_var)bn_layer.running_var.data.copy_(bn_rv)ptr += num_b# Load conv. weightsnum_w = conv_layer.weight.numel()conv_w = torch.from_numpy(weights[ptr: ptr + num_w]).view_as(conv_layer.weight)conv_layer.weight.data.copy_(conv_w)ptr += num_welse:# 对于yolov3.weights,不带bn的卷积层就是YOLO前的卷积层if "yolov3.weights" in weights_path:num_b = 255ptr += num_bnum_w = int(self.module_defs[i-1]["filters"]) * 255ptr += num_welse:# Load conv. biasnum_b = conv_layer.bias.numel()conv_b = torch.from_numpy(weights[ptr : ptr + num_b]).view_as(conv_layer.bias)conv_layer.bias.data.copy_(conv_b)ptr += num_b# Load conv. weightsnum_w = conv_layer.weight.numel()conv_w = torch.from_numpy(weights[ptr : ptr + num_w]).view_as(conv_layer.weight)conv_layer.weight.data.copy_(conv_w)ptr += num_w# 确保指针到达权重的最后一个位置assert ptr == len(weights)# 保存DarkNet类型权重(*.weights)def save_darknet_weights(self, path, cutoff=-1):"""@:param path    - 新的权重的路径@:param cutoff  - 保存0到cutoff层之间的所有层权重(cutoff=-1代表所有层全部保留)"""fp = open(path, "wb")self.header_info[3] = self.seenself.header_info.tofile(fp)# Iterate through layersfor i, (module_def, module) in enumerate(zip(self.module_defs[:cutoff], self.module_list[:cutoff])):if module_def["type"] == "convolutional":conv_layer = module[0]# If batch norm, load bn firstif module_def["batch_normalize"]:bn_layer = module[1]bn_layer.bias.data.cpu().numpy().tofile(fp)bn_layer.weight.data.cpu().numpy().tofile(fp)bn_layer.running_mean.data.cpu().numpy().tofile(fp)bn_layer.running_var.data.cpu().numpy().tofile(fp)# Load conv biaselse:conv_layer.bias.data.cpu().numpy().tofile(fp)# Load conv weightsconv_layer.weight.data.cpu().numpy().tofile(fp)fp.close()

所以这个工程对我们来说是非常容易上手使用的,特别是关注公众号时间稍长的读者都知道我们针对U版的YOLOV3写了一个非常不错的PDF,地址如下:从零开始学习YOLOv3教程资源分享 。

切回正题,我们现在可以方便的加载预训练模型了,既可以是BackBone也可以是整个YOLOV3模型,那么接下来我们就可以针对这个YOLOV3模型进行「稀疏训练」。当然也可以不用预训练模型直接从头开始稀疏训练,但这样可能训练时间需要更久一些。注意在训练之前应该先组织好自己的目标检测数据集,这一点在上面的PDF或者作者的README都写的很清楚这里就不再讲了。

2.1 稀疏训练的原理

深度学习算法优化系列七 | ICCV 2017的一篇模型剪枝论文,也是2019年众多开源剪枝项目的理论基础 的想法是对于每一个通道都引入一个缩放因子,然后和通道的输出相乘。接着联合训练网络权重和这些缩放因子,最后将小缩放因子的通道直接移除,微调剪枝后的网络,特别地,目标函数被定义为:

损失函数

其中(x,y)代表训练数据和标签,W是网络的可训练参数,第一项是CNN的训练损失函数。g(.)是在缩放因子上的乘法项,是两项的平衡因子。论文的实验过程中选择,即L1正则化,这也被广泛的应用于稀疏化。次梯度下降法作为不平滑(不可导)的L1惩罚项的优化方法,另一个建议是使用平滑的L1正则项取代L1惩罚项,尽量避免在不平滑的点使用次梯度。

这里的缩放因子就是BN层的gamma参数。

train.py的实现中支持了稀疏训练,其中下面这2行代码即添加了稀疏训练的稀疏系数,注意是作用在BN层的缩放系数上的:

parser.add_argument('--sparsity-regularization', '-sr', dest='sr', action='store_true',help='train with channel sparsity regularization')
parser.add_argument('--s', type=float, default=0.01, help='scale sparse rate')

而稀疏训练的具体实现就在工程下面的utils/prune_utils.py中,代码如下:

class BNOptimizer():@staticmethoddef updateBN(sr_flag, module_list, s, prune_idx):if sr_flag:for idx in prune_idx:# Squential(Conv, BN, Lrelu)bn_module = module_list[idx][1]bn_module.weight.grad.data.add_(s * torch.sign(bn_module.weight.data))  # L1

可以看到这里实现了一个BNOptimizer类,并重写了updateBN成员函数,然后在train.py中执行反向传播之后再手动调用这个函数更新一下BN层的梯度,如下:

稀疏训练

2.2 YOLOV3模型剪枝

在稀疏训练之后我们就可以考虑对YOLOV3模型进行剪枝了,即调用工程下的test_prune.py文件,代码解释如下:

from models import *
from utils.utils import *
import torch
import numpy as np
from copy import deepcopy
from test import evaluate
from terminaltables import AsciiTable #AsciiTable是最简单的表。它使用+,|和-字符来构建边框。
import time
from utils.prune_utils import * #剪枝相关的实现都在这里class opt():model_def = "config/yolov3-hand.cfg" # cfg文件,存储网络结构data_config = "config/oxfordhand.data" # 存储类别,训练验证集路径,类别对应名字等model = 'checkpoints/yolov3_ckpt.pth' # 稀疏训练之后的模型#%%
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #设备
model = Darknet(opt.model_def).to(device)
model.load_state_dict(torch.load(opt.model)) #加载模型
# 解析config文件
data_config = parse_data_config(opt.data_config)
valid_path = data_config["valid"] #获取验证集路径
class_names = load_classes(data_config["names"]) #加载类别对应名字eval_model = lambda model:evaluate(model, path=valid_path, iou_thres=0.5, conf_thres=0.01,nms_thres=0.5, img_size=model.img_size, batch_size=8)
obtain_num_parameters = lambda model:sum([param.nelement() for param in model.parameters()])#前向推理的模型origin_model_metric = eval_model(model)#原始模型的评价指标(还没有剪枝)
origin_nparameters = obtain_num_parameters(model)#原始模型的参数
# 返回CBL组件的id,单独的Conv层的id,以及需要被剪枝的层的id
CBL_idx, Conv_idx, prune_idx= parse_module_defs(model.module_defs)
# 获取CBL组件的BN层的权重,即Gamma参数,我们会根据这个参数来剪枝
bn_weights = gather_bn_weights(model.module_list, prune_idx)
# 按照Gamma参数的大小进行排序,注意[0]返回的是值不是下标
sorted_bn = torch.sort(bn_weights)[0]# 避免剪掉所有channel的最高阈值(每个BN层的gamma的最大值的最小值即为阈值上限)
highest_thre = []
for idx in prune_idx:highest_thre.append(model.module_list[idx][1].weight.data.abs().max().item())
highest_thre = min(highest_thre)# 找到highest_thre对应的下标对应的百分比
percent_limit = (sorted_bn==highest_thre).nonzero().item()/len(bn_weights)print(f'Threshold should be less than {highest_thre:.4f}.')
print(f'The corresponding prune ratio is {percent_limit:.3f}.')#开始剪枝
def prune_and_eval(model, sorted_bn, percent=.0):# 请看https://blog.csdn.net/sodalife/article/details/89461030的解释model_copy = deepcopy(model)# 需要剪枝的下标阈值thre_index = int(len(sorted_bn) * percent)# 需要剪枝的权重阈值,即<thre那么这个通道就剪枝掉,因为这个通道不那么重要了thre = sorted_bn[thre_index]print(f'Channels with Gamma value less than {thre:.4f} are pruned!')remain_num = 0for idx in prune_idx:bn_module = model_copy.module_list[idx][1]# 返回不需要剪枝的通道下标mask = obtain_bn_mask(bn_module, thre)# 记录保留的通道数目remain_num += int(mask.sum())# BN层的权重(gamma)乘以这个mask,就相当于剪枝了bn_module.weight.data.mul_(mask)# 计算剪枝后的模型的mAPmAP = eval_model(model_copy)[2].mean()print(f'Number of channels has been reduced from {len(sorted_bn)} to {remain_num}')print(f'Prune ratio: {1-remain_num/len(sorted_bn):.3f}')print(f'mAP of the pruned model is {mAP:.4f}')# 返回需要剪枝的权重阈值return thre
# 表示剪枝掉85%的参数
percent = 0.85
# 求需要剪枝的权重阈值
threshold = prune_and_eval(model, sorted_bn, percent)
# 获取每一个BN层通道状态
def obtain_filters_mask(model, thre, CBL_idx, prune_idx):pruned = 0total = 0num_filters = []filters_mask = []for idx in CBL_idx:bn_module = model.module_list[idx][1]# 如果idx是在剪枝下标的列表中,就执行剪枝if idx in prune_idx:mask = obtain_bn_mask(bn_module, thre).cpu().numpy()# 保留的通道数remain = int(mask.sum())# 剪掉的通道数pruned = pruned + mask.shape[0] - remainif remain == 0:print("Channels would be all pruned!")raise Exceptionprint(f'layer index: {idx:>3d} \t total channel: {mask.shape[0]:>4d} \t 'f'remaining channel: {remain:>4d}')else:# 不用剪枝就全部保留mask = np.ones(bn_module.weight.data.shape)remain = mask.shape[0]total += mask.shape[0]num_filters.append(remain)filters_mask.append(mask.copy())prune_ratio = pruned / totalprint(f'Prune channels: {pruned}\tPrune ratio: {prune_ratio:.3f}')# 输出每层保留的通道数列表和每一个需要剪枝的BN层的保留通道数状态return num_filters, filters_mask
# 调用上面的函数
num_filters, filters_mask = obtain_filters_mask(model, threshold, CBL_idx, prune_idx)#映射成一个字典,idx->mask
CBLidx2mask = {idx: mask for idx, mask in zip(CBL_idx, filters_mask)}
# 获得剪枝后的模型
pruned_model = prune_model_keep_size(model, prune_idx, CBL_idx, CBLidx2mask)
# 对剪枝后的模型进行评价
eval_model(pruned_model)# 拷贝一份原始模型的参数
compact_module_defs = deepcopy(model.module_defs)
# 遍历需要剪枝的CBL模块,将通道数设置为剪枝后的通道数
for idx, num in zip(CBL_idx, num_filters):assert compact_module_defs[idx]['type'] == 'convolutional'compact_module_defs[idx]['filters'] = str(num)#compact_model是剪枝之后的网络的真实结构(注意上面的剪枝网络只是把那些需要剪枝的卷积层/BN层/激活层通道的权重置0了,并没有保存剪枝后的网络)
compact_model = Darknet([model.hyperparams.copy()] + compact_module_defs).to(device)
# obtain_num_parameters = lambda model:sum([param.nelement() for param in model.parameters()])
# 计算参数量,MFLOPs
compact_nparameters = obtain_num_parameters(compact_model)
# 为剪枝后的真实网络结构重新复制权重参数
init_weights_from_loose_model(compact_model, pruned_model, CBL_idx, Conv_idx, CBLidx2mask)# 随机初始化一个输入
random_input = torch.rand((1, 3, model.img_size, model.img_size)).to(device)
# 获取模型的推理时间
def obtain_avg_forward_time(input, model, repeat=200):model.eval()start = time.time()with torch.no_grad():for i in range(repeat):output = model(input)avg_infer_time = (time.time() - start) / repeatreturn avg_infer_time, output
# 分别获取原始模型和剪枝后的模型的推理时间和输出
pruned_forward_time, pruned_output = obtain_avg_forward_time(random_input, pruned_model)
compact_forward_time, compact_output = obtain_avg_forward_time(random_input, compact_model)
# 计算原始模型推理结果和剪枝后的模型的推理结果,如果差距比较大说明哪里错了
diff = (pruned_output-compact_output).abs().gt(0.001).sum().item()
if diff > 0:print('Something wrong with the pruned model!')#%%
# 在测试集上测试剪枝后的模型, 并统计模型的参数数量
compact_model_metric = eval_model(compact_model)#%%
# 比较剪枝前后参数数量的变化、指标性能的变化
metric_table = [["Metric", "Before", "After"],["mAP", f'{origin_model_metric[2].mean():.6f}', f'{compact_model_metric[2].mean():.6f}'],["Parameters", f"{origin_nparameters}", f"{compact_nparameters}"],["Inference", f'{pruned_forward_time:.4f}', f'{compact_forward_time:.4f}']
]
print(AsciiTable(metric_table).table)#%%
# 生成剪枝后的cfg文件并保存模型
pruned_cfg_name = opt.model_def.replace('/', f'/prune_{percent}_')
pruned_cfg_file = write_cfg(pruned_cfg_name, [model.hyperparams.copy()] + compact_module_defs)
print(f'Config file has been saved: {pruned_cfg_file}')compact_model_name = opt.model.replace('/', f'/prune_{percent}_')
torch.save(compact_model.state_dict(), compact_model_name)
print(f'Compact model has been saved: {compact_model_name}')

然后我们针对utils/prune_utils.py里面的一些核心函数再解析一下,首先parse_module_defs这个函数:

def parse_module_defs(module_defs):CBL_idx = []#Conv+BN+ReLUConv_idx = []#Convfor i, module_def in enumerate(module_defs):if module_def['type'] == 'convolutional':if module_def['batch_normalize'] == '1':CBL_idx.append(i)else:Conv_idx.append(i)ignore_idx = set()#哪些层不需要剪枝for i, module_def in enumerate(module_defs):if module_def['type'] == 'shortcut':ignore_idx.add(i-1)identity_idx = (i + int(module_def['from']))if module_defs[identity_idx]['type'] == 'convolutional':ignore_idx.add(identity_idx)elif module_defs[identity_idx]['type'] == 'shortcut':ignore_idx.add(identity_idx - 1)ignore_idx.add(84)ignore_idx.add(96)prune_idx = [idx for idx in CBL_idx if idx not in ignore_idx]# 返回CBL组件的id,单独的Conv层的id,以及需要被剪枝的层的idreturn CBL_idx, Conv_idx, prune_idx

接下来是gather_bn_weights函数:

def gather_bn_weights(module_list, prune_idx):size_list = [module_list[idx][1].weight.data.shape[0] for idx in prune_idx]bn_weights = torch.zeros(sum(size_list))index = 0for idx, size in zip(prune_idx, size_list):bn_weights[index:(index + size)] = module_list[idx][1].weight.data.abs().clone()index += size# 获取CBL组件的BN层的权重,即Gamma参数,我们会根据这个参数来剪枝return bn_weights

再看下obtain_bn_mask函数,这个函数返回是否需要剪枝的通道状态:

def obtain_bn_mask(bn_module, thre):thre = thre.cuda()# ge(a, b)相当于 a>= bmask = bn_module.weight.data.abs().ge(thre).float()# 返回通道是否需要剪枝的通道状态return mask

还有prune_model_keep_size函数,这个函数将原始模型利用我们刚获得的每个CBL模块的通道保留状态值对每个层的权重进行更新,包括卷积层,BN层和LeakyReLU层。需要注意的是上面的prune_and_eval函数只是更新了BN层剪枝后的权重,没有更新卷积层的权重和LeakyReLU层的权重,代码实现如下:

def prune_model_keep_size(model, prune_idx, CBL_idx, CBLidx2mask):# 先拷贝一份原始的模型参数pruned_model = deepcopy(model)# 对需要剪枝的层分别处理for idx in prune_idx:# 需要保留的通道mask = torch.from_numpy(CBLidx2mask[idx]).cuda()# 获取BN层的gamma参数,即BN层的权重bn_module = pruned_model.module_list[idx][1]bn_module.weight.data.mul_(mask)# 获取保留下来的通道产生的激活值,注意是每个通道分别获取的activation = F.leaky_relu((1 - mask) * bn_module.bias.data, 0.1)# 两个上采样层前的卷积层next_idx_list = [idx + 1]if idx == 79:next_idx_list.append(84)elif idx == 91:next_idx_list.append(96)# 对下一层进行处理for next_idx in next_idx_list:# 当前层的BN剪枝之后会对下一个卷积层造成影响next_conv = pruned_model.module_list[next_idx][0]# dim=(2,3)即在(w,h)维度上进行求和,因为是通道剪枝,一个通道对应着(w,h)这个矩形conv_sum = next_conv.weight.data.sum(dim=(2, 3))# 将卷积层的权重和激活值想乘获得剪枝后的每个通道的偏执,以更新下一个BN层或者下一个带偏执的卷积层的偏执(因为单独的卷积层是不会被剪枝的,所以只对偏执有影响)offset = conv_sum.matmul(activation.reshape(-1, 1)).reshape(-1)if next_idx in CBL_idx:next_bn = pruned_model.module_list[next_idx][1]next_bn.running_mean.data.sub_(offset)else:next_conv.bias.data.add_(offset)bn_module.bias.data.mul_(mask)# 返回剪枝后的模型return pruned_model

最后就是本文最核心的代码部分了,在上面的test_prune.py中代码段如下:

# 拷贝一份原始模型的参数
compact_module_defs = deepcopy(model.module_defs)
# 遍历需要剪枝的CBL模块,将通道数设置为剪枝后的通道数
for idx, num in zip(CBL_idx, num_filters):assert compact_module_defs[idx]['type'] == 'convolutional'compact_module_defs[idx]['filters'] = str(num)#compact_model是剪枝之后的网络的真实结构(注意上面的剪枝网络只是把那些需要剪枝的卷积层/BN层/激活层通道的权重置0了,并没有保存剪枝后的网络)
compact_model = Darknet([model.hyperparams.copy()] + compact_module_defs).to(device)
# obtain_num_parameters = lambda model:sum([param.nelement() for param in model.parameters()])
# 计算参数量,MFLOPs
compact_nparameters = obtain_num_parameters(compact_model)
# 为剪枝后的真实网络结构重新复制权重参数
init_weights_from_loose_model(compact_model, pruned_model, CBL_idx, Conv_idx, CBLidx2mask)

可以看到获得剪枝后的网络结构不难,要给这个剪枝后的网络结构重新拷贝一份参数看起来麻烦一些,我们一起来看看这个init_weights_from_loose_model函数,代码如下:

def init_weights_from_loose_model(compact_model, loose_model, CBL_idx, Conv_idx, CBLidx2mask):for idx in CBL_idx:compact_CBL = compact_model.module_list[idx]loose_CBL = loose_model.module_list[idx]# np.argwhere返回非0元素的索引,X[:,0]是numpy中数组的一种写法,表示对一个二维数组,取该二维数组第一维中的所有数据,第二维中取第0个数据out_channel_idx = np.argwhere(CBLidx2mask[idx])[:, 0].tolist()# 获取剪枝后的模型当前BN层的权重compact_bn, loose_bn         = compact_CBL[1], loose_CBL[1]compact_bn.weight.data       = loose_bn.weight.data[out_channel_idx].clone()compact_bn.bias.data         = loose_bn.bias.data[out_channel_idx].clone()compact_bn.running_mean.data = loose_bn.running_mean.data[out_channel_idx].clone()compact_bn.running_var.data  = loose_bn.running_var.data[out_channel_idx].clone()# 获取剪枝后的模型当前卷积层的权重,这和上一个卷积层的剪枝情况有关input_mask = get_input_mask(loose_model.module_defs, idx, CBLidx2mask)in_channel_idx = np.argwhere(input_mask)[:, 0].tolist()compact_conv, loose_conv = compact_CBL[0], loose_CBL[0]# 拷贝权重到剪枝后的模型中去tmp = loose_conv.weight.data[:, in_channel_idx, :, :].clone()compact_conv.weight.data = tmp[out_channel_idx, :, :, :].clone()for idx in Conv_idx:compact_conv = compact_model.module_list[idx][0]loose_conv = loose_model.module_list[idx][0]# 虽然当前层是不带BN的卷积层,但仍然和上一个层的剪枝情况是相关的input_mask = get_input_mask(loose_model.module_defs, idx, CBLidx2mask)in_channel_idx = np.argwhere(input_mask)[:, 0].tolist()# 拷贝权重到剪枝后的模型中去compact_conv.weight.data = loose_conv.weight.data[:, in_channel_idx, :, :].clone()compact_conv.bias.data   = loose_conv.bias.data.clone()

其中还有一个关键函数get_input_mask,这是获取网络中当前层的前一层的通道状态,因为在剪枝的时候当前层的通道保留情况是受到它前面是哪些层影响的。

def get_input_mask(module_defs, idx, CBLidx2mask):# 获取上一层的通道状态if idx == 0:return np.ones(3)if module_defs[idx - 1]['type'] == 'convolutional':return CBLidx2mask[idx - 1]elif module_defs[idx - 1]['type'] == 'shortcut':return CBLidx2mask[idx - 2]elif module_defs[idx - 1]['type'] == 'route':route_in_idxs = []for layer_i in module_defs[idx - 1]['layers'].split(","):if int(layer_i) < 0:route_in_idxs.append(idx - 1 + int(layer_i))else:route_in_idxs.append(int(layer_i))if len(route_in_idxs) == 1:return CBLidx2mask[route_in_idxs[0]]elif len(route_in_idxs) == 2:return np.concatenate([CBLidx2mask[in_idx - 1] for in_idx in route_in_idxs])else:print("Something wrong with route module!")raise Exception

到这里剪枝这部分就讲完了,我基本上是一句句理清了整个流程,希望对想接触模型剪枝的同学有所帮助。

2.3 微调

在训练完之后如果准确率不够高还需要适当的FineTune一下,因为剪枝之后模型的结构有变化可能会对准确率有些影响,所以可以适当的微调几个Epoch。

3. 结果

https://github.com/Lam1360/YOLOv3-model-pruning/blob/master/config/prune_yolov3-hand.cfg展示了在Oxfard人手数据集上对YOLOV3模型进行剪枝后的模型结构,可以看到部分卷积层的通道数大幅度减少。

最后,剪枝前后的模型指标对比如下:

剪枝前后YOLOV3模型对比

4. 结论

本文还是展示了如何对YOLOV3模型进行剪枝的原理和详细代码解析,希望可以帮助到正在学习模型剪枝的同学。不过需要强调的是,不是所有的模型通过这个方法都能得到很好的剪枝效果,这和你模型本身的容量以及数据集等等都是相关的,后面我会继续分享更多的模型加速技术,谢谢观看。

5. 参考

  • https://github.com/Lam1360/YOLOv3-model-pruning

如何让你的YOLOV3模型更小更快?相关推荐

  1. 苹果公司提出Mobile-ViT | 更小更轻精度更高,MobileNets或成为历史

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨ChaucerG 来源丨集智书童 MobileviT是一个用于移动设备的轻量级通用可视化Tran ...

  2. Google 开源的依赖注入库,比 Spring 更小更快!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:GinoBeFunny zhuanlan.zhihu.com ...

  3. 海尔微型计算机云悦mini2a,更小更轻新 主机云悦mini2A参数曝光

    大哥大流行的年代,没人能想到手机能发展到多小,世界上第一台30吨重的计算机诞生时,也没人能想到主机能发展到只有一瓶矿泉水的重量.海尔电脑旗下的云悦mini可谓主机进化史上的缩影,0.6L体积,0.45 ...

  4. hp510台式计算机,体积更小更时尚 惠普Pavilion 510台式机简评

    体积更小更时尚 惠普Pavilion 510台式机简评 2016年07月13日 01:00作者:王普编辑:王普文章出处:泡泡网原创 分享 随着DIY市场的萎靡,PC行业早已没有当年那番景气,但是大家对 ...

  5. EfficientNet v2网络学习记录--更小更快

    论文地址:2021 CVPR --Google <EfficientNetV2: Smaller Models and Faster Training> 亮点: 使用训练感知神经结构搜索和 ...

  6. 蜂鸟视图JS SDK v3.0:五大亮点,打造更小更快的可视化地图应用

    近期,蜂鸟视图发布了FengMap JavaScript SDK v3.0版本,该版本在不减少任何功能的情况下,对原有的渲染内核进行了优化,使得数据加载性能提升了30%,渲染性能提升了25%,包体大小 ...

  7. 麻省理工人工智能实验室发现更小更容易训练的神经网络

    https://www.toutiao.com/a6688201699858842119/ 2019-05-07 16:38:26 神经网络技术起源于上世纪五.六十年代,当时叫感知机,拥有输入层.输出 ...

  8. 尺寸直降28%!斯坦德推出更小更灵活的AMR物流机器人

    经由客户项目现场测试打磨,结合3C制造场景下产线空间受限等挑战,斯坦德推出了新型号AMR物流机器人--Oasis300C.该型号AMR延续了同系列产品功能设定,同时凭借更小的体型在更多极限场景与运动控 ...

  9. Poor Man's BERT: 更小更快的Transformer模型

    文 | sliderSun 源 | 知乎 NLP模型的大小不断增加,随之而来的是可用性降低,TinyBERT,MobileBERT,和DistilBERT都提出了一个独特的知识蒸馏框架,其共同目标是在 ...

最新文章

  1. 内存分配的原理__进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)
  2. C语言Catalan number卡特兰数(使用n个键可以搜索多少个二叉搜索树)的算法(附完整源码)
  3. 懒加载 jquery代码
  4. 我们去的地方一定会有屎
  5. c与指针 从一个字符串中提取子串_利用双指针解LeetCode第1297题:子串的最大出现次数
  6. 手推RNN BPTT(back propagation through time)反向传播
  7. 阶段5 3.微服务项目【学成在线】_day01 搭建环境 CMS服务端开发_20-页面查询服务端开发-创建CMS服务工程-测试Controller...
  8. c语言编程串级控制,组态王-串级控制
  9. 洛谷P1427 小鱼的数字游戏
  10. 如何解决hangfire使用redis存储时,如果采用了prefix报“Key has MOVED from Endpoint”的错...
  11. 干货 | 产品经理如何画脑图
  12. 亚马逊运营怎么做广告?六大方法!
  13. mp2 解码器kjmp输出32bit
  14. Hoj 1789 Electricity
  15. 学习数据可视化,读哪本书比较好?
  16. 会计学原理学习笔记——第三章——账户与复式记账(3.5生产准备业务核算——费用发生业务核算)
  17. PHP empty()
  18. 凝胶色谱法GPC仪器原理
  19. 技术派-减少connect超时设置
  20. 中兴通讯三位元老级副总裁辞职 或与违规有关

热门文章

  1. I春秋CTF训练营web题解(一)
  2. 旭日图更好地呈现数据的层次结构,细致划分各项数据
  3. Python神经网络编程学习记录(一)
  4. ca根证书校验 java_JAVA-Android-根据CA证书验证X509Certificate(颁发者证书)
  5. 解决 k8s flannel网络 一直 Init:ImagePullBackOff和coredns状态为Pending
  6. Word文件如何查看字数
  7. 最有效的穴位按摩减肥法
  8. 三跨考生准备考研复试(机试)之路(日记版)
  9. TCP/IP 模型 与 OSI 七层模型的对应关系
  10. zabbix Trapper 监控项配置