Классификация изображений с помощью веб-приложения

Опубликовано: 4 Января, 2022

Обнаружение аварийных транспортных средств с помощью CNN

Мотивация: Постановка проблемы:
Нам нужно создать классификатор, который сможет различать автомобили для экстренных и неэкстренных служб. Транспортные средства для экстренных служб помечены как 1, а транспортные средства, не предназначенные для экстренных случаев, - 0. В этой статье я собираюсь показать подход, который я использовал для создания моделей. которые оказались на 147 месте из 10000.
Модели, показанные в этой статье, представляют собой сверточные нейронные сети. Я постарался сделать код максимально простым. Читатели должны иметь некоторые знания о нейронных сетях.

Этапы решения проблемы:
  1. Загрузка и визуализация данных
  2. Очистка данных
  3. Моделирование
  4. Трансферное обучение
  5. Настройка параметров
  6. Окончательная модель.

Код: загрузка и визуализация данных

# imports
import numpy as np
import os
import matplotlib.pyplot as plt
from PIL import Image, ImageOps, ImageFilter, ImageEnhance
import pandas as pd
# importing pytorch library.
import torchvision.transforms as transforms
import torch.nn.functional as F
import torch.nn as nn
from torch.utils.data import Dataset, random_split, DataLoader

Мы будем использовать:

  • numpy : для хранения изображений в массивах,
  • matplotlib : для визуализации изображений,
  • PILLOW или (PIL): библиотека для загрузки и преобразования изображений
  • Pytorch : Для нашей платформы глубокого обучения.

Загрузка данных:
На приведенном выше изображении показаны предоставленные нам наборы данных: все изображения поезда и тестового набора находятся в папке изображений, а файлы CVS поезда и теста содержат имена изображений.
Код:

# name of the image folder
imagePaths = 'images'
# reading the train.csv file using pandas
trainImages = pd.read_csv( 'train.csv' )
# reading the test.csv file using pandas
testImages = pd.read_csv( 'test.csv' )
# reading the submission file using padnas
samples = pd.read_csv( 'sample_submission.csv' )

Код: загрузка изображений в массивы numpy

# defining train and labels list to store images and labels respectively.
train = []
labels = []
for image, label in zip (trainImages.iloc[:, 0 ], trainImages.iloc[:, 1 ]):
# create a image path and store in img_path variable
imgPath = os.path.join(imagePaths, image)
# Use PIl Image class to load the image
img = Image. open (imgPath)
# apply median filter to the image this helps in reduing noise
img = img. filter (ImageFilter.MedianFilter)
# convert the image to numpy array and store the loaded images into train
train.append(np.asarray(img))
# store the label into the labels list
labels.append(label)

Код: открытие и отображение изображений.

# create subplots using the plt.suplots function
# the number of subplots depend on the n_rows and n_cols
# all the suplots are stored in ax variables
_, ax = plt.subplots(nrows = 4 , ncols = 7 , figsize = ( 12 , 12 ))
# iterate throught the ax variable by flattening it
for index, i in enumerate (ax.flatten()):
# the imshow is used to show the image
i.imshow(train[index])
# set the title
i.set_title(index)
# this below lines makes the code better visualize.
i.set_xticks([])
i.set_yticks([])

Выход:

Вывод указанной выше ячейки.


Теперь, когда у нас есть изображения, хранящиеся в классах train и output, хранящиеся в метках, мы можем перейти к следующему шагу.

Очистка данных

В этом разделе мы рассмотрим пропущенные классифицированные метки и образцы неправильных изображений. Удалив эти изображения, моя точность увеличила val_score на 2%. Он вырос с 94% до 96%, а иногда и до 97%.

Miss labelledImages: код, используемый для визуализации данных, такой же, как и выше

Этикетки, не прошедшие классификацию

Неправильные данные: изображения панелей мониторинга.

При удалении этих изображений точность становится более стабильной (меньше колебаний). Следует отметить, что я смог удалить эти изображения приборной панели, потому что я не нашел никаких похожих изображений в тестовых данных.



Определение DatasetClass: для модели, загружающей набор данных с диска, pytorch предоставляет DatasetClass, используя его, нам не нужно помещать всю модель в память.

Код:

# Creating a VehicleDataset class for loading the images and labels .
# the following class needs to extend from the Dataset class
# provided by pytorch framework and implement the __len__ and __getitem__ methods.
class VehicleDataset(Dataset):
def __init__( self , csv_name, folder, transform = None , label = False ):
self .label = label
self .folder = folder
print (csv_name)
self .dataframe = pd.read_csv( self .folder + '/' + csv_name + '.csv' )
self .tms = transform
def __len__( self ):
return len ( self .dataframe)
def __getitem__( self , index):
row = self .dataframe.iloc[index]
imgIndex = row[ 'image_names' ]
imageFile = self .folder + '/' + img_index
image = Image. open (image_file)
if self .label:
target = row[ 'emergency_or_not' ]
if target = = 0 :
encode = torch.FloatTensor([ 1 , 0 ])
else :
encode = torch.FloatTensor([ 0 , 1 ])
return self .tms(image), encode
return self .tms(image)
# creating objects of VehicleDataset
# the deep learing models accepts the image to be in tensor fomat
# this is done using the transorms.ToTensor() methods
transform = transforms.Compose([transforms.ToTensor(),
])
'''
arguments:
csv_name - name of the csv file in out case train.csv
folder - folder in which the images are stored
transform - tansforms the image to tensor,
label - used to differentaite between train and test set.
''' '
trainDataset = VehicleDataset( 'train' , 'images' , label = True , transform = transform)

Теперь, когда у нас есть готовый конвейер данных, нам нужно создать модель глубокого обучения.

Модель CNN:

В этом посте предполагается, что вы знакомы с нейронными сетями, поскольку объяснение этого выходит за рамки данной статьи. Я собираюсь использовать CNN (сверточную нейронную сеть). Модель имеет 3 основных уровня, называющих уровень conv2d, пакетную норму и максимальный пул 2d, функция активации, используемая здесь, - relu:

Код:

# the EmergencyCustomModel class defines our Neural Network
# It inherites from the ImageClassificationBase class which has heler methods
# for printing the loss and accuracy at each epochs.
class EmergencyCustomModel(ImageClassificationBase):
def __init__( self ):
super ().__init__()
self .network = nn.Sequential(
nn.Conv2d( 3 , 32 , kernel_size = 3 , padding = 1 ),
nn.BatchNorm2d( 32 ),
nn.ReLU(),
nn.MaxPool2d( 2 , 2 ),
nn.Conv2d( 32 , 64 , kernel_size = 3 , stride = 1 , padding = 1 ),
nn.BatchNorm2d( 64 ),
nn.ReLU(),
nn.MaxPool2d( 2 , 2 ),
nn.Conv2d( 64 , 64 , kernel_size = 3 , stride = 1 , padding = 1 ),
nn.BatchNorm2d( 64 ),
nn.ReLU(),
nn.MaxPool2d( 2 , 2 ),
nn.Conv2d( 64 , 128 , kernel_size = 3 , stride = 1 , padding = 1 ),
nn.BatchNorm2d( 128 ),
nn.ReLU(),
nn.MaxPool2d( 2 , 2 ),
nn.Conv2d( 128 , 128 , kernel_size = 3 , stride = 1 , padding = 1 ),
nn.BatchNorm2d( 128 ),
nn.ReLU(),
nn.MaxPool2d( 2 , 2 ),
nn.Conv2d( 128 , 256 , kernel_size = 3 , stride = 1 , padding = 1 ),
nn.BatchNorm2d( 256 ),
nn.ReLU(),
nn.AdaptiveAvgPool2d( 1 ),
nn.Flatten(),
nn.Linear( 256 , 128 ),
nn.ReLU(),
nn.Linear( 128 , 64 ),
nn.ReLU(),
nn.Linear( 64 , 2 ),
# nn.Sigmoid(),
)
def forward( self , xb):
return self .network(xb)

В этом блокноте можно найти полное определение модели в моем репозитории на github.

Функция обучения:

Код: следующая функция используется для обучения всех моделей в посте.

# defining the training method.
# the evalution method is used to calculate validation accuracy.
@torch .no_grad()
def evaluate(model, val_loader):
model. eval ()
outputs = [model.validation_step(batch) for batch in val_loader]
return model.validation_epoch_end(outputs)
# The fit method is used to train the model
# parametes
'''
epochs: no. of epochs the model trains
max_lr: maximum learning rate.
train_loader: here we pass the train dataset
val_loader: here we pass the val_dataset
opt_func : The learning agorithem that performs gradient descent.
model : the neural network to train on.
'''
def fit(epochs, max_lr, model, train_loader, val_loader,
weight_decay = 0 , grad_clip = None , opt_func = torch.optim.SGD):
torch.cuda.empty_cache()
history = []
# Set up cutom optimizer with weight decay
optimizer = opt_func(model.parameters(), max_lr, weight_decay = weight_decay)
# the loop iterates from 0 to number of epochs.
# the model needs to be set in the train model by calling the model.train.
for epoch in range (epochs):
# Training Phase
model.train()
train_losses = []
for batch in train_loader:
loss = model.training_step(batch)
train_losses.append(loss)
loss.backward()
# Gradient clipping
if grad_clip:
nn.utils.clip_grad_value_(model.parameters(), grad_clip)
optimizer.step()
optimizer.zero_grad()
# Validation phase
result = evaluate(model, val_loader)
result[ 'train_loss' ] = torch.stack(train_losses).mean().item()
model.epoch_end(epoch, result)
history.append(result)
return history

Перед началом обучения нам необходимо разделить наши данные на набор для обучения и проверки. Это сделано для того, чтобы модель хорошо обобщалась на невидимых данных. Мы проведем деление 80-20, 80% поезд и 20% тест. После разделения данных нам нужно передать наборы данных в загрузчик данных, это предоставляется pytorch.

Код: разделение и создание загрузчиков данных.

# the batchSize is the number of images passes by the loader at a time.
# reduce this number if theres an out of memory error.
batchSize = 32
valPct = 0.2
# code for splitting the data
# valPct variable is used to split dataset
valSize = int (valPct * len (trainDataset))
trainSize = len (trainDataset) - valSize
trainDs, valDs = random_split(trainDataset, [trainSize, valSize])
# Creating dataloaders.
train_loader = DataLoader(trainDs, batchSize)
val_loader = DataLoader(valDs, batchSize)

Теперь мы готовы начать обучение, вызвав метод fit ().

customModel = EmergencyCustomModel()
epochs = 10
lr = 0.01
# save the history to visualize later.
history = fit(epochs, lr, customModel, trainDl, valDl)

Вывод вышеуказанного кода:

вывод функции соответствия


Весь код доступен в репозитории GitHub, ссылка на который приведена ниже.

Код: функция построения графика используется для построения графиков потерь и точности, показанных ниже.

'''
parameters:
epochs = number of epochs the model was trained on
hist = the history returned by the fit function.
'''
def plot(hist, epochs = 10 ):
trainLoss = []
valLoss = []
valScore = []
for i in range (epochs):
trainLoss.append(hist[i][ 'train_loss' ])
valLoss.append(hist[i][ 'val_loss' ])
valScore.append(hist[i][ 'val_score' ])
plt.plot(trainLoss, label = 'train_loss' )
plt.plot(valLoss, label = 'val_loss' )
plt.legend()
plt.title( 'loss' )
plt.figure()
plt.plot(valScore, label = 'val_score' )
plt.legend()
plt.title( 'accuarcy' )
# calling the function
plot(history)

Результат: построение графиков потерь и точности.

графики потерь и точности

Переоснащения намного меньше, и val_accuracy достигает своего пикового значения на уровне 90%. и здесь я снова хотел бы добавить, что когда я создал собственную модель в keras, высота val_score, которую я смог достичь, составила 83%, изменение структуры дало нам увеличение на 7%. Еще одна вещь, размер режима, используя pytorch, я могу использовать модель, имеющую более 3 слоев Conv2d, без чрезмерной подгонки. Но в керасе я мог использовать только 2 слоя, не более того, что что-то большее или меньшее только увеличивало бы стоимость обучения без повышения точности.

Трансферное обучение:
Использование предварительно обученных моделей: я использовал две архитектуры моделей: resnet и densenet. Одна вещь: модели densenet не дают результатов, почти аналогичных результатам моделей resnet с более низкими эпохами, и, что наиболее важно, сохраненная модель занимает половину пространства памяти.

Код:

# to use the pretrained model we make use of the torchvision.models library
class ResNet50(ImageClassificationBase):
def __init__( self ):
super ().__init__()
# this following line adds the downloads the resnet50 model is it doent exits
# and stores it in pretrainedModle
self .pretrainedModel = models.resnet50(pretrained = True )
# since this model was trained on ImageNet data which has 1000 classes but for
# problem we have only 2 so will need to modify the final layer of the model
feature_in = self .pretrainedModel.fc.inFeatures
self .pretrainedModel.fc = nn.Linear(feature_in, 2 )
def forward( self , x):
return self .pretrainedModel(x)
# Trainin the model.
# final Learning with
lr = 1e - 4
epochs = 5
optFunc = torch.optim.Adam
# Here I have made use of the wd this is used as a regularization parameter
# It helps in preventing overfittting and helps our model to generalize.
bestWd = 1e - 4
custom_model = to_device(ResNet50(), device)
hist = fit(epochs, lr, customModel, trainDl, valDl, bestWd, optFunc)

Результат: построение графиков потерь и точности.

здесь можно увидеть много переоборудования, а теперь и улучшение val_score. Я решил попробовать применить стратегию обучения циклическому планировщику, вот результат. Мне все еще нужно поэкспериментировать с этим методом, но, как вы видите. Я немного уменьшил переоснащение, но значение val_accuracy все еще низкое.

Использование Densenet169: плотная сеть похожа на Resnet, в которой вместо добавления пропускаемого соединения она объединяет ее, поэтому блоки называются плотными блоками.

Код:

class Densenet169(ImageClassificationBase):
def __init__( self ):
super ().__init__()
# the below statement is used to downlaod and store the pretrained model.
self .pretrained_model = models.densenet169(pretrained = True )
feature_in = self .pretrained_model.classifier.in_features
self .pretrained_model.classifier = nn.Linear(feature_in, 2 )
def forward( self , x):
return self .pretrained_model(x)
Training the model
# final Learning with
lr = 1e - 4
epochs = 5
optFunc = torch.optim.Adam
bestWd = 1e - 4