Download as pdf or txt
Download as pdf or txt
You are on page 1of 14

pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

Introduction
Waste disposal is often a concern for various reasons including eutrophication, toxic
waste consumption by animals and land, air or water pollution. Segregating the waste
into organic waste and recyclable waste is a good practice to follow. But, manually
performing the task is very cumbersome. Hence, the dataset
(https://www.kaggle.com/techsash/waste-classification-data) suggests the use of ML to
automate the classification process.

PyTorch Basics
PyTorch is very popular because of its ease of use and applications in numerous fields of
machine learning. In simple terms, PyTorch is basically a python framework that allows
tensor computation with strong GPU acceleration for constructing deep neural networks.
It provides flexibility and stability for deep learning.

Step 1: Importing libraries and check if CUDA is


available.
In [9]: import torch
import numpy as np
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

import matplotlib.pyplot as plt

import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torchvision import datasets, transforms, models

# check if CUDA is available


train_on_gpu = torch.cuda.is_available()

if not train_on_gpu:
print('CUDA is not available. Training on CPU ...')
else:
print('CUDA is available! Training on GPU ...')

CUDA is not available. Training on CPU ...

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 1 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

Step 2: Data Augmentation


Data Augmentation is basically cropping, resizing, flipping the image data to get more
accurate results. This can be done in PyTorch using transforms . Also, Normalization
helps get data within a range and reduces the skewness which helps learn faster and
better. Normalization is done in PyTorch by transforms.Normalize wherein two tuples
are passed, one tuple has mean for all the three RGB channels followed by the second
tuple having standard deviation for all three channels. Data Augmentation is mostly done
in training data as it is important to have more accuracy in training so that eventually test
accuracy is better. This also increases the amount of training images. Hence, multiple
augmentations can be applied on train samples.

In [10]: from torchvision import datasets


import torchvision.transforms as transforms
from torch.utils.data.sampler import SubsetRandomSampler

# number of subprocesses to use for data loading


num_workers = 0
# how many samples per batch to load
batch_size = 32
# percentage of training set to use as validation
valid_size = 0.2

# convert data to a normalized torch.FloatTensor


train_transforms = transforms.Compose([transforms.RandomRotation(30),
transforms.RandomResizedCrop(224
transforms.RandomHorizontalFlip(
transforms.ToTensor(),
transforms.Normalize([0.485, 0.4
[0.229, 0.2

test_transforms = transforms.Compose([transforms.Resize(255),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.45
[0.229, 0.22

Loading the directories and training, testing data.

In [17]: data_dir = r'C:\Users\pintoo\Desktop\EPICS PROJECT\waste classification


train_data = datasets.ImageFolder(data_dir + r'\TRAIN', transform=train
test_data = datasets.ImageFolder(data_dir + r'\TEST', transform=test_tr

In deep learning, often the training set is split into train samples and validation samples
to cross check accuracies. This is done using SubsetRandomSampler.

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 2 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

The DataLoader takes a dataset (such as you would get from ImageFolder) and returns
batches of images and the corresponding labels. You can set various parameters like the
batch size and if the data is shuffled after each epoch.

In [18]: num_train = len(train_data)


indices = list(range(num_train))
np.random.shuffle(indices)
split = int(np.floor(valid_size * num_train))
train_idx, valid_idx = indices[split:], indices[:split]

# define samplers for obtaining training and validation batches


train_sampler = SubsetRandomSampler(train_idx)
valid_sampler = SubsetRandomSampler(valid_idx)

# prepare data loaders (combine dataset and sampler)


train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch
sampler=train_sampler, num_workers=num_workers)
valid_loader = torch.utils.data.DataLoader(train_data, batch_size=batch
sampler=valid_sampler, num_workers=num_workers)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=batch_s
num_workers=num_workers)

In [19]: print(len(train_loader))

565

In [20]: #defining classes

classes=['O','R']

In [21]: import matplotlib.pyplot as plt


%matplotlib inline

# helper function to un-normalize and display an image


def imshow(img):
img = img / 2 + 0.5 # unnormalize
plt.imshow(np.transpose(img, (1, 2, 0)))

Plotting the images to understand the data. O= Organic Waste R= Recyclable Waste

In [24]: # obtain one batch of training images


dataiter = iter(train_loader)
# images, labels = dataiter.next()
images, labels = next(dataiter)
images = images.numpy() # convert images to numpy for display

# plot the images in the batch, along with the corresponding labels
fig = plt.figure(figsize=(25, 4))
# display 20 images

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 3 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

for idx in np.arange(20):


ax = fig.add_subplot(2, int(20/2), idx+1, xticks=[], yticks=[])
imshow(images[idx])
ax.set_title(classes[labels[idx]])

Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 4 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

In [25]: batch = next(iter(train_loader))


print(batch[0].shape)
plt.imshow(batch[0][0].permute(1, 2, 0))
print(batch[1][0])

Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).

torch.Size([32, 3, 224, 224])


tensor(0)

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 5 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

Transfer Learning
Transfer Learning refers to the process of using already existing pre-trained models for
other applications by tweaking the last few layers and using it to classify our desired
classes. Once trained, these models work astonishingly well as feature detectors for
images they weren't trained on. Using a pre-trained network on images not in the training
set is called transfer learning. Here we'll use transfer learning to train a network that can
classify our organic and recyclable waste photos with near perfect accuracy.

With torchvision.models these pre-trained networks can be downloaded and used


in applications.

Here, the DenseNet121 is used. DenseNet is a very powerful model with 121 layers. For
more information about densenet, Click Here
(https://www.kaggle.com/pytorch/densenet121).

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 6 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

Densenet architecture

Img source:https://pytorch.org/hub/pytorch_vision_densenet/
(https://pytorch.org/hub/pytorch_vision_densenet/)

Dense Convolutional Network (DenseNet), connects each layer to every other layer in a
feed-forward fashion. The 1-crop error rates on the imagenet dataset with the pretrained
model are 25.35 for top-1 error and 7.83 for top-5 error.

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 7 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

In [26]: model = models.densenet121(pretrained=True)


model

C:\Users\pintoo\AppData\Local\Programs\Python\Python312\Lib\site
-packages\torchvision\models\_utils.py:208: UserWarning: The par
ameter 'pretrained' is deprecated since 0.13 and may be removed
in the future, please use 'weights' instead.
warnings.warn(
C:\Users\pintoo\AppData\Local\Programs\Python\Python312\Lib\site
-packages\torchvision\models\_utils.py:223: UserWarning: Argumen
ts other than a weight enum or `None` for 'weights' are deprecat
ed since 0.13 and may be removed in the future. The current beha
vior is equivalent to passing `weights=DenseNet121_Weights.IMAGE
NET1K_V1`. You can also use `weights=DenseNet121_Weights.DEFAULT
` to get the most up-to-date weights.
warnings.warn(msg)
Downloading: "https://download.pytorch.org/models/densenet121-a6
39ec97.pth" to C:\Users\pintoo/.cache\torch\hub\checkpoints\dens
enet121-a639ec97.pth
100.0%

Out[26]: DenseNet(
(features): Sequential(

In [27]: # Use GPU if it's available


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model = models.densenet121(pretrained=True)

# Freeze parameters so we don't backprop through them


for param in model.parameters():
param.requires_grad = False

model.classifier = nn.Sequential(nn.Linear(1024, 256),


nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(256, 2),
nn.LogSoftmax(dim=1))

criterion = nn.NLLLoss()

# Only train the classifier parameters, feature parameters are frozen


optimizer = optim.Adam(model.classifier.parameters(), lr=0.003)

model.to(device);

Training the Model

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 8 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

Training the model for desired number of epochs and keeping track of train loss and
validation loss. If the validation loss decreases, the model is saved.The simplest thing to
do is simply save the state dict with torch.save .

In [28]: # number of epochs to train the model


n_epochs = 20

valid_loss_min = np.Inf # track change in validation loss

for epoch in range(1, n_epochs+1):

# keep track of training and validation loss


train_loss = 0.0
valid_loss = 0.0

###################
# train the model #
###################
model.train()
for data, target in train_loader:
# move tensors to GPU if CUDA is available
if train_on_gpu:
data, target = data.cuda(), target.cuda()
# clear the gradients of all optimized variables
optimizer.zero_grad()
# forward pass: compute predicted outputs by passing inputs to
output = model(data)
# calculate the batch loss
loss = criterion(output, target)
# backward pass: compute gradient of the loss with respect to m
loss.backward()
# perform a single optimization step (parameter update)
optimizer.step()
# update training loss
train_loss += loss.item()*data.size(0)

######################
# validate the model #
######################
model.eval()
for data, target in valid_loader:
# move tensors to GPU if CUDA is available
if train_on_gpu:
data, target = data.cuda(), target.cuda()
# forward pass: compute predicted outputs by passing inputs to
output = model(data)
# calculate the batch loss
loss = criterion(output, target)
# update average validation loss
valid_loss += loss.item()*data.size(0)

# calculate average losses

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 9 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

train_loss = train_loss/len(train_loader.sampler)
valid_loss = valid_loss/len(valid_loader.sampler)

# print training/validation statistics


print('Epoch: {} \tTraining Loss: {:.6f} \tValidation Loss: {:.6f}'
epoch, train_loss, valid_loss))

# save model if validation loss has decreased


if valid_loss <= valid_loss_min:
print('Validation loss decreased ({:.6f} --> {:.6f}). Saving m
valid_loss_min,
valid_loss))
torch.save(model.state_dict(), 'model_waste.pt')
valid_loss_min = valid_loss

Epoch: 1 Training Loss: 0.300196 Validation Loss:


0.312278
Validation loss decreased (inf --> 0.312278). Saving model ...
Epoch: 2 Training Loss: 0.255958 Validation Loss:
0.213042
Validation loss decreased (0.312278 --> 0.213042). Saving model
...
Epoch: 3 Training Loss: 0.235990 Validation Loss:
0.206464
Validation loss decreased (0.213042 --> 0.206464). Saving model
...
Epoch: 4 Training Loss: 0.236506 Validation Loss:
0.217614
Epoch: 5 Training Loss: 0.229986 Validation Loss:
0.207588
Epoch: 6 Training Loss: 0.233450 Validation Loss:
0.199507
Validation loss decreased (0.206464 --> 0.199507). Saving model
...
Epoch: 7 Training Loss: 0.222976 Validation Loss:
0.201736
Epoch: 8 Training Loss: 0.220735 Validation Loss:
0.228033
Epoch: 9 Training Loss: 0.216805 Validation Loss:
0.193980
Validation loss decreased (0.199507 --> 0.193980). Saving model
...
Epoch: 10 Training Loss: 0.216173 Validation Loss:
0.202743
Epoch: 11 Training Loss: 0.215030 Validation Loss:
0.213749
Epoch: 12 Training Loss: 0.213124 Validation Loss:
0.188995
Validation loss decreased (0.193980 --> 0.188995). Saving model
...
Epoch: 13 Training Loss: 0.207516 Validation Loss:
0.190489
Epoch: 14 Training Loss: 0.207228 Validation Loss:
0.192732

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 10 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

Epoch: 15 Training Loss: 0.209266 Validation Loss:


0.188298
Validation loss decreased (0.188995 --> 0.188298). Saving model
...
Epoch: 16 Training Loss: 0.210928 Validation Loss:
0.204957
Epoch: 17 Training Loss: 0.206780 Validation Loss:
0.213534
Epoch: 18 Training Loss: 0.203793 Validation Loss:
0.187286
Validation loss decreased (0.188298 --> 0.187286). Saving model
...
Epoch: 19 Training Loss: 0.199648 Validation Loss:
0.191725
Epoch: 20 Training Loss: 0.199830 Validation Loss:
0.181744
Validation loss decreased (0.187286 --> 0.181744). Saving model
...

Loading the last saved model for testing.

In [29]: model.load_state_dict(torch.load('model_waste.pt'))

Out[29]: <All keys matched successfully>

Testing the model

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 11 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

In [30]: # track test loss


test_loss = 0.0
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))

model.eval()
# iterate over test data
for data, target in test_loader:
# move tensors to GPU if CUDA is available
if train_on_gpu:
data, target = data.cuda(), target.cuda()
# forward pass: compute predicted outputs by passing inputs to the
output = model(data)
# calculate the batch loss
loss = criterion(output, target)
# update test loss
test_loss += loss.item()*data.size(0)
# convert output probabilities to predicted class
_, pred = torch.max(output, 1)
# compare predictions to true label
correct_tensor = pred.eq(target.data.view_as(pred))
correct = np.squeeze(correct_tensor.numpy()) if not train_on_gpu el
# calculate test accuracy for each object class
for i in range(2):
label = target.data[i]
class_correct[label] += correct[i].item()
class_total[label] += 1

# average test loss


test_loss = test_loss/len(test_loader.dataset)
print('Test Loss: {:.6f}\n'.format(test_loss))

for i in range(2):
if class_total[i] > 0:
print('Test Accuracy of %5s: %2d%% (%2d/%2d)' % (
classes[i], 100 * class_correct[i] / class_total[i],
np.sum(class_correct[i]), np.sum(class_total[i])))
else:
print('Test Accuracy of %5s: N/A (no training examples)' % (cla

print('\nTest Accuracy (Overall): %2d%% (%2d/%2d)' % (


100. * np.sum(class_correct) / np.sum(class_total),
np.sum(class_correct), np.sum(class_total)))

Test Loss: 0.184592

Test Accuracy of O: 100% (88/88)


Test Accuracy of R: 91% (64/70)

Test Accuracy (Overall): 96% (152/158)

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 12 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

The model has 91% accuracy for Recyclable waste. But, a whopping 98% accuracy for
organic waste.

Visualizing the Results


In [33]: # obtain one batch of test images
dataiter = iter(test_loader)
# images, labels = dataiter.next()
images, labels = next(dataiter)
images.numpy()

# move model inputs to cuda, if GPU available


if train_on_gpu:
images = images.cuda()

# get sample outputs


output = model(images)
# convert output probabilities to predicted class
_, preds_tensor = torch.max(output, 1)
preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.sq

# plot the images in the batch, along with predicted and true labels
fig = plt.figure(figsize=(25, 4))
for idx in np.arange(20):
ax = fig.add_subplot(2, int(20/2), idx+1, xticks=[], yticks=[])
imshow(images.cpu()[idx])
ax.set_title("{} ({})".format(classes[preds[idx]], classes[labels[i
color=("green" if preds[idx]==labels[idx].item() else

Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 13 of 14
pytorch-waste-classification-using-densenet - Jupyter Notebook 10/05/24, 9:45 AM

0..1] for floats or [0..255] for integers).


Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([
0..1] for floats or [0..255] for integers).

In [ ]:

http://localhost:8889/notebooks/pytorch-waste-classification-using-densenet.ipynb Page 14 of 14

You might also like