← Master Index
Vol. 03 Module 3.2 Lecture

Abstraction

Object-Oriented Programming

Module Capstone — Bringing OOP Together

You have learned the four pillars: Classes and Objects for structure and state; Inheritance for reuse; Polymorphism for interchangeable components; Encapsulation for safe boundaries.

Abstraction is the design skill that sits above all four: define what a component must do without specifying how. Abstract base classes, protocols, and thin interfaces let you build ML systems where datasets, models, trainers, and evaluators plug together cleanly.

This capstone assembles a miniature ML pipeline using OOP principles you will recognize in PyTorch, scikit-learn, and production MLOps tooling.

Learning Objectives

By the end of this lesson, students should be able to:

  • Define abstraction as separating essential behavior from implementation details.
  • Create abstract base classes (ABCs) with abc.ABC and @abstractmethod.
  • Design interfaces for Dataset, Model, Trainer, and Evaluator components.
  • Implement concrete classes that satisfy abstract contracts.
  • Explain how abstraction enables swapping components without rewriting orchestration code.
  • Map this module’s patterns to upcoming libraries in Module 3.3 (NumPy, PyTorch, pandas).

What Abstraction Means in ML Engineering

Definition — Abstraction

An abstraction is a simplified model of a system that exposes only what callers need. In OOP, abstractions are often expressed as abstract base classes or protocols that declare required methods without implementing them. Concrete subclasses provide the details.

When you call model.fit(X, y) in scikit-learn, you abstract away whether the solver uses coordinate descent or LBFGS. When you call DataLoader(dataset) in PyTorch, you abstract away whether samples come from JPEGs, Parquet, or a database. Good abstractions reduce cognitive load and enable testing with mocks.

Abstract Base Classes in Python

from abc import ABC, abstractmethod

class BaseDataset(ABC):
    @abstractmethod
    def __len__(self):
        pass

    @abstractmethod
    def __getitem__(self, index):
        pass


class BaseModel(ABC):
    @abstractmethod
    def fit(self, dataset):
        pass

    @abstractmethod
    def predict(self, features):
        pass

You cannot instantiate BaseDataset() directly—Python raises TypeError until every @abstractmethod is implemented in a subclass. This enforces the contract at object creation time.

Capstone: A Mini ML Framework

Let’s build a composable training system. Each piece is abstract; concrete implementations plug in.

1. Concrete Dataset

class ListDataset(BaseDataset):
    def __init__(self, features, labels):
        self._features = features
        self._labels = labels

    def __len__(self):
        return len(self._features)

    def __getitem__(self, index):
        return self._features[index], self._labels[index]

2. Concrete Model Wrapper

from sklearn.linear_model import LogisticRegression
import numpy as np

class SklearnClassifierWrapper(BaseModel):
    def __init__(self):
        self._clf = LogisticRegression(max_iter=1000)

    def fit(self, dataset):
        X = np.array([dataset[i][0] for i in range(len(dataset))])
        y = np.array([dataset[i][1] for i in range(len(dataset))])
        self._clf.fit(X, y)

    def predict(self, features):
        return self._clf.predict([features])[0]

3. Abstract Trainer and Evaluator

class Trainer(ABC):
    @abstractmethod
    def run(self, model: BaseModel, train_data: BaseDataset):
        pass


class Evaluator(ABC):
    @abstractmethod
    def score(self, model: BaseModel, test_data: BaseDataset) -> float:
        pass


class SimpleTrainer(Trainer):
    def run(self, model, train_data):
        model.fit(train_data)
        return model


class AccuracyEvaluator(Evaluator):
    def score(self, model, test_data):
        correct = 0
        for i in range(len(test_data)):
            x, y = test_data[i]
            if model.predict(x) == y:
                correct += 1
        return correct / len(test_data)

4. Orchestration — Abstraction in Action

def experiment(train_data, test_data, model, trainer, evaluator):
    """Orchestrator depends ONLY on abstractions — not concrete classes."""
    trained = trainer.run(model, train_data)
    return evaluator.score(trained, test_data)

train = ListDataset([[0, 0], [1, 1], [1, 0]], [0, 1, 1])
test = ListDataset([[0, 1], [1, 1]], [1, 1])

accuracy = experiment(
    train, test,
    SklearnClassifierWrapper(),
    SimpleTrainer(),
    AccuracyEvaluator(),
)
print(f"Accuracy: {accuracy:.2f}")
Abstract interfaces — BaseDataset, BaseModel, Trainer, Evaluator Concrete implementations — ListDataset, SklearnClassifierWrapper, etc. Orchestratorexperiment() wires components without knowing internals Swap any layer — New model, new metrics, new data source; orchestrator unchanged

The Four Pillars in One Pipeline

Pillar Where It Appears
Encapsulation ListDataset hides _features; wrapper hides sklearn estimator
Abstraction BaseModel, Trainer ABCs define contracts without implementation
Inheritance SklearnClassifierWrapper(BaseModel) reuses interface, specializes behavior
Polymorphism experiment() accepts any model/trainer/evaluator satisfying the ABC

Abstraction in Real Frameworks

PyTorch

nn.Module abstracts forward computation. Dataset abstracts sample access. Optimizer abstracts parameter updates.

scikit-learn

BaseEstimator abstracts fit/predict. Pipelines abstract multi-step workflows.

MLOps

Model registries abstract artifact storage. Feature stores abstract training/serving feature logic.

Bridge to Module 3.3

In Module 3.3: AI & Data Libraries, you will use these abstractions in production tools: NumPy arrays behind tensor operations, pandas DataFrames behind tabular pipelines, PyTorch modules behind GPU training, and matplotlib figures behind experiment visualization. OOP is not academic—it is how those libraries stay composable.

Testing with Abstractions

Abstract interfaces make unit tests easy. Swap a heavy model with a mock:

class MockModel(BaseModel):
    def fit(self, dataset):
        return self

    def predict(self, features):
        return 1   # always predict class 1

# Test evaluator logic without training a real model
mock = MockModel()
assert AccuracyEvaluator().score(mock, test) >= 0.0

Design Guidelines for ML Code

Common Misconceptions

Misconception 1: “Abstraction means making code harder to read.”

Reality: Good abstraction hides incidental complexity and surfaces intent. trainer.run(model, data) is clearer than 200 lines of mixed I/O and math.

Misconception 2: “ABCs are required for every small project.”

Reality: Start simple. Introduce ABCs when you have multiple implementations or need test doubles. Duck typing is often enough until complexity grows.

Misconception 3: “OOP and functional Python conflict.”

Reality: ML code mixes both: classes for stateful components (models, datasets), pure functions for transforms. Use the right tool per layer.

Quick Knowledge Check

  1. Short Answer: What happens if you instantiate a class with unimplemented @abstractmethods? Answer: TypeError at instantiation.
  2. True/False: experiment() must import sklearn to work. Answer: False—it depends only on abstract interfaces; sklearn is hidden inside the wrapper.
  3. Short Answer: Name the four OOP pillars covered in this module. Answer: Encapsulation, Abstraction, Inheritance, Polymorphism.
  4. Short Answer: Why are mock models useful? Answer: Test orchestration and metrics without expensive training.
  5. Multiple Choice: Which Module 3.3 library provides the nn.Module abstraction? (a) pandas, (b) PyTorch, (c) matplotlib, (d) Jupyter. Answer: (b) PyTorch.
  6. Short Answer: What does an abstraction expose versus hide? Answer: It exposes essential behavior (the contract) and hides implementation details.
  7. True/False: You can instantiate BaseDataset() directly if it still has unimplemented abstract methods. Answer: False—Python raises TypeError until every @abstractmethod is implemented.
  8. Short Answer: Name the four component roles in the mini ML framework. Answer: Dataset, Model, Trainer, and Evaluator.
  9. Multiple Choice: Abstraction mainly lets you: (a) store more tensors on GPU, (b) swap implementations without rewriting orchestration, (c) skip unit tests, (d) avoid classes. Answer: (b).
  10. True/False: Duck typing can serve as an interface until you introduce ABCs. Answer: True—ABCs are useful when you need multiple implementations or test doubles.

Key Takeaways

  • Abstraction defines what without how—essential for composable ML systems.
  • ABCs (abc.ABC, @abstractmethod) enforce interfaces at instantiation.
  • Separate Dataset, Model, Trainer, and Evaluator concerns; orchestrate with thin glue code.
  • All four OOP pillars work together: encapsulate state, abstract contracts, inherit defaults, polymorph at runtime.
  • Real frameworks (PyTorch, sklearn, MLOps tools) are built on these same patterns.
  • Module 3.3 applies these ideas with industry-standard data and AI libraries.

Further Reading & References

Official Documentation

Trainer’s Guide

Capstone exercise: Students implement PyTorchClassifierWrapper(BaseModel) using a tiny nn.Linear network. Swap it into experiment() without changing the orchestrator.

Discussion: Which parts of a real Hugging Face training script map to Dataset, Model, Trainer, and Evaluator?

Bridge: Open NumPy and PyTorch next—the abstractions you designed by hand are the ones those libraries formalize.

What’s Next You have completed Object-Oriented Programming. Continue to Module 3.3: AI & Data Libraries for NumPy, pandas, PyTorch, and the tools that power modern ML workflows.