一、背景介绍

在 AI 项目落地过程中,大量历史模型、开源预训练模型基于 TensorFlow 完成训练,而昇思 MindSpore 作为全场景深度学习框架,无法直接加载 TensorFlow 原生 ckpt、pb 冻结图文件。模型转换工作分为两大核心任务:网络结构迁移、权重参数迁移。

TensorFlow 与 MindSpore 在存储格式、权重排布、算子实现存在多处差异:

  1. 文件格式:TensorFlow ckpt 为 TF 私有 protobuf 格式,MindSpore ckpt 使用独立 proto 定义,二者互不兼容;冻结 pb(Frozen Graph)保存计算图与权重,是转换输入标准格式。

  2. 权重维度顺序差异:TensorFlow 卷积权重维度顺序为[kernel_h, kernel_w, in_channel, out_channel];MindSpore Conv2d 卷积权重为[out_channel, in_channel, kernel_h, kernel_w],转换时必须做轴转置;全连接层权重同样存在维度反转问题。

  3. 算子语义差异:部分激活、池化、归一化算子参数定义不同,直接导入会出现推理结果偏差。

TensorFlow 模型迁移到 MindSpore 主要分为三类目标场景:训练迁移(在 MindSpore 继续微调)、云侧推理(导出 MindIR 在昇腾 NPU 执行推理)、端侧推理(转换为 ms 模型部署端侧设备)。

适用场景

  1. 存量 TensorFlow 开源预训练模型迁移到昇腾硬件,进行微调训练;

  2. 已有 TF 业务模型,需要迁移 MindSpore 做高性能推理部署;

  3. 算法原型基于 TensorFlow 完成,需要迁移整套网络权重到 MindSpore 工程。

重要前置约束:TensorFlow 原始模型需要导出Frozen pb 冻结图,将变量全部固化为常量;未冻结的 checkpoint 无法直接用于 MindConverter 工具转换。

二、TensorFlow 模型转换开发模式

2.1 三种开发接入模式

开发模式

适用场景

MindSpore 接入方式

核心特点

MindConverter 工具自动转换(推荐)

CNN、通用 CV 模型,算子覆盖度高

命令行 mindconverter 读取 pb 文件,自动生成 MindSpore 网络脚本 + ckpt 权重文件

自动化程度高;算子缺失需要手动修改生成脚本;输入 shape 转换时固定,动态 shape 需要二次修改代码

手动权重映射转换

大模型、复杂自定义算子,工具转换失败场景

读取 TF 权重,构建参数映射字典,维度转置,调用 save_checkpoint 生成 ms ckpt

可控性强,适配复杂模型;开发工作量大,需要人工核对每一层参数名与 shape

中间格式中转(pb→ONNX→MindSpore)

TF2 复杂模型,MindConverter 解析 pb 报错

TensorFlow 导出 ONNX,再把 ONNX 导入 MindSpore/MindSpore Lite

兼容 TF2 新版模型;中间层引入精度损耗风险,算子链变多,需要结果对齐校验

注意:MindConverter 仅支持 TensorFlow1.x 冻结 pb;TensorFlow2 建议优先导出 ONNX 中转方案。转换完成后,必须执行精度对齐校验,使用相同输入,对比 TensorFlow 输出与 MindSpore 输出误差。

2.2 整体数据流

TensorFlow原始ckpt → 导出Frozen pb冻结图(固化变量)
→ 选择转换模式(MindConverter /手动映射 /ONNX中转)
→ 生成MindSpore网络脚本 + MindSpore ckpt权重
→ 加载网络与权重,执行精度对齐测试
→ 导出MindIR模型,用于昇腾NPU训练/推理

2.3 关键约束(昇腾部署重点)

  1. MindConverter 转换时--shape参数会固化输入张量尺寸;若业务需要动态输入,转换完成后修改网络脚本,替换固定维度为 - 1;

  2. 卷积层、全连接层权重维度顺序必须转置,否则模型推理完全错误;

  3. 转换后 Dropout 训练算子会丢失,需要手动补齐网络中的 Dropout 逻辑,仅推理不受影响;

  4. 算子映射缺失时,转换报告中会标记未转换算子,需要手动替换为 MindSpore 等价算子或者自定义算子。

三、核心模块内容详解

3.1 核心概念

  • Frozen pb 冻结图:将 TensorFlow 计算图中所有 Variable 变量转换为 Const 常量,网络结构和权重全部打包到 pb 二进制文件,是转换工具的标准输入。

  • MindConverter:MindInsight 配套工具,解析 TF pb 计算图,生成对应的 MindSpore Cell 网络脚本、ckpt 权重、转换报告与权重映射表,报告记录算子兼容情况、参数映射关系。

  • 参数手动映射:当自动转换失败,手动读取 TF 权重,修正维度排布,组装 MindSpore Parameter 列表,保存 ckpt 文件。

3.2 核心接口定义

  1. mindconverter命令行工具:解析 pb,输出 model.py 网络脚本与 ckpt 权重;

  2. tf.train.load_checkpoint():读取 TensorFlow 原始 ckpt 权重;

  3. mindspore.save_checkpoint():保存组装完成的参数字典,生成 MindSpore ckpt 文件;

  4. mindspore.load_checkpoint():加载转换后的 ckpt 权重到 MindSpore 网络;

  5. mindspore.export():加载网络与权重导出 MindIR 离线推理模型,供昇腾推理使用。

3.3 对接昇腾 NPU 要点

转换完成的 MindSpore 网络,需要在昇腾设备上完成精度对齐;加载 ckpt 后调用 export 导出 MindIR,用于 CANN 推理;转换后的模型不能直接使用 TF pb 文件送入昇腾推理,必须经过 MindSpore 生成 MindIR。

四、代码实战

环境依赖:mindspore‑ascend,mindinsight,tensorflow==1.15;示例分为:①导出 TensorFlow 冻结 pb;②MindConverter 命令转换;③手动权重映射代码实现;④转换后精度对齐验证。

4.1 TensorFlow 导出 Frozen pb 冻结图(TF1.15)

import tensorflow as tf
from tensorflow.python.framework import graph_util

# 加载训练好的TF ckpt,构建推理图
saver = tf.train.import_meta_graph("./tf_model/model.ckpt.meta")
graph = tf.get_default_graph()
input_node = graph.get_tensor_by_name("input:0")
output_node = graph.get_tensor_by_name("output:0")

with tf.Session() as sess:
    saver.restore(sess, "./tf_model/model.ckpt")
    # 冻结图,变量转常量
    output_graph_def = graph_util.convert_variables_to_constants(
        sess, graph.as_graph_def(), ["output"]
    )
    with tf.gfile.GFile("./frozen_model.pb", "wb") as f:
        f.write(output_graph_def.SerializeToString())
print("冻结pb导出完成")

4.2 MindConverter 命令行自动转换

mindconverter \
--model_file ./frozen_model.pb \
--shape 1,28,28,1 \
--input_nodes input:0 \
--output_nodes output:0 \
--output ./ms_output \
--report ./ms_output/report

执行完成后,ms_output目录生成:model.py(MindSpore 网络脚本)、model.ckpt(转换权重)、转换报告,报告查看算子是否全部成功映射。

4.3 手动权重映射转换示例(当自动转换失败)

以 LeNet 卷积网络为例,处理卷积、全连接权重转置,构建 MindSpore 可加载 ckpt。

import tensorflow as tf
import numpy as np
from mindspore import Tensor, save_checkpoint

def tf_ckpt_to_ms_ckpt(tf_ckpt_path, ms_ckpt_out):
    reader = tf.train.NewCheckpointReader(tf_ckpt_path)
    param_dict = {}
    var_shape_map = reader.get_variable_to_shape_map()

    for tf_param_name in var_shape_map.keys():
        weight_np = reader.get_tensor(tf_param_name)
        # 卷积权重维度转置:[h,w,in,out] → [out,in,h,w]
        if "conv" in tf_param_name and "weight" in tf_param_name:
            weight_np = np.transpose(weight_np, axes=[3, 2, 0, 1])
        # 全连接kernel转置 [in,out] → [out,in]
        if "fc" in tf_param_name and "kernel" in tf_param_name:
            weight_np = np.transpose(weight_np, axes=[1, 0])
        # 参数名映射,根据实际网络修改key
        ms_param_name = tf_param_name.replace("/", ".")
        param_dict[ms_param_name] = Tensor(weight_np)
    # 保存MindSpore ckpt
    save_checkpoint(param_dict, ms_ckpt_out)
    print(f"生成MindSpore ckpt:{ms_ckpt_out}")

if __name__ == "__main__":
    tf_ckpt_to_ms_ckpt("./tf_model/model.ckpt", "./manual.ckpt")

4.4 转换后精度对齐校验代码

固定相同输入,对比 TensorFlow 输出与 MindSpore 输出,验证转换正确性。

import tensorflow as tf
import mindspore as ms
import numpy as np
from mindspore import load_checkpoint, load_param_into_net
from model import Net  # MindConverter生成的网络

# 1.构造测试输入
test_input = np.random.rand(1,28,28,1).astype(np.float32)

# 2.TensorFlow推理输出
graph = tf.Graph()
with graph.as_default():
    with tf.Session() as sess:
        gd = tf.GraphDef()
        with tf.gfile.GFile("./frozen_model.pb", "rb") as f:
            gd.ParseFromString(f.read())
        tf.import_graph_def(gd, name="")
        tf_input = graph.get_tensor_by_name("input:0")
        tf_out = graph.get_tensor_by_name("output:0")
        tf_result = sess.run(tf_out, feed_dict={tf_input: test_input})

# 3.MindSpore推理输出
ms.set_context(mode=ms.GRAPH_MODE, device_target="Ascend")
net = Net()
params = load_checkpoint("./ms_output/model.ckpt")
load_param_into_net(net, params)
ms_result = net(ms.Tensor(test_input))

# 误差校验
diff = np.max(np.abs(tf_result - ms_result.asnumpy()))
print(f"最大误差:{diff}")
if diff < 1e‑4:
    print("转换精度对齐通过")
else:
    print("存在精度偏差,请检查权重转置、算子参数")

4.5 导出 MindIR 用于昇腾 NPU 推理

from mindspore import export
input_tensor = ms.Tensor(np.random.rand(1,28,28,1).astype(np.float32))
export(net, input_tensor, file_name="model", file_format="MINDIR")

五、常见问题与调优实践

Q1:MindConverter 转换报错 1000001,pb 解析失败

  • 根因:转换环境 TensorFlow 版本低于生成 pb 的 TF 版本;pb 文件不是 Frozen 冻结图,存在 Variable 变量未固化;

  • 解决:统一 TensorFlow 版本;重新导出 Frozen pb 冻结图,确认全部变量转为常量。

Q2:转换后 MindSpore 推理结果完全不对

  • 根因:卷积、全连接权重未做维度转置;参数名映射不匹配;输入数据 NHWC/NCHW 格式混淆;

  • 解决:核对权重维度,手动转置卷积、全连接 kernel;确认输入张量数据格式与原 TF 一致。

Q3:转换报告提示部分算子未映射

  • 根因:MindConverter 暂不支持该 TF 算子映射;

  • 解决:查看转换报告定位算子位置,修改生成的 model.py 脚本,替换为 MindSpore 等价算子,复杂算子实现自定义算子。

Q4:MindConverter 转换后 Dropout 消失

  • 根因:转换工具以推理模式解析 pb,训练相关算子不会保留;

  • 解决:手动在 MindSpore 网络脚本中添加nn.Dropout层,用于微调训练,推理阶段不受影响。

Q5:转换后的网络只支持固定输入 shape

  • 根因:MindConverter 根据--shape参数固化输入维度;

  • 解决:转换完成后修改网络脚本,将 shape 中固定 batch 维度改为‑1,实现动态输入。

六、总结

TensorFlow 模型迁移昇思 MindSpore 分为自动工具转换、手动权重映射、ONNX 中转三种模式。优先选择 MindConverter 做自动化迁移,遇到算子缺失、大模型复杂场景采用手动权重映射,同时必须注意卷积、全连接权重维度转置这个高频易错点。

开发关键点:

  1. 原始 TensorFlow 模型必须导出 Frozen pb 冻结图,作为转换输入;

  2. 卷积、全连接权重维度排布 TF 与 MindSpore 不一致,转换务必做转置;

  3. 转换完成后必须做精度对齐校验,对比 TF 与 MindSpore 输出误差;

  4. 自动转换会固化输入 shape,训练需要 Dropout 算子要手动补齐;

  5. 最终导出 MindIR 格式,用于昇腾 NPU 离线推理部署。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐