Tutorial: Optimizing Neural Networks using Keras (with Image Recognition Case Study)
Introduction
Neural networks have revolutionized the field of machine learning, enabling breakthrough performance on complex tasks like image recognition, natural language processing, and more. However, the power of neural networks comes with a caveat – they can be notoriously difficult to train and optimize.
Many beginners make the mistake of thinking that building a neural network is as simple as stacking some layers together and letting it rip. In reality, getting a neural network to perform well requires carefully tuning a dizzying array of hyperparameters and making many architecture decisions. What activation functions should you use? How many layers and neurons? What learning rate and optimizer? The list goes on.
Fortunately, the popular deep learning library Keras makes building neural networks much more approachable for practitioners. Keras provides a simple, consistent interface for defining neural network models and includes a suite of tools to aid in model optimization and evaluation.
In this tutorial, we‘ll dive into the details of neural network optimization using Keras. We‘ll start by reviewing some key hyperparameters that impact neural network performance. Then we‘ll walk through a case study of tuning a convolutional neural network for an image recognition task. Along the way, we‘ll share tips and best practices to help you get the most out of your Keras models. Let‘s get started!
Neural Network Hyperparameters
While neural networks are powerful, their performance depends heavily on the configuration of their hyperparameters. Let‘s review some of the most important hyperparameters that you‘ll need to tune:
Number of layers and neurons: This determines the complexity of patterns the network can learn. More layers and neurons can learn richer representations, but may be prone to overfitting. Typical models range from a few to dozens of layers.
Activation functions: Activations determine how neurons respond to input. Popular choices are ReLU for hidden layers and softmax or sigmoid for output layers. The choice of activation impacts learning dynamics and model output.
Learning rate: The learning rate controls how much weights are updated each training iteration. Too high a learning rate leads to instability, while too low trains slowly. Typical values range from 1e-1 to 1e-6.
Optimizers: The optimization algorithm, such as SGD, Adam, or RMSprop, determines how the model is updated based on the loss gradients. Adaptive methods like Adam are popular, but SGD can be effective too.
Regularization: Regularization helps combat overfitting by constraining the model. Methods include L1/L2 regularization, dropout, and early stopping. The regularization strength is a key hyperparameter.
Batch size: The number of examples used in each training iteration. Larger batches provide more accurate gradient estimates but train slower. Typical batch sizes range from 32 to 512.
Besides these hyperparameters, you‘ll also need to make architecture choices like the number and size of convolutional/pooling layers (for CNNs), whether to use transfer learning, and data preprocessing options. The optimal configuration will depend on your particular dataset and task.
Keras Neural Network Example
To make these concepts concrete, let‘s see how we can build and optimize a neural network using Keras. We‘ll tackle the classic CIFAR-10 image classification dataset.
First, we load the CIFAR-10 dataset from Keras and preprocess the data:
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
X_train = X_train.astype(‘float32‘) / 255.0
X_test = X_test.astype(‘float32‘) / 255.0
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
Next, we define a baseline convolutional neural network model:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
def create_model():
model = Sequential()
model.add(Conv2D(32, (3, 3), activation=‘relu‘, padding=‘same‘, input_shape=(32, 32, 3)))
model.add(Conv2D(32, (3, 3), activation=‘relu‘))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation=‘relu‘, padding=‘same‘))
model.add(Conv2D(64, (3, 3), activation=‘relu‘))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(128, (3, 3), activation=‘relu‘, padding=‘same‘))
model.add(Conv2D(128, (3, 3), activation=‘relu‘))
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(128, activation=‘relu‘))
model.add(Dropout(0.5))
model.add(Dense(10, activation=‘softmax‘))
return model
This model has three Conv2D-Conv2D-MaxPooling blocks to learn hierarchical representations, followed by fully-connected layers to produce the class probabilities.
We can instantiate and train the model as follows:
model = create_model()
model.compile(optimizer=‘adam‘,
loss=‘categorical_crossentropy‘,
metrics=[‘accuracy‘])
history = model.fit(X_train, y_train,
validation_data=(X_test, y_test),
epochs=50,
batch_size=128)
model.evaluate(X_test, y_test)
By default, this model achieves around 75% test accuracy. Not bad for a first attempt, but let‘s see how we can do better!
Hyperparameter Tuning
There are many hyperparameters we could tune to improve our model. Let‘s focus on a few important ones:
Learning rate: The Adam default of 0.001 is often pretty good, but let‘s experiment with 0.0001 and 0.01:
model = create_model()
model.compile(optimizer=Adam(learning_rate=0.0001), loss=‘categorical_crossentropy‘, metrics=[‘accuracy‘])
Training with a lower learning rate of 0.0001 gets us to ~77% accuracy, a small improvement. The higher learning rate of 0.01 performs worse. So a lower learning rate helped – Adam was able to fine-tune the weights better.
Regularization: Our model is still overfitting somewhat. Let‘s add L2 regularization to the Conv2D layers:
model.add(Conv2D(32, (3, 3), kernel_regularizer=l2(0.0005), activation=‘relu‘, padding=‘same‘, input_shape=(32, 32, 3)))
With L2 regularization, we reach ~79% accuracy and the gap between train and validation accuracy is smaller. The regularization helped constrain the model complexity.
Batch size: We can also try increasing the batch size from 128 to 256 for faster training:
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=50, batch_size=256)
The larger batch size slightly improves optimization stability and gets us to ~79.5% accuracy.
Augmentation: We can further improve generalization with data augmentation:
datagen = ImageDataGenerator(rotation_range=15,
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=True)
datagen.fit(X_train)
history = model.fit(datagen.flow(X_train, y_train, batch_size=256),
steps_per_epoch=len(X_train) / 256,
validation_data=(X_test, y_test),
epochs=50)
By training on slightly modified copies of the input data, we can get to ~81% test accuracy. Not a huge gain, but it all helps!
After experimenting with these and other hyperparameters, we‘ve significantly improved on our baseline model. There would still be room for further optimization, but this demonstrates the general approach.
Here are some visualizations of how the training progressed:


We can see that the training was relatively stable, with accuracy increasing and loss decreasing smoothly over time. The gap between train and validation performance indicates that the model is overfitting somewhat, but it‘s not too extreme.
Advanced Techniques – Transfer Learning
While we‘ve been able to achieve good performance training our model from scratch, we can go even further using transfer learning. Transfer learning involves starting with a pre-trained model (usually on a large dataset like ImageNet) and fine-tuning it for a new task.
We can easily implement transfer learning in Keras using the VGG16 pre-trained model:
from tensorflow.keras.applications.vgg16 import VGG16
base_model = VGG16(weights=‘imagenet‘, include_top=False, input_shape=(32, 32, 3))
for layer in base_model.layers:
layer.trainable = False
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(256, activation=‘relu‘)(x)
predictions = Dense(10, activation=‘softmax‘)(x)
transfer_model = Model(inputs=base_model.input, outputs=predictions)
transfer_model.compile(optimizer=SGD(learning_rate=0.001, momentum=0.9),
loss=‘categorical_crossentropy‘,
metrics=[‘accuracy‘])
Here we loaded the VGG16 model pre-trained on ImageNet, froze the convolutional layers, and added new fully-connected layers on top. We only need to train these new top layers, but the model can still benefit from the rich feature representations learned by VGG16 on ImageNet.
After fine-tuning this model for 20 epochs we get to a test accuracy of 87% – significantly better than our model trained from scratch! The pre-trained representations give the model a very helpful starting point.
Transfer learning is an extremely useful technique for optimizing neural networks, especially when your dataset is small. Modern deep learning is gravitating towards pre-training on large general datasets, then fine-tuning for specific tasks. Keep this in your toolbelt!
Conclusion
In this tutorial, we‘ve gone through the key aspects of optimizing neural networks using Keras. We reviewed critical hyperparameters like the learning rate, regularization, and batch size, and demonstrated their effects on a real CNN model. We also discussed transfer learning as a powerful approach for boosting neural network performance.
Keras makes building models easy, but optimizing them still takes careful thought and experimentation. When tuning your own models, focus on a few hyperparameters at a time, use visualizations to monitor training dynamics, and be patient. Also, don‘t be afraid to leverage pre-trained models when possible – they can give you a huge head start!
Here are some key takeaways:
-
Neural network performance depends heavily on hyperparameter configuration. Key hyperparameters include learning rate, number of layers, regularization, and batch size.
-
Start with a simple model and get a baseline, then experiment with tuning different hyperparameters. Look at learning curves to diagnose problems like overfitting or instability.
-
Data augmentation and transfer learning can significantly improve model generalization, especially for small datasets. Take advantage of pre-trained models when possible.
-
Keras makes building models easy, but you still need to be thoughtful about architecture and optimization choices. Use the tools Keras provides and be willing to experiment.
I hope this tutorial has given you a solid foundation for optimizing neural networks using Keras. The concepts and techniques we‘ve covered are broadly applicable – try them out on your own projects! With practice and patience, you‘ll be able to consistently build neural networks that achieve strong performance. Good luck!
Resources
Keras documentation: https://keras.io/
Keras code examples: https://keras.io/examples/
‘Practical recommendations for gradient-based training of deep architectures‘ paper: https://arxiv.org/abs/1206.5533
‘Cyclical Learning Rates for Training Neural Networks‘ paper: https://arxiv.org/abs/1506.01186
Fast.ai Deep Learning course: https://course.fast.ai/