我在尝试使用Keras和TensorFlow后端(TensorFlow版本:1.9.0,Keras版本:2.1.6)为医学图像分类任务创建一个自定义CNN和预训练VGG16的集成模型。代码如下所示:
#load librariesfrom keras.models import Model, Inputfrom keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D, Activation, Average, Densefrom load_data import load_resized_training_data, load_resized_validation_datafrom load_data import load_resized_test_datafrom keras.losses import categorical_crossentropyfrom keras.callbacks import ModelCheckpoint, TensorBoardfrom keras.optimizers import Adamfrom keras.applications.vgg16 import VGG16import numpy as np#################################################################################load databatch_size = 8num_epochs = 1img_rows= 224img_cols = 224num_channels = 3num_classes = 2X_train, Y_train = load_resized_training_data(img_rows, img_cols)X_valid, Y_valid = load_resized_validation_data(img_rows, img_cols)X_test, Y_test = load_resized_test_data(img_rows, img_cols)print(X_train.shape, Y_train.shape, X_valid.shape, Y_valid.shape,X_test.shape, Y_test.shape)X_train = X_train.astype('float32')X_valid = X_valid.astype('float32')X_test = X_test.astype('float32')X_train /= 255X_valid /= 255X_test /= 255 ############################################################################### '''由于两个模型处理的数据形状相同,因此定义一个单一的输入层供每个模型使用是合理的。'''input_shape = X_train[0,:,:,:].shapeprint(input_shape) # 224, 224, 3model_input = Input(shape=input_shape)print(model_input) # Tensor("input_1:0", shape=(?, 224, 224, 3), dtype=float32)###############################################################################'''定义第一个模型:一个以函数式API形式的简单顺序模型'''x = Conv2D(16, kernel_size=(3, 3), activation='relu')(model_input)x = MaxPooling2D((2, 2))(x)x = Conv2D(32, (3, 3), activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(64, (3, 3), activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(128, (3, 3), activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(256, (3, 3), activation='relu')(x)x = MaxPooling2D((2, 2))(x)x = Conv2D(512, (3, 3), activation='relu')(x)x = Conv2D(2, (1, 1))(x)x = GlobalAveragePooling2D()(x)x = Activation(activation='softmax')(x)custom_model = Model(inputs=model_input, outputs=x, name='custom_cnn')###############################################################################def compile_and_train(model, num_epochs): model.compile(loss=categorical_crossentropy, optimizer=Adam(), metrics=['acc']) filepath = 'weights/' + model.name + '.{epoch:02d}-{val_acc:.2f}.hdf5' checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_weights_only=True, save_best_only=True, mode='auto', period=1) tensor_board = TensorBoard(log_dir='logs/', histogram_freq=0, batch_size=batch_size) history = model.fit(X_train, Y_train, batch_size=batch_size, epochs=num_epochs, verbose=1, callbacks=[checkpoint, tensor_board], validation_data=(X_valid, Y_valid)) return history#compile and train the model_ = compile_and_train(custom_model, num_epochs=num_epochs)###############################################################################def evaluate_error(model): pred = model.predict(X_test, batch_size = batch_size) pred = np.argmax(pred, axis=1) pred = np.expand_dims(pred, axis=1) # make same shape as y_test error = np.sum(np.not_equal(pred, Y_test)) / Y_test.shape[0] return errorevaluate_error(custom_model)###############################################################################'''第二个模型是一个预训练的VGG16模型,使用ImageNet权重初始化,将从第一层开始训练'''vgg16_model = VGG16(weights='imagenet', include_top=False, input_shape=(img_rows, img_cols, 3))x = vgg16_model.outputx = GlobalAveragePooling2D()(x)predictions = Dense(num_classes, activation='softmax')(x)vgg16_custom_model = Model(inputs=vgg16_model.input, outputs=predictions, name='vgg16_cnn')#compile and train the model_ = compile_and_train(vgg16_custom_model, num_epochs=num_epochs)#Evaluate the model by calculating the error on the test setevaluate_error(vgg16_custom_model)###############################################################################custom_model.load_weights('weights/custom_cnn.01-0.60.hdf5')vgg16_custom_model.load_weights('weights/vgg16_cnn.01-0.50.hdf5')models = [custom_model, vgg16_custom_model]###############################################################################def ensemble(models, model_input): outputs = [model.outputs[0] for model in models] y = Average()(outputs) model = Model(inputs=model_input, outputs=y, name='ensemble') return modelensemble_model = ensemble(models, model_input)evaluate_error(ensemble_model)###############################################################################
代码在创建集成定义之前运行正常。在定义集成时,我遇到了以下错误:
RuntimeError: Graph disconnected: cannot obtain value for tensor Tensor("input_7:0", shape=(?, 224, 224, 3), dtype=float32) at layer "input_7". The following previous layers were accessed without issue: []
我不确定是否可以以这种方式将model_input
传递给预训练的VGG16。如何修改集成定义?
回答:
问题在于VGG模型的输入没有被ensemble_model
的输入层喂入。要解决这个问题,你需要修改ensemble_model
的定义,并创建一个新的输入层,然后将其传递给两个模型:
def ensemble(models): input_img = Input(shape=input_shape) outputs = [model(input_img) for model in models] # get the output of model given the input image y = Average()(outputs) model = Model(inputs=input_img, outputs=y, name='ensemble') return modelensemble_model = ensemble(models)