158 lines
5.3 KiB
Python
158 lines
5.3 KiB
Python
#importing necessary libraries
|
|
import matplotlib.pyplot as plt
|
|
import torch
|
|
import numpy as np
|
|
from torch import nn
|
|
from torch import optim
|
|
from torchvision import datasets, models, transforms
|
|
import torch.nn.functional as F
|
|
import torch.utils.data
|
|
import pandas as pd
|
|
from collections import OrderedDict
|
|
from PIL import Image
|
|
import argparse
|
|
import json
|
|
|
|
# define Mandatory and Optional Arguments for the script
|
|
parser = argparse.ArgumentParser (description = "Parser of prediction script")
|
|
|
|
parser.add_argument ('image_dir', help = 'Provide path to image. Mandatory argument', type = str)
|
|
parser.add_argument ('load_dir', help = 'Provide path to checkpoint. Mandatory argument', type = str)
|
|
parser.add_argument ('--top_k', help = 'Top K most likely classes. Optional', type = int)
|
|
parser.add_argument ('--category_names', help = 'Mapping of categories to real names. JSON file name to be provided. Optional', type = str)
|
|
parser.add_argument ('--GPU', help = "Option to use GPU. Optional", type = str)
|
|
|
|
# a function that loads a checkpoint and rebuilds the model
|
|
def loading_model (file_path):
|
|
checkpoint = torch.load (file_path) #loading checkpoint from a file
|
|
if checkpoint ['arch'] == 'alexnet':
|
|
model = models.alexnet (pretrained = True)
|
|
else: #vgg13 as only 2 options available
|
|
model = models.vgg13 (pretrained = True)
|
|
model.classifier = checkpoint ['classifier']
|
|
model.load_state_dict (checkpoint ['state_dict'])
|
|
model.class_to_idx = checkpoint ['mapping']
|
|
|
|
for param in model.parameters():
|
|
param.requires_grad = False #turning off tuning of the model
|
|
|
|
return model
|
|
|
|
# function to process a PIL image for use in a PyTorch model
|
|
def process_image(image):
|
|
''' Scales, crops, and normalizes a PIL image for a PyTorch model,
|
|
returns an Numpy array
|
|
'''
|
|
im = Image.open (image) #loading image
|
|
width, height = im.size #original size
|
|
|
|
# smallest part: width or height should be kept not more than 256
|
|
if width > height:
|
|
height = 256
|
|
im.thumbnail ((50000, height), Image.ANTIALIAS)
|
|
else:
|
|
width = 256
|
|
im.thumbnail ((width,50000), Image.ANTIALIAS)
|
|
|
|
width, height = im.size #new size of im
|
|
#crop 224x224 in the center
|
|
reduce = 224
|
|
left = (width - reduce)/2
|
|
top = (height - reduce)/2
|
|
right = left + 224
|
|
bottom = top + 224
|
|
im = im.crop ((left, top, right, bottom))
|
|
|
|
#preparing numpy array
|
|
np_image = np.array (im)/255 #to make values from 0 to 1
|
|
np_image -= np.array ([0.485, 0.456, 0.406])
|
|
np_image /= np.array ([0.229, 0.224, 0.225])
|
|
|
|
#PyTorch expects the color channel to be the first dimension but it's the third dimension in the PIL image and Numpy array.
|
|
#The color channel needs to be first and retain the order of the other two dimensions.
|
|
np_image= np_image.transpose ((2,0,1))
|
|
return np_image
|
|
|
|
#defining prediction function
|
|
def predict(image_path, model, topkl, device):
|
|
''' Predict the class (or classes) of an image using a trained deep learning model.
|
|
'''
|
|
# Implement the code to predict the class from an image file
|
|
image = process_image (image_path) #loading image and processing it using above defined function
|
|
|
|
#we cannot pass image to model.forward 'as is' as it is expecting tensor, not numpy array
|
|
#converting to tensor
|
|
if device == 'cuda':
|
|
im = torch.from_numpy (image).type (torch.cuda.FloatTensor)
|
|
else:
|
|
im = torch.from_numpy (image).type (torch.FloatTensor)
|
|
|
|
im = im.unsqueeze (dim = 0) #used to make size of torch as expected. as forward method is working with batches,
|
|
#doing that we will have batch size = 1
|
|
|
|
#enabling GPU/CPU
|
|
model.to (device)
|
|
im.to (device)
|
|
|
|
with torch.no_grad ():
|
|
output = model.forward (im)
|
|
output_prob = torch.exp (output) #converting into a probability
|
|
|
|
probs, indeces = output_prob.topk (topkl)
|
|
probs = probs.cpu ()
|
|
indeces = indeces.cpu ()
|
|
probs = probs.numpy () #converting both to numpy array
|
|
indeces = indeces.numpy ()
|
|
|
|
probs = probs.tolist () [0] #converting both to list
|
|
indeces = indeces.tolist () [0]
|
|
|
|
mapping = {val: key for key, val in
|
|
model.class_to_idx.items()
|
|
}
|
|
|
|
classes = [mapping [item] for item in indeces]
|
|
classes = np.array (classes) #converting to Numpy array
|
|
|
|
return probs, classes
|
|
|
|
#setting values data loading
|
|
args = parser.parse_args ()
|
|
file_path = args.image_dir
|
|
|
|
#defining device: either cuda or cpu
|
|
if args.GPU == 'GPU':
|
|
device = 'cuda'
|
|
else:
|
|
device = 'cpu'
|
|
|
|
#loading JSON file if provided, else load default file name
|
|
if args.category_names:
|
|
with open(args.category_names, 'r') as f:
|
|
cat_to_name = json.load(f)
|
|
else:
|
|
with open('cat_to_name.json', 'r') as f:
|
|
cat_to_name = json.load(f)
|
|
pass
|
|
|
|
#loading model from checkpoint provided
|
|
model = loading_model (args.load_dir)
|
|
|
|
#defining number of classes to be predicted. Default = 1
|
|
if args.top_k:
|
|
nm_cl = args.top_k
|
|
else:
|
|
nm_cl = 1
|
|
|
|
#calculating probabilities and classes
|
|
probs, classes = predict (file_path, model, nm_cl, device)
|
|
|
|
#preparing class_names using mapping with cat_to_name
|
|
class_names = [cat_to_name [item] for item in classes]
|
|
|
|
for l in range (nm_cl):
|
|
print("Number: {}/{}.. ".format(l+1, nm_cl),
|
|
"Class name: {}.. ".format(class_names [l]),
|
|
"Probability: {:.3f}..% ".format(probs [l]*100),
|
|
)
|