0 шт.
Выбрать страницу

Как решить задачу классификации с помощью глубокого обучения?

Задачи классификации

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

Пример, который я привожу будет решен с помощью PyTorch. Мы будем распознавать изображения.

Этот ноутбук доступен в моем Github репозитории:

git clone https://github.com/andreiliphd/reinforcement-content.git

Если нет Git, то его нужно установить.

Linux:

sudo apt-get update
sudo apt-get install git

Windows: скачайте Git с сайта git-scm.com.

Если вы нашли ошибку на сайте, ее можно исправить самостоятельно сделав Pull Request в Git.

Этапы решения задачи

  1. Загрузка и аугментация данных.
  2. Декларирование модели.
  3. Инстанциирование модели.
  4. Инстанциирование функции потерь(лосс).
  5. Инстанциирование оптимизатора.
  6. Создание тренировочной петли(лупа).

Загрузка и аугментация данных

Типы загрузки:

  1. Ручная
  2. Автоматическая(даталоадер)

Преимущества даталоадера:

  • Формирует батчи. Батч — это несколько образцов данных.
  • Формирует метки. Метки — это правильные ответы, на которых учится нейронная сеть.
  • Позволяет загружать данные, которые не помещаются в память вашего компьютера, так как данные загружаются по частям.
  • Не надо вручную менять форму данных. Форма данных цветной картинки (224, 224, 3), форма данных для PyTorch (1, 3, 224, 224), то есть (количество батчей, высота картинки, ширина картинки, количество каналов).
  • Стандартизирует, то есть приводит к данным между 0 и 1 и нормализует данные.
  • Конвертирует в тензор.
  • Делает аугментацию, то есть поворачивают, зеркально отображает, добавляет шум в ваши данные. Очень полезная функция, которая улучшает качество модели. Вы можете настроить аугментацию.
  • Соединяет отдельные элементы датасета в единый тензор.
  • Также принято делить датасет или другими словами Ваши данные на тренировочный, тестовой и валидационный. Но для упрощения мы этого делать не будем.

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

Загружаем нужные нам библиотеки.

In [1]:
import matplotlib.pyplot as plt
import numpy as np
import torch
from torchvision import transforms
from torchvision import datasets

Инстанциируем аугментацию данных:

  1. Меняем размер изображения.
  2. Вырезаем центр изображения.
  3. Конвертируем в тензор.
  4. Нормализируем данные.
In [2]:
image_transforms = {'train': transforms.Compose([transforms.Resize(224),
                                     transforms.CenterCrop(224),
                                     transforms.ToTensor(),
                                     transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                                          std=[0.229, 0.224, 0.225])]),
                   }
loaders = {'train': torch.utils.data.DataLoader(datasets.ImageFolder('datasets/images/', 
                                                                            transform=image_transforms['train']),
                                                        batch_size=128, shuffle=True)}

Мы взяли датасет из Kaggle под названием "Oil Storage Tanks".

In [3]:
show_transforms = transforms.Compose([transforms.Resize(224),
                                     transforms.CenterCrop(224),
                                     transforms.ToTensor()
                                     ])
                   
show_loaders = torch.utils.data.DataLoader(datasets.ImageFolder('datasets/images/', 
                                                                transform=show_transforms), 
                                           batch_size=128, shuffle=True)


def imshow(img):
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))

dataiter = iter(show_loaders)
images, _ = dataiter.next()

fig = plt.figure(figsize=(20, 4))
plot_size=20
for idx in np.arange(plot_size):
    ax = fig.add_subplot(2, plot_size/2, idx+1, xticks=[], yticks=[])
    imshow(images[idx])

show_transforms отвечает за аугментацию. datasets.ImageFolder отвечает за доставку одного экземпляра данных и за метки. show_loaders отвечает за загрузку и делает итератор, который мы будем использовать когда перейдем к процессу тренировки модели.

Декларируем модель

In [4]:
import torch.nn as nn
import torch.nn.functional as F

class NeuralNet(nn.Module):
    def __init__(self):
        super(NeuralNet, self).__init__()
        self.c1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3)
        self.c2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3)
        self.c3 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3)
        self.fc1 = nn.Linear(in_features=6272, out_features=512)
        self.fc2 = nn.Linear(in_features=512, out_features=256)
        self.fc3 = nn.Linear(in_features=256, out_features=412)
        self.fc4 = nn.Linear(in_features=412, out_features=2)
        
    def forward(self, x):
        x = F.relu(F.max_pool2d(self.c1(x), 3))
        x = F.relu(F.max_pool2d(self.c2(x), 3))
        x = F.relu(F.max_pool2d(self.c3(x), 3))
        x = x.view(x.size(0), -1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = self.fc4(x)

        return x

torch мы уже импортировали. Но здесь мы присваиваем короткие имена вложенным библиотекам, чтобы было удобнее получать к ним доступ и для повышения читабельности кода.

import torch.nn as nn
import torch.nn.functional as F
class NeuralNet(nn.Module):
    ### TODO: choose an architecture, and complete the class
    def __init__(self):
        pass

    def forward(self, x):
        pass
        return x

Это класс, который мы создаем для нашей нейронной сети. Классы нужны, грубо говоря, для организации кода, для его структурирования. Мы наследуем от nn.Module его функционал. __init__ — инициализирует класс. forward — определяет функционал Вашей нейронной сети. Просматривая forward вы можете понять, логику операций нейронной сети. Последовательность совершаемых операций нужно просматривать в forward.

        self.c1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3)

Здесь мы инстанцируем сверточной слой. Класс должен быть инстанциирован для его использования. self.c1 содержит инстанс класса nn.Conv2d. in_channels=3 — количество каналов изображения, так как оно у нас цветное, то у нас три канала RGB(красный, зеленый, черный). out_channels=32 — количество выходных каналов, цифра должна соответствовать в следующем далее nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3) слое. Как Вы видите in_channels=32 в следующем слое — это очень важно, происходит операции с многомерным массивом и как вы знаете из линейной алгебры форма данных должна соответствовать, иначе операция не определена. kernel_size=3 — размер окна с помощью которого сверточный слой анализирует изображение. Инструкцию по nn.Conv2d можно найти здесь https://pytorch.org/docs/stable/nn.html#conv2d.

        self.fc4 = nn.Linear(in_features=412, out_features=2)

Линейный слой, самый простой. Рассчитывается по формуле wx+b, где w — веса, который инициализируется случайным образом, b — смещение, тоже случайной число, x — входные данные. w и b оптимизируются в ходе обучения с помощью оптимизатора. Как и со сверточным слоем, здесь происходит инстанциирование класса, который мы будем использовать в forward.

Обратите внимание на последнюю строчку в __init__. self.fc4 = nn.Linear(in_features=412, out_features=2). out_features=2 — это количество категорий ваших изображений. То есть, если у вас два типа изображений, то значение здесь должно быть 2. Если 100 то 100.

Рассматриваем forward:

def forward(self, x):
    ## Define forward behavior
    x = F.relu(F.max_pool2d(self.c1(x), 3))
    x = F.relu(F.max_pool2d(self.c2(x), 3))
    x = F.relu(F.max_pool2d(self.c3(x), 3))
    x = x.view(x.size(0), -1)
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = F.relu(self.fc3(x))
    x = self.fc4(x)

    return x

Конкретно эту строчку:

    x = F.relu(F.max_pool2d(self.c1(x), 3))

self.c1(x) использует инстанс класса nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3).

Рассматриваем F.max_pool2d(self.c1(x), 3). F.max_pool2d() — это уже готовая функция, доступная для использования. Функция, используя окно, максимальное значение пикселей. То есть, это как дополнительный фильтр, который вычленяет важное.

F.relu(F.max_pool2d(self.c1(x), 3)). F.relu() — это тоже готовая функция активации ReLU. Смысл работы ее простой, ноль, если значение пикселя меньше ноля и само значение, если больше.

Далее идет x = x.view(x.size(0), -1) — это так называемый Flatten слой, который Вы можете встретить в других библиотеках глубокого обучения. x.size(0) — это количество батчей. Остальное просто все распрямляется в единую форму данных. Например, если у вас было [1, 3, 254, 224], то станет [1, 7168]. Как мы видим в следующем слое входной канал равен 6272 self.fc1 = nn.Linear(in_features=6272, out_features=512). Зачем нужно распрямление? Это нужно, чтобы линейный слой self.fc1 = nn.Linear(in_features=7168, out_features=512) смог принять данные, так как требуется специальный формат для каждого типа слоя, который Вы можете найти в инструкции на официальном сайте PyTorch. Инструкция для линейного слоя находится здесь https://pytorch.org/docs/stable/nn.html#linear.

Важно, обратить внимание на предпоследнюю строчку в x = self.fc4(x) — не используйте функцию активации в конце в данном случае.

Не используйте Softmax при классификации изображений — это существенно замедляет работу сети. Softmax встроен в CrossEntropyLoss.

In [5]:
model = NeuralNet()

Если у вас есть GPU переносим все на видеокарту.

In [6]:
if torch.cuda.is_available():
    model = model.cuda()

Инстанциирование функции потерь(лосс)

Определяем функцию потерь — функция потерь определает насколько наши данные отличаются от того, что производит наша нейронная сеть. В данном случае, мы используем инстанс torch.nn.CrossEntropyLoss(), который идеально подходит для задач классификации изображений. Инструкцию по torch.nn.CrossEntropyLoss() можно найти по адресу https://pytorch.org/docs/stable/nn.html#crossentropyloss.

torch.nn.CrossEntropyLoss - это обертка для torch.nn.NLLLoss, поэтому иногда лучше использовать torch.nn.NLLLoss.

In [7]:
criterion = torch.nn.CrossEntropyLoss()

Инстанциирование оптимизатора

In [8]:
optimizer = torch.optim.Adam(model.parameters(), lr=0.0003)

lr = 0.0003 - это коэффициент обучения, который определяет насколько оптимизатор оптимизирует нашу нейронную сеть. Он это делает маленькими шагами.

Не думайте, что чем меньше коэффициент обучения, тем более точный будет прогноз. Или чем большей коэффициент обучения, тем быстрее вы обучите модель. Это не всегда правда. Посмотрите эту золотую статью, в которой приведены наиболее эффективные значения коэффициента обучения https://medium.com/octavian-ai/which-optimizer-and-learning-rate-should-i-use-for-deep-learning-5acb418f9b2.

Создание тренировочной петли(лупа)

In [9]:
for epoch in range(10):
    for data, label in loaders['train']:
        if torch.cuda.is_available():
            data, label = data.cuda(), label.cuda()
        output = model(data)    
        loss = criterion(output, label)
        print('Epoch: ', epoch, 'Loss: ', loss.item())
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
Epoch:  0 Loss:  0.6889708638191223
Epoch:  1 Loss:  0.6411091685295105
Epoch:  2 Loss:  0.5819492936134338
Epoch:  3 Loss:  0.5292767882347107
Epoch:  4 Loss:  0.5352854132652283
Epoch:  5 Loss:  0.547598123550415
Epoch:  6 Loss:  0.5261277556419373
Epoch:  7 Loss:  0.5065215826034546
Epoch:  8 Loss:  0.5018476843833923
Epoch:  9 Loss:  0.5041195750236511

Обучение

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

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

In [ ]: