子类化tf.keras.Model构建UNet时model.fit报Keras符号输入转numpy数组错误
问题定位与解决方案
核心错误原因
- 子类化
tf.keras.Model的call方法中错误访问预训练骨干网络的静态.output属性:你在call方法里直接拿encoder[idx].output和encoder.pop(-1).output,这些是构建Functional模型时用的静态符号张量,在子类化模型的动态前向传播过程中,这些符号张量无法参与实时计算,传入后续层时就会触发符号张量转numpy的报错。
- 子类化
- 损失函数与输出层不匹配:你的输出层是
Conv2D(1, activation='sigmoid'),属于二分类输出,但你用了SparseCategoricalCrossentropy多分类损失,维度不匹配也会间接导致张量计算异常。
- 损失函数与输出层不匹配:你的输出层是
- 前向传播逻辑错误:call方法中你先将
self.backbone(input_layer)赋值给x,但后续完全没有用到这个动态计算的结果,反而直接用了静态的bridge张量,逻辑完全错位。
- 前向传播逻辑错误:call方法中你先将
修复方案
第一步:调整骨干网络特征提取逻辑
不要在call方法里遍历层拿静态output,改为在__init__阶段就构建一个多输出的Functional骨干模型,直接返回所有需要的编码器特征层的动态计算结果:
class UNet(tf.keras.Model): def __init__(self, model_size="base", num_classes=3, img_size=224, filters_root=32, kernel_size=(3, 3), backbone=None, dropout_rate=.0, dropout_decay=None, local_pooling_layer=layers.MaxPool2D((2, 2)), **kwargs ): super().__init__(name="UNet", **kwargs) if model_size not in ["small", "base"]: model_size = "base" print("Model size input is not amongst {}. Model size is defaulted to base.".format(["small", "base"])) self.model_size = model_size self.num_classes = num_classes self.img_size = img_size self.filters_root = filters_root self.kernel_size = kernel_size self.backbone = backbone if backbone else get_unet_backbone( backbone_name="Efficient_net", pretrained_weights="imagenet") self.backbone.trainable = False # 冻结编码器骨干 self.dropout_rate = dropout_rate # 初始化阶段就构建多输出骨干模型,提前拿到需要的特征层 encoder_output_layer_names = [l.name for l in self.backbone.layers if "a_activation" in l.name] if self.model_size == "small": encoder_output_layer_names = encoder_output_layer_names[:3] + encoder_output_layer_names[-2:] # 构建返回所有编码器特征的功能模型 self.encoder_model = tf.keras.Model( inputs=self.backbone.input, outputs=[self.backbone.get_layer(name).output for name in encoder_output_layer_names] ) def call(self, input_layer, training=False): # 直接拿到所有编码器层的动态计算结果 encoder_features = self.encoder_model(input_layer, training=training) # 最后一个特征作为桥接层 x = encoder_features[-1] # 倒序遍历前面的特征层做跳跃连接 for idx in range(len(encoder_features)-2, 0, -1): x = decoder_block(x, encoder_features[idx], filters=self.filters_root * 2 ** idx, kernel_size=self.kernel_size, dropout_rate=self.dropout_rate) # 单类分割输出用1通道+sigmoid,多类分割可改为num_classes通道+softmax outputs = layers.Conv2D(1, kernel_size=(1, 1), padding="same", activation="sigmoid", kernel_initializer=LecunNormal(seed=global_seed), bias_initializer=LecunNormal(seed=global_seed))(x) return outputs
第二步:修正损失函数配置
如果是二分类/单类分割,改为二进制交叉熵:
model.compile( loss=tf.keras.losses.BinaryCrossentropy(from_logits=False) , optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4), metrics=['accuracy'] )
如果是多类分割,输出层改为Conv2D(num_classes, activation='softmax'),保留SparseCategoricalCrossentropy即可。
可选兼容处理
如果还有类似报错,可以在代码开头加入全局配置禁用严格的张量类型检查:
import tensorflow as tf tf.compat.v1.disable_eager_execution()
内容的提问来源于stack exchange,提问作者Phan Nguyen
相关产品推荐
相关产品推荐

