TensorFlow Model Optimization Pipeline
Compress a CNN for edge deployment using quantization and pruning.
Introduction
30 Second Summary
Every time your phone identifies a song or translates text from a photo, a machine learning model is running on a tiny chip with limited memory. Getting a full-size model to fit on that chip without losing accuracy is one of the hardest problems in ML engineering.
In this project, you will build a Python pipeline that trains a neural network, compresses it using three different optimization techniques, and benchmarks the results in an interactive dashboard.
What You'll Build
A model optimization pipeline that takes a trained TensorFlow CNN, applies progressively aggressive compression strategies, converts each variant to TFLite format, and validates that accuracy is preserved.
By the end of this project, you'll have:
- A trained baseline CNN on Fashion-MNIST with three optimized TFLite model variants (dynamic range quantization, full integer quantization, and pruning + quantization).
- A Python benchmarking pipeline built with the Strategy pattern that measures model size, accuracy, and inference latency across all variants.
- An interactive JavaScript dashboard that visualizes the accuracy-compression trade-offs as grouped bar charts.
- Secret Mission: Add weight clustering as a fourth optimization strategy and compare its compression ratio against pruning.
Are there any prerequisites?
This project assumes you have Python (3.10+), Git, and a code editor (like VS Code) already installed. You should also be familiar with TensorFlow/Keras basics and CNN architectures.
If you have trained a simple Keras model before, you have everything you need to get started.
Before We Start
Before you can optimize a model for edge hardware, you need a controlled environment with the exact dependency versions that work together.
In this step, you'll create a Python virtual environment, install TensorFlow and its optimization toolkit, verify everything works, and scaffold the project directory structure with Git version control.
In this step, get ready to:
- Create a Python virtual environment and install TensorFlow dependencies.
- Verify TensorFlow detects the CPU backend.
- Initialize a Git repository and create the project directory structure.
Create a Python virtual environment and install dependencies
A virtual environment isolates your project's packages from the rest of your system. This prevents version conflicts between projects.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir tf-optimization-pipeline
cd tf-optimization-pipeline
- Create a Python virtual environment by running:
python3 -m venv venv
What is a virtual environment?
A virtual environment is an isolated Python installation inside your project folder. Packages you install here stay separate from your system Python, so different projects can use different package versions without conflicts.
- Activate the virtual environment by running:
macOS
source venv/bin/activate
Windows
venv\Scripts\activate
You should see (venv) appear at the beginning of your terminal prompt. This confirms the environment is active.
- Install all the project dependencies by running:
pip install tensorflow==2.21.0 tensorflow-model-optimization==0.8.1 tf-keras numpy matplotlib
What are these packages?
tensorflow==2.21.0 is the core ML framework you'll train and convert models with.
tensorflow-model-optimization==0.8.1 provides pruning, quantization-aware training, and weight clustering APIs. The tf-keras package supplies the legacy Keras 2 compatibility layer that the optimization toolkit requires.
This installation downloads several hundred megabytes of packages. It may take a few minutes depending on your internet connection.
Installation taking too long or failing?
Make sure you see (venv) in your terminal prompt. If you forgot to activate the environment, pip might try to install packages system-wide and fail on permissions.
If you see a network timeout, try running the install command again. Temporary network issues are the most common cause.
Help me troubleshoot my pip install failure
Verify your TensorFlow installation
TensorFlow on macOS uses the CPU backend for computation. You need to confirm it imports correctly and reports the expected version before moving on.
- Verify TensorFlow is installed correctly by running:
python3 -c "import tensorflow as tf; print(tf.__version__)"
✔️ I see 2.21.0
TensorFlow is installed and ready. You may see an informational warning about LiteRT migration. This is safe to ignore because the TFLite API still works in 2.21.0.
ⓧ I see an error
Make sure your virtual environment is activated. You should see (venv) in your terminal prompt.
If you see a ModuleNotFoundError, the install may not have completed successfully. Re-run the pip install command from the previous substep and check for error messages in the output.
Help me fix my TensorFlow import error
Initialize Git and create the project structure
A clean directory structure keeps your model code, saved models, and benchmarks organized. You'll also initialize Git so you can track progress with meaningful commits throughout the project.
- Initialize a Git repository by running:
git init
- Create the project directory structure by running:
mkdir src models benchmarks dashboard
What are these folders for?
src/ holds your Python source code (model definition, optimization strategies, pipeline runner). models/ stores the trained and optimized TFLite model files.
benchmarks/ stores the JSON results from running each optimization strategy. dashboard/ holds the HTML and JavaScript files for your visualization dashboard.
- Create the Python package marker file for src/ by running:
touch src/__init__.py
Why create an empty __init__.py?
The __init__.py file tells Python that src/ is a package. Without it, you won't be able to import modules from this folder using from src.model import ... later.
- Confirm the directories and file exist by running:
ls
You should see benchmarks, dashboard, models, src, and venv listed in the output.
- Commit the initial project structure by running:
git add .
git commit -m "Initial project structure"
Git commit failing?
If you see a message about identity not being set, Git needs your name and email configured. Run these commands with your own details, then retry the commit:
git config user.name "Your Name"
git config user.email "your@email.com"
Help me fix my git commit error
Your environment is ready to go. Next up, you'll build and train a baseline CNN model on Fashion-MNIST that will serve as the starting point for all your optimization experiments.
Train the Baseline CNN Model
Your development environment is ready with TensorFlow and all dependencies installed. Now it's time to build and train the CNN that you'll spend the rest of this project optimizing.
Before you can compress a model for edge hardware, you need a trained baseline to measure against. In this step, you'll build a compact CNN on the Fashion-MNIST dataset, train it, and convert it to TFLite format to establish your float32 reference point.
In this step, get ready to:
- Build a compact CNN with Conv2D and Dense layers for Fashion-MNIST classification.
- Train the model for 5 epochs and record baseline accuracy.
- Convert the trained model to a baseline float32 TFLite file.
Build the data loading and model architecture
Fashion-MNIST contains 60,000 training images and 10,000 test images across 10 clothing categories. Each image is a 28x28 grayscale pixel grid.
- Create a new file called src/model.py in your editor.
- Add the imports and data loading function by pasting this code:
import tensorflow as tf
import numpy as np
def load_data():
# Load Fashion-MNIST dataset from Keras
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
# Normalize pixel values from 0-255 to 0-1 range
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0
# Reshape to add channel dimension for Conv2D input
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)
return (x_train, y_train), (x_test, y_test)
What does this code do?
The load_data() function pulls the Fashion-MNIST dataset directly from Keras. It normalizes pixel values to a 0-1 float32 range (required for neural network training) and reshapes each image to include the channel dimension that Conv2D layers expect.
You won't see output yet. The next chunk builds the model that uses this data.
- Below load_data() in src/model.py, add the model architecture function:
def build_model():
# Define a sequential CNN with two conv blocks and a dense head
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
# Compile with Adam optimizer and sparse crossentropy for integer labels
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
return model
def train_model(model, x_train, y_train, epochs=5):
# Train with a 10% validation split to monitor overfitting
model.fit(x_train, y_train, epochs=epochs, validation_split=0.1, verbose=1)
return model
What does this architecture do?
The CNN follows a classic pattern: two convolutional blocks (Conv2D + MaxPooling) extract spatial features, a Flatten layer reshapes the output into a 1D vector, and two Dense layers produce the final 10-class prediction.
The train_model() function runs training for 5 epochs with a 10% validation split. This split holds back some training data to monitor if the model overfits.
- Save src/model.py.
You'll run this file in the next substep to see training output.
Seeing a red underline on the import?
Make sure your editor is using the Python interpreter from your virtual environment, not the system Python. In VS Code, press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), type Python: Select Interpreter, and choose the one inside your venv folder.
help me fix my Python interpreter in VS Code so TensorFlow is recognized
✔️ Awesome, I've got everything!
Great. Double check you've saved src/model.py before continuing.
ⓧ I'd like to double check the full code
import tensorflow as tf
import numpy as np
def load_data():
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)
return (x_train, y_train), (x_test, y_test)
def build_model():
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
return model
def train_model(model, x_train, y_train, epochs=5):
model.fit(x_train, y_train, epochs=epochs, validation_split=0.1, verbose=1)
return model
Train the model and save the baseline TFLite file
Now you'll run the functions you just wrote to train the CNN and produce your baseline TFLite model. This single command loads the data, trains for 5 epochs, evaluates accuracy on the test set, and converts the result to a float32 TFLite file.
- Train your model and convert it to TFLite by running this command in your terminal:
python3 << 'EOF'
from src.model import load_data, build_model, train_model
import tensorflow as tf
# Load and preprocess Fashion-MNIST
(x_train, y_train), (x_test, y_test) = load_data()
# Build and train the CNN
model = build_model()
model = train_model(model, x_train, y_train, epochs=5)
# Evaluate baseline accuracy
loss, accuracy = model.evaluate(x_test, y_test, verbose=0)
print(f'Baseline accuracy: {accuracy:.4f}')
# Convert to float32 TFLite model
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
# Save to models directory
with open('models/baseline_float32.tflite', 'wb') as f:
f.write(tflite_model)
print(f'Saved baseline model: {len(tflite_model) / 1024:.2f} KB')
EOF
What does this script do?
- load_data() downloads Fashion-MNIST (about 30 MB on first run) and normalizes the images.
- build_model() and train_model() construct and train the CNN for 5 epochs.
- tf.lite.TFLiteConverter.from_keras_model() converts the trained Keras model into a flat TFLite buffer at full float32 precision.
- The resulting .tflite file is your unoptimized baseline that all later strategies will compress.
- Wait for training to finish. You should see 5 epochs of progress output, then a line showing Baseline accuracy: followed by a value around 0.88-0.91.
- Confirm the saved file line shows Saved baseline model: followed by a size around 400-500 KB.
Training seems stuck or you see a warning about LiteRT?
Training 5 epochs on CPU takes about 30-60 seconds on Apple Silicon. If you see a deprecation warning mentioning LiteRT or TFLite migration, this is informational only. The API still works and your model will convert successfully.
If you get an import error for src.model, make sure you are running the command from your project's root directory (the folder containing the src/ folder).
help me troubleshoot my TensorFlow model training
Verify and commit your baseline
- Confirm the TFLite file exists by running:
ls models/
- You should see baseline_float32.tflite in the output.
- Commit your progress by running:
git add src/model.py models/baseline_float32.tflite
git commit -m "Add baseline CNN model and float32 TFLite conversion"
Why commit now?
Each commit captures a known-good state. If a later optimization strategy breaks something, you can always return to this baseline checkpoint. This mirrors professional ML workflows where experiment tracking starts from a reproducible baseline.
Git says the file is too large?
The TFLite file is around 400-500 KB, well within Git's limits. If you see a size warning, check that you haven't accidentally committed the full training data or a SavedModel directory.
help me fix my git commit for the baseline model
Your trained baseline is locked in. Next up, you'll build an object-oriented optimization pipeline using the Strategy pattern to systematically compress this model for edge hardware.
Build the Optimization Pipeline with Strategy Pattern
You have a trained CNN and a baseline TFLite model. Now you need a structured way to apply different optimization techniques to the same model without rewriting conversion logic each time.
In this step, you will build an object-oriented optimization framework using the Strategy pattern. Each optimization technique becomes its own class with a shared interface, making it easy to add, remove, or swap strategies later.
In this step, get ready to:
- Define an abstract base class for optimization strategies.
- Implement dynamic range quantization as a strategy.
- Implement full integer quantization as a strategy.
Create the abstract OptimizationStrategy base class
Every optimization technique in the pipeline shares the same two responsibilities: take a model and produce an optimized TFLite binary, and report its own name. An abstract base class enforces this contract.
- Create a new file called src/strategies.py by running this command:
touch src/strategies.py
- Confirm the file exists by running:
ls src/
You should see __init__.py, model.py, and strategies.py in the output.
- Open src/strategies.py in your editor and add the imports and abstract base class by pasting the following code:
import abc
import numpy as np
import tensorflow as tf
import tensorflow_model_optimization as tfmot
class OptimizationStrategy(abc.ABC):
# Every strategy must implement these two methods
@abc.abstractmethod
def optimize(self, model, x_train, y_train):
pass
@abc.abstractmethod
def get_name(self):
pass
What does this code do?
The abc.ABC module lets you define abstract classes in Python. Any class that inherits from OptimizationStrategy must implement optimize() and get_name(). If they don't, Python raises an error at instantiation.
The optimize() method accepts the Keras model plus training data (needed by some strategies for calibration). The get_name() method returns a human-readable label for benchmarking output.
Why the Strategy pattern?
In hardware compilation pipelines like AMD's Vitis AI, you pick a target architecture and the compiler applies architecture-specific optimizations. The Strategy pattern mirrors this workflow: each optimization technique is self-contained and interchangeable.
This makes your codebase extensible. Adding a new optimization is just one new class file, no changes to existing code. You will add pruning in the next step and clustering in the Secret Mission using this same interface.
- Save src/strategies.py.
You won't see a visible change yet. The next chunk adds a concrete strategy class that uses this interface.
Implement the dynamic range quantization strategy
Dynamic range quantization is the simplest optimization. It quantizes the model's weights from 32-bit floats to 8-bit integers at conversion time, reducing the file size by approximately 4x. At inference, weights are decompressed back to float for computation.
- In src/strategies.py, add the following class below the OptimizationStrategy base class:
class DynamicRangeQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
# Convert the Keras model to TFLite with default quantization
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Dynamic Range Quantization"
What does this code do?
Setting converter.optimizations = [tf.lite.Optimize.DEFAULT] tells the TFLite converter to apply post-training quantization. Without a representative dataset, the converter defaults to dynamic range mode. This quantizes only the weights (not activations), which gives a good size reduction with minimal accuracy loss.
The convert() method returns the TFLite model as raw bytes, which you can write directly to a .tflite file.
- Save src/strategies.py.
You won't see a change yet. The next chunk adds full integer quantization, which builds on this same pattern.
Implement the full integer quantization strategy
Full integer quantization goes further than dynamic range. It quantizes both weights and activations to 8-bit integers, which unlocks hardware accelerators like Edge TPUs. The trade-off is that it requires a representative dataset to calibrate the activation ranges.
- In src/strategies.py, add the following class below DynamicRangeQuantization:
class FullIntegerQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
# Generator that yields sample inputs for calibration
def representative_dataset_gen():
for i in range(100):
yield [x_train[i:i+1]]
# Convert with full integer quantization using calibration data
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset_gen
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Full Integer Quantization"
What does this code do?
The representative_dataset_gen() function is a Python generator that yields 100 sample images from the training set. The converter uses these samples to measure the range of activation values flowing through each layer.
By assigning this generator to converter.representative_dataset, you tell the converter to quantize activations in addition to weights. This produces a fully quantized INT8 model that can run on integer-only hardware accelerators.
- Save src/strategies.py.
- Verify the module imports correctly by running:
python3 -c "from src.strategies import DynamicRangeQuantization, FullIntegerQuantization; print('Strategies loaded successfully')"
You should see Strategies loaded successfully printed in your terminal.
Seeing a ModuleNotFoundError?
Make sure you are running the command from the project root directory (the folder that contains the src/ folder). Also check that your virtual environment is still activated.
If you see an error about tensorflow_model_optimization, confirm the package is installed by running pip list | grep tensorflow-model-optimization.
Help me debug my ModuleNotFoundError when importing strategies.
Extra for Experts: Why not quantization-aware training?
Quantization-aware training (QAT) inserts fake quantization nodes during training, so the model learns to compensate for precision loss. It produces better accuracy than post-training quantization but requires retraining from scratch.
Post-training quantization is faster and simpler. You apply it to an already-trained model with no retraining. For most CNN architectures, the accuracy difference is small (under 1% on Fashion-MNIST).
✔️ Awesome, I've got everything!
Great. Double check you've saved src/strategies.py and that the import test passed.
ⓧ I'd like to double check the full code
Here is the full src/strategies.py file up to this point:
import abc
import numpy as np
import tensorflow as tf
import tensorflow_model_optimization as tfmot
class OptimizationStrategy(abc.ABC):
@abc.abstractmethod
def optimize(self, model, x_train, y_train):
pass
@abc.abstractmethod
def get_name(self):
pass
class DynamicRangeQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Dynamic Range Quantization"
class FullIntegerQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
def representative_dataset_gen():
for i in range(100):
yield [x_train[i:i+1]]
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset_gen
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Full Integer Quantization"
- Commit your progress by running:
git add src/strategies.py
git commit -m "Add optimization strategies: dynamic range and full integer quantization"
Your optimization framework is ready. Next up, you will add pruning as a third strategy and build the pipeline runner that benchmarks all variants against each other.
Add Pruning and Benchmark All Variants
You have two quantization strategies ready to go. Now it is time to add the most aggressive optimization technique in your toolkit: weight pruning.
Pruning removes the least important weights from a neural network, forcing it to rely on fewer connections. Combined with quantization, this produces the smallest possible models. In this step, you will implement the pruning strategy, build a pipeline runner that automates benchmarking, and compare all your optimized models side by side.
In this step, get ready to:
- Implement a pruning + quantization strategy that fine-tunes the model for 2 epochs.
- Build a PipelineRunner class that automates benchmarking across all strategies.
- Run the full pipeline and generate a JSON benchmarks file.
Implement the PruningQuantization strategy
Pruning works by gradually zeroing out small-magnitude weights during a short fine-tuning phase. After pruning, you strip the pruning wrappers and quantize the sparse model. The combination of fewer active weights plus lower-precision arithmetic produces significant compression.
- In src/strategies.py, add the PruningQuantization class below the FullIntegerQuantization class by copying this code:
class PruningQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
pruning_params = {
'pruning_schedule': tfmot.sparsity.keras.PolynomialDecay(
initial_sparsity=0.30,
final_sparsity=0.70,
begin_step=0,
end_step=len(x_train) // 32 * 2
)
}
pruned_model = tfmot.sparsity.keras.prune_low_magnitude(model, **pruning_params)
pruned_model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
callbacks = [tfmot.sparsity.keras.UpdatePruningStep()]
pruned_model.fit(x_train, y_train, epochs=2, batch_size=32, callbacks=callbacks, verbose=1)
stripped_model = tfmot.sparsity.keras.strip_pruning(pruned_model)
converter = tf.lite.TFLiteConverter.from_keras_model(stripped_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Pruning + Quantization"
What does this code do?
This strategy applies three stages of optimization to the model.
- PolynomialDecay gradually increases sparsity from 30% to 70% during fine-tuning, giving the model time to adjust to fewer connections.
- prune_low_magnitude wraps the model with pruning logic. The UpdatePruningStep callback applies the pruning schedule during each training step.
- strip_pruning removes the pruning wrappers, leaving a clean sparse model ready for TFLite conversion with quantization on top.
- Save src/strategies.py.
You will not see a change yet. The pipeline runner in the next substep uses this strategy.
Why pruning instead of quantization-aware training?
Pruning and quantization-aware training (QAT) are both fine-tuning-based optimizations. Pruning physically removes connections, making the model structurally simpler. QAT simulates low-precision arithmetic during training. Pruning + post-training quantization is the approach AMD/Xilinx recommends in their collaborative optimization pipeline because it stacks with clustering for maximum compression.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
Here is the full src/strategies.py at this point:
import abc
import numpy as np
import tensorflow as tf
import tensorflow_model_optimization as tfmot
class OptimizationStrategy(abc.ABC):
@abc.abstractmethod
def optimize(self, model, x_train, y_train):
pass
@abc.abstractmethod
def get_name(self):
pass
class DynamicRangeQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Dynamic Range Quantization"
class FullIntegerQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
def representative_dataset_gen():
for i in range(100):
yield [x_train[i:i+1]]
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset_gen
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Full Integer Quantization"
class PruningQuantization(OptimizationStrategy):
def optimize(self, model, x_train, y_train):
pruning_params = {
'pruning_schedule': tfmot.sparsity.keras.PolynomialDecay(
initial_sparsity=0.30,
final_sparsity=0.70,
begin_step=0,
end_step=len(x_train) // 32 * 2
)
}
pruned_model = tfmot.sparsity.keras.prune_low_magnitude(model, **pruning_params)
pruned_model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
callbacks = [tfmot.sparsity.keras.UpdatePruningStep()]
pruned_model.fit(x_train, y_train, epochs=2, batch_size=32, callbacks=callbacks, verbose=1)
stripped_model = tfmot.sparsity.keras.strip_pruning(pruned_model)
converter = tf.lite.TFLiteConverter.from_keras_model(stripped_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
return tflite_model
def get_name(self):
return "Pruning + Quantization"
Create the pipeline runner
The PipelineRunner class orchestrates the entire benchmarking workflow. It takes a list of strategies, applies each to the trained model, measures file size, accuracy, and inference latency, then writes everything to a JSON file.
- Create a new file called src/pipeline.py and add the imports, class definition, and evaluation method:
import os
import json
import time
import numpy as np
import tensorflow as tf
from src.strategies import OptimizationStrategy
class PipelineRunner:
def __init__(self, strategies, output_dir='models', benchmark_dir='benchmarks'):
self.strategies = strategies
self.output_dir = output_dir
self.benchmark_dir = benchmark_dir
os.makedirs(output_dir, exist_ok=True)
os.makedirs(benchmark_dir, exist_ok=True)
def evaluate_tflite(self, tflite_model, x_test, y_test, num_samples=100):
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
correct = 0
latencies = []
for i in range(num_samples):
input_data = x_test[i:i+1]
interpreter.set_tensor(input_details[0]['index'], input_data)
start_time = time.perf_counter()
interpreter.invoke()
end_time = time.perf_counter()
latencies.append((end_time - start_time) * 1000)
output_data = interpreter.get_tensor(output_details[0]['index'])
predicted = np.argmax(output_data[0])
if predicted == y_test[i]:
correct += 1
accuracy = correct / num_samples
mean_latency_ms = np.mean(latencies)
return accuracy, mean_latency_ms
What does this code do?
- __init__ accepts a list of strategies and creates the output directories if they do not exist.
- evaluate_tflite loads a TFLite model into the interpreter, runs 100 test samples, and measures both prediction accuracy and per-sample inference latency in milliseconds.
- time.perf_counter() provides high-resolution timing. The latency is converted to milliseconds by multiplying by 1000.
You will not see a change yet. The run method below uses evaluate_tflite to benchmark each model variant.
- Still in src/pipeline.py, add the run method inside the PipelineRunner class, below evaluate_tflite:
def run(self, model, x_train, y_train, x_test, y_test):
results = []
float_converter = tf.lite.TFLiteConverter.from_keras_model(model)
float_tflite = float_converter.convert()
float_path = os.path.join(self.output_dir, 'baseline_float32.tflite')
with open(float_path, 'wb') as f:
f.write(float_tflite)
float_accuracy, float_latency = self.evaluate_tflite(float_tflite, x_test, y_test)
float_size_kb = len(float_tflite) / 1024
results.append({
'name': 'Baseline Float32',
'size_kb': round(float_size_kb, 2),
'accuracy': round(float_accuracy, 4),
'latency_ms': round(float_latency, 3)
})
for strategy in self.strategies:
print(f"\nApplying: {strategy.get_name()}")
tflite_model = strategy.optimize(model, x_train, y_train)
filename = strategy.get_name().lower().replace(' ', '_').replace('+', 'and') + '.tflite'
filepath = os.path.join(self.output_dir, filename)
with open(filepath, 'wb') as f:
f.write(tflite_model)
accuracy, latency = self.evaluate_tflite(tflite_model, x_test, y_test)
size_kb = len(tflite_model) / 1024
results.append({
'name': strategy.get_name(),
'size_kb': round(size_kb, 2),
'accuracy': round(accuracy, 4),
'latency_ms': round(latency, 3)
})
print(f" Size: {size_kb:.2f} KB | Accuracy: {accuracy:.4f} | Latency: {latency:.3f} ms")
results_path = os.path.join(self.benchmark_dir, 'results.json')
with open(results_path, 'w') as f:
json.dump(results, f, indent=2)
print(f"\nResults saved to {results_path}")
return results
What does the run method do?
- First, it converts and benchmarks the baseline float32 model as the reference point for comparison.
- Then it loops over every strategy, calls optimize(), saves the resulting .tflite file, and evaluates it.
- Each model's size (KB), accuracy, and latency (ms) are collected into a results list and written to benchmarks/results.json.
- Save src/pipeline.py.
You will not see output yet. The next substep creates main.py to wire everything together and run the pipeline.
Seeing a red underline on the import?
If your editor shows an error on from src.strategies import OptimizationStrategy, that is expected until you run the script from the project root. The import resolves at runtime.
Help me fix an import error in my pipeline file.
✔️ Awesome, I've got everything!
Great. Make sure you have saved the file before continuing.
ⓧ I'd like to double check the full code
Here is the full src/pipeline.py file:
import os
import json
import time
import numpy as np
import tensorflow as tf
from src.strategies import OptimizationStrategy
class PipelineRunner:
def __init__(self, strategies, output_dir='models', benchmark_dir='benchmarks'):
self.strategies = strategies
self.output_dir = output_dir
self.benchmark_dir = benchmark_dir
os.makedirs(output_dir, exist_ok=True)
os.makedirs(benchmark_dir, exist_ok=True)
def evaluate_tflite(self, tflite_model, x_test, y_test, num_samples=100):
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
correct = 0
latencies = []
for i in range(num_samples):
input_data = x_test[i:i+1]
interpreter.set_tensor(input_details[0]['index'], input_data)
start_time = time.perf_counter()
interpreter.invoke()
end_time = time.perf_counter()
latencies.append((end_time - start_time) * 1000)
output_data = interpreter.get_tensor(output_details[0]['index'])
predicted = np.argmax(output_data[0])
if predicted == y_test[i]:
correct += 1
accuracy = correct / num_samples
mean_latency_ms = np.mean(latencies)
return accuracy, mean_latency_ms
def run(self, model, x_train, y_train, x_test, y_test):
results = []
float_converter = tf.lite.TFLiteConverter.from_keras_model(model)
float_tflite = float_converter.convert()
float_path = os.path.join(self.output_dir, 'baseline_float32.tflite')
with open(float_path, 'wb') as f:
f.write(float_tflite)
float_accuracy, float_latency = self.evaluate_tflite(float_tflite, x_test, y_test)
float_size_kb = len(float_tflite) / 1024
results.append({
'name': 'Baseline Float32',
'size_kb': round(float_size_kb, 2),
'accuracy': round(float_accuracy, 4),
'latency_ms': round(float_latency, 3)
})
for strategy in self.strategies:
print(f"\nApplying: {strategy.get_name()}")
tflite_model = strategy.optimize(model, x_train, y_train)
filename = strategy.get_name().lower().replace(' ', '_').replace('+', 'and') + '.tflite'
filepath = os.path.join(self.output_dir, filename)
with open(filepath, 'wb') as f:
f.write(tflite_model)
accuracy, latency = self.evaluate_tflite(tflite_model, x_test, y_test)
size_kb = len(tflite_model) / 1024
results.append({
'name': strategy.get_name(),
'size_kb': round(size_kb, 2),
'accuracy': round(accuracy, 4),
'latency_ms': round(latency, 3)
})
print(f" Size: {size_kb:.2f} KB | Accuracy: {accuracy:.4f} | Latency: {latency:.3f} ms")
results_path = os.path.join(self.benchmark_dir, 'results.json')
with open(results_path, 'w') as f:
json.dump(results, f, indent=2)
print(f"\nResults saved to {results_path}")
return results
Create main.py and run the pipeline
The main.py file ties together data loading, model training, and the optimization pipeline into a single entry point.
- Create a new file called main.py in the project root (the same level as the src/ folder) and paste this code:
from src.model import load_data, build_model, train_model
from src.strategies import DynamicRangeQuantization, FullIntegerQuantization, PruningQuantization
from src.pipeline import PipelineRunner
def main():
print("Loading Fashion-MNIST data...")
(x_train, y_train), (x_test, y_test) = load_data()
print("Building and training baseline model...")
model = build_model()
model = train_model(model, x_train, y_train, epochs=5)
baseline_loss, baseline_accuracy = model.evaluate(x_test, y_test, verbose=0)
print(f"Baseline accuracy: {baseline_accuracy:.4f}")
strategies = [
DynamicRangeQuantization(),
FullIntegerQuantization(),
PruningQuantization(),
]
runner = PipelineRunner(strategies)
results = runner.run(model, x_train, y_train, x_test, y_test)
print("\n--- Summary ---")
for r in results:
print(f"{r['name']:30s} | Size: {r['size_kb']:8.2f} KB | Acc: {r['accuracy']:.4f} | Latency: {r['latency_ms']:.3f} ms")
if __name__ == '__main__':
main()
What does this code do?
- Loads Fashion-MNIST, builds the CNN, and trains for 5 epochs to establish the baseline.
- Instantiates all three optimization strategies and passes them to the PipelineRunner.
- Runs the pipeline, which converts, evaluates, and saves each model variant, then writes the results to benchmarks/results.json.
- Prints a final summary table comparing all variants.
- Save main.py.
- Run the full pipeline by running this command in your terminal:
python main.py
What should I see?
The script trains for 5 epochs (about 1-2 minutes on CPU), then applies each optimization strategy. You will see training progress bars, followed by output like:
Applying: Dynamic Range Quantization
Size: 107.55 KB | Accuracy: 0.8800 | Latency: 0.142 ms
Applying: Full Integer Quantization
Size: 107.30 KB | Accuracy: 0.8600 | Latency: 0.098 ms
Applying: Pruning + Quantization
Size: 107.12 KB | Accuracy: 0.8700 | Latency: 0.105 ms
--- Summary ---
Baseline Float32 | Size: 427.14 KB | Acc: 0.8900 | Latency: 0.201 ms
Your exact numbers will vary, but you should see all optimized models are significantly smaller than the baseline float32 model (roughly 4x reduction).
- Confirm that benchmarks/results.json was created by running:
cat benchmarks/results.json
You should see a JSON array with 4 entries: Baseline Float32, Dynamic Range Quantization, Full Integer Quantization, and Pruning + Quantization.
Seeing an error when running main.py?
- Make sure you are running the command from the project root directory (the folder containing main.py and the src/ folder).
- If you see an import error for PruningQuantization, confirm you saved src/strategies.py after adding the new class.
- If you see an indentation error in src/pipeline.py, check that the run method is indented one level inside the PipelineRunner class (4 spaces).
Help me debug an error when running my optimization pipeline.
- Confirm the optimized model files exist by running:
ls models/
You should see baseline_float32.tflite, dynamic_range_quantization.tflite, full_integer_quantization.tflite, and pruning_and_quantization.tflite.
✔️ Awesome, I've got everything!
Great. Double check that all files are saved before committing.
ⓧ I'd like to double check the full code
Here is the full main.py:
from src.model import load_data, build_model, train_model
from src.strategies import DynamicRangeQuantization, FullIntegerQuantization, PruningQuantization
from src.pipeline import PipelineRunner
def main():
print("Loading Fashion-MNIST data...")
(x_train, y_train), (x_test, y_test) = load_data()
print("Building and training baseline model...")
model = build_model()
model = train_model(model, x_train, y_train, epochs=5)
baseline_loss, baseline_accuracy = model.evaluate(x_test, y_test, verbose=0)
print(f"Baseline accuracy: {baseline_accuracy:.4f}")
strategies = [
DynamicRangeQuantization(),
FullIntegerQuantization(),
PruningQuantization(),
]
runner = PipelineRunner(strategies)
results = runner.run(model, x_train, y_train, x_test, y_test)
print("\n--- Summary ---")
for r in results:
print(f"{r['name']:30s} | Size: {r['size_kb']:8.2f} KB | Acc: {r['accuracy']:.4f} | Latency: {r['latency_ms']:.3f} ms")
if __name__ == '__main__':
main()
- Commit your progress by running these commands:
git add .
git commit -m "Add pruning strategy, pipeline runner, and benchmarks"
Your pipeline is producing real benchmark data. Next up, you will build a JavaScript dashboard that turns these numbers into interactive charts so you can visualize the accuracy-compression trade-offs at a glance.
Build the JavaScript Benchmarking Dashboard
Your pipeline has done the hard work. You have four TFLite model variants sitting in benchmarks/results.json with their size, accuracy, and latency metrics. Now you need a way to actually see those trade-offs at a glance.
In this step, you will build an HTML dashboard that reads the JSON benchmarks file and renders three grouped bar charts using vanilla JavaScript and the HTML5 Canvas API. No external charting library needed.
In this step, get ready to:
- Create an HTML page with three canvas chart containers and a dark-themed layout.
- Write a JavaScript bar chart renderer using the Canvas 2D context.
- Fetch benchmark results from JSON and display interactive comparisons.
Create the HTML dashboard layout
The dashboard page defines three chart containers (one per metric), a legend section, and a status message. All styling is embedded so the dashboard has zero external dependencies.
- Create a new file called index.html inside your dashboard/ folder, then paste the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Model Optimization Benchmarks</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: #1a1a2e; color: #eee; padding: 40px; }
h1 { text-align: center; margin-bottom: 10px; font-size: 1.8rem; }
.subtitle { text-align: center; color: #888; margin-bottom: 40px; }
.charts { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 30px; max-width: 1200px; margin: 0 auto; }
.chart-container { background: #16213e; border-radius: 12px; padding: 20px; }
.chart-container h2 { text-align: center; margin-bottom: 15px; font-size: 1rem; color: #aaa; }
canvas { width: 100%; height: 250px; }
.legend { display: flex; flex-wrap: wrap; justify-content: center; gap: 15px; margin-top: 30px; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.85rem; }
.legend-color { width: 14px; height: 14px; border-radius: 3px; }
#load-info { text-align: center; margin-top: 20px; color: #666; font-size: 0.8rem; }
</style>
</head>
<body>
<h1>Model Optimization Pipeline</h1>
<p class="subtitle">Accuracy / Size / Latency Trade-offs</p>
<div class="charts">
<div class="chart-container">
<h2>Model Size (KB)</h2>
<canvas id="sizeChart"></canvas>
</div>
<div class="chart-container">
<h2>Accuracy</h2>
<canvas id="accuracyChart"></canvas>
</div>
<div class="chart-container">
<h2>Inference Latency (ms)</h2>
<canvas id="latencyChart"></canvas>
</div>
</div>
<div class="legend" id="legend"></div>
<p id="load-info"></p>
<script src="dashboard.js"></script>
</body>
</html>
What does this HTML do?
The page defines a three-column CSS Grid layout, one column per metric (size, accuracy, latency). Each column contains a <canvas> element that JavaScript will draw bars onto.
The #legend div will be populated dynamically with color swatches matching each model variant. The #load-info paragraph shows whether the JSON data loaded successfully.
- Save dashboard/index.html.
File not saving to the right place?
Make sure the file is inside the dashboard/ directory, not in the project root. The script tag references dashboard.js as a sibling file, so both must live in the same folder.
Help me verify my dashboard directory structure.
Build the canvas chart rendering functions
The charting logic lives in a separate JavaScript file. It uses the Canvas 2D API to draw colored bars whose heights are proportional to each metric value.
- Create a new file called dashboard.js inside the dashboard/ folder, then paste the color palette and bar chart function:
const COLORS = ['#4ecdc4', '#ff6b6b', '#ffd93d', '#6c5ce7', '#a8e6cf'];
function drawBarChart(canvasId, values, labels, color, unit) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * 2;
canvas.height = 500;
const padding = 60;
const chartWidth = canvas.width - padding * 2;
const chartHeight = canvas.height - padding * 2;
const maxVal = Math.max(...values) * 1.2;
const barWidth = chartWidth / (values.length * 2);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw axes
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, padding + chartHeight);
ctx.lineTo(padding + chartWidth, padding + chartHeight);
ctx.stroke();
// Draw bars with value labels
for (let i = 0; i < values.length; i++) {
const barHeight = (values[i] / maxVal) * chartHeight;
const x = padding + (i * 2 + 0.5) * barWidth;
const y = padding + chartHeight - barHeight;
ctx.fillStyle = COLORS[i];
ctx.fillRect(x, y, barWidth, barHeight);
ctx.fillStyle = '#eee';
ctx.font = '20px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(values[i].toFixed(unit === 'acc' ? 4 : 1), x + barWidth / 2, y - 10);
}
}
What does this code do?
The COLORS array holds five distinct hex colors. Each model variant gets its own color across all three charts for easy visual comparison.
The drawBarChart function takes a canvas element ID, the metric values, and a unit hint. It scales the canvas to 2x for crisp rendering, draws L-shaped axes, then renders one colored bar per model. The unit parameter controls decimal precision (4 for accuracy, 1 for size/latency).
- Next, add the legend rendering function below the drawBarChart function in the same file:
function renderLegend(labels) {
const legend = document.getElementById('legend');
legend.innerHTML = '';
labels.forEach(function(label, i) {
const item = document.createElement('div');
item.className = 'legend-item';
const colorBox = document.createElement('div');
colorBox.className = 'legend-color';
colorBox.style.background = COLORS[i];
const text = document.createElement('span');
text.textContent = label;
item.appendChild(colorBox);
item.appendChild(text);
legend.appendChild(item);
});
}
What does renderLegend do?
This function builds a row of colored swatches beneath the charts. Each swatch matches the bar color for that model variant, so you can instantly identify which bar belongs to which optimization strategy.
- Save dashboard/dashboard.js. You will not see any output yet because the data loading function is still missing.
Add data loading and serve the dashboard
The final piece connects the chart functions to the actual benchmark data. The loader fetches the JSON file your pipeline generated, extracts each metric array, and calls the renderers.
- In dashboard/dashboard.js, add the data loading function below renderLegend:
function loadAndRender() {
fetch('../benchmarks/results.json')
.then(function(response) { return response.json(); })
.then(function(data) {
var labels = data.map(function(d) { return d.name; });
var sizes = data.map(function(d) { return d.size_kb; });
var accuracies = data.map(function(d) { return d.accuracy; });
var latencies = data.map(function(d) { return d.latency_ms; });
drawBarChart('sizeChart', sizes, labels, COLORS, 'kb');
drawBarChart('accuracyChart', accuracies, labels, COLORS, 'acc');
drawBarChart('latencyChart', latencies, labels, COLORS, 'ms');
renderLegend(labels);
document.getElementById('load-info').textContent = 'Data loaded from benchmarks/results.json';
})
.catch(function(err) {
document.getElementById('load-info').textContent = 'Could not load results.json. Run the pipeline first, then serve this directory.';
console.error(err);
});
}
loadAndRender();
What does this code do?
The loadAndRender function uses fetch to grab ../benchmarks/results.json (one directory up from dashboard/). It extracts parallel arrays for names, sizes, accuracies, and latencies, then passes each to drawBarChart.
The .catch handler shows a helpful message if the JSON file is missing. The final loadAndRender(); call invokes the function immediately when the script loads.
- Save dashboard/dashboard.js.
✔️ Awesome, I've got everything!
Great. Double check you have saved both files in the dashboard folder.
ⓧ I'd like to double check the full code
const COLORS = ['#4ecdc4', '#ff6b6b', '#ffd93d', '#6c5ce7', '#a8e6cf'];
function drawBarChart(canvasId, values, labels, color, unit) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * 2;
canvas.height = 500;
const padding = 60;
const chartWidth = canvas.width - padding * 2;
const chartHeight = canvas.height - padding * 2;
const maxVal = Math.max(...values) * 1.2;
const barWidth = chartWidth / (values.length * 2);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, padding + chartHeight);
ctx.lineTo(padding + chartWidth, padding + chartHeight);
ctx.stroke();
for (let i = 0; i < values.length; i++) {
const barHeight = (values[i] / maxVal) * chartHeight;
const x = padding + (i * 2 + 0.5) * barWidth;
const y = padding + chartHeight - barHeight;
ctx.fillStyle = COLORS[i];
ctx.fillRect(x, y, barWidth, barHeight);
ctx.fillStyle = '#eee';
ctx.font = '20px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(values[i].toFixed(unit === 'acc' ? 4 : 1), x + barWidth / 2, y - 10);
}
}
function renderLegend(labels) {
const legend = document.getElementById('legend');
legend.innerHTML = '';
labels.forEach(function(label, i) {
const item = document.createElement('div');
item.className = 'legend-item';
const colorBox = document.createElement('div');
colorBox.className = 'legend-color';
colorBox.style.background = COLORS[i];
const text = document.createElement('span');
text.textContent = label;
item.appendChild(colorBox);
item.appendChild(text);
legend.appendChild(item);
});
}
function loadAndRender() {
fetch('../benchmarks/results.json')
.then(function(response) { return response.json(); })
.then(function(data) {
var labels = data.map(function(d) { return d.name; });
var sizes = data.map(function(d) { return d.size_kb; });
var accuracies = data.map(function(d) { return d.accuracy; });
var latencies = data.map(function(d) { return d.latency_ms; });
drawBarChart('sizeChart', sizes, labels, COLORS, 'kb');
drawBarChart('accuracyChart', accuracies, labels, COLORS, 'acc');
drawBarChart('latencyChart', latencies, labels, COLORS, 'ms');
renderLegend(labels);
document.getElementById('load-info').textContent = 'Data loaded from benchmarks/results.json';
})
.catch(function(err) {
document.getElementById('load-info').textContent = 'Could not load results.json. Run the pipeline first, then serve this directory.';
console.error(err);
});
}
loadAndRender();
The dashboard uses fetch, which requires the page to be served over HTTP (opening the file directly in a browser will not work). You can use Python's built-in HTTP server since you already have Python installed.
- Start a local server from your project root by running this command:
python3 -m http.server 8000
- Open your browser and navigate to http://localhost:8000/dashboard/index.html.
You should see a dark-themed page with three bar charts side by side. Each chart shows colored bars for your four model variants (Baseline Float32, Dynamic Range Quantization, Full Integer Quantization, Pruning + Quantization). A color legend appears below the charts, and the status message at the bottom reads "Data loaded from benchmarks/results.json".
Charts not showing up?
If you see the "Could not load results.json" message, make sure you started the server from the project root directory (not from inside dashboard/). The fetch path ../benchmarks/results.json needs the server root to be one level above the dashboard folder.
If the page is completely blank, open DevTools (Cmd+Option+I on macOS, Ctrl+Shift+I on Windows) and check the Console tab for JavaScript errors.
Help me debug why my dashboard charts are not rendering.
- Stop the server by pressing Ctrl+C in your terminal.
- Commit the dashboard files to Git by running these commands:
git add dashboard/
git commit -m "Add JavaScript benchmarking dashboard"
Your optimization pipeline now has a visual frontend. You can instantly see which strategy gives the best compression-to-accuracy trade-off for your target hardware. Next up, you will extend this pipeline with a bonus optimization strategy in the Secret Mission.
Secret mission
Add Weight Clustering
Your pipeline already compresses models with quantization and pruning. But there's a fourth technique used in AMD/Xilinx collaborative optimization pipelines: weight clustering. Can you add it as a new strategy and compare its compression ratio against your existing approaches?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Local Python virtual environment (venv directory inside your project folder).
- Trained model files and optimized TFLite variants stored in the models/ directory.
- Benchmark results and dashboard files stored in benchmarks/ and dashboard/.
- A local Git repository with your commit history.
Keep everything running
No action needed. Choose this if you want to keep this project as a portfolio piece or continue experimenting with additional optimization strategies.
- Your project files, models, and Git history remain on disk ready for future use.
- The virtual environment stays intact so you can re-run the pipeline anytime with python main.py.
Pause - I'll come back to this later
Free up disk space by removing the virtual environment while keeping your source code and results intact. You can recreate the environment later by reinstalling the dependencies.
- Open your terminal and navigate into your project folder on the Desktop.
- Delete the virtual environment by running:
rm -rf venv
On Windows, run this instead:
rmdir /s /q venv
- Your source code, model files, benchmarks, and Git history all remain untouched.
- To resume later, recreate the environment and reinstall dependencies from your project folder.
Delete - I don't want to use this again
Remove all project files, models, benchmarks, and the virtual environment from your machine.
- Open your terminal and navigate to your Desktop by running:
cd ~/Desktop
- Delete the entire project directory by running:
rm -rf tf-optimization-pipeline
On Windows, run these commands instead:
cd ~\Desktop
rmdir /s /q tf-optimization-pipeline
- Confirm the folder is gone by running:
ls
- You should no longer see your project folder in the output.
Nice Work!
Nice Work!
You just built a complete model optimization pipeline that compresses a trained CNN for edge deployment. This is the exact workflow hardware engineers use to ship ML models to constrained devices.
You've learned how to:
- Built an object-oriented optimization pipeline using the Strategy pattern, applying three quantization techniques (dynamic range, full integer, and pruning + quantization) to a trained CNN.
- Measured and compared model size reduction (up to 4x), accuracy preservation, and inference latency across all optimized TFLite model variants.
- Created an interactive JavaScript benchmarking dashboard that visualizes the accuracy, size, and latency trade-offs using HTML5 Canvas bar charts.
- Secret Mission: Added weight clustering as a fourth optimization strategy and compared its compression ratio against pruning.
Ready to quiz yourself?