Учебное пособие по передаче обучения с использованием CNN глубокого обучения Resnet-18 для достижения точности проверки 97%.

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

В этом уроке я собираюсь рассмотреть применение одного из таких современных алгоритмов, Resnet-18, для прогнозирования животных из 10 классов с использованием популярной среды глубокого обучения PyTorch, разработанной исследовательской лабораторией искусственного интеллекта Facebook. Я покажу, как трансферное обучение может достичь очень высокой точности (97% здесь, на тестовых данных).

В этом уроке я пройду следующие шаги:

Импорт и форматирование
Загрузка и предварительная обработка данных
Обучение и визуализация модели
Визуализация результатов

Импорт и форматирование

import os
import numpy as np
import torch
from torch import nn
from torch import optim 
from torch.autograd import Variable
import torch.utils.data as data
import torchvision
from torchvision import models
import pandas as pd
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils
from torchvision.datasets import ImageFolder

res_18_model = models.resnet18(pretrained=True)

Обратите внимание, что в конце я импортирую модель resnet18 из PyTorch и выбираю pretrained=True. Это связано с тем, что для трансферного обучения мы обычно используем (некоторые или большинство) весов, полученных алгоритмом глубокого обучения на других данных. Затем мы модифицируем несколько слоев, чтобы применить их к нашему случаю.

T = transforms.Compose([
     transforms.Resize((224,224)),
     transforms.ToTensor(),
     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

Нам нужно скачать изображения с kaggle и распаковать их. Затем мы преобразуем набор данных в стандартизированный формат, используя встроенную платформу преобразований PyTorch. Это позволяет нам изменить размер изображения до определенного стандартного формата (в данном случае 224x224) и выполнить другую предварительную обработку. Для моделей трансферного обучения важно стандартизировать входные данные, чтобы они соответствовали тому, на чем обучалась исходная модель (насколько это возможно). В противном случае вы рискуете получить ужасную производительность (по опыту :)). Для Resnet-18 официальная документация PyTorch предлагает transform.Normalize([0,485, 0,456, 0,406], [0,229, 0,224, 0,225]). Первый массив, состоящий из 3 элементов, соответствует среднему значению по 3 каналам RGB. Второй массив, также состоящий из 3 элементов, соответствует стандартному отклонению по тем же 3 каналам.

Загрузка и предварительная обработка данных

#download the dataset from here: https://www.kaggle.com/alessiocorrado99/animals10 

dataset = ImageFolder('./archive/raw-img/', transform=T)
train_set, val_set = torch.utils.data.random_split(dataset, [int(len(dataset)*.8), len(dataset)-int(len(dataset)*.8)])
train_loader = torch.utils.data.DataLoader(train_set, batch_size=64)
test_loader = torch.utils.data.DataLoader(val_set, batch_size=64)

Затем мы загружаем данные из соответствующей папки изображений, применяем преобразования и случайным образом разделяем на 80% обучения и 20% тестирования. Теперь мы готовы к обучению (почти!) Но есть проблема… Если вы посмотрите на архитектуру Resnet-18, она выглядит довольно сложной — это только первые несколько слоев. Всего есть 18 слоев, выполняющих различные действия (свертка, пакетная норма, релу, макспул и т. д.).

Однако, если вы посмотрите на последний слой, он имеет 1000 признаков. Это связано с тем, что модель Resnet-18 изначально была обучена прогнозировать 1000 классов. Это не соответствует нашему набору данных о животных, который имеет 10 классов.

Поэтому нам нужно изменить как минимум этот последний слой. Это оказывается довольно просто, как показано ниже:

res_18_model.fc= nn.Linear(512, 10)

Все, теперь мы можем начать тренировку!

Обучение и оценка модели

model=res_18_model
if(torch.cuda.is_available()==True):
    model=res_18_model.cuda()
    
optimiser=optim.SGD(model.parameters(),lr=1e-2)
loss=nn.CrossEntropyLoss()

У меня есть графический процессор NVIDIA, поэтому я делаю его доступным для обучения. Две важные переменные здесь — оптимизатор и функции потерь. Для оптимизатора я использую функцию стохастического градиентного спуска от PyTorch. Я использую здесь перекрестную энтропию, так как это обычно используемая функция потерь для прогнозирования нескольких классов.

# My training and validation loops
nb_epochs = 5
acc_tot=np.zeros(nb_epochs)
for epoch in range(nb_epochs):
    losses = list()
    accuracies = list()
    model.train()     
    for batch in train_loader: 

        x,y = batch
        if(torch.cuda.is_available()==True):
            x=x.cuda()
            y=y.cuda()        


        # 1 forward
        l = model(x) # l: logits

        #2 compute the objective function
        J = loss(l,y)

        # 3 cleaning the gradients
        model.zero_grad()
        # optimiser.zero_grad()
        # params.grad.zero_()

        # 4 accumulate the partial derivatives of J wrt params
        J.backward()

        # 5 step in the opposite direction of the gradient
        optimiser.step()



        losses.append(J.item())
        accuracies.append(y.eq(l.detach().argmax(dim=1)).float().mean())

    print(f'Epoch {epoch + 1}', end=', ')
    print(f'training loss: {torch.tensor(losses).mean():.2f}', end=', ')
    print(f'training accuracy: {torch.tensor(accuracies).mean():.2f}')


    losses = list()
    accuracies = list() 
    model.eval()
    for batch in test_loader: 
        x,y = batch
        if(torch.cuda.is_available()==True):
            x=x.cuda()
            y=y.cuda()

        with torch.no_grad(): 
            l = model(x)

        #2 compute the objective function
        J = loss(l,y)

        losses.append(J.item())
        accuracies.append(y.eq(l.detach().argmax(dim=1)).float().mean())

    print(f'Epoch {epoch + 1}',end=', ')
    print(f'validation loss: {torch.tensor(losses).mean():.2f}', end=', ')
    print(f'validation accuracy: {torch.tensor(accuracies).mean():.2f}')
    acc_tot[epoch]=torch.tensor(accuracies).mean().numpy()

Визуализация

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

def imformat(inp, title=None):
    """Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    return(inp)

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

class_names = dataset.classes
translate = {"cane": "dog", "cavallo": "horse", "elefante": "elephant", "farfalla": "butterfly", "gallina": "chicken", "gatto": "cat", "mucca": "cow", "pecora": "sheep", "scoiattolo": "squirrel", "dog": "cane", "cavallo": "horse", "elephant" : "elefante", "butterfly": "farfalla", "chicken": "gallina", "cat": "gatto", "cow": "mucca", "spider": "ragno", "squirrel": "scoiattolo"}
t_inv = {v: k for k, v in translate.items()}

Наконец, давайте визуализируем результаты!

train_loader2 = torch.utils.data.DataLoader(train_set, batch_size=9)

plt.figure(figsize=(15, 13))

inputs, classes = next(iter(train_loader2))
preds=model(inputs.cuda()).argmax(dim=1)


for i in range(0,9):
    ax = plt.subplot(3, 3, i + 1)
    img=imformat(inputs[i])
    
    plt.imshow((img))

    try:
        plt.title('True:'+str(t_inv[class_names[classes[i]]])+'    Pred:'+str(t_inv[class_names[preds[i]]]))
    except:
        plt.title('True:'+str(translate[class_names[classes[i]]])+'    Pred:'+str(translate[class_names[preds[i]]]))
    if(i==9):
        plt.axis("off")

И успеха! Визуализация результатов правильно обученного алгоритма — это очень мощно!

Вот код на Github. Удачного обучения глубокому переносу!

Источники:

  1. https://www.youtube.com/watch?v=OMDn66kM9Qc&ab_channel=PyTorchLightning
  2. https://pytorch.org/vision/main/generated/torchvision.transforms.Normalize.html
  3. https://www.kaggle.com/alessiocorrado99/animals10 (Лицензия GPL2)

Если вы еще не являетесь участником Medium и хотите поддержать таких писателей, как я, зарегистрируйтесь по моей реферальной ссылке: https://skanda-vivek.medium.com/membership

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

Чтобы получать еженедельные обзоры данных, подпишитесь здесь!