Skip to main content

Command Palette

Search for a command to run...

Enhancing Efficiency: 10 Decorators I Use Daily as a Tech MLE

Published
5 min read
Enhancing Efficiency: 10 Decorators I Use Daily as a Tech MLE
P

Machine Learning Engineer at Nextdoor specializing in content moderation. Former Meta Data Science intern with a Data Science degree from Northeastern University. 3 years of experience building ML and deep learning models for fintech and insurance industries. Passionate about NLP and Computer Vision. Daily learner in ML, software development, and tech trivia.

Introduction:

Machine Learning Engineers (MLEs) are the architects of AI, sculpting intelligent systems that can recognize patterns, make predictions, and automate tasks. In this journey, Python is our trusty companion, and Python decorators are our secret weapons. Decorators are elegant and powerful tools that allow us to modify the behavior of functions or methods. As an MLE in the tech world, I rely on a set of 10 decorators daily to streamline my machine learning workflows. In this blog, I'll introduce you to these essential decorators with practical code examples, making your journey into the world of machine learning a little more exciting.

Decorator 1: Memoization

Memoization is like having a photographic memory for your functions. It caches the results of expensive function calls and reuses them when the same inputs occur again. This can drastically improve the efficiency of your ML pipelines.

def memoize(func):
    cache = {}

    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]

    return wrapper

@memoize
def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)

Decorator 2: Timing

Timing your code is crucial in ML, especially when optimizing algorithms. This decorator calculates the execution time of a function.

import time

def timing(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start} seconds to run.")
        return result

    return wrapper

@timing
def train_model(data):
    # Training code here
    pass

Decorator 3: Validation

Validation is a cornerstone of machine learning. This decorator adds input validation to your functions, ensuring that you're working with the right data types.

def validate_input(*types):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i, arg in enumerate(args):
                if not isinstance(arg, types[i]):
                    raise TypeError(f"Argument {i+1} should be of type {types[i]}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_input(int, list)
def train_model(iterations, data):
    # Training code here
    pass

Decorator 4: Retry

In ML, we often deal with flaky data sources or external APIs. This decorator retries a function a specified number of times if it fails.

import random

def retry(max_retries):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Error: {e}")
                    wait_time = random.uniform(0.1, 1.0)
                    time.sleep(wait_time)
            raise Exception(f"Max retries ({max_retries}) exceeded.")
        return wrapper
    return decorator

@retry(max_retries=3)
def fetch_data():
    # Data fetching code here
    pass

Decorator 5: Logging

Logging is your best friend when debugging ML models. This decorator logs function inputs, outputs, and exceptions.

import logging

def log_function(func):
    logging.basicConfig(filename='ml_engineer.log', level=logging.INFO)

    def wrapper(*args, **kwargs):
        try:
            result = func(*args, **kwargs)
            logging.info(f"{func.__name__}({args}, {kwargs}) returned {result}")
            return result
        except Exception as e:
            logging.error(f"{func.__name__}({args}, {kwargs}) raised an exception: {e}")
            raise

    return wrapper

@log_function
def train_model(data, epochs=10):
    # Training code here
    pass

Decorator 6: Parameter Validation

Machine learning models often have numerous hyperparameters. This decorator ensures that the hyperparameters passed to your functions are within acceptable ranges.

def validate_hyperparameters(param_ranges):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for param, value in kwargs.items():
                if param in param_ranges:
                    min_val, max_val = param_ranges[param]
                    if not (min_val <= value <= max_val):
                        raise ValueError(f"{param} should be between {min_val} and {max_val}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@param_validate({'learning_rate': (0.001, 0.1), 'batch_size': (16, 128)})
def train_model(data, learning_rate=0.01, batch_size=32):
    # Training code here
    pass

Decorator 7: Data Preprocessing

Data preprocessing is a crucial step in ML pipelines. This decorator handles data preprocessing tasks, such as scaling and feature extraction, before passing the data to your functions.

def preprocess_data(func):
    def wrapper(*args, **kwargs):
        data = args[0]  # Assuming the first argument is the data
        # Data preprocessing code here
        preprocessed_data = data  # Replace with actual preprocessing logic
        return func(preprocessed_data, **kwargs)
    return wrapper

@preprocess_data
def train_model(data, learning_rate=0.01):
    # Training code here
    pass

Decorator 8: Model Persistance

Once you've trained a model, you'll want to save it for later use. This decorator automatically saves the trained model to a specified file path.

import joblib

def save_model(model_path):
    def decorator(func):
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            model = args[0]  # Assuming the first argument is the trained model
            joblib.dump(model, model_path)
            return result
        return wrapper
    return decorator

@save_model('my_model.pkl')
def train_model(data, epochs=10):
    # Training code here
    pass

Decorator 9: Performance Profiling

Understanding the performance of your ML code is crucial for optimization. This decorator profiles your code and provides insights into its execution.

import cProfile

def profile_performance(func):
    def wrapper(*args, **kwargs):
        profiler = cProfile.Profile()
        result = profiler.runcall(func, *args, **kwargs)
        profiler.print_stats()
        return result
    return wrapper

@profile_performance
def train_model(data, epochs=10):
    # Training code here
    pass

Decorator 10: Experiment Tracking

Keeping track of experiments is essential in machine learning research. This decorator logs experiment details, including hyperparameters and performance metrics.

def track_experiment(experiment_name):
    def decorator(func):
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            experiment_details = {
                'name': experiment_name,
                'hyperparameters': kwargs,
                'performance_metrics': result  # Replace with actual metrics
            }
            # Log experiment details to a tracking system (e.g., MLflow)
            print(f"Logged experiment: {experiment_details}")
            return result
        return wrapper
    return decorator

@track_experiment('experiment_1')
def train_model(data, learning_rate=0.01, batch_size=32):
    # Training code here
    pass

Conclusion

These ten Python decorators are indispensable companions for any Machine Learning Engineer. They streamline your code, enhance efficiency, and provide valuable insights, making your journey in the realm of machine learning not only more productive but also incredibly rewarding. With these decorators at your disposal, you're well-equipped to tackle the complexities and challenges of modern AI with confidence and ease. Happy coding, and may your algorithms shine brighter than ever!