5

I want to use bottlenecks for transfer learning using InceptionV3 in Keras. I've used some of the tips on creating, loading and using bottlenecks from https://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html

My problem is that I don't know how to use a bottleneck (numpy array) as input to an InceptionV3 with a new top layer.

I get the following error:

ValueError: Error when checking input: expected input_3 to have shape (None, None, None, 3) but got array with shape (248, 8, 8, 2048)

248 refers to the total number of images in this case.

I know that this line is wrong, but I dont't know how to correct it:

model = Model(inputs=base_model.input, outputs=predictions)

What is the correct way to input the bottleneck into InceptionV3?

Creating the InceptionV3 bottlenecks:

def create_bottlenecks():
datagen = ImageDataGenerator(rescale=1. / 255)

model = InceptionV3(include_top=False, weights='imagenet')

# Generate bottlenecks for all training images
generator = datagen.flow_from_directory(
    train_data_dir,
    target_size=(img_width, img_height),
    batch_size=batch_size,
    class_mode=None,
    shuffle=False)

nb_train_samples = len(generator.filenames)
bottlenecks_train = model.predict_generator(generator, int(math.ceil(nb_train_samples / float(batch_size))), verbose=1)
np.save(open(train_bottlenecks_file, 'w'), bottlenecks_train)

# Generate bottlenecks for all validation images
generator = datagen.flow_from_directory(
    validation_data_dir,
    target_size=(img_width, img_height),
    batch_size=batch_size,
    class_mode=None,
    shuffle=False)

nb_validation_samples = len(generator.filenames)

bottlenecks_validation = model.predict_generator(generator, int(math.ceil(nb_validation_samples / float(batch_size))), verbose=1)
np.save(open(validation_bottlenecks_file, 'w'), bottlenecks_validation)

Loading the bottlenecks:

def load_bottlenecks(src_dir, bottleneck_file):
    datagen = ImageDataGenerator(rescale=1. / 255)
    generator = datagen.flow_from_directory(
        src_dir,
        target_size=(img_width, img_height),
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=False)

    num_classes = len(generator.class_indices)

    # load the bottleneck features saved earlier
    bottleneck_data = np.load(bottleneck_file)

    # get the class lebels for the training data, in the original order
    bottleneck_class_labels = generator.classes

    # convert the training labels to categorical vectors
    bottleneck_class_labels = to_categorical(bottleneck_class_labels, num_classes=num_classes)

    return bottleneck_data, bottleneck_class_labels

Starting training:

def start_training():
global nb_train_samples, nb_validation_samples

create_bottlenecks()

train_data, train_labels = load_bottlenecks(train_data_dir, train_bottlenecks_file)
validation_data, validation_labels = load_bottlenecks(validation_data_dir, validation_bottlenecks_file)

nb_train_samples = len(train_data)
nb_validation_samples = len(validation_data)

base_model = InceptionV3(weights='imagenet', include_top=False)

# add a global spatial average pooling layer
x = base_model.output
x = GlobalAveragePooling2D()(x)

# let's add a fully-connected layer
x = Dense(1024, activation='relu')(x)

# and a logistic layer -- let's say we have 2 classes
predictions = Dense(2, activation='softmax')(x)

# What is the correct input? Obviously not base_model.input.
model = Model(inputs=base_model.input, outputs=predictions)

# first: train only the top layers (which were randomly initialized)
# i.e. freeze all convolutional InceptionV3 layers
for layer in base_model.layers:
    layer.trainable = False

model.compile(optimizer=optimizers.SGD(lr=0.01, momentum=0.9), loss='categorical_crossentropy', metrics=['accuracy'])

# train the model on the new data for a few epochs
history = model.fit(train_data, train_labels,
                    epochs=epochs,
                    batch_size=batch_size,
                    validation_data=(validation_data, validation_labels),
)

Any help would be appreciated!

Ivo
  • 1,768
  • 20
  • 19

1 Answers1

5

This error happens when you try to train your model with input data in a different shape from the shape your model supports.

Your model supports (None, None, None, 3), meaning:

  • Any number of images
  • Any height
  • Any width
  • 3 channels

So, you must make sure that train_data (and validation_data) matches this shape.

The system is telling that train_data.shape = (248,8,8,2048)

I see that train_data comes from load_botlenecks. Is it really supposed to be coming from there? What is train data supposed to be? An image? Something else? What is a bottleneck?


Your model starts in the Inception model, and the Inception model takes images.

But if bottlenecks are already results of the Inception model, and you want to feed only bottlenecks, then the Inception model should not participate of anything at all.

Start from:

inputTensor = Input((8,8,2048)) #Use (None,None,2048) if bottlenecks vary in size    
x = GlobalAveragePooling2D()(inputTensor)

.....

Create the model with:

model = Model(inputTensor, predictions)

The idea is:

  • Inception model: Image -> Inception -> Bottlenecks
  • Your model: Bottlenecks -> Model -> Labels

The combination of the two models is only necessary when you don't have the bottlenecks preloaded, but you have your own images for which you want to predict the bottlenecks first. (Of course you can work with separate models as well)

Then you're going to input only images (the bottlenecks will be created by Inception and passed to your model, everything internally):

  • Combined model: Image -> Inception ->(bottlenecks)-> Model -> Labels

For that:

inputImage = Input((None,None,3))
bottleNecks = base_model(inputImage)
predictions = model(bottleNecks)

fullModel = Model(inputImage, predictions)
Daniel Möller
  • 84,878
  • 18
  • 192
  • 214
  • Yes, currently InceptionV3 expects an Image, but I want to 'feed' it with bottlenecks, which have a different shape. Bottlenecks are usually created to save computational steps. First I have removed the top layer of InceptionV3 which was trained on ImageNet and had 1000 classes. A bottleneck is the output for an image passed through InceptionV3 and can be reused. – Ivo Sep 19 '17 at 22:44
  • Updated, considering: 1 - bottlenecks are outputs from Inception // 2 - you want to feed only bottlenecks. – Daniel Möller Sep 19 '17 at 23:27
  • Thanks! Adjusting the input tensor was the solution. Using images and bottlenecks as input would not make any sense, but thanks for the effort. I removed the second part to prevent anyone getting confused. Hope that's okay for you. – Ivo Sep 20 '17 at 13:26
  • I think I'll keep it there with a little more explanation. That's very plausible usage, if you don't have the preloaded bottlenecks, of if you're going to input unknown images for which no bottleneck has been generated yet. – Daniel Möller Sep 20 '17 at 14:39
  • But then one would have to change the code after having created the bottlenecks, so they can be utilized as input. Besides, if Inception is to be fed with Images there is no need for an additional input tensor, since it already has one (basemodel.input). The code that I've provided creates the bottlenecks first if they are not present. I think the addition might cause confusion, but it's your choice. – Ivo Sep 20 '17 at 15:20
  • When creating a combined model like that, all 3 models exist at the same time and you can use any of them. If you want to input bottlenecks and get labels, use `model`. If you want to input images and get bottlenecks, use `base_model`. If you want to input images and get labels, use `fullModel`. In your case, you're right to go with only `model` (so you don't need to create those other 2). – Daniel Möller Sep 20 '17 at 15:23