Exporting YOLOX Models from PyTorch to TensorFlow.js
- Introduction
- Getting Started with the Code
- Setting Up Your Python Environment
- Importing the Required Dependencies
- Setting Up the Project
- Loading the Checkpoint Data
- Converting the Model to TensorFlow
- Exporting the Model to TensorFlow.js
- Conclusion
Introduction
Welcome back to this series on training YOLOX models for real-time applications! Previously, we demonstrated how to fine-tune a YOLOX model in PyTorch by creating a hand gesture detector. This tutorial builds on that by showing how to export the model to TensorFlow.js.
TensorFlow.js is an open-source hardware-accelerated JavaScript library for training and deploying machine learning models in web browsers. Converting our YOLOX model to TensorFlow.js allows us to run and integrate it directly into web applications without server-side processing.
Check out a live demo using the YOLOX model in a Unity WebGL application at the link below:
We’ll first use a tool called nobuco to translate the PyTorch model to a TensorFlow Keras model. We can then use the official TensorFlow.js conversion tool to export the Keras model to a TensorFlow.js Graph model.
By the end of this tutorial, you will have a TensorFlow.js version of our YOLOX model that you can deploy to web applications and have it run locally in web browsers.
Getting Started with the Code
As with the previous tutorial, the code is available as a Jupyter Notebook.
Jupyter Notebook | Google Colab |
---|---|
GitHub Repository | Open In Colab |
Setting Up Your Python Environment
We’ll need to add a couple of new packages to our Python environment.
Run the following command to install these additional libraries:
# Install additional packages
pip install nobuco tensorflowjs
You will need to downgrade the following TensorFlow-related packages for nobuco:
pip install "keras<3.0.0" "tensorflow<2.16" "tensorflow-decision-forests<1.10.0"
Importing the Required Dependencies
With our environment updated, we can dive into the code. First, we will import the necessary Python dependencies into our Jupyter Notebook.
# Import Python Standard Library dependencies
import json
from pathlib import Path
# Import YOLOX package
from cjm_yolox_pytorch.model import build_model
from cjm_yolox_pytorch.inference import YOLOXInferenceWrapper
# Import the pandas package
import pandas as pd
# Import PyTorch dependencies
import torch
# Import Nobuco dependencies
from nobuco import pytorch_to_keras, ChannelOrder
# Import TensorFlow.js dependencies
from tensorflowjs import converters, quantization
Setting Up the Project
In this section, we’ll set the folder locations for our project and training session with the PyTorch checkpoint.
Set the Directory Paths
# The name for the project
= f"pytorch-yolox-object-detector"
project_name
# The path for the project folder
= Path(f"./{project_name}/")
project_dir
# Create the project directory if it does not already exist
=True, exist_ok=True)
project_dir.mkdir(parents
# The path to the checkpoint folder
= Path(project_dir/f"2023-08-17_16-14-43")
checkpoint_dir
pd.Series({"Project Directory:": project_dir,
"Checkpoint Directory:": checkpoint_dir,
='columns') }).to_frame().style.hide(axis
Project Directory: | pytorch-yolox-object-detector |
---|---|
Checkpoint Directory: | pytorch-yolox-object-detector/2023-08-17_16-14-43 |
Loading the Checkpoint Data
Now, we can load the colormap and normalization stats used during training and initialize a YOLOX model with the saved checkpoint.
Load the Colormap
# The colormap path
= list(checkpoint_dir.glob('*colormap.json'))[0]
colormap_path
# Load the JSON colormap data
with open(colormap_path, 'r') as file:
= json.load(file)
colormap_json
# Convert the JSON data to a dictionary
= {item['label']: item['color'] for item in colormap_json['items']}
colormap_dict
# Extract the class names from the colormap
= list(colormap_dict.keys())
class_names
# Make a copy of the colormap in integer format
= [tuple(int(c*255) for c in color) for color in colormap_dict.values()] int_colors
Load the Normalization Statistics
# The normalization stats path
= checkpoint_dir/'norm_stats.json'
norm_stats_path
# Read the normalization stats from the JSON file
with open(norm_stats_path, "r") as f:
= json.load(f)
norm_stats_dict
# Convert the dictionary to a tuple
= (norm_stats_dict["mean"], norm_stats_dict["std_dev"])
norm_stats
# Print the mean and standard deviation
pd.DataFrame(norm_stats)
0 | 1 | 2 | |
---|---|---|---|
0 | 0.5 | 0.5 | 0.5 |
1 | 1.0 | 1.0 | 1.0 |
Load the Model Checkpoint
# The model checkpoint path
= list(checkpoint_dir.glob('*.pth'))[0]
checkpoint_path
# Load the model checkpoint onto the CPU
= torch.load(checkpoint_path, map_location='cpu') model_checkpoint
Load the Trained YOLOX Model
# Select the YOLOX model configuration
= checkpoint_path.stem
model_type
# Create a YOLOX model with the number of output classes equal to the number of class names
= build_model(model_type, len(class_names))
model
# Initialize the model with the checkpoint parameters and buffers
model.load_state_dict(model_checkpoint)
<All keys matched successfully>
Converting the Model to TensorFlow
Before exporting the model, we’ll wrap it with the preprocessing steps as we did previously. These steps will be included in the TensorFlow.js model, reducing the code we need to write when deploying the model.
Prepare the Model for Inference
The YOLOXInferenceWrapper
class has some optional settings we did not explore in the previous tutorial. The scale_inp
parameter will scale pixel data from the range [0,255]
to [0,1]
, and channels_last
sets the model to expect input tensors in channels-last format.
Image data in JavaScript tends to be in the range [0,255]
, so we’ll want to enable the scale_inp
setting. The nobuco conversion tool automatically sets the model to the channels-last format for TensorFlow.
Additionally, we can turn off the post-processing steps to compute the predicted bounding box information and probability scores. We’ll need to do this when converting the model to TensorFlow using the nobuco tool as it throws an error with them enabled.
# Convert the normalization stats to tensors
= torch.tensor(norm_stats[0]).view(1, 3, 1, 1)
mean_tensor = torch.tensor(norm_stats[1]).view(1, 3, 1, 1)
std_tensor
# Set the model to evaluation mode
eval();
model.
# Wrap the model with preprocessing and post-processing steps
= YOLOXInferenceWrapper(model,
wrapped_model
mean_tensor,
std_tensor, =True, # Scale input values from the rang [0,255] to [0,1]
scale_inp=False, # Have the model expect input in channels-last format
channels_last=False # Enable or disable post-processing steps
run_box_and_prob_calculation )
Prepare the Input Tensor
We need a sample input tensor for the conversion process.
= torch.randn(1, 3, 224, 224) input_tensor
The exported TensorFlow.js model will lock to this input resolution, so pick dimensions suitable for your intended use case.
Convert the PyTorch Model to Keras
We use the pytorch_to_keras
function included with nobuco to convert the YOLOX model from PyTorch to a Keras model. While we can stick with the default channel order for the model input, we need to maintain the output channel order from the original PyTorch model.
= pytorch_to_keras(
keras_model
wrapped_model, =[input_tensor],
args=ChannelOrder.PYTORCH,
outputs_channel_order )
Save the Keras Model in SavedModel format
Next, we save the Keras model in TensorFlow’s SavedModel format, the recommended format for exporting to TensorFlow.js.
# Set the folder path for the SavedModel files
= Path(f"{checkpoint_dir}/{colormap_path.stem.removesuffix('-colormap')}-{model_type}-tf")
savedmodel_dir # Save the TensorFlow model to disk
="tf") keras_model.save(savedmodel_dir, save_format
Exporting the Model to TensorFlow.js
With our TensorFlow model saved to disk, we can use the TensorFlow.js conversion tool to export it to a TensorFlow.js Graph model.
Since the model will run locally in the browser, it must first download to the user’s device. The larger the model, the longer users must wait for it to download.
Fortunately, the TensorFlow.js conversion tool lets us quantize the model weights (i.e., convert them from 32-bit floating-point precision to 8-bit integers), significantly reducing their file size.
# Set the path for TensorFlow.js model files
= f"{savedmodel_dir}js-uint8"
tfjs_model_dir
# Convert the TensorFlow SavedModel to a TensorFlow.js Graph model
=str(savedmodel_dir),
converters.convert_tf_saved_model(saved_model_dir=tfjs_model_dir,
output_dir={quantization.QUANTIZATION_DTYPE_UINT8:True}
quantization_dtype_map )
- Don’t forget to download the archive file containing the TensorFlow.js model files from the Colab Environment’s file browser. (tutorial link)
Conclusion
Congratulations on reaching the end of this tutorial! We previously trained a YOLOX model in PyTorch for hand gesture detection, and now we’ve exported that model to TensorFlow.js. With it, we can deploy our model to the web and run it locally in users’ browsers.
- Feel free to post questions or problems related to this tutorial in the comments below. I try to make time to address them on Thursdays and Fridays.
I’m Christian Mills, a deep learning consultant specializing in practical AI implementations. I help clients leverage cutting-edge AI technologies to solve real-world problems.
Interested in working together? Fill out my Quick AI Project Assessment form or learn more about me.