Understanding the Sequential vs Functional APIs in Keras for Building Neural Networks
Deep learning and neural networks have revolutionized the field of machine learning in recent years. Inspired by the complex structure of the human brain, these computational models learn intricate relationships between inputs and outputs, even for non-linear patterns. A basic neural network is composed of an input layer, one or more hidden layers, and an output layer – each containing a number of nodes or neurons.
While neural networks are very powerful, they can also be quite complex to architect and computationally expensive to train, especially as the number of layers and neurons increases. Fortunately, we have high-level deep learning libraries like Keras that make the process of building neural networks much more accessible and efficient.
Keras is an open-source neural network library written in Python that runs on top of TensorFlow, another popular deep learning framework. It was developed with the goal of enabling fast experimentation through a user-friendly and modular approach.
Keras offers two main ways to build neural networks:
- Sequential API
- Functional API
In this article, we‘ll dive into the differences between these two approaches, look at code examples of each, and discuss when you might want to use one over the other. By the end, you‘ll have a solid understanding of the sequential and functional APIs in Keras and how to leverage them for your own deep learning projects.
The Sequential API
The sequential API is the simpler of the two options in Keras. As the name suggests, it allows you to create a model layer-by-layer in a linear stack. You start by instantiating a Sequential model and then use the .add() method to add layers one at a time, specifying the number of neurons and activation function for each.
Here‘s a basic example of building a neural network using the sequential API in Keras:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
# Create a sequential model
model = Sequential()
# Add layers
model.add(Dense(64, activation=‘relu‘, input_shape=(10,)))
model.add(Dense(32, activation=‘relu‘))
model.add(Dense(1, activation=‘sigmoid‘))
# Compile the model
model.compile(optimizer=‘adam‘,
loss=‘binary_crossentropy‘,
metrics=[‘accuracy‘])
This creates a simple feedforward neural network for binary classification with an input layer of 10 features, two hidden layers with 64 and 32 neurons respectively (using ReLU activation), and a single output neuron (using sigmoid activation).
The main advantage of the sequential API is its simplicity – building a model is very straightforward and requires minimal code. You don‘t need to explicitly define the input shape, as the model automatically infers it from the first layer.
However, the sequential API has some key limitations:
- Models must be linear stacks of layers with a single input and output tensor
- No way to branch or skip layers
- Layers cannot be shared or reused
- Cannot handle multiple inputs or outputs
Due to these constraints, the sequential API is best suited for relatively simple neural network architectures without complex branching or shared layers. For anything more advanced, you‘ll likely want to use Keras‘ functional API instead.
The Functional API
The functional API is a more flexible way to build neural networks in Keras. Rather than creating models sequentially with the .add() method, the functional API uses a declarative approach. You explicitly define the input tensors and then call layers as functions to specify how data should flow through the graph to generate output tensors.
Here‘s the same binary classification model recreated using the functional API:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense
# Define input tensor
inputs = Input(shape=(10,))
# Call layers on input tensor
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(32, activation=‘relu‘)(x)
outputs = Dense(1, activation=‘sigmoid‘)(x)
# Create model from inputs and outputs
model = Model(inputs=inputs, outputs=outputs)
# Compile the model
model.compile(optimizer=‘adam‘,
loss=‘binary_crossentropy‘,
metrics=[‘accuracy‘])
As you can see, the code is a bit more verbose but follows the same general flow:
- Define the input tensor(s)
- Call layers as functions, specifying input tensors
- Define output tensor(s)
- Create the model by specifying inputs and outputs
The key advantage of the functional API is that it allows for much more advanced model architectures through branching, layer sharing, and multiple inputs/outputs. For example, here‘s how you could branch the model to generate two output predictions:
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense
# Define input tensor
inputs = Input(shape=(10,))
# Call shared layers
x = Dense(64, activation=‘relu‘)(inputs)
x = Dense(32, activation=‘relu‘)(x)
# Branch into two outputs
out1 = Dense(1, activation=‘sigmoid‘, name=‘out1‘)(x)
out2 = Dense(1, activation=‘sigmoid‘, name=‘out2‘)(x)
# Create model with two outputs
model = Model(inputs=inputs, outputs=[out1, out2])
This is a simple example, but you could extend it to have multiple input branches as well that converge into shared layers. The functional API allows you to build very complex graphs that the sequential API cannot handle.
Another benefit of the functional API is layer reuse – because the layers are not tied to a specific model instance, they can be called multiple times on different inputs like reusable functions. This makes them easy to share between models.
Real-world Example: Predicting Power Plant Energy Output
To illustrate the power of the functional API, let‘s walk through a more complex real-world example. We‘ll use the Combined Cycle Power Plant dataset from UCI Machine Learning Repository to predict the net hourly electrical energy output and exhaust vacuum of the plant based on ambient environmental conditions.
The dataset contains 9568 data points with 4 input features:
- Temperature (T)
- Ambient Pressure (AP)
- Relative Humidity (RH)
- Exhaust Vacuum (V)
And 2 target outputs:
- Net hourly electrical energy output (EP)
- Exhaust vacuum (V)
Our goal is to build a deep learning model using Keras functional API that can predict both EP and V from the input ambient variables. Here‘s the full code:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense
# Load data
data = pd.read_csv(‘Folds5x2_pp.csv‘)
# Split features and targets
X = data.drop([‘PE‘, ‘V‘], axis=1)
y1 = data[‘PE‘]
y2 = data[‘V‘]
# Split into train and test sets
X_train, X_test, y1_train, y1_test, y2_train, y2_test = train_test_split(X, y1, y2, test_size=0.2, random_state=42)
# Scale the input data
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# Define input tensor
inputs = Input(shape=(4,))
# Hidden layers
x = Dense(128, activation=‘relu‘)(inputs)
x = Dense(64, activation=‘relu‘)(x)
x = Dense(32, activation=‘relu‘)(x)
# Output layers
out1 = Dense(1, name=‘energy‘)(x)
out2 = Dense(1, name=‘exhaust‘)(x)
# Create model
model = Model(inputs=inputs, outputs=[out1, out2])
# Compile model
model.compile(optimizer=‘adam‘,
loss={‘energy‘:‘mse‘, ‘exhaust‘:‘mse‘},
metrics={‘energy‘:‘mae‘, ‘exhaust‘:‘mae‘})
# Train model
history = model.fit(x=X_train,
y={‘energy‘: y1_train, ‘exhaust‘: y2_train},
epochs=50,
batch_size=32,
validation_data=(X_test, {‘energy‘:y1_test, ‘exhaust‘:y2_test}))
# Evaluate model
results = model.evaluate(X_test, {‘energy‘:y1_test, ‘exhaust‘:y2_test})
print(f‘Test loss, Test MAE - Energy: {results[0]}, {results[1]}‘)
print(f‘Test loss, Test MAE - Exhaust: {results[2]}, {results[3]}‘)
Here‘s a step-by-step breakdown:
- Load the data and split into input features X and target variables y1, y2
- Split data into train and test sets
- Scale the input features to zero mean and unit variance
- Define the model architecture using functional API:
- Input layer for 4 ambient variables
- 3 hidden layers with 128, 64, 32 neurons and ReLU activation
- 2 output layers for energy output and exhaust vacuum
- Compile model specifying optimizer and loss functions for each output
- Train model on energy and exhaust targets simultaneously
- Evaluate model performance on test set
By leveraging the functional API, we‘re able to elegantly build a multi-output regression model to jointly predict two quantities of interest from the same input data. The .compile() and .fit() methods allow us to specify different loss functions for each output.
After 50 training epochs, the model achieves ~3.9 MAE for energy output and ~0.9 MAE for exhaust vacuum on the held-out test set. Not bad for a first pass, and the model could likely be improved further through hyperparameter tuning and feature engineering. The key takeaway is that the functional API empowers you to build these more complex models in a streamlined way.
Wrapping Up
In summary, Keras offers two APIs for building neural networks: sequential and functional. The sequential API is simpler and suitable for most feedforward models, while the functional API is more expressive and supports advanced architectures with branching, shared layers, and multiple inputs/outputs.
As a general rule of thumb, start with the sequential API for quick prototyping and switch to functional API if your model requires more flexibility. It‘s also a good idea to use the functional API from the get-go for complex multi-input/output models, as it will make your code cleaner and easier to follow.
I hope this article clarified the differences between the sequential and functional APIs in Keras and how to leverage them in practice. To learn more, I recommend checking out the official Keras documentation and tutorials. With a bit of practice, you‘ll be building state-of-the-art neural networks in no time!