<jupyter_start><jupyter_text>Classifying images of everyday objects using a neural network<jupyter_code>import torch
import torchvision
import numpy as np
import matplotlib.pyplot as plt
import torch.nn as nn
import torch.nn.functional as F
from torchvision.datasets import CIFAR10
from torchvision.transforms import transforms
from torchvision.utils import make_grid
from torch.utils.data.dataloader import DataLoader
from torch.utils.data import random_split<jupyter_output><empty_output><jupyter_text>Step 1: Exploring the CIFAR10 dataset<jupyter_code>dataset = CIFAR10(root = '/content/', download = True, train = True)
test_dataset = CIFAR10(root = '/content/', download = True, train = False)
len(dataset), len(test_dataset)
dataset[0]
image, label = dataset[0]
plt.imshow(image)
print(image)
print('Label:', label)
image, label = dataset[2354]
plt.imshow(image)
print('Label:', label)
# Transforming the images to Tensors

dataset = CIFAR10(root = '/content/', download = True, train = True, transform = transforms.ToTensor())
test_dataset = CIFAR10(root = '/content/', download = True, train = False, transform = transforms.ToTensor())
img_tensor, label = dataset[0]
print(f"img_tensor Shape: {img_tensor.shape}, label: {label} \n")
print(img_tensor)
dataset_size = len(dataset)
test_dataset_size = len(test_dataset)
classes = dataset.classes

img_tensor, label = dataset[0]
img_shape = img_tensor.shape

print(f"# Images in the training dataset: {dataset_size} \n")
print(f"# Images in the test dataset: {test_dataset_size} \n")
print(f"Output classes in the dataset: {classes} \n")
print("# Classes: ", len(classes), "\n")
print(f"Shape of the image tensor: {img_shape} \n")<jupyter_output># Images in the training dataset: 50000 

# Images in the test dataset: 10000 

Output classes in the dataset: ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] 

# Classes:  10 

Shape of the image tensor: torch.Size([3, 32, 32])<jupyter_text>Looking at a sample image from the dataset.`matplotlib` expects channels to be the last dimension of the image tensors (whereas in PyTorch they are the first dimension), so we'll the `.permute` tensor method to shift channels to the last dimension.<jupyter_code>img, label = dataset[0]
plt.imshow(img.permute((1, 2, 0)))
print('Label (numeric):', label)
print('Label (textual):', classes[label])
img, label = dataset[3450]
plt.imshow(img.permute((1, 2, 0)))
print('Label (numeric):', label)
print('Label (textual):', classes[label])
# Determining # images belonging to each class

for i in range(len(classes)):
  num_images = 0
  for _, label in dataset:
    if label == i:
      num_images += 1
  print(f"# {classes[i]} images: {num_images}")<jupyter_output># airplane images: 5000
# automobile images: 5000
# bird images: 5000
# cat images: 5000
# deer images: 5000
# dog images: 5000
# frog images: 5000
# horse images: 5000
# ship images: 5000
# truck images: 5000<jupyter_text>Step 2: Preparing the data for training<jupyter_code># Splitting the dataset into training and validation sets
val_percent = 0.1
val_size = int(len(dataset) * val_percent)
train_size = len(dataset) - val_size

train_ds, val_ds = random_split(dataset, [train_size, val_size])
len(train_ds), len(val_ds)
# Creating DataLoaders
batch_size = 256

train_loader = DataLoader(train_ds, batch_size, shuffle = True)
val_loader = DataLoader(train_ds, batch_size)
test_loader = DataLoader(test_dataset, batch_size)
# Visualizing a batch of data using the `make_grid` function 
for images, _ in train_loader:
  print('images.shape:', images.shape)
  plt.figure(figsize = (16,8))
  plt.axis('off')
  plt.imshow(make_grid(images, nrow=16).permute((1, 2, 0)))
  break<jupyter_output>images.shape: torch.Size([256, 3, 32, 32])<jupyter_text>Step 3: Creating the Model<jupyter_code>def accuracy(outputs, labels):
    _, preds = torch.max(outputs, dim=1)
    return torch.tensor(torch.sum(preds == labels).item() / len(preds))
class ImageClassificationBase(nn.Module):

    def training_step(self, batch):
        images, labels = batch 
        out = self(images)                  # Generate predictions
        loss = F.cross_entropy(out, labels) # Calculate loss
        return loss
    
    def validation_step(self, batch):
        images, labels = batch 
        out = self(images)                    # Generate predictions
        loss = F.cross_entropy(out, labels)   # Calculate loss
        acc = accuracy(out, labels)           # Calculate accuracy
        return {'val_loss': loss, 'val_acc': acc}
        
    def validation_epoch_end(self, outputs):
        batch_losses = [x['val_loss'] for x in outputs]
        epoch_loss = torch.stack(batch_losses).mean()   # Combine losses
        batch_accs = [x['val_acc'] for x in outputs]
        epoch_acc = torch.stack(batch_accs).mean()      # Combine accuracies
        return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}

    def epoch_end(self, epoch, result):
      print(f"Epoch [{epoch}], val_loss: {result['val_loss']:.4f}, val_acc: {result['val_acc']:.4f}")
def evaluate(model, val_loader):
    outputs = [model.validation_step(batch) for batch in val_loader]
    return model.validation_epoch_end(outputs)

def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.Adam):
    history = []
    optimizer = opt_func(model.parameters(), lr)
    for epoch in range(epochs):
        # Training Phase 
        for batch in train_loader:
            loss = model.training_step(batch)
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
        # Validation phase
        result = evaluate(model, val_loader)
        model.epoch_end(epoch, result)
        history.append(result)
        
    return history<jupyter_output><empty_output><jupyter_text>Checking if a GPU is available and the required NVIDIA CUDA drivers are installed<jupyter_code>torch.cuda.is_available()<jupyter_output><empty_output><jupyter_text>Defining a helper function to ensure that our code uses the GPU if available and defaults to using the CPU if it isn't.<jupyter_code>def get_default_device():
  """ Pick GPU if available """
  if torch.cuda.is_available():
    return torch.device('cuda')
  else:
    return torch.device('cpu')
device = get_default_device()
device<jupyter_output><empty_output><jupyter_text>Moving the data to the device. Data can either be a tensor or a model. By default the data sits on the CPU.<jupyter_code>def to_device(data, device):
  """Move tensor(s) to chosen device"""
  if isinstance(data, (list, tuple)):
    return [to_device(x, device) for x in data]
  return data.to(device, non_blocking=True)
# Demonstration
# Moving a batch to the GPU

for images, labels in train_loader:
  print(images.shape)
  print(images.device)
  immages = to_device(images, device)
  print(images.device)
  break<jupyter_output>torch.Size([256, 3, 32, 32])
cpu
cpu<jupyter_text>Each time we get a batch of data we'll have to move it onto the GPU and once the processing is done and we try to move the next batch then the previous batch of data is automatically vacated by the GPU - carried out by DeviceDataLoader.We take our training loader and pass it into the device data loader. We take our validation loader and pass it into the device data loader. The device dataloader will automatically move batches of data to the selected device.<jupyter_code>class DeviceDataLoader():
  """Wrap a dataloader to move data to a device"""
  def __init__(self, dl, device):
      self.dl = dl
      self.device = device
      
  def __iter__(self):
      """Yield a batch of data after moving it to device"""
      for b in self.dl: 
          yield to_device(b, self.device)

  def __len__(self):
      """Returns the Number of batches within the DataLoader"""
      return len(self.dl)
train_loader = DeviceDataLoader(train_loader, device)
val_loader = DeviceDataLoader(val_loader, device)
test_loader = DeviceDataLoader(test_loader, device)<jupyter_output><empty_output><jupyter_text>When we try to access a batch of data within a for loop from a validation data loader or train data loader, the batch of data will automatically be moved to the selected device.<jupyter_code>for xb, yb in val_loader:
  print('xb.device:', xb.device)
  print('yb', yb)
  break
print(xb.shape)
print(xb.size())
print(xb.size(0))
print(xb.view(xb.size(0), -1).shape)
# helper functions for plotting the losses & accuracies.

def plot_losses(history):
    losses = [x['val_loss'] for x in history]
    plt.plot(losses, '-x')
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Loss vs. No. of epochs')

def plot_accuracies(history):
    accuracies = [x['val_acc'] for x in history]
    plt.plot(accuracies, '-x')
    plt.xlabel('epoch')
    plt.ylabel('accuracy')
    plt.title('Accuracy vs. No. of epochs')<jupyter_output><empty_output><jupyter_text>Step 4: Training the Model<jupyter_code>input_size = 3*32*32
output_size = 10<jupyter_output><empty_output><jupyter_text>Extending the ImageClassificationBase class to complete the model definition.<jupyter_code>class CIFAR10Model(ImageClassificationBase):
    def __init__(self):
        super().__init__()
        self.linear1 = nn.Linear(input_size, 512)
        self.linear2 = nn.Linear(512, 1024)
        self.linear3 = nn.Linear(1024, 512)
        self.linear4 = nn.Linear(512, 256)
        self.linear5 = nn.Linear(256, output_size)
        
    def forward(self, xb):
        # Flatten images into vectors
        xb = xb.view(xb.size(0), -1)              # [128, 3072]
        # Apply layers & activation functions
        out = self.linear1(xb)                    # [3072, 32]
        out = F.leaky_relu(out)
        out = self.linear2(out)                   # [32, 64]
        out = F.leaky_relu(out)
        out = self.linear3(out)                   # [64, 64]
        out = F.leaky_relu(out)                    
        out = self.linear4(out)                   # [64, 64]
        out = F.leaky_relu(out)
        out = self.linear5(out)                   # [64, 10]
        
        return out<jupyter_output><empty_output><jupyter_text>Instantiate the model, and move it the appropriate device.<jupyter_code>model = to_device(CIFAR10Model(), device)
total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params
history = [evaluate(model, val_loader)]
history
%%time
epoch, lr = 5, 0.001
history += fit(epoch, lr, model, train_loader, val_loader)
%%time
epoch, lr = 5, 0.001
history += fit(epoch, lr, model, train_loader, val_loader)
%%time
epoch, lr = 3, 0.0005
history += fit(epoch, lr, model, train_loader, val_loader)
%%time
epoch, lr = 3, 0.00005
history += fit(epoch, lr, model, train_loader, val_loader)
%%time
epoch, lr = 3, 0.000005
history += fit(epoch, lr, model, train_loader, val_loader)
%%time
epoch, lr = 3, 0.0000001
history += fit(epoch, lr, model, train_loader, val_loader)
torch.save(model.state_dict(), 'Deeeeep 67.pth')
plot_losses(history)
plot_accuracies(history)
test_acc = evaluate(model, test_loader)
test_acc<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11538115381153811, 'fraction_numerical': 0.022410224102241024, 'mean_word_length': 4.046321525885559, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '331783', 'license': 'MIT', 'n_tokens_mistral': 3787, 'n_tokens_neox': 3570, 'n_words': 1051, 'path': 'CIFAR10_Image_Classification_using_DNN.ipynb', 'repo_name': 'Parth-Rawri/CIFAR10', 'size': '11111'}