Keras带对比损失的Siamese Network示例保存加载模型报unknown opcode如何解决
报错根因
unknown opcode报错并非Python版本兼容问题,本质是该示例包含自定义损失类ContrastiveLoss、自定义模型类SiameseModel,直接保存为h5格式时,Keras不会自动序列化自定义逻辑,加载时缺少对应类的声明就会触发解析错误。
完整功能代码
所有自定义类必须在训练、加载阶段都提前声明,且代码完全一致。
基础依赖与自定义逻辑定义
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers # 自定义对比损失类 class ContrastiveLoss(keras.losses.Loss): def __init__(self, margin=1, **kwargs): super().__init__(**kwargs) self.margin = margin def call(self, y_true, y_pred): squared_pred = tf.square(y_pred) margin_squared = tf.square(tf.maximum(self.margin - y_pred, 0)) return tf.reduce_mean(y_true * squared_pred + (1 - y_true) * margin_squared) def get_config(self): config = super().get_config() config.update({"margin": self.margin}) return config # 自定义孪生网络模型类 class SiameseModel(keras.Model): def __init__(self, siamese_network, margin=1): super().__init__() self.siamese_network = siamese_network self.margin = margin self.loss_tracker = keras.metrics.Mean(name="loss") @property def metrics(self): return [self.loss_tracker] def train_step(self, data): (inputs, targets) = data with tf.GradientTape() as tape: loss = self._compute_loss(inputs, targets) gradients = tape.gradient(loss, self.siamese_network.trainable_weights) self.optimizer.apply_gradients(zip(gradients, self.siamese_network.trainable_weights)) self.loss_tracker.update_state(loss) return {"loss": self.loss_tracker.result()} def test_step(self, data): (inputs, targets) = data loss = self._compute_loss(inputs, targets) self.loss_tracker.update_state(loss) return {"loss": self.loss_tracker.result()} def _compute_loss(self, inputs, targets): img1, img2 = inputs feature1 = self.siamese_network(img1) feature2 = self.siamese_network(img2) distance = tf.sqrt(tf.reduce_sum(tf.square(feature1 - feature2), axis=1)) loss = ContrastiveLoss(margin=self.margin)(targets, distance) return loss def get_config(self): config = super().get_config() config.update({ "siamese_network": self.siamese_network, "margin": self.margin }) return config @classmethod def from_config(cls, config): return cls(**config) # 特征提取子网络构建 def create_embedding_network(input_shape=(28, 28, 1)): inputs = layers.Input(shape=input_shape) x = layers.Conv2D(32, (3,3), activation='relu')(inputs) x = layers.MaxPooling2D()(x) x = layers.Conv2D(64, (3,3), activation='relu')(x) x = layers.MaxPooling2D()(x) x = layers.Flatten()(x) x = layers.Dense(128, activation='relu')(x) return keras.Model(inputs, x)
模型保存代码(训练完成后执行)
# 假设model为训练完成的SiameseModel实例 # 方案1:保存完整h5模型(包含结构+权重+训练配置) model.save("siamese_contrastive.h5", save_format="h5") # 方案2:仅保存权重(文件更小,需要提前构建相同结构的模型再加载) model.save_weights("siamese_contrastive_weights.h5")
模型加载代码
# 方案1:加载完整h5模型 loaded_model = keras.models.load_model( "siamese_contrastive.h5", custom_objects={ "SiameseModel": SiameseModel, "ContrastiveLoss": ContrastiveLoss } ) # 方案2:加载单独权重 # 1. 构建和训练阶段结构完全一致的模型 embedding_net = create_embedding_network() siamese_model = SiameseModel(embedding_net) siamese_model.compile(optimizer=keras.optimizers.Adam(1e-3)) # 2. 加载权重 siamese_model.load_weights("siamese_contrastive_weights.h5")
注意事项
- 两个自定义类均实现了
get_config方法,用于支持Keras的序列化逻辑,不可省略 - 加载模型前必须先声明两个自定义类,代码和训练阶段完全一致
- 无需保留训练配置时,可导出为SavedModel格式,兼容性更强,无需手动传入
custom_objects参数
内容的提问来源于stack exchange,提问作者LOL
相关产品推荐
相关产品推荐

