Проблема классификации в машинном обучении заключается в том, что входным данным присваивается метка на основе их характеристик. Примерами являются распознавание лиц, обнаружение спама и распознавание цифр. Это метод контролируемого обучения, в котором используются дискретные данные. В зависимости от данных существуют разные типы классификации, т. е. бинарная, многоклассовая и многоуровневая, но в этой статье основное внимание уделяется многоклассовой классификации.

1. Получите и разархивируйте набор данных

Используемые данные получены из Kaggle и содержат 4242 помеченных изображения цветов: ромашки (0), одуванчики (1), розы (2), подсолнухи (3) и тюльпаны (4). В скобках указаны названия цветов.

Загрузка набора данных Kaggle может быть выполнена с помощью следующих шагов:

1. !pip install -q kaggle
2. from google.colab import files
3. upload *kaggle.json* file
4. ! mkdir ~/.kaggle
5. ! cp kaggle.json ~/.kaggle/
6. ! chmod 600 ~/.kaggle/kaggle.json
7. ! kaggle datasets list
8. ! kaggle datasets download <name of dataset>
9. ! unzip <name-of-file>

2. Импортируйте соответствующие библиотеки

Затем мы импортируем необходимые библиотеки и пакеты. Ведутся споры о том, какой тип фреймворка лучше всего использовать для машинного обучения, но лично я предпочитаю PyTorch, так как его проще использовать и понимать начинающим. Мы также импортируем библиотеки для визуализации изображений и результатов.

import torch
import pandas as pd
import cv2
import numpy as np 

import torchvision 
import torch.nn as nn
from torch.utils.data import Dataset
import torchvision.transforms as transforms
from torch.utils.data.dataloader import DataLoader
from torchvision.datasets import ImageFolder
import matplotlib.pyplot as plt
from PIL import Image
from sklearn.model_selection import train_test_split

3. Разделение наборов данных на наборы для обучения, проверки и тестирования.

Прежде чем разделить данные, мы сначала создадим функцию для изменения размера и нормализации изображений. Это связано с тем, что скорость обработки модели сильно зависит от размера изображений, а уменьшение размера изображения означает более быструю обработку. Нормализация данных — это их деление на норму вектора. Он используется для приведения всех числовых значений в наборе данных к одному масштабу без изменения различий в диапазонах или потери информации.

def prepare_imgs(imgs):

  prep = torchvision.transforms.Compose([
      torchvision.transforms.Resize((224, 224)),  # smaller sized imagaes give faster results
      torchvision.transforms.ToTensor(),  # Trnsfor to tensor
      torchvision.transforms.Normalize(mean =[0.6520, 0.6200, 0.5089] , std =[0.2128, 0.2142, 0.3178] ) # Normalise to get data in range
  ])
  
  transformed_img = prep(imgs)

  return transformed_img

Затем с помощью функции torchvision.dataset все изображения преобразуются в тензоры, нормализуются, масштабируются и затем сохраняются в папке. После чего папку можно разделить на наборы для обучения, проверки и тестирования.

dataset = torchvision.datasets.ImageFolder('/content/flowers', transform = prepare_imgs)
plt.imshow(Image.open(dataset.imgs[3000][0]))

train_set, rem_set = train_test_split(dataset, test_size= 0.2)
print('the training set size is: ', len(train_set))
print('Remaining  set size is :', len(rem_set))

Разделите оставшийся набор на проверку и тестирование:

validation_set, test_set = train_test_split(rem_set, test_size= 0.05)
print('the validation set size is: ', len(validation_set))
print('Test set size is :', len(test_set))

Затем мы используем загрузчик данных для более легкого доступа к набору данных. Как следует из названия, для загрузки данных используется загрузчик данных. Он делает это, создавая мини-пакеты набора данных и перемешивая набор данных, чтобы мы не получали одинаковые изображения.

batch_size = 32 #This is the number of samples used to estimate error gradient before updating the weights 
num_work= 2 # how many subprocess wil be carried out for dataloding

train_loader = DataLoader(train_set, batch_size, shuffle= True, num_workers=num_work)
val_loader = DataLoader(validation_set, batch_size, shuffle= True, num_workers=num_work)
test_loader =DataLoader(test_set, batch_size, shuffle= True, num_workers=num_work)

4. Создание функции точности

Точность — это процент правильных прогнозов. Он рассчитывается путем деления правильного прогноза на общее количество прогнозов. Мы рассчитываем точность модели, оценивая предсказанные метки по сравнению с фактическими. В этом случае на выходе CNN будет список из 5 длин вероятности каждой метки. Чтобы получить предсказанную метку, мы берем индекс наибольшей вероятности в списке.

def accuracy(output, labels):
  _, preds = torch.max(output, dim=1)
  return torch.tensor(torch.sum(preds == labels).item() / len(preds) ) *100

5. Построение CNN (сверточной нейронной сети)

CNN — это среда глубокого обучения, в основном применяемая к изображениям. Он берет входное изображение и применяет к нему методы, чтобы отличить его от других.
Он способен распознавать особенности во входных изображениях, которые служат основой для классификации, а также уменьшает размер изображений для более быстрой обработки. не теряя деталей.

a.) Слой свертки
Слой свертки CNN служит для применения фильтра/ядра к изображению и, таким образом, извлечения признаков. Думайте об этом как о фильтре повышения резкости, который более четко показывает детали изображения.
Это ядро ​​скользит по изображению для всех трех каналов (RGB), пока изображение не будет полностью свернуто. Фильтры часто представляют собой ядра 3x3, но размеры могут различаться, однако они должны представлять собой квадратную матрицу.
Операция свертки выполняется путем точечного умножения изображения и дополненного ядра в области Фурье.
В PyTorch сверточный слой вызывается с помощью torch.nn.Conv2d().

Выход сверточного слоя можно рассчитать следующим образом:

b.) Слой пула
Этот уровень отвечает за уменьшение размера, тем самым уменьшая вычислительную мощность, используемую для обработки изображений. Он также удобен для извлечения из изображения инвариантных к вращению и смещению функций.
Два типа объединения: максимальное и среднее. Максимальный пул берет максимальный пиксель из части изображения, посещаемой ядром. Он также выполняет шумоподавление. В то время как средний пул принимает среднее значение пикселей в части, посещаемой ядром.
В pytorch максимальный пул равен torch.nn.MaxPool2d(), а средний пул равен torch.nn .AvgPool2d()

Выход из слоя пула рассчитывается как:

c.) Полносвязные слои

Эти слои отвечают за соединение нейронов в нейронной сети. Это то, что гарантирует, что компьютер изучит функции, извлеченные из слоев свертки и объединения. Здесь также применяются веса в нейронной сети.
Некоторые линейные слои включают:
 – ReLu (выпрямленный линейный слой)
 – Softmax
 – Линейный слой
- Сигмовидный слой

CNN:

model = nn.Sequential(
    #layer1
    nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=1), # Output size= 16 x224 x224
    nn.ReLU(),
    nn.Conv2d(in_channels=32, out_channels =64,kernel_size=3, stride=1, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(2,2),# Output size= 64 x 112 x 112

    #Layer 2
    nn.Conv2d(in_channels= 64, out_channels=128,kernel_size=3, stride=1, padding=1), 
    nn.ReLU(),
    nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1), 
    nn.ReLU(),
    nn.MaxPool2d(2,2), # Output size= 128 x 56 x 56

    #Layer 3
    nn.Conv2d(in_channels= 128, out_channels=256,kernel_size=3, stride=1, padding=1), 
    nn.ReLU(),
    nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1), 
    nn.ReLU(),
    nn.MaxPool2d(2,2), # Output size= 256 x 28 x 28

    # Fully connected layers 
    nn.Flatten(),
    nn.Linear(256*28*28, 1024),
    nn.ReLU(),
    nn.Linear(1024, 512),
    nn.ReLU(),
    nn.Linear(512, 5),


)
device= torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
# Note:
# stride in a cnn is resposible for the amount of movement a kernel moves over an image

6. Определение оптимизатора и выбор функции потерь

В кейсе используется оптимизатор adam, подробнее о нем можно узнать по этой ссылке

optimizer =torch.optim.Adam(params=model.parameters(), lr= 0.001)

Кросс-энтропийные потери — это тип функции потерь, наиболее часто используемый в алгоритмах классификации. В классификации каждая метка класса имеет вероятность 1 и нулевую вероятность для других классов. Модель сообщает нам вероятность того, что входное изображение принадлежит каждой метке класса, а потеря перекрестной энтропии вычисляет разницу в распределении вероятностей.

def calc_loss(prediction ,actual):
  loss = nn.CrossEntropyLoss()

  return loss(prediction, actual)

7. Обучение модели

Теперь, наконец, пришло время обучить модель! Модель обучается на 30 эпох с записью потерь и точности в каждую эпоху.

def train_model(model, optim, train_data, epochs=30):
  '''
  model: the model to be trained
  optim: The optimizer chosen in this case Adam
  train_data: training data
  '''

  #training sequence
  epoch_loss = []
  epoch_accuracy =[]

  for epoch in range(epochs):

    for img, label  in train_data:
      #transforming images to tensor and resizing 
      # img =prepare_imgs(img) images already transformed to tensor
      img =img.to(device)

      label  = label.to(device)

      #resetting gradients
      optim.zero_grad()

      #running img through model
      preds = model(img)

      #calculate loss
      #cross entropy loss a reducing loss is good as the closer to zero the more accurate
      _loss = calc_loss(preds, label)
      loss =_loss.item()

      #Calclulate accuracy
    
      acc = accuracy(preds, label)

      #loss step backwards 
      _loss.backward()
      optim.step()

    # results 
    epoch_loss.append(_loss.detach().cpu().numpy())
    epoch_accuracy.append(acc.detach().cpu().numpy())
    print( 'Epoch: {}'.format(epoch+1), 'loss: {:.4f}'.format(loss), 'accuracy: {:.2f}'.format(acc) )

  return epoch_loss, epoch_accuracy
loss, acc= train_model(model, optimizer, train_loader)

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

def validation(model, optim, val_data, epochs=30):

  val_loss =[]
  val_accuracy =[]

  
  for epoch in range(epochs):

    for img, label  in val_data:
  
      img =img.to(device)

      label  = label.to(device)

      #running img through model
      preds = model(img)

      #calculate loss
      loss = calc_loss(preds, label)
      
      #Calclulate accuracy
      
      acc = accuracy(preds, label)
     

    val_accuracy.append(acc.detach().cpu().numpy())
    val_loss.append(loss.detach().cpu().numpy())
    print( 'Epoch {}'.format(epoch+1), 'loss {:.2f}'.format(loss), 'accuracy {:.2f} '.format(acc))

  return val_loss, val_accuracy
val_loss, val_acc = validation(model, optimizer, val_loader)

Сохраните обученную модель, чтобы ее можно было загружать снова и снова.

torch.save(model.state_dict(), 'Flowers_class.pth')

8. Оценка модели

Строим диаграмму потерь при обучении и валидации, чтобы лучше понять, что происходит.

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

Готово! Поздравляем, вы только что построили работающую CNN!