
The most direct approach to constructing a neural network in Keras is by defining it as a linear sequence of layers. This method is exceptionally clear and is implemented using the keras.Sequential class. A Sequential model is appropriate when your model architecture is a simple stack, where the output of one layer feeds directly into the input of the next, without any branches, merges, or multiple inputs and outputs. This covers a significant number of common use cases, particularly for standard feedforward networks.
The construction process involves first instantiating a Sequential model and then incrementally adding layers to it using the .add() method. Each layer added to the model represents a specific data transformation. For the first layer in the model, it is essential to specify the expected shape of the input data. Keras uses this information to create the necessary weights and connections. For all subsequent layers, Keras automatically infers the input shape from the output shape of the preceding layer.
Consider the task of building a simple classifier for 28×28 grayscale images, which are flattened into vectors of 784 elements. The construction would proceed as follows:
import keras # Define a Sequential model model = keras.Sequential() # Add layers one by one model.add(keras.layers.Input(shape=(784,))) model.add(keras.layers.Dense(64, activation='relu')) model.add(keras.layers.Dense(64, activation='relu')) model.add(keras.layers.Dense(10, activation='softmax'))
Let’s deconstruct this example. We begin by creating an empty Sequential model. The first addition, keras.layers.Input(shape=(784,)), explicitly defines the model’s entry point. It declares that the model expects input data as batches of vectors, where each vector has 784 elements. While you can also specify the input shape directly in the first processing layer (e.g., Dense), using a separate Input layer is the modern, recommended practice for clarity. Next, we add a keras.layers.Dense layer. This is a standard, fully-connected neural network layer. The first argument, 64, specifies the number of neurons, or units, in the layer. The activation='relu' argument specifies the Rectified Linear Unit activation function, which will be applied to the output of each neuron. The second Dense layer is similar, but notice we do not need to specify its input shape; Keras deduces that it must accept inputs of shape (64,) from the previous layer. Finally, the output layer is another Dense layer with 10 units, corresponding to the 10 possible classes in our classification problem. Its activation function is 'softmax', which converts the layer’s raw output logits into a probability distribution over the 10 classes.
An alternative, more compact syntax for defining the same model is to pass a list of layers directly to the Sequential constructor. This achieves the identical result in fewer lines of code.
import keras
model = keras.Sequential(
[
keras.layers.Input(shape=(784,)),
keras.layers.Dense(64, activation="relu"),
keras.layers.Dense(64, activation="relu"),
keras.layers.Dense(10, activation="softmax"),
]
)
Once a model is constructed, it is crucial to inspect its architecture to verify that the layers are connected as intended and to understand its complexity. The .summary() method provides a textual summary of the model, including the sequence of layers, their output shapes, and the number of trainable parameters in each layer.
model.summary()
Executing this command produces a table that details the model’s structure. For our example, the output would look similar to this:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 64) 50240
dense_1 (Dense) (None, 64) 4160
dense_2 (Dense) (None, 10) 650
=================================================================
Total params: 55,050
Trainable params: 55,050
Non-trainable params: 0
_________________________________________________________________
The “Output Shape” column shows the shape of the tensor that each layer outputs. The None dimension is a placeholder for the batch size, which is typically flexible. The “Param #” column indicates the number of trainable weights and biases in each layer. For the first Dense layer, this is calculated from its connection to the 784-dimensional input: (784 * 64) + 64 = 50,240 parameters. For the second Dense layer, it’s (64 * 64) + 64 = 4,160. This summary is an indispensable tool for debugging the model’s architecture before proceeding to the training phase.
ProCase 2 Pcs Screen Protector for iPad A16 2025 11th Generation 11 Inch/iPad 10th 2022 10.9 Inch, Tempered Glass Film Guard -Clear
$6.99 (as of July 17, 2026 15:16 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Designing sophisticated model architectures
While the Sequential model is effective for linear stacks of layers, real-world problems often demand more complex architectures. Models may require multiple independent inputs, produce multiple outputs, or involve layers that are shared across different parts of the network graph. Such non-linear topologies cannot be implemented with the Sequential API. For these advanced cases, Keras provides the Functional API, a powerful and flexible way to define models as directed acyclic graphs of layers.
The core principle of the Functional API is that a model is built by defining its inputs and outputs and the computational graph that connects them. Instead of adding layers sequentially, you create layer instances and then call them on tensors to create new tensors. This “layer-as-a-function” paradigm gives you fine-grained control over the data flow.
Let’s consider a model that processes two distinct inputs: a main text input and an auxiliary input containing metadata. The model will process these inputs through separate pathways before merging them to produce a final prediction. This is a classic multi-input architecture.
import keras # Define the model's inputs main_input = keras.Input(shape=(100,), dtype='int32', name='main_input') aux_input = keras.Input(shape=(10,), name='aux_input') # The first branch processes the main input x = keras.layers.Embedding(output_dim=512, input_dim=10000)(main_input) x = keras.layers.LSTM(32)(x) # The second branch processes the auxiliary input y = keras.layers.Dense(16, activation='relu')(aux_input) # Merge the two branches by concatenating their feature vectors combined = keras.layers.concatenate([x, y]) # Apply a final classifier on the merged features main_output = keras.layers.Dense(1, activation='sigmoid', name='main_output')(combined) # Instantiate the model by specifying its inputs and outputs model = keras.Model(inputs=[main_input, aux_input], outputs=main_output)
In this construction, we first declare the model’s entry points using keras.Input. Each input is defined with its expected shape and an optional name. The main_input is processed by an Embedding layer followed by an LSTM layer, a common pattern for sequence data. The aux_input is processed by a simple Dense layer. The key step is the merge operation: keras.layers.concatenate takes a list of tensors and joins them along a specified axis (the last axis by default). The resulting merged tensor is then passed to a final Dense layer for classification. Finally, we instantiate the keras.Model by providing a list of its inputs and its single output. The model.summary() for this architecture would clearly show the two input layers and the graph structure connecting them.
The Functional API is equally adept at creating models with multiple outputs. This is useful when a model needs to perform several related prediction tasks from the same input data. For example, a model analyzing a news article might need to predict both its category and whether it is likely to be clickbait. Each prediction task would be handled by a separate output “head” of the network.
import keras
# Define a single input for the model
input_data = keras.Input(shape=(256,), name='input_data')
# A shared block of layers to extract features from the input
shared_features = keras.layers.Dense(128, activation='relu')(input_data)
shared_features = keras.layers.Dense(64, activation='relu')(shared_features)
# First output head: predicts a category
category_output = keras.layers.Dense(10, activation='softmax', name='category_output')(shared_features)
# Second output head: predicts a binary score
score_output = keras.layers.Dense(1, activation='sigmoid', name='score_output')(shared_features)
# Create the model with one input and two outputs
model = keras.Model(
inputs=input_data,
outputs=[category_output, score_output]
)
Here, a single input tensor is processed through a common stack of Dense layers. The output of this shared block, shared_features, serves as the input to two different output layers. The category_output layer performs a 10-class classification, while the score_output layer performs binary classification. The final keras.Model is defined by specifying the single input and a list of the two outputs. When this model is trained, it will require a separate loss function for each output, and the final loss will be a weighted sum of the individual losses.
A particularly powerful feature enabled by the Functional API is layer sharing. A single layer instance can be reused multiple times within the same model. This allows the model to learn features that are common across different inputs. For instance, if a model needs to compare the similarity of two sentences, it’s logical to process both sentences through the same Embedding layer.
import keras # Create a single Embedding layer instance shared_embedding = keras.layers.Embedding(input_dim=1000, output_dim=128) # Define two separate inputs for two sentences input_a = keras.Input(shape=(None,), dtype='int32') input_b = keras.Input(shape=(None,), dtype='int32') # Both inputs are processed through the SAME embedding layer encoded_a = shared_embedding(input_a) encoded_b = shared_embedding(input_b) # The rest of the model would then process encoded_a and encoded_b...
By using the same shared_embedding layer instance for both inputs, we ensure that identical words are mapped to identical embedding vectors, regardless of which input they appeared in. This not only promotes learning a more robust and generalized representation of words but also significantly reduces the number of parameters in the model, as the weight matrix of the embedding layer is shared. Any updates to the layer’s weights during training, based on data from input_a, will affect the processing of input_b, and vice versa. This mechanism is fundamental for building sophisticated models like Siamese networks. The flexibility to create these complex graphs of computation is the primary reason the Functional API is the tool of choice for research and the development of novel network architectures. When you need to go beyond a simple stack of layers, the Functional API provides the necessary primitives to construct nearly any model you can represent on a whiteboard. It allows you to define intricate data flows, making it an indispensable tool for the advanced practitioner. The resulting model object, whether built with the Sequential or Functional API, behaves identically in terms of training, evaluation, and inference workflows, providing a consistent experience once the construction phase is complete. The choice of API is purely a matter of architectural complexity. For models with any form of non-linear topology, the Functional API is not just an option; it is a necessity. The ability to manipulate tensors directly and define explicit connections between layers gives you complete control over the model’s design, from multi-input/multi-output systems to models with residual connections and shared feature extractors. This level of control is essential when translating a novel architectural idea into working code.
Finalizing the model for training and evaluation
Once the model’s architecture is defined, the next critical step is to configure its learning process. This is accomplished via the .compile() method. This step does not involve any training yet; rather, it specifies the optimizer, the loss function, and the metrics that will be used during the subsequent training phase. The configuration you provide in compile() finalizes the model and prepares it for fitting to data.
The first key argument to compile() is the optimizer. The optimizer is the algorithm that implements a specific variant of Stochastic Gradient Descent (SGD) to iteratively update the model’s weights in response to the output of the loss function. Keras provides a wide range of built-in optimizers, such as SGD, RMSprop, and Adam. You can specify an optimizer by its string identifier if you are content with its default parameters.
# Compiling a model using a string identifier for the optimizer model.compile(optimizer='adam', ...)
For more control, you can pass an instance of an optimizer class from the keras.optimizers module. This allows you to tune its hyperparameters, such as the learning rate, which is a common and impactful modification.
import keras # Compiling a model using an optimizer instance to set a custom learning rate model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.001), ...)
The second essential argument is the loss function, also known as the objective function. The loss function quantifies the difference between the model’s predictions and the true target values. The optimizer’s goal is to minimize this value. The choice of loss function is determined by the nature of the problem. For a binary classification problem, you would typically use 'binary_crossentropy'. For a multi-class classification problem where labels are one-hot encoded, 'categorical_crossentropy' is appropriate. If the labels are integers, use 'sparse_categorical_crossentropy'. For regression problems, common choices include 'mean_squared_error' (MSE) or 'mean_absolute_error' (MAE). Similar to optimizers, these can be specified by string or by passing an instance of a loss class from keras.losses.
The third argument, metrics, is a list of functions that are used to monitor the model’s performance during training and evaluation. A metric function is similar to a loss function, but the results from evaluating metrics are not used when training the model. You can use any loss function as a metric. A common choice for classification problems is 'accuracy', which calculates the proportion of correct predictions. You can provide a list of metrics, which can be specified as strings or as instances of metric classes from keras.metrics.
Bringing these components together, a typical compile call for the multi-class classification model we defined earlier would look like this:
# A simple compile call for a multi-class classification model
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
For the multi-output models created with the Functional API, the compile step offers additional flexibility. You can specify different loss functions and metrics for each output. This is achieved by passing dictionaries to the loss and metrics arguments, where the dictionary keys correspond to the names of the output layers.
Furthermore, it is often necessary to weight the contribution of each output’s loss to the final loss value that the model minimizes. Some tasks might be more important than others, or the scales of the loss values might differ significantly. The loss_weights argument, which accepts a list or dictionary, allows you to assign a specific coefficient to each output’s loss. For the multi-output model that predicts both a category and a score, the compile step could be configured as follows:
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=1e-3),
loss={
'category_output': 'categorical_crossentropy',
'score_output': 'binary_crossentropy'
},
loss_weights={'category_output': 1.0, 'score_output': 0.5},
metrics={
'category_output': [keras.metrics.Precision(), keras.metrics.Recall()],
'score_output': ['accuracy']
}
)
In this configuration, we have assigned a specific loss function to each named output. The loss for the score_output is weighted to be half as important as the loss for the category_output during the optimization process. We are also tracking precision and recall for the category prediction and simple accuracy for the score prediction. This fine-grained control is essential for training complex models effectively. Once compiled, the model instance is fully prepared for the training process, which involves calling the .fit() method with the training data.
