Last modified: Oct 24, 2026
Custom Layers in Keras
Keras gives you many built-in layers. Dense, Conv2D, and LSTM cover most needs. But real projects often need something new. That is where custom layers come in.
A custom layer lets you define your own computation. You control the weights, the math, and the output shape. Keras keeps everything else working, like training and saving.
This guide walks you through custom layers from scratch. You will see the core methods, full code examples, and common pitfalls. By the end, you can build clean and reusable layers of your own.
Why Build a Custom Layer?
Built-in layers are flexible, but not infinite. Sometimes you need a special operation. Maybe a layer that combines two inputs in a new way. Maybe a layer with a custom activation or normalization step.
You could write this logic outside the model. But then it breaks the model graph. It also makes saving and loading harder. A proper custom layer keeps everything inside the model.
Custom layers also help with code reuse. Once you write a layer, you can use it in many models. You can share it with your team. You can even publish it.
The Three Core Methods
Every Keras custom layer is a Python class. It extends keras.layers.Layer. Three methods matter most: __init__, build, and call.
The __init__ method sets up configuration. It stores hyperparameters like units or activation. It also calls the parent constructor.
The build method creates the weights. Keras calls it once, when the layer first sees input. This is where you use add_weight to define trainable parameters.
The call method runs the forward pass. It takes the input tensor and returns the output tensor. This is where the real math happens.
A Simple Custom Layer Example
Let's start with a basic example. This layer scales its input by a trainable factor. It also adds a trainable bias.
import tensorflow as tf
from tensorflow import keras
class ScaleBiasLayer(keras.layers.Layer):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self, input_shape):
# Create one trainable weight per input feature
self.scale = self.add_weight(
name="scale",
shape=(input_shape[-1],),
initializer="ones",
trainable=True,
)
self.bias = self.add_weight(
name="bias",
shape=(input_shape[-1],),
initializer="zeros",
trainable=True,
)
def call(self, inputs):
# Forward pass: x * scale + bias
return inputs * self.scale + self.bias
Notice the pattern. Weights are created in build, not in __init__. That is because the layer does not know the input shape until it is called.
The call method stays simple. It only contains the forward math. This keeps the code readable and easy to debug.
Using the Layer in a Model
Now let's plug the layer into a small model. We will use a simple regression task.
model = keras.Sequential([
keras.layers.Input(shape=(4,)),
ScaleBiasLayer(),
keras.layers.Dense(1),
])
model.compile(optimizer="adam", loss="mse")
model.summary()
Here is the output of the summary:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
scale_bias_layer (ScaleBias (None, 4) 8
Layer)
dense (Dense) (None, 1) 5
=================================================================
Total params: 13
Trainable params: 13
Non-trainable params: 0
The layer adds eight parameters. That is four scales and four biases. Keras tracks them automatically during training.
Adding Configuration and Serialization
A good custom layer should be easy to save and reload. Keras handles this if you follow two rules. First, pass **kwargs to the parent constructor. Second, store your config values as attributes.
You can also override get_config. It returns a dictionary of your hyperparameters. This lets Keras rebuild the layer from disk.
class MyActivationLayer(keras.layers.Layer):
def __init__(self, alpha=0.1, **kwargs):
super().__init__(**kwargs)
self.alpha = alpha
def call(self, inputs):
# Leaky-style activation with a custom slope
return tf.where(inputs > 0, inputs, self.alpha * inputs)
def get_config(self):
config = super().get_config()
config.update({"alpha": self.alpha})
return config
With this setup, keras.models.load_model can rebuild the layer without errors. This is a common source of bugs in deployed models, so do not skip it.
Custom Layers with Multiple Inputs
Some layers need more than one tensor. The call method can accept a list. Just make sure the order is clear.
class WeightedSumLayer(keras.layers.Layer):
def call(self, inputs):
a, b = inputs
# Blend two tensors with a fixed ratio
return 0.7 * a + 0.3 * b
# Use it inside a functional model
input_a = keras.layers.Input(shape=(8,))
input_b = keras.layers.Input(shape=(8,))
merged = WeightedSumLayer()([input_a, input_b])
model = keras.Model(inputs=[input_a, input_b], outputs=merged)
This pattern is useful for fusion layers. You can mix features from different branches of a network.
Best Practices for Clean Custom Layers
Keep the call method pure. Avoid side effects and global state. This makes the layer predictable and testable.
Use descriptive names for weights. When you debug a model, clear names save time.
Prefer add_weight over raw TensorFlow variables. Keras then handles device placement and tracking for you.
Test each layer on its own. Feed a small tensor and check the output shape and values. This catches shape bugs early.
If your layer relies on external data formats or services, keep that logic outside the layer. For example, if you load records from a binary schema, you might first compile and use .proto files in Python to decode them. The layer should only see clean tensors.
Custom Layers in Real Pipelines
In production, models often sit inside larger systems. A custom layer might preprocess features before training. The data may come from a message queue or a database.
Suppose your features arrive as events. You could produce and consume Kafka messages in Python to stream them into your pipeline. The layer then receives clean arrays, not raw events.
Other times, the data lives in a relational store. You can query Oracle Database from Python to build your training set. Again, the layer stays focused on math.
This separation matters. Layers should be small and focused. Data plumbing belongs elsewhere. That keeps your model code readable and your layers reusable.
Common Mistakes to Avoid
Do not create weights in __init__. The input shape is unknown at that point. Always use build.
Do not forget to call super().__init__(**kwargs). Skipping it breaks name scoping and serialization.
Do not use Python loops over tensors in call. Use vectorized TensorFlow ops instead. Loops are slow and break graph mode.
Do not hardcode batch sizes. Use None-safe shapes and rely on broadcasting.
Testing Your Custom Layer
A quick smoke test is always worth it. Feed random data and check the output.
import numpy as np
layer = ScaleBiasLayer()
x = np.random.rand(2, 4).astype("float32")
y = layer(x)
print(y.shape)
The output confirms the shape is correct:
(2, 4)
You can also check that weights update after a training step. This proves the layer is properly connected to the optimizer.
Conclusion
Custom layers in Keras are powerful and surprisingly simple. You only need three methods: __init__, build, and call. Add get_config for safe saving.
Start small. Write a layer that does one clear thing. Test it in isolation. Then plug it into a model.
Keep your layers focused on math. Keep data loading and plumbing outside. This habit makes your models easier to read, test, and ship.
With these patterns, you can extend Keras to fit almost any problem. Your custom layers will stay clean, reusable, and production-ready.