Singleton Design Pattern in Machine Learning

Design patterns are the best practices in a software engineering but they are mostly missing in machine learning studies. Software engineers and data scientists / machine learning engineers have very different backgrounds. This might be the reason of this misunderstanding. In this post, I’m going to explain singleton design pattern on a machine learning application in Python.

Singleton whisky

Definition

The definition of the singleton design pattern is very simple. It is a procedure restricting you to initialize a class once. That definition might be very abstract but it fits machine learning practices well.


🙋‍♂️ You may consider to enroll my top-rated machine learning course on Udemy

Decision Trees for Machine Learning

A machine learning lifecycle consists of three main stages: model building, training and prediction. Training is handled rare and it is an offline requirement. On the other hand, we build models and make predictions always in production. Also, we have to deal with very complex models. For example, VGG-Face has 145M, FaceNet has 22M, ArcFace has 34M parameters. We have to build those complex models before make predictions.

Use case

Please consider a case requiring to verify many face pairs. Face pairs could be stored in a python list. We will build a for loop and call verification function to handle this task as shown in the following code snippet.

#main.py

import function

face_pairs = [
     ["img1.jpg", "img2.jpg"],
     ["img1.jpg", "img3.jpg"],
     ["img1.jpg", "img4.jpg"],
     ["img1.jpg", "img5.jpg"],
     ["img1.jpg", "img6.jpg"],
     ["img1.jpg", "img7.jpg"],
     ["img1.jpg", "img8.jpg"],
     ["img1.jpg", "img9.jpg"],
     ["img1.jpg", "img10.jpg"],
     ["img1.jpg", "img11.jpg"]
]

for face_pair in face_pairs:
    img1 = face_pair[0]
    img2 = face_pair[1]
    function.verify(img1, img2)

Verify function will be called 10 times because the length of face_pairs variable is 10.

Let’s mention the content of verify function. We need to pass each face image to the face recognition model to find its embedding. That’s why, we should build the face recognition model. BTW, I’m using the build model function under deepface interface directly.

#function.py
from deepface import DeepFace #pip install deepface

def verify(img1, img2):
     model = DeepFace.build_model("Facenet")
     img1_embedding = model.predict(img1)
     img2_embedding = model.predict(img1)

Facenet model requires 3.10 seconds to be built and prediction completed in 1.05 seconds in macbook pro. I cannot decrease the required time for prediction but I really need to build the FaceNet model in each iteration? This is ridiculous. It causes 10 x 3.10 seconds or half minute. On the other hand, I can build the FaceNet model once and use the pre-built one in the following iterations. That’s the main idea of singleton!

Object oriented programming

OOP comes with a strong inheritance mechanism. Here, if we define a wrapper class and build the Facenet model in its initialization, it will satisfy our requirement.

#wrapper.py

from deepface import DeepFace

class Wrapper:
  def __init__(self):
    self.model = DeepFace.build_model("Facenet")

  def verify(self, img1, img2):
    img1_embedding = self.model.predict(img1)
    img2_embedding = self.model.predict(img1)

We will initialize the wrapper class once and it will build Facenet model once as well. Then, we don’t spend time for model building in the verify function anymore. This practice will save 3 seconds times the length of face pairs. I becomes more meaningful if you need to deal with a large number of face pairs.

from wrapper import Wrapper

obj = Wrapper()

for face_pair in face_pairs:
    img1 = face_pair[0]
    img2 = face_pair[1]
    obj.verify(img1, img2)

Handling singleton with regular python

If you have a software engineering background, then object oriented approach fits in your mind. On the other hand, class structures might be confusing if you are not coming from developer world. We can handle singleton design pattern with regular python as well. To be honest, I like the simplicity of Python and I enjoy this approach.





Firstly, we are going to define the global model variable and check its existence in globals. If it does not exist in globals, then we will build the model. Then, we will return the model.

#function.py

from deepface import DeepFace

def build_model():
     global model
     if not "model_obj" in globals():
          model = DeepFace.build_model("Facenet")
    return model

def verify(img1, img2):
     model = build_model()
     img1_embedding = model.predict(img1)
     img2_embedding = model.predict(img1)

Thus, Facenet model will be built in the first iteration of the for loop. Thereafter, pre-built Facenet model will be used in the rest of iterations. Time consumption will same with object oriented approach but architecture becomes simpler in this case.

#main.py

import function

for face_pair in face_pairs:
    img1 = face_pair[0]
    img2 = face_pair[1]
    function.verify(img1, img2)
Conclusion

So, we have mentioned how to apply singleton software design pattern for a machine learning application in Python. Design patterns are best practices in software engineering but they can be missed by machine learning practitioners. However, this is a must practice for a production-driven machine learning application. Besides, this design pattern is much more important in machine learning field because we spend lots of time to build very complex models often.


Like this blog? Support me on Patreon

Buy me a coffee