Github Link
https://github.com/Natan-Asrat/tensorflow_model_subclassing
Contact
- LinkedIn: Natan Asrat
- Gmail: nathanyilmaasrat@gmail.com
- Telegram: Natan Asrat
- X: Natan Asrat Yilma
- Youtube: Natville
The Setup
Introduction
In this project i explored how to implement a subclass model, and subclass layer inheriting from tensorflow.keras.models.Model and tensorflow.keras.layers.Layer.
Libraries Used
- TensorFlow
- numpy
- matplotlib
Imports
Python
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Layer, Dense, Dropout, Softmax, concatenate
from tensorflow.keras.datasets import reuters
from tensorflow.keras.utils import to_categorical
The Code
Create a Simple Model using the Model Subclassing API
Python
class MyModel(Model):
def __init__(self):
super(MyModel, self).__init__()
self.dense_1 = Dense(64, activation='relu')
self.dense_2 = Dense(10)
self.dropout = Dropout(0.4)
self.dense_3 = Dense(5)
self.softmax = Softmax()
def call(self, inputs, training=True):
x = self.dense_1(inputs)
if training:
x = self.dropout(x)
y1 = self.dense_2(inputs)
y2 = self.dense_3(y1)
concat = concatenate([x, y2])
return self.softmax(concat)
model = MyModel()
Custom Layers
Python
class MyLayer(Layer):
def __init__(self, units, input_dim):
super(MyLayer, self).__init__()
self.w = self.add_weight(
shape=(input_dim, units),
initializer='random_normal'
)
self.b = self.add_weight(
shape=(units,),
initializer='zeros'
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
dense_layer = MyLayer(3,5)
Specify Trainable Weights
Python
class MyLayer(Layer):
def __init__(self, units, input_dim):
super(MyLayer, self).__init__()
self.w = self.add_weight(
shape=(input_dim, units),
initializer='random_normal',
trainable=False
)
self.b = self.add_weight(
shape=(units,),
initializer='zeros',
trainable=False
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
dense_layer = MyLayer(3,5)
Create a Custom Layer to Accumulate Means of Output Values
Python
class MyLayerMean(Layer):
def __init__(self, units, input_dim):
super(MyLayerMean, self).__init__()
self.w = self.add_weight(
shape=(input_dim, units),
initializer='random_normal'
)
self.b = self.add_weight(
shape=(units,),
initializer='zeros'
)
self.sum_activation = tf.Variable(
initial_value=tf.zeros((units,)),
trainable=False
)
self.number_call = tf.Variable(
initial_value=0,
trainable=False
)
def call(self, inputs):
activations = tf.matmul(inputs, self.w) + self.b
self.sum_activation.assign_add(tf.reduce_sum(activations, axis=0))
self.number_call.assign_add(inputs.shape[0])
return activations , self.sum_activation/tf.cast(
self.number_call, tf.float32
)
dense_layer = MyLayerMean(3,5)
Test the layer:
Python
y, activation_means = dense_layer(tf.ones((1, 5)))
print(activation_means.numpy())
Output:
[ 0.16241717 0.03885861 -0.07925268]
Create a Dropout Layer as a Custom Layer
Python
class MyDropout(Layer):
def __init__(self, rate):
super(MyDropout, self).__init__()
self.rate = rate
def call(self, inputs):
# Define forward pass for dropout layer
return tf.nn.dropout(inputs, rate=self.rate)
Implement the Custom Layers into a Model
Python
class MyModel(Model):
def __init__(self, units_1, input_dim_1, units_2, units_3):
super(MyModel, self).__init__()
# Define layers
self.layer_1 = MyLayer(units_1, input_dim_1)
self.dropout_1 = MyDropout(0.5)
self.layer_2 = MyLayer(units_2, units_1)
self.dropout_2 = MyDropout(0.5)
self.softmax = Softmax()
self.layer_3 = MyLayer(units_3, units_2)
def call(self, inputs):
# Define forward pass
x = self.layer_1(inputs)
x = tf.nn.relu(x)
x = self.dropout_1(x)
x = self.layer_2(x)
x = tf.nn.relu(x)
x = self.dropout_2(x)
x = self.layer_3(x)
return self.softmax(x)
Custom Training Loops
Define the Custom Layers and Model
Python
class MyDropout(Layer):
def __init__(self, rate):
super(MyDropout, self).__init__()
self.rate = rate
def call(self, inputs):
# Define forward pass for dropout layer
return tf.nn.dropout(inputs, rate=self.rate)
class MyLayer(Layer):
def __init__(self, units):
super(MyLayer, self).__init__()
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer='random_normal',
name="kernel"
)
self.b = self.add_weight(
shape=(self.units,),
initializer='zeros',
name="bias"
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
# Build the model using custom layers with the model subclassing API
class MyModel(Model):
def __init__(self, units_1, units_2, units_3):
super(MyModel, self).__init__()
self.layer_1 = MyLayer(units_1)
self.dropout_1 = MyDropout(0.5)
self.layer_2 = MyLayer(units_2)
self.dropout_2 = MyDropout(0.5)
self.softmax = Softmax()
self.layer_3 = MyLayer(units_3)
def call(self, inputs):
# Define forward pass
x = self.layer_1(inputs)
x = tf.nn.relu(x)
x = self.dropout_1(x)
x = self.layer_2(x)
x = tf.nn.relu(x)
x = self.dropout_2(x)
x = self.layer_3(x)
return self.softmax(x)
model = MyModel(64, 64, 46)
Load the Reuters Dataset and Define the class_names
Python
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000)
class_names = ['cocoa','grain','veg-oil','earn','acq','wheat','copper','housing','money-supply',
'coffee','sugar','trade','reserves','ship','cotton','carcass','crude','nat-gas',
'cpi','money-fx','interest','gnp','meal-feed','alum','oilseed','gold','tin',
'strategic-metal','livestock','retail','ipi','iron-steel','rubber','heat','jobs',
'lei','bop','zinc','orange','pet-chem','dlr','gas','silver','wpi','hog','lead']
Preprocess the Data
Python
def bag_of_words(text_samples, elements=10000):
output = np.zeros((len(text_samples), elements))
for i, word in enumerate(text_samples):
output[i, word] = 1.
return output
x_train = bag_of_words(train_data)
x_test = bag_of_words(test_data)
Define the Loss Function and Optimizer
Python
loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
def loss(model, x, y, wd):
kernel_variables = []
for l in model.layers:
for w in l.weights:
if 'kernel' in w.name:
kernel_variables.append(w)
wd_penalty = wd * tf.reduce_sum([tf.reduce_sum(tf.square(k)) for k in kernel_variables])
y_ = model(x)
return loss_object(y_true=y, y_pred=y_) + wd_penalty
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
Train the Model
Define a function to compute the forward and backward pass:
Python
def grad(model, inputs, targets, wd):
with tf.GradientTape() as tape:
loss_value = loss(model, inputs, targets, wd)
return loss_value, tape.gradient(loss_value, model.trainable_variables)
Implement the training loop:
Python
start_time = time.time()
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, train_labels))
train_dataset = train_dataset.batch(32)
train_loss_results = []
train_accuracy_results = []
num_epochs = 10
weight_decay=0.005
for epoch in range(num_epochs):
epoch_loss_avg = tf.keras.metrics.Mean()
epoch_accuracy = tf.keras.metrics.CategoricalAccuracy()
for x, y in train_dataset:
loss_value, grads = grad(model, x, y, weight_decay)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
epoch_loss_avg(loss_value)
epoch_accuracy(to_categorical(y), model(x))
train_loss_results.append(epoch_loss_avg.result())
train_accuracy_results.append(epoch_accuracy.result())
print(f"Epoch {epoch}, Loss {epoch_loss_avg.result()}, Accuracy {epoch_accuracy.result()} ")
print("Duration :{:.3f}".format(time.time() - start_time))
Evaluate the Model
Create a Dataset object for the test set:
Python
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, test_labels))
test_dataset = test_dataset.batch(32)
Collect average loss and accuracy:
Python
epoch_loss_avg = tf.keras.metrics.Mean()
epoch_accuracy = tf.keras.metrics.CategoricalAccuracy()
Loop over the test set and print scores:
Python
for x, y in test_dataset:
# Optimize the model
loss_value = loss(model, x, y, weight_decay)
# Compute current loss
epoch_loss_avg(loss_value)
# Compare predicted label to actual label
epoch_accuracy(to_categorical(y), model(x))
print("Test loss: {:.3f}".format(epoch_loss_avg.result().numpy()))
print("Test accuracy: {:.3%}".format(epoch_accuracy.result().numpy()))
Plot the Learning Curves
Python
fig, axes = plt.subplots(2, sharex=True, figsize=(12, 8))
fig.suptitle('Training Metrics')
axes[0].set_ylabel("Loss", fontsize=14)
axes[0].plot(train_loss_results)
axes[1].set_ylabel("Accuracy", fontsize=14)
axes[1].set_xlabel("Epoch", fontsize=14)
axes[1].plot(train_accuracy_results)
plt.show()

Predict from the Model
Python
predicted_label = np.argmax(model(x_train[np.newaxis,0]),axis=1)[0]
print("Prediction: {}".format(class_names[predicted_label]))
print(" Label: {}".format(class_names[train_labels[0]]))