4. Keras模型导出:构建、保存与可视化

好,我们进入第四章。这一章其实很实在——就是教你如何把一个Keras模型从零搭出来,存成.h5文件,再把它的结构看个清清楚楚。别小看这一步,我在项目里见过太多人模型训练完了,结果导出格式不对,或者压根不知道模型里有多少参数,最后部署到STM32上直接翻车。

说白了,这一步是连接训练和部署的桥梁。桥没搭好,后面全白干。

4.1 构建一个简单的Keras模型

我们先从最基础的开始。假设你要做一个二分类任务,输入是4个特征,输出是0或1。这种场景在嵌入式里很常见,比如传感器数据分类、简单故障检测。

我个人习惯用Sequential模型,简单直观。来看代码:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(16, activation='relu', input_shape=(4,), name='dense_1'),
    layers.Dense(8, activation='relu', name='dense_2'),
    layers.Dense(1, activation='sigmoid', name='output')
])

model.summary()

嗯,这里要注意几点:

  • input_shape 必须明确指定,不然后面导出会报错。我踩过这个坑。
  • name 参数建议加上,方便后面可视化时辨认层。
  • 激活函数选 sigmoid 是因为二分类输出需要0~1之间的概率值。

运行 model.summary() 你会看到类似这样的输出:

层名 输出形状 参数量
dense_1 (Dense) (None, 16) 80
dense_2 (Dense) (None, 8) 136
output (Dense) (None, 1) 9

总参数量225个。对于STM32来说,这个量级非常友好。我曾经把一个30万参数的模型硬塞到STM32F4上,结果RAM直接爆了。所以,嵌入式模型的第一原则就是:参数越少越好。

4.2 保存为.h5文件

模型搭好了,接下来就是保存。Keras提供了两种保存方式,但这里我只推荐一种:

model.save('my_model.h5')

就这么一行。它会保存模型的架构、权重、训练配置以及优化器状态。为什么强调这个?因为STM32Cube.AI在导入时,需要完整的模型结构信息,光有权重文件是不够的。

注意: 不要用 model.save_weights() 保存。它只保存权重,不保存结构。STM32Cube.AI无法直接导入这种文件。我刚开始做的时候就用错了,折腾了半天才发现问题。

如果你只想保存架构(比如用于可视化),可以用:

# 保存为JSON格式
json_string = model.to_json()
with open('model_architecture.json', 'w') as f:
    f.write(json_string)

但说实话,对于STM32部署,.h5 文件才是王道。

4.3 模型结构可视化

光看 summary() 的文字输出还不够直观。我建议你用Keras自带的可视化工具,把模型结构画出来:

from tensorflow.keras.utils import plot_model

plot_model(model, to_file='model_structure.png', show_shapes=True, show_layer_names=True)

这会生成一张PNG图片,清晰展示每一层的输入输出形状和连接关系。对于调试和文档编写都非常有用。

不过,如果你在服务器上跑代码,没有图形界面,可以用下面这个SVG方案——这也是我推荐的方式,因为可以直接嵌入到文档或网页中:

Keras模型结构图(4→16→8→1) 输入层 (4) Dense_1 (16, ReLU) Dense_2 (8, ReLU) 输出层 (1, Sigmoid) 总参数量:225 | 可训练参数:225 | 不可训练参数:0 适合STM32F4及以上系列部署
关键点: 可视化不只是为了好看。它能帮你快速发现结构错误,比如层连接顺序不对、输入输出维度不匹配。我在项目中就靠这个发现过一次全连接层顺序写反的问题。

4.4 参数统计与分析

参数统计是很多人忽略的一步。你想想看,STM32的RAM和Flash就那么点,模型参数一多,根本装不下。所以,在导出之前,一定要搞清楚你的模型到底有多“重”。

Keras提供了几种方式来获取参数信息:

# 方式1:直接看summary
model.summary()

# 方式2:获取可训练参数数量
trainable_params = sum([tf.keras.backend.count_params(w) for w in model.trainable_weights])
print(f'可训练参数: {trainable_params}')

# 方式3:获取每层参数详情
for layer in model.layers:
    params = layer.count_params()
    print(f'{layer.name}: {params} 个参数')

输出结果:

层名 权重形状 偏置形状 参数量
dense_1 (4, 16) (16,) 80
dense_2 (16, 8) (8,) 136
output (8, 1) (1,) 9

这里有个小技巧:参数量 = 权重数 + 偏置数。比如 dense_1 层,权重是4×16=64个,偏置是16个,加起来80个。这个计算方式在STM32Cube.AI里也会用到,它会根据参数量估算Flash占用。

经验值参考: 对于STM32F4系列,建议模型总参数量控制在10万以内;对于STM32H7系列,可以放宽到50万左右。超过这个量,推理速度会明显下降,甚至可能因为RAM不足而无法加载。

4.5 导出前的最终检查清单

在把模型交给STM32Cube.AI之前,我建议你过一遍这个清单:

  1. 输入形状确认:input_shape 是否与传感器数据匹配?
  2. 输出激活函数:分类用softmax/sigmoid,回归用linear,别搞混。
  3. 参数总量:是否在目标芯片的承受范围内?
  4. 文件格式:必须是 .h5,不是 .hdf5 或 .weights.h5。
  5. 模型可加载性:用 keras.models.load_model() 试加载一次,确保文件没损坏。

我曾经有一次,模型训练了三天,结果导出时忘了加 input_shape,STM32Cube.AI直接报错。那种感觉,嗯,不想再体验第二次。