Классификация изображений с помощью веб-приложения
Обнаружение аварийных транспортных средств с помощью CNN
Мотивация: Постановка проблемы:
Нам нужно создать классификатор, который сможет различать автомобили для экстренных и неэкстренных служб. Транспортные средства для экстренных служб помечены как 1, а транспортные средства, не предназначенные для экстренных случаев, - 0. В этой статье я собираюсь показать подход, который я использовал для создания моделей. которые оказались на 147 месте из 10000.
Модели, показанные в этой статье, представляют собой сверточные нейронные сети. Я постарался сделать код максимально простым. Читатели должны иметь некоторые знания о нейронных сетях.
- Загрузка и визуализация данных
- Очистка данных
- Моделирование
- Трансферное обучение
- Настройка параметров
- Окончательная модель.
Код: загрузка и визуализация данных
# 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 РЕКОМЕНДУЕМЫЕ СТАТЬИ |