3、图冻结概念:什么是图冻结、为什么需要冻结、冻结前后模型结构的变化、冻结与权重固化的关系
在TFLite Micro的模型转换流程中,“图冻结”(Graph Freezing)是连接训练框架(如TensorFlow)与嵌入式推理引擎之间的关键桥梁。很多开发者初次接触时,容易将“冻结”误解为“停止训练”或“保存模型”,但实际上,图冻结是一个将训练图(Training Graph)转换为推理图(Inference Graph)的结构重构过程。它不仅仅是权重的固化,更是对计算图中所有可变状态(如变量节点、控制流依赖、训练专用操作)的彻底清理。理解图冻结的本质,是后续进行常量折叠和算子规整的前提,因为只有先获得一个“静态”的计算图,才能安全地执行进一步的优化。
上图清晰地展示了图冻结的核心逻辑:左侧是训练阶段的计算图,包含大量用于反向传播和参数更新的节点;右侧是冻结后的推理图,所有可训练参数被转换为常量,训练专用节点被移除。下面我们将从四个维度深入剖析图冻结的概念。
3.1 核心原理:从“可变状态”到“静态数据”的转变
图冻结的本质是将计算图中的所有变量(Variable)节点替换为常量(Const)节点,并删除所有与训练相关的操作。在TensorFlow中,训练图通常包含以下动态元素:
- Variable节点:存储模型权重和偏置,其值在训练过程中会被反复更新。
- Assign/ResourceApply节点:负责将优化器计算出的梯度更新到Variable中。
- BatchNorm(批量归一化)的训练参数:如moving_mean和moving_variance,在训练时是变量,推理时应作为固定常量。
- Dropout层:训练时随机丢弃神经元,推理时必须移除。
- 控制流依赖:如while_loop、cond等,在训练中用于动态图结构,推理时通常不需要。
冻结过程通过tf.compat.v1.graph_util.convert_variables_to_constants函数实现。该函数会遍历图中的所有节点,将Variable节点的当前值取出,生成对应的Const节点,并重写所有引用该Variable的消费者节点,使其直接指向Const节点。同时,通过output_node_names参数指定输出节点,冻结工具会从输出节点反向遍历,自动剪除所有不参与推理计算的节点(如优化器、损失函数、梯度计算等)。
关键理解:冻结不是“保存模型”,而是“生成一个新图”。保存模型(如Checkpoint)保留的是训练状态,可以继续训练;冻结后的图是一个独立的、自包含的推理图,不再依赖任何训练上下文。
3.2 为什么需要冻结:TFLite Micro的硬性约束
TFLite Micro面向的是MCU(微控制器)级别的设备,其内存通常只有几十KB到几百KB,且没有操作系统支持动态内存分配。这种环境对模型提出了严苛要求:
| 约束条件 | 训练图(未冻结) | 推理图(冻结后) |
|---|---|---|
| 内存分配 | 需要动态分配Variable存储空间 | 所有权重为常量,可静态分配 |
| 执行流程 | 存在Assign等副作用节点,需多次执行 | 单次前向传播,无副作用 |
| 算子支持 | 包含训练专用算子(如GradientDescent) | 仅保留推理算子(如Conv2D、DepthwiseConv2D) |
| 模型大小 | 包含优化器状态(如动量、Adam的m/v) | 仅包含权重和偏置,体积显著减小 |
| 确定性 | 图结构依赖输入数据(如Dropout mask) | 图结构完全固定,可预测执行路径 |
具体来说,TFLite Micro的转换器(Converter)在接收模型时,期望输入的是一个“纯函数”:给定输入张量,输出张量,中间不涉及任何状态读写。如果直接传入未冻结的训练图,转换器会因无法处理Variable节点而报错,或者生成的模型在MCU上无法运行(因为MCU没有TensorFlow运行时环境)。因此,冻结是TFLite Micro转换流程的前置必要条件。
3.3 冻结前后模型结构的变化:一个微型案例拆解
为了直观理解冻结带来的结构变化,我们以一个包含BatchNorm和Dropout的简单卷积块为例。假设训练图的结构如下:
输入 (Placeholder)
→ Conv2D (权重W: Variable, 偏置b: Variable)
→ BatchNorm (gamma: Variable, beta: Variable, moving_mean: Variable, moving_variance: Variable)
→ ReLU
→ Dropout (keep_prob: Placeholder)
→ 输出
冻结后,该图变为:
输入 (Placeholder)
→ Conv2D (权重W: Const, 偏置b: Const)
→ BatchNorm (gamma: Const, beta: Const, moving_mean: Const, moving_variance: Const)
→ ReLU
→ 输出
具体变化体现在三个层面:
- 节点类型替换:所有Variable节点被替换为Const节点,其值从检查点(Checkpoint)文件中读取并写入图定义。
- 训练节点剪除:Dropout节点被完全移除(因为推理时keep_prob恒为1.0),BatchNorm的is_training参数被固定为False,其内部的计算路径被简化为
scale * (x - mean) / sqrt(var + eps) + beta。 - 控制流简化:如果训练图中存在条件分支(如tf.cond),冻结工具会尝试将其转换为静态分支;如果无法转换,则会报错提示开发者修改模型结构。
实战提示:在冻结时,务必指定正确的output_node_names。如果指定错误,冻结工具可能会保留大量无关节点,导致生成的模型体积膨胀,甚至包含训练操作。建议在训练脚本中显式命名输出张量,例如:tf.identity(logits, name='output_logits')。
3.4 冻结与权重固化的关系:是“包含”而非“等同”
很多资料将“冻结”和“权重固化”混为一谈,但严格来说,权重固化是冻结的核心操作之一,但冻结还包含更广泛的结构清理工作。我们可以用以下公式来理解:
图冻结 = 权重固化(变量转常量) + 训练节点剪除 + 控制流简化 + 图结构重写
权重固化(Weight Freezing)仅指将Variable的值固定为常量,这可以通过convert_variables_to_constants实现。但一个只做了权重固化的图,仍然可能包含Assign节点(虽然它们不再被调用)、优化器状态变量等冗余信息。图冻结则更进一步,它会从输出节点反向遍历,将所有不参与前向推理的节点全部删除,确保最终生成的图是“最小化”的推理图。
此外,冻结还涉及一个重要的细节:将训练时的BatchNorm参数折叠到卷积层中。在训练图中,BatchNorm是一个独立的层;但在推理时,为了减少计算量,通常会将BatchNorm的缩放因子和偏移量融合到前一层的卷积权重中。这个操作虽然不属于“权重固化”的范畴,但却是图冻结过程中常见的优化步骤(在TensorFlow 2.x中,tf.keras模型的freeze操作会自动处理此问题)。
3.5 常见误区与总结复盘
在实际教学中,我发现学员对图冻结存在以下典型误区:
- 误区一:认为冻结后模型无法再用于训练。实际上,冻结生成的.pb文件确实无法直接训练,但原始Checkpoint文件仍然保留,可以随时恢复训练。冻结只是生成一个推理副本。
- 误区二:混淆冻结与量化。冻结是结构变换,不改变数值精度;量化是数值压缩,将float32转为int8。两者是独立的优化步骤,通常先冻结,再量化。
- 误区三:忽略输出节点名称。在TensorFlow 1.x中,如果使用
saved_model或checkpoint冻结,必须明确指定输出节点;否则冻结工具可能因无法确定边界而报错或生成错误图。 - 误区四:认为冻结后模型一定变小。冻结主要移除的是优化器状态和训练节点,如果模型本身权重占主导,冻结后体积可能变化不大。真正的体积缩减通常来自后续的量化步骤。