You are a person with a lot of experience in deep learning models. You are currently helping someone build their deep learning model. The person in question is getting the following error. /bin/python /home/pradyumna/Desktop/Mahindra/Sem6/NN/Assignment/MNIST.py
Traceback (most recent call last):
  File "/home/pradyumna/Desktop/Mahindra/Sem6/NN/Assignment/MNIST.py", line 5, in <module>
    from tqdm import tqdm
ModuleNotFoundError: No module named 'tqdm'

Also look and correct any problems in the code

import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from tqdm import tqdm

# Check if GPU is available
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")

# Load and split the MNIST dataset
transform = transforms.Compose([transforms.ToTensor(), 
                                transforms.Normalize((0.5,), (0.5,))])

trainset = torchvision.datasets.MNIST(root='./data', train=True,
                                      download=True, transform=transform)

testset = torchvision.datasets.MNIST(root='./data', train=False,
                                     download=True, transform=transform)

# Define the CNN architecture
class Net(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = torch.nn.Conv2d(1, 32, 3)
        self.bn1 = torch.nn.BatchNorm2d(32)
        self.pool = torch.nn.MaxPool2d(2, 2)
        self.conv2 = torch.nn.Conv2d(32, 64, 3)
        self.bn2 = torch.nn.BatchNorm2d(64)
        self.conv3 = torch.nn.Conv2d(64, 64, 3)
        self.bn3 = torch.nn.BatchNorm2d(64)
        self.fc1 = torch.nn.Linear(64 * 3 * 3, 64)  # Adjust the input size based on the output of conv3
        self.dropout = torch.nn.Dropout(0.5)
        self.fc2 = torch.nn.Linear(64, 10)

    def forward(self, x):
        x = self.pool(torch.nn.functional.relu(self.bn1(self.conv1(x))))
        x = self.pool(torch.nn.functional.relu(self.bn2(self.conv2(x))))
        x = torch.nn.functional.relu(self.bn3(self.conv3(x)))
        x = torch.flatten(x, 1)  # Flatten the output of the convolutional layers
        x = self.fc1(x)  # The input size is automatically determined by torch.flatten()
        x = torch.nn.functional.relu(x)
        x = self.dropout(x)
        x = self.fc2(x)
        return x

# Train with Adam optimizer, batch size of 32, and epochs from 1 to 100
optimizer_class = torch.optim.Adam
batch_size = 32
epochs = 100

model = Net().to(device)  # move model to GPU
criterion = torch.nn.CrossEntropyLoss()
optimizer = optimizer_class(model.parameters())

trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False)

train_losses = []
train_accs = []
test_accs = []

for epoch in tqdm(range(epochs), desc="Epochs"):
    
    running_loss = 0.0
    correct = 0
    total = 0
    
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)  # move data to GPU
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    
    train_loss = running_loss / len(trainloader)
    train_acc = correct / total
    train_losses.append(train_loss)
    train_accs.append(train_acc)
    
    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            images, labels = images.to(device), labels.to(device)  # move data to GPU
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
    test_acc = correct / total
    test_accs.append(test_acc)
    
    print(f"Epoch [{epoch+1}/{epochs}], Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f}")

# Plot the loss and accuracy curves
plt.figure(figsize=(12,4))
plt.subplot(1,3,1)
plt.plot(train_losses)
plt.xlabel('Epoch')
plt.ylabel('Training Loss')
plt.subplot(1,3,2)
plt.plot(train_accs)
plt.xlabel('Epoch')
plt.ylabel('Training Accuracy')  
plt.subplot(1,3,3)
plt.plot(test_accs)
plt.xlabel('Epoch')
plt.ylabel('Test Accuracy')
plt.suptitle(f'Model - Optimizer: {optimizer_class.__name__}, Batch Size: {batch_size}, Epochs: {epochs}')
plt.tight_layout()
plt.show()