我在训练过程中定义了一个Keras模型,如下所示:
img = keras.Input(shape=[65, 65, 2]) bnorm = keras.layers.BatchNormalization()(img) ... model = keras.Model(img, outputprob)
然而,在服务过程中,我的输入有所不同。因此,我定义了一个输入层(验证to_img
的形状也为(65, 65, 2)
),并尝试使用以下代码进行模型组合:
to_img = keras.layers.Lambda(...)(json_input) model_output = model(to_img) serving_model = keras.Model(json_input, model_output)
然而,我得到了以下错误:
tensorflow.python.framework.errors_impl.InvalidArgumentError: Shape must be rank 4 but is rank 3 for 'model/batch_normalization/cond/FusedBatchNorm' (op: 'FusedBatchNorm') with input shapes: [65,65,2], [2], [2], [0], [0].
这似乎表明批量维度未能通过。为什么会这样?
编辑:我尝试过的方法包括:
(1) 在所有层中明确设置trainable=False
,但这似乎没有任何效果:
model_core = model for layer in model_core.layers: layer.trainable = False model_output = model_core(to_img)
(2) 尝试扩展预处理的结果:
to_img = keras.layers.Lambda( lambda x : preproc(x))(json_input) to_img = keras.layers.Lambda( lambda x : tf.expand_dims(x, axis=0) )(to_img)
这导致了一个错误:AttributeError: 'Model' object has no attribute '_name'
,发生在serving_model = keras.Model(json_input, model_output)
这行。
(3) 将Lambda层更改为使用map_fn
来逐个处理数据:
to_img = keras.layers.Lambda( lambda items: K.map_fn(lambda x: preproc, items))(json_input)
这导致了一个形状错误,表明预处理函数接收到的是[65,2]的项目,而不是[65,65,2]。这表明Lambda层一次处理一个示例。
(4) 以下是模型的完整代码:
img = keras.Input(shape=[height, width, 2]) # 模型的卷积部分 cnn = keras.layers.BatchNormalization()(img) for layer in range(nlayers): nfilters = nfil * (layer + 1) cnn = keras.layers.Conv2D(nfilters, (ksize, ksize), padding='same')(cnn) cnn = keras.layers.Activation('elu')(cnn) cnn = keras.layers.BatchNormalization()(cnn) cnn = keras.layers.MaxPooling2D(pool_size=(2, 2))(cnn) cnn = keras.layers.Flatten()(cnn) cnn = keras.layers.Dropout(dprob)(cnn) cnn = keras.layers.Dense(10, activation='relu')(cnn) # 模型的特征工程部分 engfeat = keras.layers.Lambda( lambda x: engineered_features(x, height//2))(img) # 连接两个部分 both = keras.layers.concatenate([cnn, engfeat]) ltgprob = keras.layers.Dense(1, activation='sigmoid')(both) # 创建模型 model = keras.Model(img, ltgprob) def rmse(y_true, y_pred): import tensorflow.keras.backend as K return K.sqrt(K.mean(K.square(y_pred - y_true), axis=-1)) optimizer = tf.keras.optimizers.Adam(lr=params['learning_rate'], clipnorm=1.) model.compile(optimizer=optimizer, loss='binary_crossentropy', metrics=['accuracy', 'mse', rmse])
以及预处理函数的代码:
def reshape_into_image(features, params): # 堆叠输入形成2通道输入 # features['ref'] 是 [-1, height*width] # 堆叠图像是 [-1, height*width, n_channels] n_channels = 2 stacked = tf.concat([features['ref'], features['ltg']], axis=1) height = width = PATCH_SIZE(params) return tf.reshape(stacked, [height, width, n_channels])
以及服务层:
# 1. 从JSON中提取多个输入的层 height = width = PATCH_SIZE(hparams) json_input = keras.layers.concatenate([ keras.layers.Input(name='ref', dtype=tf.float32, shape=(height * width,)), keras.layers.Input(name='ltg', dtype=tf.float32, shape=(height * width,)), ], axis=0) # 2. 将json_input转换为图像(模型所需的格式) to_img = keras.layers.Lambda( lambda x: reshape_into_image(features={ 'ref': tf.reshape(x[0], [height * width, 1]), 'ltg': tf.reshape(x[1], [height * width, 1]) }, params=hparams), name='serving_reshape')(json_input) # 3. 现在,使用训练好的模型进行预测 model_output = model(to_img) # 4. 创建服务模型 serving_model = keras.Model(json_input, model_output)
回答:
考虑到样本轴,您的模型输入形状是(?, 65, 65, 2)
,其中?
可以是一个或多个。因此,您需要修改Lambda层(实际上是其中的包装函数),使得其输出也为(?, 65, 65, 2)
。一种方法是在包装函数中使用K.expand_dims(out, axis=0)
,这样输出形状将为(1, 65, 65, 2)
。
顺便提一下,K
指的是后端:from keras import backend as K
。
此外,请注意,您必须定义Lambda包装的函数以保留批量轴;否则,很可能在定义该函数时出了问题。
更新:
错误AttributeError: 'Model' object has no attribute '_name'
是因为您将json_input
作为模型的输入传递。然而,它不是输入层,而是concatenation
层的输出。为了解决这个问题,首先定义输入层,然后将它们传递给concatenation
层和Model
类,像这样:
inputs = [keras.layers.Input(name='ref', dtype=tf.float32, shape=(height * width,)), keras.layers.Input(name='ltg', dtype=tf.float32, shape=(height * width,))]json_input = keras.layers.concatenate(inputs, axis=0)# ...serving_model = keras.Model(inputs, model_output)
更新2:我认为您可以写得更简单一些,不必陷入这么多不必要的麻烦。您想从形状为(?, h*w)
的两个张量转换到形状为(?, h, w, 2)
的张量。您可以使用Reshape
层,这样做的话会是:
from keras.layers import Reshapeinputs = [keras.layers.Input(name='ref', dtype=tf.float32, shape=(height * width,)), keras.layers.Input(name='ltg', dtype=tf.float32, shape=(height * width,))]reshape_layer = Reshape((height, width, 1))r_in1 = reshape_layer(inputs[0])r_in2 = reshape_layer(inputs[1])img = concatenate([r_in1, r_in2])output = model(img)serving_model = keras.Model(inputs, output)
不需要任何自定义函数或Lambda层。
顺便说一句,如果您有兴趣知道,批量轴移除的问题是由以下这行代码引起的:
return tf.reshape(stacked, [height, width, n_channels])
在重塑时,您没有考虑批量轴。