news 2026/9/28 15:50:43

YOLOv8s结构化剪枝实战:从BN稀疏化到模型通道重建

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv8s结构化剪枝实战:从BN稀疏化到模型通道重建

做模型压缩这两年,我经手的检测模型里YOLOv8s剪枝算是最常被问到的需求之一。原因很简单:YOLOv8s本身是个不错的基线,但部署到边缘设备或者追求高帧率时,参数和FLOPs还是嫌多。剪枝,尤其是结构化剪枝,是比量化更温和、也更考验细节的一条路。今天我就把这套针对yolov8s的剪枝源码拆开讲清楚,从原理到踩坑,顺序捋一遍,你需要跑通或者复现的时候按这个来就行。

1. 项目概述与需求拆解

1.1 为什么选择yolov8s作为剪枝对象

YOLOv8s在YOLOv8系列里属于“小但够用”的定位。相比n版本精度更高,相比m版本速度快不少,很多项目拿它当初始模型,训练完直接转ONNX再用TensorRT部署。但问题也随之而来:如果你的摄像头路数多、芯片算力有限,或者对延迟有硬性要求,哪怕是s版本也得再瘦身。

剪枝的目标不是把模型压到极致,而是尽量在保住mAP的前提下把冗余通道去掉。我做过几次实验,物体检测模型经常存在大量接近零贡献的通道,尤其是骨干网络后几层的特征通道。这些通道在训练时参与了前向传播,但重要性很低,删掉之后网络依然能输出相近的结果。yolov8s因为结构规整、卷积层密集,剪枝效果好也容易分析,不像一些轻量化网络本身通道已经抠得很紧,剪了就崩。

1.2 剪枝能带来什么实际收益

大家最关心的其实是三个数字:模型体积、FLOPs、推理延迟。对一个输入尺寸640x640的yolov8s来说,原始模型大小大概在22MB左右(权重文件),FLOPs在28G左右。我用这套源码做过一轮实验,剪掉35%的通道后,模型体积降到12MB上下,FLOPs降到16G附近,GPU推理延迟约降低了25%,CPU上降低更明显。

不过这里我要先泼一盆冷水:延迟不一定和FLOPs严格同比降低。原因在于剪枝后的通道数如果不满足某些硬件对齐要求,内存访问的连续性反而受影响。TensorRT优化之后效果会好很多,但直接在PyTorch里测,有时候只快10%到15%。所以你在看效果时一定要用目标推理框架测,不要只盯着FLOPs数字。

1.3 这套源码到底解决什么问题

网上的剪枝代码零零散散,很多是针对分类网络的,拿到检测模型上直接跑不通。YOLOv8有C2f模块和Detect检测头,结构比ResNet复杂,shortcut连接也更多。如果直接套用普通通道剪枝脚本,弹出的第一个错误就是维度不匹配。

这套源码的核心思路是围绕YOLOv8本身定制的,主要解决三件事:

  • 支持对C2f模块、骨干和检测头做通道级剪枝
  • 自动处理shortcut残差分支的通道对齐问题
  • 提供剪枝后的模型结构重新生成策略,避免手工改yaml改到崩溃

简而言之,你需要的不是一个通用剪枝库,而是一套能适配实际检测模型、能直接落地跑通的流程。下面的内容就是把这套流程的每个环节说清楚。

2. 剪枝方案选型:结构化剪枝与非结构化剪枝

2.1 两种主要剪枝路线对比

剪枝算法的大致分类,很多新手一开始会混淆。简单说:

非结构化剪枝是把权重矩阵里接近0的小权重单独置零,得到的是稀疏矩阵。这种剪枝不会改变网络结构,但需要专门的稀疏推理库或者硬件支持才能加速。所谓“细粒度剪枝”说的也是这种。非结构化剪枝的压缩率可以做得很高,但在常规CPU和GPU上没有明显加速收益。

结构化剪枝是把整个通道或者整个卷积核删掉。删除之后特征图的通道数变少,后续卷积层的输入维度也变小,网络结构真实变瘦。这种剪枝不需要特殊硬件,任何深度学习框架都能正常加载推理。

我说得直白一点:你要部署,选结构化剪枝;你只是研究算法或者追求极限压缩率,非结构化剪枝可以玩,但工程化价值低很多。这套源码做的通道剪枝就是结构化剪枝的一种,特别适合YOLOv8这种多卷积层的网络。

2.2 为什么选择基于BN层的通道剪枝

通道剪枝的关键问题是:怎么判断哪个通道该剪。判断方式有很多,比如基于权重大小、基于激活值统计、基于梯度信息,但工程上最常用也最好实现的,是借助批归一化层(BN)的缩放因子gamma。

YOLOv8的每个卷积模块后面基本都跟了BN层。BN做的事情是把输入归一化到均值0方差1,然后再做一次线性变换:

[ y = \gamma \hat{x} + \beta ]

这里的gamma是每个通道一个可学习的缩放因子。如果某个通道的gamma值被训练到接近0,那么这个通道的输出基本就是一个常数偏移,对后续结果几乎没有贡献,删除它是很合理的。

基于这个思路,网络瘦身(Network Slimming)方法会在训练损失函数里额外加入对gamma的L1正则惩罚,让gamma尽量稀疏化。优化器在正常训练loss之外,还会往gamma的反方向推,把一部分通道逼到接近0。等训练结束后剪枝时,只要统计gamma分布,设置一个阈值,把低于阈值的通道删掉就行。

这套做法实现起来不复杂,训练开销也小,不需要额外的辅助网络或者复杂的显著性计算。对YOLOv8这种深层卷积网络特别适用。

2.3 整体流程框架

整个剪枝流程我总结为四个阶段:

  1. 稀疏化训练:在正常训练目标上加入gamma的L1正则,让BN缩放因子稀疏化
  2. 通道重要度分析:遍历模型所有带BN的层,统计gamma值分布
  3. 结构化剪枝:根据剪枝比例生成每层保留通道的掩码,重建模型结构
  4. 微调恢复:剪枝后的模型精度会有一定损失,需要用原数据集短时间训练找回

这四个阶段里,最容易翻车的是第三阶段。因为YOLOv8里除了普通卷积,还有C2f模块内的跨层连接和Detect检测头的分支结构,这些地方的通道对齐关系必须小心处理。下面我逐个环节展开。

3. 源码实现核心细节

3.1 环境准备与依赖版本

先说依赖。剪枝本身不需要太多额外库,核心是PyTorch和Ultralytics。我测试的版本组合如下:

依赖项推荐版本备注
Python3.8 / 3.103.8以上都行,3.11有些算子编译麻烦
PyTorch1.13 / 2.0.12.0以上对int8量化支持更好
Ultralytics8.0.xx8.0和8.1的模型结构略有差异,注意版本
CUDA11.7 / 12.1按你的显卡驱动装
onnx1.14+导出阶段使用

我踩过的第一个坑就是Ultralytics版本。8.0.40和8.1.x之间,C2f模块的表示方式有微调,如果用旧脚本加载新模型,解析层名时会漏掉一部分。所以源码里建议锁定ultralytics==8.0.127,这个版本结构稳定,网上资料也多。

3.2 稀疏化训练:给BN层加L1正则

稀疏化训练是整个流程里最需要耐心的部分。核心代码不复杂,关键是在每次反向传播后对gamma做额外惩罚。

以ultralytics框架为例,训练循环里可以拿到模型返回的loss,然后再手动加上稀疏项:

import torch def sparse_loss(model, lambda_sparse=1e-5): """ 计算BN层gamma的L1范数,用于让gamma稀疏化 """ loss_sparse = 0.0 for name, module in model.named_modules(): if isinstance(module, torch.nn.BatchNorm2d): # 只对包含gamma的BN层做惩罚 if module.weight is not None: loss_sparse += torch.abs(module.weight).sum() return lambda_sparse * loss_sparse # 在训练循环中叠加 loss = loss_box + loss_cls + loss_dfl loss += sparse_loss(model, lambda_sparse=1e-4) loss.backward() optimizer.step()

这里lambda_sparse的取值很关键。太大会让精度掉很多,太小gamma稀疏效果差。根据我的测试,YOLOv8s在COCO类数据集上,lambda_sparse取1e-4到3e-4比较合适。如果你自己数据集只有几个类别,任务简单,可以稍微加大到5e-4。

有一个很常见的误区:稀疏化训练不是只做几个epoch就够了。如果你是从头训练,应该从第一个epoch就开始加稀疏惩罚,训练到模型收敛。如果是加载预训练权重再稀疏化,至少需要训练80到120个epoch,否则gamma分布没有足够时间被惩罚项推开。

训练完稀疏化模型后,先跑一遍验证集,记录此时的mAP。这个mAP是后面剪枝后微调效果的对照基准。

3.3 通道剪枝与模型结构重建

剪枝阶段,我会遍历模型的所有卷积层,找出每个卷积层对应的BN层,然后根据gamma值排序来确定哪些通道要保留。

YOLOv8的结构中有这些需要特别注意的地方:

  • C2f模块由多个Bottleneck组成,Bottleneck里有shortcut分支,剪枝时所有并行的卷积层输出通道必须保持一致
  • Detect头有多个不同尺度的分支,不能单独看每个分支的gamma,否则会出现通道数不一致
  • 下采样层的通道数变化倍率需要和后续层匹配

我写了一个简单的层解析函数,把每层的依赖关系整理成字典:

def parse_model_channels(model): """ 返回每个卷积层对应的输入通道、输出通道以及依赖的层 """ channel_info = [] prev_channels = 3 # 输入图片RGB通道 for name, module in model.named_modules(): if isinstance(module, torch.nn.Conv2d): in_ch = module.in_channels out_ch = module.out_channels channel_info.append({ 'name': name, 'in_channels': in_ch, 'out_channels': out_ch, 'bn_attached': find_bn_after(model, name), # 找到紧接的BN层 }) return channel_info

剪枝的核心是生成一个“保留通道掩码”。假设某个卷积层有128个通道,我们想保留70%,那就对它的BN层gamma按绝对值从大到小排序,取前90个通道的索引作为保留集合。

但有shortcut的地方不能简单这么算。对于C2f里存在残差连接的卷积组,所有相关层必须共享同一个保留下标,否则输出通道数对不上,模型直接报错。

为了处理这个问题,我在源码里做了一步“通道对齐传播”:

  1. 记录每个卷积层的输入来源层
  2. 如果某层的输入来自另一个剪枝后的层,则它的输入通道保留下标由上游层决定
  3. 对于残差相加的地方,把两个分支的保留下标取并集

举个例子,一个Bottleneck模块的结构是:

x -> conv1(1x1) -> conv2(3x3) -> 加上x -> 输出

如果conv1和conv2的输出通道都被剪枝,最后相加时x的通道数必须等于conv2的输出通道数。所以x本身不需要剪枝,但conv1的保留下标一定要由Shortcut分支的输入决定。这个逻辑在源码里是用一个依赖传播函数做的。

剪枝时我不用PyTorch的register_buffer之类的东西,而是直接重建全新模型。首选方法是用ultralytics的yaml配置思路,生成一个剪枝后的新yaml,然后从零加载预训练权重中对应保留的部分。

具体做法是这样的:剪枝后,我会生成一个字典,记录每一层的“原始权重索引到新模型索引”的映射,然后拷贝权重:

def prune_state_dict(old_state_dict, keep_indices_map): new_state_dict = {} for key, value in old_state_dict.items(): # 找到这个key对应的层和参数类型(weight/bias/running_mean等) layer_name, param_type = split_key(key) if layer_name in keep_indices_map: keep_idx = keep_indices_map[layer_name] if param_type == 'weight': # 输出通道对应BN缩放因子 if value.dim() == 4: # 卷积权重 shape: [out_c, in_c, kh, kw] new_value = value[keep_idx] elif value.dim() == 1: # BN weight/bias等 new_value = value[keep_idx] else: new_value = value # 其他情况 else: new_value = value new_state_dict[key] = new_value else: # 没有被剪枝的层原样复制 new_state_dict[key] = value return new_state_dict

这里有一个关键点:如果剪掉了输入通道,那么当前卷积层的输入权重也要按上一个层的保留下标来索引。也就是卷积权重要同时做两次索引,一次是输出通道,一次是输入通道。很容易写错。

完整的权重映射函数如下:

def remap_conv_weight(weight, keep_out_idx, keep_in_idx): """ weight shape: [out_c, in_c, kh, kw] 按输出通道保留下标和输入通道保留下标分别筛选 """ return weight[keep_out_idx][:, keep_in_idx, :, :]

注意,对于1x1卷积和3x3卷积都一样处理,因为kh、kw维度不受影响。

3.4 微调恢复精度

剪枝完成后,千万不要直接拿剪枝后的模型任何数据都不跑就部署。因为gamma稀疏化训练会把一些重要通道的值也压小,直接剪掉会导致特征表示不完整。必须做微调。

微调的epoch不需要像从头训练那么长,一般用原学习率的1/10,训练20到30个epoch即可。比如原训练lr是0.001,微调用0.0001。权重衰减可以不变,但稀疏惩罚项在微调阶段要去掉,否则又会让gamma稀疏,影响恢复。

微调时我习惯把模型结构保存为新的yaml,并用ultralytics的DetectionTrainer加载权重。此时需要注意,剪枝后的模型权重键名要和yaml结构的层名一一对应。如果出现加载错误,多半是yaml中层的定义和权重shape对不上。

微调完再跑验证集,正常情况下mAP能恢复到剪枝前mAP的97%以上。如果差得比较多,检查剪枝比例是否太大或者微调lr是不是没调对。

4. 实操过程与参数调优

4.1 数据准备与训练超参数设置

YOLOv8剪枝训练和你平时的训练流程差不多,数据集格式还是一样的。我这里给一个具体的配置参考,以你自己的数据集为例:

# dataset.yaml train: /path/to/train/images val: /path/to/val/images nc: 5 names: ['person', 'car', 'bike', 'dog', 'cat']

稀疏化训练的超参数,我用的是:

  • epochs: 100(从预训练权重开始的话)
  • batch_size: 32(如果显存有限就16,但BN统计量会受影响)
  • lr0: 0.001
  • weight_decay: 0.0005
  • lambda_sparse: 2e-4
  • 优化器: SGD(比Adam更好控制稀疏化,gamma分布更干净)

SGD配合L1正则的效果比Adam稳定。Adam对每个参数单独调整学习率,会削弱L1惩罚的力度,稀疏化效果不理想。我刚开始用Adam试过,gamma分布拖泥带水,阈值不好定,换成SGD之后顺滑很多。

4.2 剪枝比例选择策略:不要一刀切

这是整个项目里最需要经验和耐心的环节。很多教程会告诉你“剪去30%”或者“剪去50%”,但实际不是每个层都剪相同比例才最优。

深度学习网络有个特点:靠近输入层的特征表征往往更通用,靠近输出层的特征更任务专属。对不同层采用统一比例剪枝,会导致浅层表达能力严重受损,而深层可能还有冗余。我推荐的做法是采用分段比例:

  • 骨干网络的前几层:剪枝比例控制在20%以内
  • 骨干网络的深层:剪枝比例可以到40%到50%
  • C2f模块内部:保持30%左右
  • Detect检测头:尽量少剪,最多剪10%到15%

检测头虽然参数量占比不大,但直接影响最终输出的类别和回归精度,剪多了mAP很难回来。这也是我踩过最深刻的坑,第一次统一剪50%时,检测头直接给剪残了,mAP掉了10多个点。

为了自动确定每层的比例,我在源码里加了一个简单的策略:统计每层BN的gamma绝对值总和,按照总和的反比例分配剪枝强度。总和小的层说明本身重要度集中,少剪;总和大的层说明冗余度高,多剪。

4.3 针对YOLOv8结构特点的定制处理

YOLOv8的Detect头和上一代YOLOv5有区别。YOLOv8的检测头采用了解耦结构,分类和回归分支分开,同时还用DFL(Distribution Focal Loss)。在这个框架下,检测头里包含很多卷积层,通道关系比v5复杂。

剪枝检测头的时候,必须把分类分支和回归分支对应的输出通道做一致性处理。因为最终预测时,每个分支的输出通道数会映射到类别数和锚框相关的参数上,如果剪完通道数对不上,后续解码逻辑就崩了。源码中对检测头统一做了“只修剪内部隐藏层,不修剪最终输出层”的处理。

另外,C2f模块里有一个cv1卷积、n个Bottleneck、一个cv2卷积。Bottleneck里的两个卷积层输出通道必须一致,否则残差相加没法做。而cv2卷积的输入通道是所有Bottleneck输出通道的拼接。剪Bottleneck时,必须保证所有Bottleneck用同一个保留下标集合。这个我在前面解析依赖时已经强调过,这里再加深一下印象。

我实际操作中发现一个高效方案:直接修改ultralytics的yaml,把C2f模块中的Bottleneck数量改少,同时把剪枝后的通道数填到yaml里,这样模型结构更干净。但这样改出来的结构无法复用原来的权重,需要从零训练,耗时比较长。所以我的源码里还是保留权重拷贝方式,能节省时间。

4.4 剪枝后模型的导出与验证

剪枝完成后,除了验证mAP,我还建议做两件事:导出ONNX,确认计算图正确;用目标推理框架跑一次延迟。

导出ONNX时,有几个参数值得注意:

model.eval() dummy_input = torch.randn(1, 3, 640, 640).to(device) torch.onnx.export( model, dummy_input, "pruned_yolov8s.onnx", opset_version=12, input_names=['images'], output_names=['output0'], dynamic_axes={ 'images': {0: 'batch'}, 'output0': {0: 'batch'} } )

如果你用静态batch部署,建议把dynamic_axes去掉,这样TensorRT优化时能做更多层融合。如果要用动态batch,才保留dynamic_axes。

导出之后,我自己会用onnxruntime跑一遍:

python -c " import onnxruntime as ort import numpy as np sess = ort.InferenceSession('pruned_yolov8s.onnx', providers=['CPUExecutionProvider']) x = np.random.randn(1,3,640,640).astype(np.float32) y = sess.run(None, {'images': x}) print('输出shape:', [o.shape for o in y]) "

如果输出shape正常,就说明剪枝后的模型在计算图上没有结构问题。再进一步就是转成TensorRT的engine文件,用trtexec测试延迟。TensorRT对剪枝后的模型通常很友好,因为通道数减少后,每层计算量降低,能获得比PyTorch环境更好的加速。

5. 常见问题与排查技巧实录

5.1 BN层gamma不稀疏,剪了等于白剪

这是最常遇到的问题。稀疏化训练结束后,可视化gamma直方图,发现都集中在0附近但不是真正接近0,或者分布很均匀。这说明L1正则的力度不够,或者训练epoch太短。

解决办法:

  • 检查代码里是否正确遍历到了所有BatchNorm2d层。YOLOv8内部有些模块可能用了可重参数化的结构,部分BN层不在列表里。
  • 增大lambda_sparse,但每次只增大1.5倍左右,不要一下翻十倍。
  • 训练epoch拉长,我遇到过一个数据集从80epoch加到150epoch后gamma稀疏度明显改善。

真正适合剪枝的gamma分布,应该像梳子一样,大部分接近0,少部分明显大于0。如果看不到这种分布,不要强行剪枝。

5.2 剪枝后mAP掉得厉害

剪枝掉几个点是正常的,但如果掉了超过5个点,就需要反思几个因素。

第一个是剪枝比例是否过高,尤其是检测头和C2f模块。前面说过,检测头少剪。

第二个是微调epoch和lr是否合适。我发现微调lr过大反而会破坏已经学到的特征,导致精度无法恢复。微调阶段应该让模型在原权重附近小范围移动,不要跑远。

第三个是训练稀疏化时的batch size。BN的统计量在稀疏化训练时很重要,batch size太小会引入噪声,让gamma值看起来该剪的没剪、不该剪的却很小。我用batch size 32比用16的效果好,有条件可以用64。

5.3 剪枝后模型结构输出shape对不上

这类错误通常发生在C2f的Bottleneck和Detect分支。如果只按单个卷积层的gamma排序剪枝,没有考虑残差连接,那最终拼接通道数必然出错。

我的排查方法是:剪枝后先用一个小测试输入,打印每一层输出的shape,逐层比对。定位到第一个shape不匹配的位置,再回头检查依赖关系图。源码里专门写了auto_check_shape这个函数:

def auto_check_shape(model, input_size=(1, 3, 640, 640)): x = torch.randn(*input_size) hooks = [] def hook_fn(module, input, output): print(f"{module.__class__.__name__}: {output.shape}") for name, module in model.named_modules(): if isinstance(module, torch.nn.Conv2d) or isinstance(module, torch.nn.BatchNorm2d): hooks.append(module.register_forward_hook(hook_fn)) model(x) for hook in hooks: hook.remove()

这个脚本能帮你快速定位是哪一层的通道数出了问题,不用在yaml里猜。

5.4 剪枝后推理速度没有明显提升

这个问题解决思路要分两步走。

第一步,确认剪掉的FLOPs是真的从计算图中消失了。有些工具虽然报告剪枝比例,但PyTorch模型由于结构里有Fan-in、cat操作,实际计算路径中可能有额外的零填充或者换位操作,导致前向时间没降。可以重新导出一遍ONNX,用netron看看计算图结构。

第二步,考虑硬件对齐。GPU推理对通道数通常以32或64为单位,比如TensorRT的某些kernel会按8的倍数对齐。如果你的剪枝保留了比如37个通道,反而比64个通道更慢。所以剪枝时可以做一个“通道圆整”处理,把保留下标数量对齐到8或16的整数倍。源码中支持设置align_channels=16,按这个参数自动调整剪枝策略。

我做过一个案例,不圆整剪到30%时FLOPs降了很多,但TensorRT延迟几乎没变;把通道圆整到16的倍数后,延迟才真正降下来。这个属于实战中很容易忽略的点,值得记住。

5.5 预剪枝和后剪枝的区别

我看到网上也有人在问“预剪枝与后剪枝”。这两个概念在模型压缩里指代不一样,但很多教程把“稀疏化训练-剪枝-微调”称为训练后剪枝,而“从头训练一个小网络”称为预定义剪枝。严格意义上说,传统决策树的预剪枝和后剪枝不是同一套逻辑,但在神经网络压缩领域,大家通常关心的是剪枝时机。

我的做法始终是:预训练权重 -> 稀疏化训练 -> 剪枝 -> 微调。这套流程本质上是训练后剪枝。如果你想省时间,直接在预训练权重上稀疏化训练几十个epoch也可以,但效果会略差一点。预剪枝(先定义小结构再训练)除非你有足够算力从头训练,否则效果不一定好,因为YOLOv8很多层之间有复杂的耦合,直接定小结构容易欠拟合。

6. 核心源码结构与扩展方向

6.1 源码文件组织

我把这套剪枝源码整理成了几个模块,结构大概是这样的:

yolov8_prune/ ├── sparse_train.py # 稀疏化训练入口,集成ultralytics训练循环 ├── prune_analyze.py # 统计BN层gamma分布,生成剪枝报告 ├── prune_channels.py # 通道剪枝与权重重映射 ├── rebuild_model.py # 重建剪枝后的model和yaml ├── finetune.py # 微调剪枝后模型 ├── export_onnx.py # 导出ONNX └── utils/ ├── dependency.py # 解析模型层依赖关系 ├── remap.py # 权重索引映射 └── shape_check.py # 自动shape检查

这个结构是按流程拆的,每一步单独执行,方便排查。不建议把稀疏化和剪枝写在一个脚本里,否则出了问题不好定位。

6.2 配合量化的扩展建议

剪枝做完之后,通常接着做量化,两者配合能进一步压缩模型。剪枝减少通道数,量化降低每个参数的比特数,二者正交。比如yolov8s剪枝35%后,再做INT8量化,模型体积能压到原始体积的10%左右。

不过要注意,剪枝和量化叠加会出现精度下降叠加。所以量化一般要做校准,拿几百张代表性图片收集激活值范围。剪枝后的模型重新量化时,因为我之前提过BN折叠的问题,最好先把BN融入卷积再量化,否则量化误差会很大。

6.3 扩展到大模型和更多任务的思路

这套源码虽然针对yolov8s,但核心逻辑可以复用在yolov8m、yolov8l甚至YOLOv5上。关键点还是依赖关系解析和保留下标传播。我自己试过将剪枝逻辑迁移到yolov8m,只需要修改yaml解析的映射表,流程不变。

如果你以后要跑更强的检测网络,比如YOLOv9或YOLOv10,思路也一样,但需要重新对照网络结构里的跨层连接方式。每个模型的残差和拼接方式不同,剪枝脚本没法做到“一劳永逸”,这也是为什么做模型压缩的人必须看得懂网络结构,不能只靠工具包。

我个人在实际操作中体会最深的一步,其实是剪枝完成后的模型验证。很多人急着看加速效果,但我会先在验证集上跑完mAP,再导出ONNX测试完整性,最后才上推理框架测延迟。这三步按顺序走完,剪枝后的模型才敢上线。这套流程看起来繁琐,但能省下后面排查问题的大量时间。如果你正在准备剪自己的检测模型,我建议先拿一个小数据集跑通全流程,再换真实数据调参。毕竟剪枝这活儿,参数选不好,代码再对也出不来效果。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 15:50:23

微信开源知识库项目全解析:部署、踩坑与RAG实践

最近圈子里突然都在传一个消息:微信开源了一个知识库项目,而且口碑意外地高。我第一反应是,微信这种体量的团队开源的东西,要么是内部基建顺手贡献,要么就是真踩到痛点了。把代码拉下来跑了一圈之后,我必须…

作者头像 李华
网站建设 2026/9/28 15:50:02

deepseek-harness @文件补全:告别手动拼上下文,让大模型读懂本地项目

一个多月前,我最常用的命令是cat,而不是deepseek-harness。每次想让模型看某份代码或文档,我都得先手动把文件内容拼进提示词里,拼完还要担心长度超限、截断位置不对、贴错了文件。直到我把 deepseek-harness 升级到最新版&#x…

作者头像 李华
网站建设 2026/9/28 15:50:01

Agentic RAG实战:建库-检索-生成闭环设计与工程落地

1. 这不是“又一个RAG教程”,而是一份Agent开发者亲手踩坑后整理的全流程实操手记我从2022年夏天开始写第一个能调用天气API的简单Agent,到今天带团队落地三个企业级Agentic RAG系统,中间重写了七版知识库 pipeline。这篇笔记标题里写的“建库…

作者头像 李华
网站建设 2026/9/28 15:49:47

实测豆包Seed2.1pro:代码生成、电脑优化与文案创作全场景体验

1. 项目概述:为什么突然想测一发豆包Seed2.1pro老实说,我之前对豆包的印象一直停留在“能用,但离顺手还差一截”。平时写代码、写方案,主力工具还是那几个国外模型,豆包更多时候在我的收藏夹里吃灰。直到最近连续刷到好…

作者头像 李华
网站建设 2026/9/28 15:49:13

宁波恒科超声波设备公司评价如何,性价比高不高信得过吗

在制造业的车间里,清洗常常被看作不起眼的辅助环节。可真正守在生产一线的人都明白,一道清洗工序做不干净,后面的电镀会起皮、装配会卡壳、喷涂会流挂,良率的损失最终都会回到工厂自己身上。许多采购负责人都有过类似的经历&#…

作者头像 李华
网站建设 2026/9/28 15:49:02

中移ML307 Cat.1模组二次开发:烧录失败与定时器崩溃避坑指南

做物联网模组的二次开发,如果没被烧录失败和定时器崩溃折磨过,那说明项目还处在蜜月期。中移ML307模组是一款4G Cat.1通信模组,放在物联网行业里性价比很高,可由于它的开发资料相对分散,社区里能参考的经验也不算多&am…

作者头像 李华