You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 17:24:03