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

如何合并两个Keras模型并在中间重塑以实现联合训练?

问题描述

我有两个Keras模型:

  • 编码器模型:将256×192的图像编码为128维向量,输入形状(None, 192, 256, 1),输出形状(None, 128)(None代表批量大小)
  • 比较器模型:对比来自编码器的两个向量,输入形状(None, 256),输出形状(None, 1)

需要合并成一个模型,输入形状(None, 2, 192, 256, 1)(2代表待对比的两张图像),输出形状(None, 1),同时保留原两个模型以便后续单独使用,且能用常规fit方法训练。

尝试代码

class _layer_1(keras.layers.Layer):
    def call(self, x):
        return tf.reshape(x, (x.shape[0] * x.shape[1],) + x.shape[2:])

class _layer_2(keras.layers.Layer):
    def call(self, x):
        return tf.reshape(x, (x.shape[0] // 2, x.shape[1] * 2) + x.shape[2:])

model = keras.Sequential([
    keras.Input(shape=(2, 192, 256, 1)),
    _layer_1(),
    encoder_model,
    _layer_2(),
    comparator_model
])

model.compile(optimizer=keras.optimizers.Adam(learning_rate=3e-5), loss='mean_squared_error')

报错信息

Traceback (most recent call last):
  File "comparator_train.py", line 61, in <module>
    get_models()[2].fit(np.random.sample((30, 2, 192, 256, 1),), np.random.sample((30, 1),))
  File "comparator_train.py", line 54, in get_models
    comparator_model
  File "/opt/miniconda3/lib/python3.7/site-packages/tensorflow/python/training/tracking/base.py", line 587, in _method_wrapper
    result = method(self, *args, **kwargs)
  File "/opt/miniconda3/lib/python3.7/site-packages/keras/utils/traceback_utils.py", line 67, in error_handler
    raise e.with_traceback(filtered_tb) from None
  File "/tmp/__autograph_generated_filecoqup163.py", line 12, in tf__call
    retval_ = ag__.converted_call(ag__.ld(tf).reshape, (ag__.ld(x), (((ag__.ld(x).shape[0] * ag__.ld(x).shape[1]),) + ag__.ld(x).shape[2:])), None, fscope)
TypeError: Exception encountered when calling layer "private_layer_1" (type _layer_1).

in user code:

    File "comparator_train.py", line 43, in call  *
        return tf.reshape(x, (x.shape[0] * x.shape[1],) + x.shape[2:])

    TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'


Call arguments received by layer "private_layer_1" (type _layer_1):
  • x=tf.Tensor(shape=(None, 2, 192, 256, 1), dtype=float32)

附编码器与比较器完整代码

encoder_model = keras.Sequential([
    keras.layers.Input(shape=(192, 256, 1)),
    keras.layers.SeparableConv2D(filters=128, kernel_size=12, strides=2, padding='same', activation=keras.layers.LeakyReLU(alpha=0.1)),
    keras.layers.BatchNormalization(),
    keras.layers.SeparableConv2D(filters=256, kernel_size=12, strides=3, padding='same', activation=keras.layers.LeakyReLU(alpha=0.1)),
    keras.layers.BatchNormalization(),
    keras.layers.SeparableConv2D(filters=256, kernel_size=(12, 12), strides=2, padding='same', activation=keras.layers.LeakyReLU(alpha=0.1)),
    keras.layers.BatchNormalization(),
    keras.layers.Dropout(0.8),
    keras.layers.SeparableConv2D(filters=128, kernel_size=(8, 11), strides=1, padding='valid', activation=keras.layers.LeakyReLU(alpha=0.1)),
    keras.layers.Dropout(0.14),
    keras.layers.MaxPooling2D(pool_size=(3, 3)),
    keras.layers.Flatten(),
    keras.layers.Dense(units=128, activation='sigmoid')
])

comparator_model = keras.Sequential([
    keras.layers.Input(shape=(256,)),
    keras.layers.Dense(units=256, activation=keras.layers.LeakyReLU(alpha=0.1)),
    keras.layers.Dense(units=256, activation=keras.layers.LeakyReLU(alpha=0.1)),
    keras.layers.Dense(units=1, activation='sigmoid')
])

解决方案

错误核心原因

报错是因为静态形状(x.shape)中的批量维度为None,属于NoneType,无法直接和整数进行乘法/除法运算。自定义层中使用x.shape[0]获取的是静态形状,而动态计算时需要用tf.shape(x)获取张量的动态形状值。

推荐实现方式:使用Keras Functional API

Functional API比Sequential更灵活,能直观处理多输入/分支逻辑,且无需手动做复杂的形状重塑:

import tensorflow as tf
from tensorflow import keras

# 定义合并模型的输入
input_pair = keras.Input(shape=(2, 192, 256, 1))

# 将输入拆分为两张独立图像
img1 = input_pair[:, 0, :, :, :]  # 形状:(None, 192, 256, 1)
img2 = input_pair[:, 1, :, :, :]  # 形状:(None, 192, 256, 1)

# 用同一个编码器处理两张图像(共享权重)
encoded1 = encoder_model(img1)
encoded2 = encoder_model(img2)

# 拼接两个编码向量,得到比较器需要的(None, 256)输入
combined_feat = keras.layers.concatenate([encoded1, encoded2], axis=1)

# 比较器输出结果
output = comparator_model(combined_feat)

# 构建合并模型
combined_model = keras.Model(inputs=input_pair, outputs=output)

# 编译模型
combined_model.compile(optimizer=keras.optimizers.Adam(learning_rate=3e-5), loss='mean_squared_error')

验证测试

用随机数据测试模型是否正常运行:

import numpy as np

# 生成随机输入和标签
x = np.random.random((30, 2, 192, 256, 1))
y = np.random.random((30, 1))

# 训练测试
combined_model.fit(x, y, epochs=1, batch_size=2)

# 验证原模型是否可单独使用
test_img = np.random.random((1, 192, 256, 1))
encoder_output = encoder_model.predict(test_img)
print("编码器输出形状:", encoder_output.shape)  # 应为(1, 128)

test_combined = np.random.random((1, 256))
comparator_output = comparator_model.predict(test_combined)
print("比较器输出形状:", comparator_output.shape)  # 应为(1, 1)

若坚持修复原自定义层方案

如果一定要用Sequential+自定义层的方式,需修改自定义层使用动态形状:

class _layer_1(keras.layers.Layer):
    def call(self, x):
        # 获取动态形状
        batch_size = tf.shape(x)[0]
        num_imgs = tf.shape(x)[1]
        new_shape = tf.concat([[batch_size * num_imgs], tf.shape(x)[2:]], axis=0)
        return tf.reshape(x, new_shape)

class _layer_2(keras.layers.Layer):
    def call(self, x):
        batch_size = tf.shape(x)[0] // 2
        feat_dim = tf.shape(x)[1]
        new_shape = tf.concat([[batch_size], [feat_dim * 2]], axis=0)
        return tf.reshape(x, new_shape)

# 构建模型
model = keras.Sequential([
    keras.Input(shape=(2, 192, 256, 1)),
    _layer_1(),
    encoder_model,
    _layer_2(),
    comparator_model
])

model.compile(optimizer=keras.optimizers.Adam(learning_rate=3e-5), loss='mean_squared_error')

不过这种方式不如Functional API直观,形状变换时容易出错,优先推荐第一种方案。


内容的提问来源于stack exchange,提问作者Усердный бобёр

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 02:40:32