Defining a Keras Model Using a Python Class

The most common way to define a simple Keras neural network network/model is to just crank it out as a sequence of Dense layers. For example, to create a 4-5-3 network for the Iris Dataset where the labels are already one-hot encoded:

import Keras as K
print("Creating 4-5-3 neural network ")
model = K.models.Sequential()
model.add(K.layers.Dense(units=5, input_dim=4,
  activation='tanh', kernel_initializer='glorot_uniform',
  bias_initializer='zeros'))
model.add(K.layers.Dense(units=3,
  activation='softmax', kernel_initializer='glorot_uniform',
  bias_initializer='zeros'))
model.compile(loss='categorical_crossentropy',
    optimizer='sgd', metrics=['accuracy'])

A more modular approach is to define a network/model as a Python class:

class Net(K.models.Model):
  def __init__(self):
    super(Net, self).__init__()
    my_init = K.initializers.glorot_uniform(seed=1)
    self.net = K.models.Sequential()
    self.net.add(K.layers.Dense(units=5, input_dim=4,
      activation='tanh', 
      kernel_initializer=my_init,
      bias_initializer='zeros'))
    self.net.add(K.layers.Dense(units=3,
      activation='softmax',
      kernel_initializer=my_init,
      bias_initializer='zeros'))

  def call(self, x):
    oupt = self.net(x)
    return oupt

print("\nCreating 4-5-3 neural network ")
model = Net()
model.compile(loss='categorical_crossentropy',
  optimizer='sgd', metrics=['accuracy'])

The class definition approach is a lot more code (you have to add __init__() and call() methods) and therefore adds complexity, but the design is a bit cleaner. I don’t have a clear preference. The more complex a network architecture is, the more appealng the class definition approach is. There are some non-simple scenarios, such as an autoencoder, where the class definition approach is very helpful.

Note that when you use the class definition approach, if you save the model you must use the SavedModel format rather than the simpler Save format.

Interesting.



The steampunk style combines design simplicity and complexity. Two steampunk style submarine models. I like to look at them as art but I’m not so sure I’d feel confident about traveling in them.


Demo code:

# iris_tfk.py
# Iris classification
# Anaconda3-2020.02  (Python 3.7.6)
# TensorFlow 2.6.0 (includes KerasTF 2.6.0)
# Windows 10

import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'

import numpy as np
import tensorflow as tf
from tensorflow import keras as K

class Net(K.models.Model):
  # 4-5-3
  def __init__(self):
    super(Net, self).__init__()
    my_init = K.initializers.glorot_uniform(seed=1)
    self.net = K.models.Sequential()
    self.net.add(K.layers.Dense(units=5, input_dim=4,
      activation='tanh', 
      kernel_initializer=my_init,
      bias_initializer='zeros'))
    self.net.add(K.layers.Dense(units=3,
      activation='softmax',
      kernel_initializer=my_init,
      bias_initializer='zeros'))

  def call(self, x):
    oupt = self.net(x)
    return oupt

class MyLogger(K.callbacks.Callback):
  def __init__(self, n):
    self.n = n   # print loss & acc every n epochs

  def on_epoch_end(self, epoch, logs={}):
    if epoch % self.n == 0:
      curr_loss =logs.get('loss')
      curr_acc = logs.get('accuracy') * 100
      print("epoch = %4d  loss = %0.6f  acc = %0.2f%%" % \
(epoch, curr_loss, curr_acc))

def main():
  print("\nIris dataset using Keras/TensorFlow ")
  np.random.seed(1)
  tf.random.set_seed(1)

  print("\nLoading Iris data into memory ")
  data_file = ".\\Data\\iris_data.txt"  # one-hot labels
  train_x = np.loadtxt(data_file, usecols=[0,1,2,3],
    delimiter=",",  skiprows=0, dtype=np.float32)
  train_y = np.loadtxt(data_file, usecols=[4,5,6],
    delimiter=",", skiprows=0, dtype=np.int64)

  # data_file = ".\\Data\\iris_data_ordinal_labels.txt"  # ord 
  # train_y = np.loadtxt(data_file, usecols=4,
  #   delimiter=",", skiprows=0, dtype=np.int64)  
  # train_y = tf.keras.utils.to_categorical(train_y) 

  print("\nCreating 4-5-3 neural network ")
  # model = K.models.Sequential()
  # model.add(K.layers.Dense(units=5, input_dim=4,
  #   activation='tanh', kernel_initializer='glorot_uniform',
  #   bias_initializer='zeros'))
  # model.add(K.layers.Dense(units=3, activation='softmax'))

  model = Net()
  model.compile(loss='categorical_crossentropy',
    optimizer='sgd', metrics=['accuracy'])

  my_logger = MyLogger(n=3)

  print("\nStarting training ")
  h = model.fit(train_x, train_y, batch_size=1,
    epochs=12, verbose=0, callbacks=[my_logger])  # 1=chatty
  print("Training finished ")

  eval = model.evaluate(train_x, train_y, verbose=0)
  print("\nModel evaluation: loss = %0.6f  \
    accuracy = %0.2f%% " \
    % (eval[0], eval[1]*100) )

  print("\nSaving trained model as file iris_model.h5 ")
  # model.save_weights(".\\Models\\iris_model.h5")
  # model.save(".\\Models\\iris_model.h5") 
  model.save(".\\Models\\iris_model")  # no extension

  # --------------
  np.set_printoptions(precision=4, suppress=True)
  wts = model.get_weights()
  print("\nTrained model weights and biases: ")
  for ar in wts:
    print(ar)
    print("")
  # --------------

  np.set_printoptions(precision=4)
  unknown = np.array([[6.1, 3.1, 5.1, 1.1]],
    dtype=np.float32)
  predicted = model.predict(unknown)
  print("\nUsing model to predict species for features: ")
  print(unknown)
  print("\nPredicted species is: ")
  print(predicted)

if __name__=="__main__":
  main()
This entry was posted in Keras. Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s