ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Deep Compression深度解读:剪枝、量化与霍夫曼编码的模型压缩三步法

2026/9/16 7:35:19 拓冰建站 浏览量
Deep Compression深度解读:剪枝、量化与霍夫曼编码的模型压缩三步法 1. 论文阅读随笔Deep Compression在讲什么先说结论Deep Compression是一篇2015年发表在ICLR的经典论文作者是Song Han、Huizi Mao和William J. Dally当时都在斯坦福。这篇论文解决的问题非常直接——深度学习模型太大大到在移动端和嵌入式设备上根本跑不动。作者提出了一套“三步走”的模型压缩流程剪枝、量化、霍夫曼编码直接把神经网络的存储空间压缩35倍到49倍而且精度基本不掉。这个数字放在今天看依然很能打在当时更是炸裂级别的成果。这篇论文为什么值得读因为它是模型压缩领域绕不开的里程碑。你后来看到的很多量化方法、剪枝方法包括TensorFlow Lite、NCNN、MNN这些推理引擎里的优化手段很大程度上都能追溯到这篇论文的思想。它不只是一篇“效果好”的论文而是定义了一个完整的技术框架告诉你模型压缩该从哪些维度下手、每一步的原理是什么、每一步能省多少存储和计算。如果你是做模型部署、端侧推理、或者对模型体积有严格要求比如App包体积、固件大小的工程师这篇论文是必读的。如果你刚入门深度学习、对“模型太大跑不动”这个痛点有切身体会这篇文章也能帮你建立起模型压缩的完整认知框架。我自己读这篇论文的过程里最大的感受是它的方法并不复杂每一步都是工程上很朴素的思路但组合在一起却产生了惊人的效果。这种“简单但系统”的思维方式恰恰是很多花哨论文比不上的地方。2. 三个核心步骤拆解剪枝、量化、编码2.1 网络剪枝干掉不重要的连接Deep Compression的第一步是剪枝。这里的剪枝是“权重剪枝”不是“通道剪枝”。权重剪枝的粒度很细是逐连接per-connection级别的训练完一个模型后把那些绝对值很小的权重直接置为零因为一般认为这些接近零的权重对最终输出的贡献很小删掉它们对精度的影响微乎其微。具体操作上作者的做法是正常训练一个网络得到权重设定一个阈值把低于阈值的权重置为零然后做一次重训练retrain让剩余的非零权重去弥补被剪掉部分造成的精度损失。剪枝和重训练是可以迭代多轮的每一轮剪掉一部分再训练回来一些精度直到精度下降到一个可接受的边界。这个阈值怎么定论文里给出了一个经验性的做法对于不同层剪枝比例可以不同。卷积层通常比全连接层更“耐剪”因为卷积核的冗余度相对更低但全连接层的参数冗余非常大。作者在AlexNet上把全连接层的参数剪掉了约9倍卷积层剪掉了约2倍多整体参数数量大幅下降。关键是剪枝后的模型要让稀疏矩阵参与实际计算用CSRCompressed Sparse Row或CSCCompressed Sparse Column格式存储非零权重才能真正节省存储和计算开销——这一步在工程上比“把权重设为零”本身要重要得多。我在实际项目里复现过这个思路有一个很实在的体会剪枝比例不是越高越好剪得太狠重训练也很难恢复精度。一个稳妥的做法是逐层确定剪枝率先给每层一个初始剪枝率比如卷积层70%、全连接层90%然后观察验证集精度的变化如果精度掉太多就回调。这个“精度的变化”不是线性的有时候你剪80%没事剪85%突然就崩了所以一定要小步试。2.2 权重量化共享权重缩小数值表示剪枝去掉的是“没用的连接”量化解决的是“存储精度过剩”的问题。训练好的网络权重通常是用32位浮点数FP32存储的但网络精度对这么高的数值精度真的有那么依赖吗作者认为没有。他们的方案是权重量化也叫权重共享weight sharing把连续的权重值聚成若干个离散的“桶”每个桶里的所有权重共享同一个中心值网络只需要存储这个中心值码本和每个权重对应的桶索引。具体做法是使用K-Means聚类。把每一层的权重聚成k类k是一个超参数比如256类。聚类完成后每个权重只需要用一个8位的整数256个取值来表示它的类索引而每个类对应的中心值一个FP32存放在一个长度为256的码本里。这样存储权重的成本从“每个权重32位”变成“每个权重8位共享码本”直接省了4倍。如果聚成32类就是5位索引省得更多但精度会掉。这里面有一个关键的细节聚类不是对全网络的所有权重一起做而是逐层做的。因为不同层的权重分布差异很大全连接层的权重分布往往比卷积层更集中逐层聚类能更好地匹配每一层的分布特性。在反传backward过程中梯度要对应到每个权重但由于多个权重共享同一个中心值梯度的处理方式是把同一个簇内的所有权重的梯度累加起来再更新对应的中心值。这个逻辑很巧妙简单说就是“簇内梯度求和用求和结果更新码本”。我把量化理解为“用离散值逼近连续值”的过程。你可以想象把一杯水倒进几个固定容量的容器里每个容器的容量就是聚类中心多少水都按最接近的容器去倒。最理想的状态是每个容器都被均匀填满这对应聚类时要让所有簇内的样本数量尽量均衡——这也是K-Means的一个考量点初始化时用密度大的点做聚类中心会更稳。2.3 霍夫曼编码压缩稀疏矩阵的存储前两步做完模型里大部分权重是零非零权重又变成了离散的小整数索引。这时候还剩下一个可以优化的点这些索引值的分布并不均匀。有的索引出现频率很高有的很低。霍夫曼编码做的就是用更短的码字表示高频出现的索引用更长的码字表示低频出现的索引整体上降低平均存储位数。这是一个标准的无损压缩算法本身不产生精度损失。作者在论文里报告霍夫曼编码在AlexNet上额外节省了大约5%到10%的存储空间。单独看这个数字不算大但考虑到前两步已经省了30多倍第三步是在这个基础上再省一点积少成多。这里我想特别说一句很多博客和解读会把霍夫曼编码误以为是被量化的权重值的编码其实是索引值做编码而不是权重本身。你如果想复现千万要搞清楚这一层。实际工程里这一步不少时候是可选的——如果你的部署平台对存储格式有硬性要求或者模型已经小到可以接受不一定非要上霍夫曼。但在论文的完整方案里它是第三步也是让总压缩比达到35-49倍的关键一环。3. 实验效果与关键数据解读3.1 在AlexNet上的压缩数据论文最常被引用的实验结果是在AlexNet上取得的。AlexNet本身是一个大约240MB的模型FP32存储经过Deep Compression流程后被压缩到约6.9MB压缩比是35倍左右。这240MB里全连接层占了绝对大头约90%以上的参数都在全连接层所以全连接层的剪枝和量化收益最大。具体到每一层的参数变化卷积层参数从约230万个降到约70万个全连接层从约5800万个降到约650万个。量化把每层的索引位数进一步压缩到5位或8位霍夫曼编码再挤掉一些重复模式。整个流程下来模型的top-1准确率从57.2%小幅下降到57.0%top-5准确率从80.3%下降到80.2%。这个精度损失几乎可以忽略不计。我还注意到论文报告的一个对比如果不做剪枝、只做量化和编码压缩比大概是4倍左右如果做剪枝量化、不做霍夫曼压缩比大概是27-31倍三步全上才到35倍以上。这说明每个步骤都在贡献价值而且剪枝是贡献最大的那一步。3.2 不同网络结构下的通用性论文不只是验证了AlexNet还在VGG-16、LeNet以及GoogleNet上做了实验。看这些结果很有意思你会发现压缩比和网络结构关系很大。VGG-16同样因为全连接层占比极高压缩比可以达到49倍比AlexNet还高。而GoogleNet这种用全局池化替代全连接层的结构本身的参数冗余就少得多压缩比就没那么夸张。这给我们的实操启示是如果你的模型里有很多全连接层Deep Compression的收益会非常大如果你的模型已经用了全卷积或全局池化那剪枝的收益空间相对有限重心应该放在量化和计算加速上。我在自己的项目里验证过——一个MobileNet结构的模型全连接层只有最后一个小层剪枝带来的体积收益确实不如对ResNet那样明显。3.3 压缩对功耗和计算的影响论文里还专门测了压缩对硬件功耗的影响。作者用Eyeriss加速器做了实验结果显示压缩后的网络在功耗上能降低若干倍因为他们把权重从DRAM搬到片上存储的频率大大降低了。这在移动端和嵌入式场景里是非常关键的指标——你不仅要让模型塞得下还要让它在持续推理时不把电池耗光。这个点很容易被读者忽略但恰恰是模型压缩在真实场景里最大的价值点之一。模型小不光是“能装上”还意味着“跑得快”“更省电”。因为内存访问的能耗远高于计算能耗减少权重的存储规模本质上就是在减少最耗电的DRAM访问。这是部署端优化的大方向跟这篇论文的思路完全一致。4. 代码实现思路与复现要点4.1 剪枝实现的伪代码流程论文没有给完整代码但复现的路径非常清晰。我用自己的话把剪枝的代码思路写一下核心就是用掩码mask控制哪些权重参与前向和反传# 伪代码风格的PyTorch剪枝实现 import torch import torch.nn as nn def prune_weight(weight, mask): # 前向时被剪掉的权重直接置零 return weight * mask def retrain_step(model, pruned_ratio): for layer in model.layers: weight layer.weight.data threshold torch.quantile(torch.abs(weight), pruned_ratio) mask (torch.abs(weight) threshold).float() layer.weight_mask mask # 正常做前向、损失计算、反传 # 反传时需要把梯度也乘上mask确保被剪掉的权重不会更新核心思想就两点前向传播时用mask把不需要的权重屏蔽掉反传时同样用mask把对应位置的梯度置零让被剪掉的权重在训练过程中保持为零。重训练的目的不是让剪掉的权重复活而是让活着的权重尽可能地调整到能弥补损失的位置。4.2 量化和聚类中心更新的要点量化部分我在复现时踩过一个坑就是聚类中心在反传时的更新。K-Means聚类完成后码本cluster centers是需要参与训练更新的更新方式是梯度在簇内求和然后除以簇内样本数量得到平均梯度再用这个平均梯度去更新码本值。这里不能用普通的SGD逐个权重更新因为共享中心值的权重梯度必须被“合并”后一起更新。# 量化的码本更新示意图 def update_codebook(grad, assignment, codebook, lr): for c in range(codebook.size(0)): mask (assignment c) if mask.sum() 0: grad_sum grad[mask].sum() codebook[c] - lr * grad_sum / mask.sum()为什么求平均而不是直接求和因为不同簇的样本数量可能差别很大直接求和会让大簇的中心被过度更新。虽然论文里对更新方式的具体细节没有写得很细但从实验稳定性的角度平均梯度是更稳妥的选择。4.3 存储格式索引和码本的组织方式剪枝后的权重是稀疏的量化后的权重是小整数索引。要真正实现磁盘上的大压缩比得把它们组织成CSR或CSC格式。CSR格式的核心是三个数组值数组存非零权重量化后是索引值、列索引数组存每个非零值所在的列、行指针数组标出每一行在值数组里的起始偏移。我把这个组织方式理解成“一维化”稀疏矩阵本质上就是一堆坐标和值的组合CSR格式做的就是把这个组合用紧凑的方式存下来。这种格式看起来绕但它让稀疏矩阵的存储从“二维开销”降到了“一维连续存储”压缩率和访问性能都更好。Deep Compression序列化后的权重文件本质上就是“CSR数组码本霍夫曼表”的打包。4.4 一个完整的压缩流程示例为了让大家好理解我整理一个典型的压缩流程表格其中包含每一步的输入、输出和可配置的关键参数步骤输入操作输出关键参数基线训练数据集正常训练收敛的稠密模型学习率、epoch剪枝稠密模型按阈值置零重训练稀疏模型剪枝率、阈值、重训练轮数量化稀疏模型逐层K-Means聚簇码本索引矩阵聚类数k如256霍夫曼编码索引矩阵构建编码表并替换编码后的二进制数据码表构建方式序列化存储全部数据按CSR码本编码表打包压缩文件文件格式定义每一步的输出都是下一步的输入整个流程是串行的。实操中最容易犯错的地方是重训练阶段的mask没有在反传里生效。很多初学者只做了前向的mask反传时忘了乘mask结果被剪掉的权重悄悄更新了模型精度反而崩了。5. 论文的工程意义与后续影响5.1 对模型部署的直接贡献这篇论文最大的工程意义在于它给出了一个“可落地的端侧模型压缩路径”。在2015年那个时间点移动端跑深度学习模型还是件很困难的事模型动辄几百MB内存和算力都扛不住。Deep Compression提供了一种不需要特殊硬件支持、纯软件层面就能实现的压缩方法而且压缩比很高、精度损失很小。这让研究者看到了一种可能性复杂模型经过系统工程化处理是可以在资源受限设备上运行的。现在的端侧推理引擎里很多都内置了剪枝后的稀疏计算支持以及各种量化格式int8、int4、甚至int2。虽然这些引擎的底层实现远比论文描述的复杂但思想源头都可以追溯到Deep Compression对“存储方式决定部署效率”这个命题的论证。5.2 与后续量化方法的对比Deep Compression发表之后出现了大量后续工作。比如后面非常火的PTQ训练后量化方法以及各类QAT量化感知训练方法它们在量化的粒度、校准方式、训练策略上做了很多优化。相比之下Deep Compression的逻辑是先训练再压缩压缩过程中做一小段重训练更接近于“压缩后微调”。但这不意味着Deep Compression过时了。相反它提出的“剪枝量化编码”三层递进框架至今仍然是模型压缩的标准思路。它跟后来的结构化剪枝、自动剪枝、二值化网络不是替代关系而是互补关系——你可以在Deep Compression的框架里套用更先进的剪枝算法或量化算法整体流程依然成立。6. 相关热点关键词解析与延伸阅读方向如果你在读Deep Compression的过程中想进一步深入我建议按照这几个关键词去扩展阅读稀疏神经网络Sparse Neural Networks、混合精度量化Mixed-Precision Quantization、模型蒸馏Knowledge Distillation、轻量化网络结构设计Efficient Network Design。这几个方向是当前模型落地领域最活跃的研究主题也和Deep Compression直接相关。尤其要推荐的是去读一下后来作者发表的《Learning both Weights and Connections for Efficient Neural Networks》。这篇文章把剪枝策略在CNN和RNN上的适用性做了扩展。我自己看完的感受是论文的核心贡献其实不在某一个具体的算法而在于它建立了“模型压缩三步走”的通用方法论。这个方法论今天依然在无数工程实践中被复制和改造。另一个值得关注的方向是硬件协同设计。Deep Compression的后续工作里作者把压缩后的模型跟定制硬件加速器联合优化证明了“模型结构存储格式硬件架构”三者协同设计可以比单纯优化其中任何一个都更高效。这对做AI芯片或者端侧推理引擎的人来说是个很好的启发——模型压缩不只是软件的问题它也可以反向影响硬件设计的取舍。7. 写在最后的几点实践心得我在文章开头就说了Deep Compression是一篇“朴素但系统”的论文。它的每一步都简单难的是把三步串联成一个完整的工作流并且在工程落地时把细节处理好。我在这里分享几个自己实践中踩过的坑希望能帮你少走弯路。第一件事剪枝的mask在保存模型时一定要记得一起保存。很多人训练完只存了权重没存mask推理时加载的是一个“被剪了但没留下剪枝标记”的模型等于白剪。正确做法是把mask当作模型结构的一部分持久化或者在保存前就把被剪掉的权重直接置零且后续不再更新这样保存下来的权重本身就是稀疏的不需要额外存mask。第二件事K-Means聚类时如果数据量特别大逐层聚类也要注意内存占用。AlexNet的全连接层有几千万个权重直接全部载入内存做聚类可能会有压力。我当时用mini-batch K-Means做替代效果差距非常小但内存占用少了很多。如果你不是要复现论文的精确指标完全可以用这个方式来降低成本。第三件事关于重训练的epoch数。论文里对重训练的细节写得不是特别细但在我自己的实验里重训练的学习率要比初始训练小得多一般是初始学习率的十分之一以下同时epoch也不需要太多几个epoch就能把精度恢复得很好。如果你发现重训练后精度反而上不去先检查学习率是不是太高了而不是急着加大剪枝率。最后聊一点认知层面的体会。Deep Compression之所以能成为经典不只是因为效果好更重要的是它把“模型存储”和“模型计算”拆开来看待强调了存储效率对部署的关键影响。这种角度在训练算力昂贵、网络结构动辄上亿参数的今天反而更加有现实意义。你可以不逐行复现这篇论文的代码但一定要理解它拆解问题的思路——先删冗余再降精度最后再做无损压缩。这个流程背后的逻辑其实适用于任何“资源受限”场景下的系统优化而不只是深度学习模型压缩。