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方案——这也是我推荐的方式,因为可以直接嵌入到文档或网页中:
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占用。
4.5 导出前的最终检查清单
在把模型交给STM32Cube.AI之前,我建议你过一遍这个清单:
- 输入形状确认:
input_shape是否与传感器数据匹配? - 输出激活函数:分类用softmax/sigmoid,回归用linear,别搞混。
- 参数总量:是否在目标芯片的承受范围内?
- 文件格式:必须是
.h5,不是.hdf5或.weights.h5。 - 模型可加载性:用
keras.models.load_model()试加载一次,确保文件没损坏。
我曾经有一次,模型训练了三天,结果导出时忘了加 input_shape,STM32Cube.AI直接报错。那种感觉,嗯,不想再体验第二次。