☰
三值权重网络:低比特量化与模型压缩的实用折中线
2026/10/14 7:08:49 网站建设 项目流程

三值权重网络(Ternary Weight Networks)是我做低比特量化时读过的收益最大的一篇论文之一。它解决的问题非常具体:神经网络太大、跑不动,能不能把权重压缩到只有{-1, 0, +1}三种取值,同时尽量保住精度。如果你正在做端侧部署、模型压缩,或者对量化训练刚入门,这篇非常值得精读。它不像二值网络那样极端,而是走了一条“温和压缩”的折中线,在精度和效率之间找到了一个很实用的平衡点。

先说个结论:这篇论文的思想后来几乎成了低比特权重量化的标配——先给全精度权重设一个阈值,把小于阈值的权重清零,大于阈值的权重置为±1,再乘一个缩放因子补偿误差。听起来简单,但里面的数学推导和训练细节,有不少值得展开的地方。下面我把论文的核心机制、训练流程、复现经验完整拆一遍。

1. 为什么我会关注三值权重网络:低比特量化的合理折中线

1.1 从“全精度太贵”说起:神经网络部署的真实瓶颈

神经网络训练完,权重基本都是32位浮点数。以经典的大规模视觉分类网络为例,单是卷积层的权重动辄几十上百MB,推理时每一层都要把这些浮点数从内存搬到计算单元。边缘设备的内存带宽有限,功耗预算也紧张,模型太大直接导致两个问题:一是放不进嵌入式设备的存储,二是推理速度被访存卡脖子,计算单元再快,数据喂不过来也没用。

模型压缩的思路有很多种,剪枝、蒸馏、量化。量化是最直接的一种:既然权重存成32位浮点太奢侈,那就用更少的比特数去近似它。8位量化损失很小,很多商用推理引擎都支持得很好;但再往下走,4位、2位、1位,精度往往开始明显下降。三值权重网络盯上的就是“低于8位”这个区间。

1.2 BNN的极端方案与精度损失,值得再琢磨一遍

在TWN之前,最出名的是二值权重网络,把权重直接压缩到{-1, +1}两个取值,甚至激活也压到二值。这种做法理论上可以把繁重的浮点乘累加变成位运算,收益非常诱人。但实际跑起来,精度损失是实打实的,尤其在稍大规模的数据集上一旦网络容量不够,二值化后的表达能力会明显跟不上。

为什么二值化损失大?因为{-1, +1}这个集合没有任何“空档”。所有特征都必须区分正负,但真实训练得到的权重分布里,总有大量权重绝对值接近零,它们对最终输出的贡献本就可有可无。强行把接近零的权重也变成+1或-1,相当于把所有弱信号全部放大成了强信号,引入了大量噪声。这个问题用一句话概括:二值网络把“保留重要信息”和“保留所有信息”混为一谈了。

TWN的切入点就在这里。它把权重集合扩大到{-1, 0, +1},中间加了一个0状态,让那些绝对值很小的权重直接归零。这样既保留了±1的符号判断能力,又引入了一定稀疏性,把不重要的连接真正关掉。这种“有0参与”的思路,在后续很多量化方法里都能看到影子。

2. 三值权重网络的核心机制:两行公式背后的设计逻辑

2.1 目标函数怎么定:让三值权重尽量逼近全精度

论文的出发点非常朴素。给定一层训练好的全精度权重矩阵 W,我们希望找一个三值权重矩阵 W_t 和一个缩放因子 α,让二者的平方误差尽可能小。形式化写出来就是:

minimize || W − αW_t ||²

其中 W_t 的每个元素只能取 {-1, 0, +1}。看到这个目标函数,你可能会问:为什么不直接定一个阈值把权重切成三段就完事了?因为凭感觉切出来的量化结果,不一定是在均方误差意义下最优的。论文的思路是先把问题拆成两步:给定了三值取值,最优的 α 应该怎么算;再反推最优的阈值 δ 应该怎么设。

2.2 阈值与缩放因子:论文推导的核心结论

先说缩放因子。如果 W_t 已经确定了,也就是每个权重已经被标记为-1、0或+1,那么最优的 α 有一个非常干净的闭式解:

α = (Σ 所有被量化为±1的权重) 除以 (被量化为±1的权重个数)

用文字说就是:所有非零三值权重对应的原始权重,取它们的平均值,就是能让整体均方误差最小的缩放因子。这个结果其实很好理解:α 本质上是“被保留那些权重”的公共尺度,平均值自然是最佳拟合。

再看阈值。假设 W_t 已经把绝对值小于 δ 的权重归零、绝对值大于 δ 的权重取符号,那么问题变成找一个 δ,让量化误差最小。论文里对权重分布做了近似推导,给出的推荐范围很实用:δ 取在全精度权重绝对值的均值附近,经验值大约是 0.6~0.7 倍的 E(|W|)。原作者在多个实验里常用 δ = 0.7 × mean(|W|),复现时从 0.5 到 0.8 之间调,一般都能有稳定结果。

这里要特别强调一下:这个阈值不是拍脑袋定的,它对应的是“哪些权重值得保留”的分界线。δ 太小,退化成二值网络,0 的比例接近于无;δ 太大,大部分权重被清零,有效信息大量丢失。0.7 倍均值这个值,在论文覆盖的数据集和网络结构下被验证过多次,作为默认起点非常稳。

2.3 0这个状态为什么这么关键

很多人第一次看TWN会低估0的作用。从存储角度看,{-1, 0, +1}只需要2个比特就能编码,和 {-1, +1} 的1比特相比只多了1个比特;但从表达能力角度,0给网络引入了一个“开关”机制。

我把这个类比成拍黑白照片和三分法调色:二值网络只能告诉你“亮”或“暗”,而三值网络可以明确告诉你“这个地方不重要,直接忽略”。对于卷积网络来说,很多位置的权重本身就不该起作用,这些位置置0后,不会参与计算,相当于在量化过程中自动完成了一次结构化剪枝。论文的实验也体现出这种趋势:在较深网络和大规模数据集上,TWN的精度表现明显优于同等条件下的二值网络,而且网络越深、参数越多,这个0状态的收益越明显。

此外,前向计算时0可以直接跳过,负1和正1只是取负号和取原值的区别,整个卷积层基本没有真正的乘法,只剩下加减法和筛选。这种计算模式在通用CPU和GPU上也能获得实际收益,不需要像XNOR那样依赖专门的位运算硬件,落地门槛低不少。

3. 训练环节怎么落地:STE反向传播与伪量化实现

3.1 从量化到训练:为什么不能直接求导

理想情况下,我们希望直接训练一个“天生就是三值”的网络。但问题在于,把权重切成三值的这个操作,几乎处处不可导。你没法对“if abs(w) > delta then return sign(w) else return 0”这样的函数求梯度,因为它在分界点上是跳变的,导数要么是0要么不存在。

论文采用的做法是典型的STE(直通估计器):前向传播时老老实实使用三值权重去计算网络输出,反向传播时把量化层近似当作恒等函数,让梯度直接穿透量化操作,更新底层保留的全精度权重。

实现时有一个通用的伪量化技巧:先算出量化后的权重 w_q,前向输出用 w_q 参与计算,但返回给反向传播的是“全精度权重 + (量化权重 - 全精度权重) 的停止梯度”,也就是 q = w + (w_q - w).detach()。这样一来,网络实际计算用的是 w_q,梯度回传时却直接流到 w 上,完美的STE效果。

3.2 一份可直接跑的PyTorch三值量化层

我在复现TWN时,写过一个简单的PyTorch实现,核心逻辑在这里:

import torch import torch.nn as nn import torch.nn.functional as F class Ternarize(nn.Module): def __init__(self, delta_ratio=0.7): super().__init__() self.delta_ratio = delta_ratio def forward(self, w): # w 是全精度权重,形状任意 delta = self.delta_ratio * w.abs().mean() delta = delta.detach() # 阈值不参与梯度回传 t = (w.abs() > delta).float() * w.sign() scale = (w * t).sum() / (t.sum() + 1e-8) scale = scale.detach() # 缩放因子也当作常数 w_q = scale * t # STE: 前向用 w_q,反向梯度穿透到 w return w + (w_q - w).detach() class TWNLinear(nn.Module): def __init__(self, in_features, out_features, delta_ratio=0.7): super().__init__() self.linear = nn.Linear(in_features, out_features) self.ternarize = Ternarize(delta_ratio) def forward(self, x): w = self.ternarize(self.linear.weight) return F.linear(x, w, self.linear.bias)

这个实现有几个细节值得注意。delta 和 scale 我都用了 detach,等于把阈值和缩放因子都当作“观测值”处理,训练时只通过STE更新全精度权重。这样做的原因是:第一,delta 和 scale 对权重的导数非常复杂,放开反传容易让训练不稳定;第二,理论上它们的最优值本身就是由当前权重分布决定的,哪怕不反传,每一轮前向也会自动跟着权重更新,实际效果已经足够好。

3.3 实操中的三个关键训练细节

第一,保留全精度影子权重。训练时真正被更新的仍然是32位浮点权重,三值权重只是每轮前向时从全精度权重临时“投影”出来的。这个投影过程没有把全精度权重丢弃。这也是所有STE类量化方法的共同特点:推理时是低比特,训练时仍然需要全精度的存储和更新。

第二,批量归一化对量化训练很关键。TWN的阈值依赖于当前权重的绝对值分布,一旦权重分布抖动,delta 也会跟着抖。加入BN之后,每层输出被约束在相对稳定的尺度,权重分布不会剧烈漂移,量化层训练稳定得多。我实际测试过,不用BN的版本在较深网络上很容易训飞,加BN之后明显好转。

第三,学习率要保守一些。STE回传的梯度本质上是“近似正确”,学习率过大容易在量化边界附近震荡。我通常把学习率设成同结构全精度训练的0.5到0.7倍,或者先做几个epoch的全精度预热,再把量化打开。预热这个操作在很多复现里不是必须的,但对于小数据集、浅网络,能明显减少训练初期的波动。

4. 实验效果怎么看:论文场景、精度趋势与我的评估

4.1 公开数据集上的逐步验证思路

论文的实验路径是从小到大层层递进的,这个思路本身就很值得学习。先在MNIST这类经典手写数字集上验证可行性,再到CIFAR-10这种中等规模小图分类集上对比BNN,最后在ImageNet这种百万级视觉分类集上和全精度模型做差距评估。

核心结论可以用一句话概括:TWN在两个关键维度上取得了平衡。比BNN精度更高,尤其在大模型上优势会拉大;比全精度模型内存占用少16倍——权重从32位降到2位,同时大幅减少卷积中的乘法运算。

一个让我印象很深的现象是,TWN在较深网络上的量化退化率比浅层网络小。原因其实不难理解:网络越深,冗余参数越多,0状态能够剪掉的“无效连接”也越多,量化噪声被网络自身的冗余吸收了一部分。这个观察也提醒我:量化方案好不好,不能只在MobileNet这类紧凑网络上测,还要在容量充足的网络上做对照,否则容易低估方法的真实潜力。

4.2 内存压缩与理论加速要分开算

很多人看到“权重32位降为2位,压缩16倍”就默认推理能提速16倍,这个想法需要纠正。权重内存访问只是推理开销的一部分,激活、中间特征图、BN参数这些仍然以浮点形式存在。TWN只量化了权重,激活还是高精度的,所以理论计算加速主要体现在“去掉乘法”和“减少权重访存”这两个维度上,而不是端到端的16倍加速。

更准确地说,TWN的收益应该拆成两块:一是存储收益,明确且确定,模型文件直接缩小到接近原来的1/16;二是计算收益,取决于硬件和实现方式。如果只是把量化后的2位权重塞进普通的高精度矩阵乘法库,收益非常有限;真正要吃满红利,需要重写卷积核,按三值权重的取值分支处理,让0跳过、±1变成加减法。

4.3 用今天的眼光重新审视论文结论

以现在的量化研究水准回看,TWN有两个明显的局限。

第一个是全局缩放因子太粗糙。论文对一整层只用一个 α,但后来大量实验表明,逐通道做缩放因子通常能显著降低量化误差。这背后的直觉是:不同卷积核的输出通道,权重分布并不完全一致,一个全局尺度很难同时匹配所有通道。后续的逐通道量化方法在这条线上取得了很大收益,已经成为了今天工业级量化工具的事实标准。

第二个是“只量化权重、不量化激活”带来的工程遗憾。推理引擎想要端到端跑整数运算,最好权重和激活都是低比特整数。TWN让权重变成三值后,激活仍然是浮点,意味着整个网络里依然绕不开大量的浮点运算。熟悉硬件加速的朋友应该知道,浮点和整形的混合计算路径往往比单纯整形的计算路径更难做极致优化。

不过,这些局限并不影响论文本身的经典地位。它不是用来做“最终部署方案”的,而是用来回答“低比特权重量化到底可不可行”这个问题的。从研究角度看,它把量化训练中关键的阈值选择、缩放因子推导、STE应用这些核心环节全部打通了,后续一系列多值量化方法几乎都建立在这些基础操作之上。

5. 工程部署视角:三值权重在真实设备上的收益边界

5.1 存储收益是确定的,计算收益看硬件

如果真的把TWN放到嵌入式设备上部署,第一步就是把三值权重用2比特打包。写一个简单的编码方案:用一位表示是否为0,再用一位表示符号;或者直接用一个2位查表把{-1, 0, +1}映射到{00, 01, 10}。这套操作实现难度不大,带来的Flash占用下降是一目了然的。

计算侧才是真正分水岭。传统的密集卷积核是遍历所有权重位置做乘累加,如果权重里混着大量0,等于白白浪费时间。针对三值权重,比较自然的实现是先按非零权重做一个索引表,推理时根据索引直接跳过0元素,正权重累加输入值,负权重累减输入值。实测下来,在支持普通SIMD指令的处理器上,这种定制核可以比浮点卷积快好几倍。但如果你只是把它跑在未经优化的PyTorch模型里,看着FLOPS没有降低,就容易误以为TWN没有加速效果。

另一个工程细节是BN融合。部署阶段必须把BN的均值、方差、缩放、偏移全部融合进前面的卷积层,不然推理时要单独走一遍BN,既增加访存又增加计算。在量化模型里这步尤其重要——融合后的等效权重分布会变化,阈值选取和缩放因子的最优值也会跟着变。稳妥做法是训练结束后,在验证集上重新统计BN的均值方差,再执行融合。

5.2 与后续方案的对比:全局缩放vs逐通道缩放

我复现TWN之后,顺手测试了一个简单改造:把全局 α 改成每个输出通道一个 α_k,其余训练流程完全不变。结果在同一个小型网络结构上,逐通道版本比全局版本精度高出不少,训练也更稳定。这个差距在大规模数据集上会被进一步放大。

这也是为什么我把“TWN的缩放因子是全局的”列为它最值得批判的设计之一。作为历史论文当然不该苛求,但作为工程决策者,你要清楚TWN的定位是一份入门级的“三值量化可行性证明”,而不是一份开箱即用的完整部署方案。今天的实操建议很简单:把TWN作为基线,再叠加逐通道缩放、适当的激活量化、更进阶的训练策略,去搭自己的低比特方案。

5.3 常见问题排查速查表

我在训练和部署过程中积累了一些典型的坑,整理成表供你对照:

现象可能原因排查与处理建议
量化训练不收敛,loss乱跳学习率过大,STE梯度本身是近似方向降低学习率,或先用全精度预热几个epoch
三值权重里0的比例极少delta_ratio设得太小,接近二值网络把delta_ratio从0.3逐步提高到0.7左右
0的比例过高,精度暴跌delta_ratio太大,有效连接被过度清零把delta_ratio回退到0.6~0.7,并标注参考实际分布
训练时稳定,部署后精度异常BN参数没有融合或没有重新校准统计验证集均值方差,执行BN折叠
同一套代码换网络后效果差异大网络本身容量不足,无法承受量化扰动换用容量更大的结构,或适当削减量化范围
推理速度没有提升没有定制三值卷积核,仍在跑通用浮点矩阵库实现跳过0元素的稀疏累加核,或接入支持低比特的推理引擎

最后还有一个容易被忽略的实操经验:如果某个关键层量化后误差特别大,可以把它的delta_ratio单独调大一点,让更多权重归零而不是粗暴变成±1。逐层微调阈值,往往比全局统一调参更能挽回精度,而且成本很低。

6. 这篇paper reading之后,我建议你再补两篇

TWN并不是孤立出现的,它处在一条连贯的技术脉络上。往前看,是二值网络的开创性工作,它让整个行业开始认真思考“极端低比特是否可行”;往后看,是训练式三值量化,这类方法不再手工指定缩放因子,而是把α和阈值一起纳入网络训练,让量化参数也变成可学习对象。

我对这套脉络的建议阅读顺序是:先读二值网络,理解极端量化带来的精度瓶颈;再读TWN,理解如何用“增加一个0状态”去缓解这个瓶颈;然后读训练式三值量化,理解量化参数从人工设定走向自动学习的过程。三篇串起来读,你对低比特量化的设计空间基本就有全貌了。

回到论文本身,它最值得模仿的其实是简洁:用三值集合和缩放因子两个设计元素,把量化误差降到可接受范围,整个方法干净利落。今天的量化方案越来越复杂,有逐层搜索、混合精度、可学习步长等等,但十八般武艺的最底层,仍然少不了“选阈值、算缩放、做STE”这三板斧。

我之前复现TWN时,最深的体会是:量化不是简单地砍精度,而是找到一种更经济的表达方式。0.7倍均值作为阈值,看起来只是一个经验常数,背后却隐含着“哪些权重值得保留”的分布判断。这种从数据分布出发做设计的思想,比记住某个固定公式更有迁移价值。

如果你正在研究低比特量化,不妨在跑任何复杂方案之前,先把TWN从头到尾手写一遍。用不了多少时间,但对“三值权重如何训练、如何部署、有哪些坑”的理解,会扎实很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询