Layers
A Keras layer is a callable transformation with trainable weights. Stack them, compose with the Functional API, or subclass tf.keras.layers.Layer for custom behaviour.
Layers, sequential, functional, subclass
EXAMPLE
import tensorflow as tf
from tensorflow.keras import layers, Model, Input, Sequential
# 1) Common layer types
# Dense — fully connected
layers.Dense(128, activation='relu')
layers.Dense(10, activation='softmax')
layers.Dense(1, activation='sigmoid')
# Convolutional
layers.Conv2D(32, kernel_size=3, padding='same', activation='relu')
layers.Conv2D(64, kernel_size=(3, 3), strides=(1, 1))
layers.MaxPool2D(pool_size=2)
layers.AveragePooling2D()
layers.GlobalAveragePooling2D() # spatial → vector (used before final dense)
layers.Conv1D(64, kernel_size=5) # for sequences
layers.Conv3D(32, kernel_size=3) # for volumetric data
# Recurrent
layers.LSTM(128, return_sequences=True)
layers.GRU(64)
layers.Bidirectional(layers.LSTM(64))
layers.SimpleRNN(64)
# Embedding
layers.Embedding(input_dim=10_000, output_dim=128) # vocab → dense vectors
# Regularisation
layers.Dropout(0.5)
layers.BatchNormalization()
layers.LayerNormalization()
layers.SpatialDropout2D(0.25) # drops whole feature maps
# Reshape / utility
layers.Flatten()
layers.Reshape((4, 4, 16))
layers.Permute((2, 1))
layers.Concatenate(axis=-1)
layers.Add()
layers.Multiply()
layers.Subtract()
# Activation as a separate layer (handy when you want BN before activation)
layers.ReLU()
layers.LeakyReLU(alpha=0.1)
layers.PReLU()
layers.ELU(alpha=1.0)
layers.Softmax()
layers.Activation('gelu')
# Image preprocessing
layers.Rescaling(1./255)
layers.Normalization() # use .adapt(X_train) to compute mean/std
layers.Resizing(224, 224)
layers.CenterCrop(224, 224)
layers.RandomFlip('horizontal')
layers.RandomRotation(0.1)
layers.RandomZoom(0.1)
layers.RandomContrast(0.2)
layers.RandomBrightness(0.2)
# Text preprocessing
layers.TextVectorization(max_tokens=10_000, output_sequence_length=200)
layers.StringLookup()
layers.IntegerLookup()
# Attention
layers.MultiHeadAttention(num_heads=8, key_dim=64)
layers.Attention()
layers.AdditiveAttention()
# 2) Sequential API — when each layer feeds the next
model = Sequential([
Input(shape=(28, 28, 1)),
layers.Rescaling(1./255),
layers.Conv2D(32, 3, activation='relu'),
layers.MaxPool2D(),
layers.Conv2D(64, 3, activation='relu'),
layers.MaxPool2D(),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dropout(0.5),
layers.Dense(10, activation='softmax'),
])
# 3) Functional API — when graph has branches / skip connections
inputs = Input(shape=(224, 224, 3))
x = layers.Conv2D(32, 3, padding='same', activation='relu')(inputs)
x = layers.MaxPool2D()(x)
# ResNet block (skip connection)
shortcut = x
y = layers.Conv2D(32, 3, padding='same', activation='relu')(x)
y = layers.Conv2D(32, 3, padding='same')(y)
y = layers.Add()([y, shortcut]) # element-wise add
y = layers.Activation('relu')(y)
x = layers.GlobalAveragePooling2D()(y)
x = layers.Dense(1000, activation='softmax')(x)
model = Model(inputs, x)
# Multi-input / multi-output
img_in = Input((224, 224, 3), name='image')
meta_in = Input((10,), name='metadata')
img_feat = layers.Conv2D(32, 3)(img_in)
img_feat = layers.GlobalAveragePooling2D()(img_feat)
meta_feat = layers.Dense(32, activation='relu')(meta_in)
combined = layers.Concatenate()([img_feat, meta_feat])
price_out = layers.Dense(1, name='price')(combined)
cat_out = layers.Dense(10, activation='softmax', name='category')(combined)
model = Model(inputs=[img_in, meta_in], outputs=[price_out, cat_out])
model.compile(
optimizer='adam',
loss ={'price': 'mse', 'category': 'sparse_categorical_crossentropy'},
metrics ={'price': 'mae', 'category': 'accuracy'},
loss_weights={'price': 1.0, 'category': 0.5},
)
# 4) Subclassing — full control
class ResidualBlock(layers.Layer):
def __init__(self, filters, **kwargs):
super().__init__(**kwargs)
self.conv1 = layers.Conv2D(filters, 3, padding='same')
self.bn1 = layers.BatchNormalization()
self.conv2 = layers.Conv2D(filters, 3, padding='same')
self.bn2 = layers.BatchNormalization()
def call(self, x, training=False):
residual = x
x = tf.nn.relu(self.bn1(self.conv1(x), training=training))
x = self.bn2(self.conv2(x), training=training)
x = layers.add([x, residual])
return tf.nn.relu(x)
class MyResNet(Model):
def __init__(self, num_classes):
super().__init__()
self.stem = Sequential([
layers.Conv2D(32, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
])
self.blocks = [ResidualBlock(32) for _ in range(3)]
self.head = Sequential([
layers.GlobalAveragePooling2D(),
layers.Dense(num_classes, activation='softmax'),
])
def call(self, x, training=False):
x = self.stem(x, training=training)
for block in self.blocks:
x = block(x, training=training)
return self.head(x)
model = MyResNet(10)
# 5) Inspect the model
model.summary()
model.layers # list of layer objects
model.layers[0].weights # trainable weights
model.layers[0].trainable = False # freeze a layer
# Freeze the first N layers for transfer learning
for layer in model.layers[:-5]:
layer.trainable = False
# 6) Custom layer with weights
class Linear(layers.Layer):
def __init__(self, units=32, **kwargs):
super().__init__(**kwargs)
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer='glorot_uniform',
trainable=True,
)
self.b = self.add_weight(
shape=(self.units,),
initializer='zeros',
trainable=True,
)
def call(self, inputs):
return inputs @ self.w + self.b
# 7) Layer with config (so the model can be saved + loaded)
class Linear2(layers.Layer):
def __init__(self, units=32, **kwargs):
super().__init__(**kwargs)
self.units = units
def build(self, input_shape):
self.dense = layers.Dense(self.units)
def call(self, x):
return self.dense(x)
def get_config(self):
return {**super().get_config(), 'units': self.units}
# 8) Pre-trained models — high-level layers
from tensorflow.keras.applications import ResNet50, EfficientNetV2B0, MobileNetV3Large
base = ResNet50(include_top=False, weights='imagenet', input_shape=(224, 224, 3), pooling='avg')
base.trainable = False
model = Sequential([
base,
layers.Dropout(0.3),
layers.Dense(num_classes, activation='softmax'),
])
# 9) Common patterns
# Convolutional block with regularisation
conv_block = lambda f: Sequential([
layers.Conv2D(f, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
layers.Conv2D(f, 3, padding='same'),
layers.BatchNormalization(),
layers.ReLU(),
layers.MaxPool2D(),
layers.Dropout(0.25),
])
# MLP head
mlp_head = lambda n: Sequential([
layers.GlobalAveragePooling2D(),
layers.Dense(256, activation='relu'),
layers.Dropout(0.5),
layers.Dense(n, activation='softmax'),
])
# 10) Best practices
# • Use Functional API for any non-linear graph (branches, multi-input/output)
# • Use Sequential for simple stacks (no branches)
# • Subclass when you need conditional computation (training/inference branching beyond `training` arg)
# • Always include Input layer with explicit shape — better error messages
# • Name your layers (`name='conv1'`) — easier to freeze + inspect by name
# • Use preprocessing layers IN THE MODEL — saved + loaded together, runs on GPU
# 11) Common bugs
# • Forgetting to add an activation → linear output
# • Mixing Sequential + complex graphs → can't add skip connections
# • Custom layer without get_config() → can't save / restore
# • Wrong axis on BatchNormalization / Concatenate → silently bad results
# • Freezing layers AFTER compile → compile again after freezing
Why it matters
Functional API for any non-linear graph (branches, skip connections, multi-input/output). Sequential for the simple stack case. Subclass only when you need behaviour beyond the training kwarg can express — usually rare.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
from tensorflow.keras import layers layers.Dense(64, activation='relu') layers.Conv2D(32, (3,3), padding='same') layers.LSTM(64, return_sequences=True) layers.LayerNormalization()Try it Yourself »
Discussion
Loading…