tqdm
1is a Python library for adding progress bar. It lets you configure and display a progress bar with metrics you want to track. Its ease of use and versatility makes it the perfect choice for tracking machine learning experiments.
I organize this tutorial in two parts. I will first introduce tqdm, then show an example for machine learning. For each code fragment in this article, we will import the sleep
function from Python's time
library as it will let us slow down the program to see the progress bar update.
from time import sleep
Tqdm
You can install tqdm with pip install tqdm
. The library comes with various iterators each dedicated to a specific use that I am going to present.
tqdm
is the default iterator. It takes an iterator object as argument and displays a progress bar as it iterates over it.
from tqdm import tqdm
lst = [1, 3, 5, 7, 11]
for element in tqdm(lst):
sleep(0.1)
Output is
100%|█████████████████████████████████| 5/5 [00:00<00:00, 9.90it/s]
You can see the nice output with 9.90it/s
meaning an average speed of 9.90 iterations per second. "it" for iterations can be configured to something else and this is what we will see on the next example.
trange
follows the same template as range
in Python. For example, give to trange
the number of iterations.
from tqdm import trange
iterations = 20
for i in trange(iterations, desc="Proving P=NP", unit="carrots"):
sleep(0.1)
Proving P=NP: 100%|████████████| 20/20 [00:02<00:00, 9.91carrots/s]
You can see in this example we added a (joke) description of what we are doing and the unit for each iteration.
Update the progress bar during execution
tqdm
has two methods that can update what is displayed in the progress bar.
.set_description
, is used to update the text prepended in front of the progress bar (the prefix)..set_postfix
is used to update the text appended after the progress bar (the postfix).
To use these methods, we need to assign the tqdm
iterator instance to a variable. This can be done either with the =
operator or the with
keyword in Python.
We can for example update the postfix with the list of divisors of the number i
. Let's use this function to get the list of divisors
def get_divisors(n):
divisors = []
for m in range(1, n):
if n % m == 0:
divisors.append(m)
return divisors
And here is our code with progress bar.
iterations = 10
pbar = trange(iterations, desc="Proving P=NP", unit="carrots")
for i in pbar:
sleep(0.5)
if i % 2:
pbar.set_description(f"Testing odd number {i}")
else:
pbar.set_description(f"Testing even number {i}")
pbar.set_postfix(divisors=get_divisors(i))
If you feel like a distinguished Python programmer, you may use with
keyword like so
iterations = 10
with trange(iterations,
desc="Proving P=NP", unit="carrots") as pbar:
for i in pbar:
sleep(0.5)
if i % 2:
pbar.set_description(f"Testing odd number {i}")
else:
pbar.set_description(f"Testing even number {i}")
pbar.set_postfix(divisors=get_divisors(i))
Here is the status displayed at i=6
.
Testing even number 6: 70%|██████████████▋ | 7/10 [00:03<00:01, 1.76carrots/s, divisors=[1, 2, 3]]
Track loss and accuracy
In this section, we use a neural network wrote in PyTorch and train it using tqdm
to display the loss and accuracy.
Here is the model
import torch
from torch import nn
import torch.nn.functional as F
class Perceptron(nn.Module):
def __init__(self):
super().__init__()
self.dropout2 = nn.Dropout(0.5)
self.fc1 = nn.Linear(28 * 28, 128) # 9216
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = torch.flatten(x, 1)
x = self.fc1(x)
x = F.relu(x)
x = self.dropout2(x)
x = self.fc2(x)
output = F.log_softmax(x, dim=1)
return output
This is a simple perceptron model that we can use for processing images of digits from MNIST dataset. The following code for loading MNIST dataset is inspired from a PyTorch example2.
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
from torch.optim import Adam
from torch import nn
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
dataset = datasets.MNIST('./mnist_data', train=True, download=True,
transform=transform)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = Perceptron().to(device)
optimizer = Adam(model.parameters())
criterion = nn.CrossEntropyLoss()
batch_size = 64
train_loader = DataLoader(dataset,
batch_size=batch_size,
num_workers=1,
pin_memory=True,
shuffle=True,
drop_last=True)
We just loaded our data, and defined our model and settings, we can now run a training experiment.
model.train()
for epoch in range(1, 5):
with tqdm(train_loader, unit="batch") as tepoch:
for data, target in tepoch:
tepoch.set_description(f"Epoch {epoch}")
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
predictions = output.argmax(dim=1, keepdim=True).squeeze()
loss = F.nll_loss(output, target)
correct = (predictions == target).sum().item()
accuracy = correct / batch_size
loss.backward()
optimizer.step()
tepoch.set_postfix(loss=loss.item(), accuracy=100. * accuracy)
sleep(0.1)
I am once again using sleep
function to pause the program so that we can see the update of the progress bar. As you can see, we just applied what we learned previously here, in particular with tepoch.set_postfix
and tepoch.set_description
which let you update the information displayed by the progress bar. Here is a capture of the output while the program was running
Epoch 1: 15%|▉ | 142/937 [00:16<01:32, 8.56batch/s, accuracy=89.1, loss=0.341]
This gives us an idea of how tqdm
can be used in practical.
Conclusion
You can achieve much more with tqdm
, like adapting it to Jupyter notebooks, finely configuring the progress bar updates or nesting progress bars, so I recommend you to read the documentation for more: https://github.com/tqdm/tqdm
Footnotes
"tqdm" means "progress" in arabic or is short for "te quiero demasiado" in spanish. You can pronounce it tek-a-dem if you prefer.