Title: Change input shape dimensions for fine-tuning with Keras - PyImageSearch

URL Source: https://pyimagesearch.com/2019/06/24/change-input-shape-dimensions-for-fine-tuning-with-keras/

Published Time: 2019-06-24T14:00:52+00:00

Markdown Content:
[![Image 1](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20549'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2019/06/keras_input_shape_header.png)

In this tutorial, you will learn how to change the input shape tensor dimensions for fine-tuning using Keras. After going through this guide you’ll understand how to apply transfer learning to images with different image dimensions than what the CNN was originally trained on.

A few weeks ago I published a tutorial on [transfer learning with Keras and deep learning](https://pyimagesearch.com/2019/05/20/transfer-learning-with-keras-and-deep-learning/) — soon after the tutorial was published, I received a question from Francesca Maepa who asked the following:

> Do you know of a good blog or tutorial that shows how to implement **transfer learning on a dataset that has a smaller shape than the pre-trained model?**
> 
> I created a really good pre-trained model, and would like to use some features for the pre-trained model and transfer them to a target domain that is missing certain feature training datasets and I’m not sure if I’m doing it right.

Francesca asks a great question.

Typically we think of Convolutional Neural Networks as accepting fixed size inputs (i.e., _224×224_, _227×227_, _299×299_, etc.).

**But what if you wanted to:**

1.  Utilize a pre-trained network for transfer learning…
2.  **…and then update the input shape dimensions to accept images with _different dimensions_ than what the original network was trained on?**

Why might you want to utilize different image dimensions?

There are two common reasons:

*   Your input image dimensions are _**considerably smaller**_ than what the CNN was trained on and increasing their size introduces too many artifacts and dramatically hurts loss/accuracy.
*   Your images are _**high resolution**_ and contain small objects that are hard to detect. Resizing to the original input dimensions of the CNN hurts accuracy and you postulate increasing resolution will help improve your model.

In these scenarios, you would wish to update the input shape dimensions of the CNN and then be able to perform transfer learning.

The question then becomes, **is such an update possible?**

Yes, in fact, it is.

![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

_**2020-06-04 Update:** This blog post is now TensorFlow 2+ compatible!_

In the first part of this tutorial, we’ll discuss the concept of an input shape tensor and the role it plays with input image dimensions to a CNN.

From there we’ll discuss the example dataset we’ll be using in this blog post. I’ll then show you how to:

1.  Update the input image dimensions to pre-trained CNN using Keras.
2.  Fine-tune the updated CNN. Let’s get started!

### What is an input shape tensor?

[![Image 3](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20549'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2019/06/keras_input_shape_networks.png)

**Figure 1:** Convolutional Neural Networks built with Keras for deep learning have different input shape expectations. In this blog post, you’ll learn how to change input shape dimensions for fine-tuning with Keras.

When working with Keras and deep learning, you’ve probably either utilized or run into code that loads a pre-trained network via:

model = VGG16(weights="imagenet")

The code above is initializing the VGG16 architecture and then loading the weights for the model (pre-trained on ImageNet).

**We would typically use this code when our project needs to classify input images that have class labels inside ImageNet** ([as this tutorial demonstrates](https://pyimagesearch.com/2017/03/20/imagenet-vggnet-resnet-inception-xception-keras/)).

When performing transfer learning or fine-tuning you may use the following code to leave off the fully-connected (FC) layer heads:

model = VGG16(weights="imagenet", include\_top=False)

We’re _still_ indicating that the pre-trained ImageNet weights should be used, but now we’re setting `include_top=False` , indicating that the FC head should not be loaded.

**This code would typically be utilized when you’re performing transfer learning either via [feature extraction](https://pyimagesearch.com/2019/05/27/keras-feature-extraction-on-large-datasets-with-deep-learning/) or [fine-tuning](https://pyimagesearch.com/2019/06/03/fine-tuning-with-keras-and-deep-learning/).**

Finally, we can update our code to include an input\_tensor dimension:

model = VGG16(weights="imagenet", include\_top=False,
	input\_tensor=Input(shape=(224, 224, 3)))

We’re _still_ loading VGG16 with weights pre-trained on ImageNet and we’re _still_ leaving off the FC layer heads…**but now we’re specifying an input shape of _224×224_x3** (which are the input image dimensions that VGG16 was originally trained on, as seen in **Figure 1**, _left_).

That’s all fine and good — **but what if we now wanted to fine-tune our model on _128×128_px images?**

That’s actually just a simple update to our model initialization:

model = VGG16(weights="imagenet", include\_top=False,
	input\_tensor=Input(shape=(128, 128, 3)))

**Figure 1** (_right_) provides a visualization of the network updating the input tensor dimensions — notice how the input volume is now _128x128x3_ (our updated, smaller dimensions) versus the previous _224x224x3_ (the original, larger dimensions).

Updating the input shape dimensions of a CNN via Keras is that simple!

But there are a few caveats to look out for.

### Can I make the input dimensions anything I want?

[![Image 4](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20213'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2019/06/keras_input_shape_sizes.png)

**Figure 2:** Updating a Keras CNN’s input shape is straightforward; however, there are a few caveats to take into consideration,

There are limits to how much you can update the image dimensions, both from an _accuracy/loss perspective_ and from _limitations of the network_ itself.

Consider the fact that CNNs reduce volume dimensions via two methods:

1.  **Pooling** (such as max-pooling in VGG16)
2.  **Strided convolutions** (such as in ResNet)

If your input image dimensions are too small then the CNN will naturally reduce volume dimensions during the forward propagation and then effectively “run out” of data.

**In that case your input dimensions are _too small._**

I’ve included an error of what happens during that scenario below when, for example, when using _48×48_ input images, I received this error message:

ValueError: Negative dimension size caused by subtracting 4 from 1 for 'average\_pooling2d\_1/AvgPool' (op: 'AvgPool') with input shapes: \[?,1,1,512\].

Notice how Keras is complaining that our volume is too small. You will encounter similar errors for other pre-trained networks as well. When you see this type of error, you know you need to increase your input image dimensions.

**You can also make your input dimensions _too large._**

You won’t run into any errors _per se_, but you may see your network fail to obtain reasonable accuracy due to the fact that there are not enough layers in the network to:

1.  Learn robust, discriminative filters.
2.  Naturally reduce volume size via pooling or strided convolution.

**If that happens, you have a few options:**

*   Explore other (pre-trained) network architectures that are trained on larger input dimensions.
*   Tune your hyperparameters exhaustively, focusing first on learning rate.
*   Add additional layers to the network. For VGG16 you’ll use _3×3_ CONV layers and max-pooling. For ResNet you’ll include residual layers with strided convolution.

The final suggestion will require you to update the network architecture and then perform [fine-tuning](https://pyimagesearch.com/2019/06/03/fine-tuning-with-keras-and-deep-learning/) on the newly initialized layers.

To learn more about fine-tuning and and transfer learning, along with my tips, suggestions, and best practices when training networks, make sure you refer to my book, [**_Deep Learning for Computer Vision with Python_**](https://pyimagesearch.com/deep-learning-computer-vision-python-book/).

### Our example dataset

[![Image 5](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20473'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2019/06/keras_input_shape_dataset.jpg)

**Figure 3:** A subset of the Kaggle Dogs vs. Cats dataset is used for this Keras input shape example. Using a smaller dataset not only proves the point more quickly, but also allows just about any computer hardware to be used (i.e. no expensive GPU machine/instance necessary).

The dataset we’ll be using here today is a small subset of Kaggle’s Dogs vs. Cats dataset.

We also use this dataset inside [_Deep Learning for Computer Vision with Python_](https://pyimagesearch.com/deep-learning-computer-vision-python-book/) to teach the fundamentals of training networks, ensuring that readers with either CPUs or GPUs can follow along and learn best practices when training models.

The dataset itself contains 2,000 images belonging to 2 classes (“cat” and dog”):

*   **Cat:** 1,000 images
*   **Dog:** 1,000 images

A visualization of the dataset can be seen in **Figure 3** above.

In the remainder of this tutorial you’ll learn how to take this dataset and:

1.  Update the input shape dimensions for a pre-trained CNN.
2.  Fine-tune the CNN with the smaller image dimensions.

### Configuring your development environment

To configure your system for this tutorial, I first recommend following either of these tutorials:

*   [_How to install TensorFlow 2.0 on Ubuntu_](https://pyimagesearch.com/2019/12/09/how-to-install-tensorflow-2-0-on-ubuntu/)
*   [_How to install TensorFlow 2.0 on macOS_](https://pyimagesearch.com/2019/12/09/how-to-install-tensorflow-2-0-on-macos/)

Either tutorial will help you configure you system with all the necessary software for this blog post in a convenient Python virtual environment.

Please note that [PyImageSearch does not recommend or support Windows for CV/DL projects](https://pyimagesearch.com/faqs/single-faq/can-you-help-me-do-___-on-windows/).

### Project structure

Go ahead and grab the code + dataset from the _**“Downloads**__**“**_ section of today’s blog post.

Once you’ve extracted the .zip archive, you may inspect the project structure using the `tree` command:

$ tree --dirsfirst --filelimit 10
.
├── dogs\_vs\_cats\_small
│   ├── cats \[1000 entries\]
│   └── dogs \[1000 entries\]
├── plot.png
└── train.py

3 directories, 2 files

Our dataset is contained within the `dogs_vs_cats_small/` directory. The two subdirectories contain images of our classes. If you’re working with a different dataset be sure the structure is `<dataset>/<class_name>` .

Today we’ll be reviewing the `train.py` script. The training script generates `plot.png` containing our accuracy/loss curves.

### Updating the input shape dimensions with Keras

It’s now time to update our input image dimensions with Keras and a pre-trained CNN.

Open up the `train.py` file in your project structure and insert the following code:

\# import the necessary packages
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.layers import AveragePooling2D
from tensorflow.keras.applications import VGG16
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Input
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.utils import to\_categorical
from sklearn.preprocessing import LabelBinarizer
from sklearn.model\_selection import train\_test\_split
from sklearn.metrics import classification\_report
from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import argparse
import cv2
import os

**Lines 2-20** import required packages:

*   `tensorflow.keras` and `sklearn` are for deep learning/machine learning. Be sure to refer to my extensive deep learning book, [_Deep Learning for Computer Vision with Python_](https://pyimagesearch.com/deep-learning-computer-vision-python-book/), to become more familiar with the classes and functions we use from these tools.
*   `paths` from [imutils](https://github.com/jrosebr1/imutils/blob/master/imutils/paths.py) traverses a directory and enables us to list all images in a directory.
*   `matplotlib` will allow us to plot our training accuracy/loss history.
*   `numpy` is a Python package for numerical operations; one of the ways we’ll put it to work is for “mean subtraction”, a scaling/normalization technique.
*   `cv2` is OpenCV.
*   `argparse` will be used to [read and parse command line arguments](https://pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/).

Let’s go ahead and parse the command line arguments now:

\# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-d", "--dataset", required=True,
	help="path to input dataset")
ap.add\_argument("-e", "--epochs", type=int, default=25,
	help="# of epochs to train our network for")
ap.add\_argument("-p", "--plot", type=str, default="plot.png",
	help="path to output loss/accuracy plot")
args = vars(ap.parse\_args())

Our script accepts three command line arguments via **Lines 23-30:**

*   `--dataset` : The path to our input dataset. We’re using a condensed version of Dogs vs. Cats, but you could use other binary, 2-class datasets with little or no modification as well (provided they follow a similar structure).
*   `--epochs` : The number of times we’ll pass our data through the network during training; by default, we’ll train for `25` epochs unless a different value is supplied.
*   `--plot` : The path to our output accuracy/loss plot. Unless otherwise specified, the file will be named `plot.png` and placed in the project directory. If you are conducting multiple experiments, be sure to give your plots a different name each time for future comparison purposes.

Next, we will load and preprocess our images:

\# grab the list of images in our dataset directory, then initialize
# the list of data (i.e., images) and class images
print("\[INFO\] loading images...")
imagePaths = list(paths.list\_images(args\["dataset"\]))
data = \[\]
labels = \[\]

# loop over the image paths
for imagePath in imagePaths:
	# extract the class label from the filename
	label = imagePath.split(os.path.sep)\[-2\]

	# load the image, swap color channels, and resize it to be a fixed
	# 128x128 pixels while ignoring aspect ratio
	image = cv2.imread(imagePath)
	image = cv2.cvtColor(image, cv2.COLOR\_BGR2RGB)
	image = cv2.resize(image, (128, 128))

	# update the data and labels lists, respectively
	data.append(image)
	labels.append(label)

First, we grab our `imagePaths` on **Line 35** and then initialize our `data` and `labels` (**Lines 36 and 37**).

**Lines 40-52** loop over the `imagePaths` while first extracting the labels. Each image is loaded, the color channels are swapped, and the image is resized. The images and labels are added to the `data` and `labels` lists respectively.

VGG16 was trained on _224×224_px images; however, I’d like to **draw your attention** to **Line 48**. Notice how we’ve resized our images to _128×128_px. This resizing is an example of applying transfer learning on images with different dimensions.

Although **Line 48** doesn’t fully answer Francesca Maepa’s question yet, we’re getting close.

Let’s go ahead and one-hot encode our labels as well as split our data:

\# convert the data and labels to NumPy arrays
data = np.array(data)
labels = np.array(labels)

# perform one-hot encoding on the labels
lb = LabelBinarizer()
labels = lb.fit\_transform(labels)
labels = to\_categorical(labels)

# partition the data into training and testing splits using 75% of
# the data for training and the remaining 25% for testing
(trainX, testX, trainY, testY) = train\_test\_split(data, labels,
	test\_size=0.25, stratify=labels, random\_state=42)

**Lines 55 and 56** convert our `data` and `labels` to NumPy array format.

Then, **Lines 59-61** perform _one-hot encoding_ on our labels. Essentially, this process converts our two labels (“cat” and “dog”) to arrays indicating which label is active/hot. If a training image is representative of a dog, then the value would be `[0, 1]` where “dog” is hot. Otherwise, for a “cat”, the value would be `[1, 0]` .

To reinforce the point, if for example, we had 5 classes of data, a one-hot encoded array may look like `[0, 0, 0, 1, 0]` where the 4th element is hot indicating that the image is from the 4th class. For further details, please refer to [_Deep Learning for Computer Vision with Python_](https://pyimagesearch.com/deep-learning-computer-vision-python-book/).

**Lines 65 and 66** mark 75% of our data for training and the remaining 25% for testing via the `train_test_split` function.

Let’s now initialize our data augmentation generator. We’ll also establish our ImageNet mean for mean subtraction:

\# initialize the training data augmentation object
trainAug = ImageDataGenerator(
	rotation\_range=30,
	zoom\_range=0.15,
	width\_shift\_range=0.2,
	height\_shift\_range=0.2,
	shear\_range=0.15,
	horizontal\_flip=True,
	fill\_mode="nearest")

# initialize the validation/testing data augmentation object (which
# we'll be adding mean subtraction to)
valAug = ImageDataGenerator()

# define the ImageNet mean subtraction (in RGB order) and set the
# the mean subtraction value for each of the data augmentation
# objects
mean = np.array(\[123.68, 116.779, 103.939\], dtype="float32")
trainAug.mean = mean
valAug.mean = mean

**Lines 69-76** initialize a data augmentation object for performing random manipulations on our input images during training.

**Line 80** also takes advantage of the `ImageDataGenerator` class for validation, but without any parameters — we won’t manipulate validation images with the exception of performing mean subtraction.

Both training and validation/testing generators will conduct _mean subtraction_. Mean subtraction is a scaling/normalization technique proven to increase accuracy. **Line 85** contains the mean for each respective RGB channel while **Lines 86 and 87** are then populated with the value. Later, our data generators will automatically perform the mean subtraction on our training/validation data.

_**Note:** I’ve covered data augmentation in detail in this [blog post](https://pyimagesearch.com/2018/12/24/how-to-use-keras-fit-and-fit_generator-a-hands-on-tutorial/) as well as in the Practitioner Bundle of [Deep Learning for Computer Vision with Python](https://pyimagesearch.com/deep-learning-computer-vision-python-book/). Scaling and normalization techniques such as mean subtraction are covered in DL4CV as well._

We’re performing transfer learning with **VGG16**. Let’s initialize the base model now:

\# load VGG16, ensuring the head FC layer sets are left off, while at
# the same time adjusting the size of the input image tensor to the
# network
baseModel = VGG16(weights="imagenet", include\_top=False,
	input\_tensor=Input(shape=(128, 128, 3)))

# show a summary of the base model
print("\[INFO\] summary for base model...")
print(baseModel.summary())

**Lines 92 and 93** load `VGG16` with an input shape dimension of _128×128_ using 3 channels.

_Remember, VGG16 was originally trained on 224×224 images_ — **now we’re updating the _input shape_ dimensions to handle _128×128_ images.**

**Effectively, we have now fully answered Francesca Maepa’s question!** We accomplished changing the input dimensions via two steps:

1.  We resized all of our input images to _128×128_.
2.  Then we set the input `shape=(128, 128, 3)` .

**Line 97** will print a model summary in our terminal so that we can inspect it. Alternatively, you may visualize the model graphically by studying Chapter 19 _“Visualizing Network Architectures”_ of [_Deep Learning for Computer Vision with Python_](https://pyimagesearch.com/deep-learning-computer-vision-python-book/).

Since we’re performing transfer learning, the `include_top` parameter is set to `False` (**Line 92**) — we chopped off the head!

Now we’re going to perform surgery by erecting a new head and suturing it onto the CNN:

\# construct the head of the model that will be placed on top of the
# the base model
headModel = baseModel.output
headModel = AveragePooling2D(pool\_size=(4, 4))(headModel)
headModel = Flatten(name="flatten")(headModel)
headModel = Dense(128, activation="relu")(headModel)
headModel = Dropout(0.5)(headModel)
headModel = Dense(2, activation="softmax")(headModel)

# place the head FC model on top of the base model (this will become
# the actual model we will train)
model = Model(inputs=baseModel.input, outputs=headModel)

# loop over all layers in the base model and freeze them so they will
# \*not\* be updated during the first training process
for layer in baseModel.layers:
	layer.trainable = False

**Line 101** takes the output from the `baseModel` and sets it as input to the `headModel` .

From there, **Lines 102-106** construct the rest of the head.

The `baseModel` is already initialized with ImageNet weights per **Line 92**. On **Lines 114 and 115**, we set the base layers in VGG16 as not trainable (i.e., they will not be updated during the backpropagation phase). Be sure to read my previous [fine-tuning tutorial](https://pyimagesearch.com/2019/06/03/fine-tuning-with-keras-and-deep-learning/) for further explanation.

We’re now ready to compile and train the model with our data:

\# compile our model (this needs to be done after our setting our
# layers to being non-trainable)
print("\[INFO\] compiling model...")
opt = Adam(lr=1e-4)
model.compile(loss="binary\_crossentropy", optimizer=opt,
	metrics=\["accuracy"\])

# train the head of the network for a few epochs (all other layers
# are frozen) -- this will allow the new FC layers to start to become
# initialized with actual "learned" values versus pure random
print("\[INFO\] training head...")
H = model.fit(
	x=trainAug.flow(trainX, trainY, batch\_size=32),
	steps\_per\_epoch=len(trainX) // 32,
	validation\_data=valAug.flow(testX, testY),
	validation\_steps=len(testX) // 32,
	epochs=args\["epochs"\])

_**2020-06-04 Update:** Formerly, TensorFlow/Keras required use of a method called `.fit_generator` in order to accomplish data augmentation. Now, the `.fit` method can handle data augmentation as well, making for more-consistent code. This also applies to the migration from `.predict_generator` to `.predict`. Be sure to check out my articles about [fit and fit\_generator](https://pyimagesearch.com/2018/12/24/how-to-use-keras-fit-and-fit_generator-a-hands-on-tutorial/) as well as [data augmentation](https://pyimagesearch.com/2019/07/08/keras-imagedatagenerator-and-data-augmentation/)._

Our `model` is compiled with the `Adam` optimizer and a `1e-4` learning rate (**Lines 120-122**).

We use `"binary_crossentropy"` for 2-class classification. If you have more than two classes of data, be sure to use `"categorical_crossentropy"` .

**Lines 128-133** then train our transfer learning network. Our training and validation generators are put to work in the process.

Upon training completion, we’ll evaluate the network and plot the training history:

\# evaluate the network
print("\[INFO\] evaluating network...")
predictions = model.predict(x=testX.astype("float32"), batch\_size=32)
print(classification\_report(testY.argmax(axis=1),
	predictions.argmax(axis=1), target\_names=lb.classes\_))

# plot the training loss and accuracy
N = args\["epochs"\]
plt.style.use("ggplot")
plt.figure()
plt.plot(np.arange(0, N), H.history\["loss"\], label="train\_loss")
plt.plot(np.arange(0, N), H.history\["val\_loss"\], label="val\_loss")
plt.plot(np.arange(0, N), H.history\["accuracy"\], label="train\_acc")
plt.plot(np.arange(0, N), H.history\["val\_accuracy"\], label="val\_acc")
plt.title("Training Loss and Accuracy on Dataset")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend(loc="lower left")
plt.savefig(args\["plot"\])

_**2020-06-04 Update:** In order for this plotting snippet to be TensorFlow 2+ compatible the `H.history` dictionary keys are updated to fully spell out “accuracy” sans “acc” (i.e., `H.history["val_accuracy"]` and `H.history["accuracy"]`). It is semi-confusing that “val” is not spelled out as “validation”; we have to learn to love and live with the API and always remember that it is a work in progress that many developers around the world contribute to._

**Lines 137-139** evaluate our `model` and print a classification report for statistical analysis.

We then employ `matplotlib` to plot our accuracy and loss history during training (**Lines 142-152**). The plot figure is saved to disk via **Line 153**.

### Fine-tuning a CNN using the updated input dimensions

[![Image 6](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20450'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2019/06/keras_input_shape_plot.png)

**Figure 4:** Changing Keras input shape dimensions for fine-tuning produced the following accuracy/loss training plot.

To fine-tune our CNN using the updated input dimensions **first make sure you’ve used the _“Downloads”_ section of this guide to download the (1) source code and (2) example dataset.**

From there, open up a terminal and execute the following command:

$ python train.py --dataset dogs\_vs\_cats\_small --epochs 25
Using TensorFlow backend.
\[INFO\] loading images...
\[INFO\] summary for base model...
Model: "vgg16"
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
Layer (type)                 Output Shape              Param #   
=================================================================
input\_1 (InputLayer)         \[(None, 128, 128, 3)\]     0         
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block1\_conv1 (Conv2D)        (None, 128, 128, 64)      1792      
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block1\_conv2 (Conv2D)        (None, 128, 128, 64)      36928     
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block1\_pool (MaxPooling2D)   (None, 64, 64, 64)        0         
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block2\_conv1 (Conv2D)        (None, 64, 64, 128)       73856     
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block2\_conv2 (Conv2D)        (None, 64, 64, 128)       147584    
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block2\_pool (MaxPooling2D)   (None, 32, 32, 128)       0         
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block3\_conv1 (Conv2D)        (None, 32, 32, 256)       295168    
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block3\_conv2 (Conv2D)        (None, 32, 32, 256)       590080    
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block3\_conv3 (Conv2D)        (None, 32, 32, 256)       590080    
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block3\_pool (MaxPooling2D)   (None, 16, 16, 256)       0         
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block4\_conv1 (Conv2D)        (None, 16, 16, 512)       1180160   
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block4\_conv2 (Conv2D)        (None, 16, 16, 512)       2359808   
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block4\_conv3 (Conv2D)        (None, 16, 16, 512)       2359808   
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block4\_pool (MaxPooling2D)   (None, 8, 8, 512)         0         
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block5\_conv1 (Conv2D)        (None, 8, 8, 512)         2359808   
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block5\_conv2 (Conv2D)        (None, 8, 8, 512)         2359808   
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block5\_conv3 (Conv2D)        (None, 8, 8, 512)         2359808   
\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
block5\_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0

**Our first set of output shows our updated input shape dimensions.**

Notice how our `input_1` (i.e., the `InputLayer`) has input dimensions of _128x128x3_ versus the normal _224x224x3_ for VGG16.

The input image will then forward propagate through the network until the final `MaxPooling2D` layer (i.e., `block5_pool).`

At this point, our output volume has dimensions of _4x4x512_ (for reference, VGG16 with a _224x224x3_ input volume would have the shape _7x7x512_ after this layer).

_**Note:** If your input image dimensions are too small then you risk the model, effectively, reducing the tensor volume into “nothing” and then running out of data, leading to an error. See the “Can I make the input dimensions anything I want?” section of this post for more details._

We then flatten that volume and apply the FC layers from the `headModel` , ultimately leading to our final classification.

Once our model is constructed we can then fine-tune it:

\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
None
\[INFO\] compiling model...
\[INFO\] training head...
Epoch 1/25
46/46 \[==============================\] - 4s 94ms/step - loss: 4.8791 - accuracy: 0.5334 - val\_loss: 2.3190 - val\_accuracy: 0.6854
Epoch 2/25
46/46 \[==============================\] - 3s 73ms/step - loss: 3.3768 - accuracy: 0.6274 - val\_loss: 1.2106 - val\_accuracy: 0.8167
Epoch 3/25
46/46 \[==============================\] - 3s 76ms/step - loss: 2.7036 - accuracy: 0.6887 - val\_loss: 0.9802 - val\_accuracy: 0.8333
Epoch 4/25
46/46 \[==============================\] - 3s 73ms/step - loss: 2.1932 - accuracy: 0.7105 - val\_loss: 0.8585 - val\_accuracy: 0.8583
Epoch 5/25
46/46 \[==============================\] - 3s 73ms/step - loss: 1.9197 - accuracy: 0.7425 - val\_loss: 0.6756 - val\_accuracy: 0.9021
...
46/46 \[==============================\] - 3s 76ms/step - loss: 0.6613 - accuracy: 0.8495 - val\_loss: 0.4479 - val\_accuracy: 0.9083
Epoch 21/25
46/46 \[==============================\] - 3s 74ms/step - loss: 0.6561 - accuracy: 0.8413 - val\_loss: 0.4484 - val\_accuracy: 0.9000
Epoch 22/25
46/46 \[==============================\] - 4s 95ms/step - loss: 0.5216 - accuracy: 0.8508 - val\_loss: 0.4476 - val\_accuracy: 0.9021
Epoch 23/25
46/46 \[==============================\] - 3s 70ms/step - loss: 0.5484 - accuracy: 0.8488 - val\_loss: 0.4420 - val\_accuracy: 0.9021
Epoch 24/25
46/46 \[==============================\] - 3s 70ms/step - loss: 0.5658 - accuracy: 0.8492 - val\_loss: 0.4504 - val\_accuracy: 0.8938
Epoch 25/25
46/46 \[==============================\] - 3s 70ms/step - loss: 0.5334 - accuracy: 0.8529 - val\_loss: 0.4096 - val\_accuracy: 0.8979
\[INFO\] evaluating network...
              precision    recall  f1-score   support

        cats       0.91      0.88      0.89       250
        dogs       0.89      0.91      0.90       250

    accuracy                           0.90       500
   macro avg       0.90      0.90      0.90       500
weighted avg       0.90      0.90      0.90       500

**At the end of fine-tuning we see that our model has obtained _90% accuracy_, respectable given our small image dataset.**

As **Figure 4** demonstrates, our training is also quite stable as well with no signs of overfitting.

More importantly, you now know how to change the input image shape dimensions of a pre-trained network and then apply feature extraction/fine-tuning using Keras!

Be sure to use this tutorial as a template for whenever you need to apply transfer learning to a pre-trained network with different image dimensions than what it was originally trained on.

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

Summary
-------

In this tutorial, you learned how to change input shape dimensions for fine-tuning with Keras.

We typically perform such an operation when we want to apply transfer learning, including both [feature extraction](https://pyimagesearch.com/2019/05/27/keras-feature-extraction-on-large-datasets-with-deep-learning/) and [fine-tuning](https://pyimagesearch.com/2019/06/03/fine-tuning-with-keras-and-deep-learning/).

Using the methods in this guide, you can update your input image dimensions for your pre-trained CNN and then perform transfer learning; however, there are two caveats you need to look out for:

1.  If your input images are _too small_, Keras will error out.
2.  If your input images are _too large_, you may not obtain your desired accuracy.

Be sure to refer to the _“Can I make the input dimensions anything I want?”_ section of this post for more details on these caveats, including suggestions on how to solve them.

I hope you enjoyed this tutorial!

**To download the source code to this post, and be notified when future tutorials are published here on PyImageSearch, _just enter your email address in the form below!_**

![Image 7](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!