Поначалу все эти ИИ, машинное обучение и глубокое обучение звучали ужасно, как что-то вроде машинного кода. Если это ваша ситуация сейчас, то я когда-то был на ваших шоу. Поработав, я убедился, что это не так уж и страшно. Надеюсь, к концу чтения вы со мной согласитесь.

В этой статье мы рассмотрим создание классификатора изображений. Ага! Вы меня правильно поняли, классификатор изображений. Не бойтесь, мы пройдем все этапы и обязательно будет весело.

Мы будем модулем PyTorch nn для обучения и тестирования нашей модели на данных CIFAR-10. CIFAR означает Канадский институт перспективных исследований. И 10 означает 10 классов изображений, включенных в набор данных, а именно: самолет, автомобиль, птица, кошка, олень, собака, лягушка, лошадь, корабль, грузовик. Таким образом, наша модель сможет работать с этими предметами. Вы можете скачать набор данных с сайта kaggle.

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

Есть также несколько предварительно обученных моделей. На данный момент наиболее производительной моделью, обученной и протестированной на наборе данных CIFAR-10, является GPipe с точностью 99,0%. Цель этой статьи не в том, чтобы превзойти эту точность, мы просто хотим запачкать руки созданием собственного nn.

Когда дело доходит до построения nn, в основном используются три этапа;

  • Подготовка и изучение наших данных
  • Сборка и обучение и
  • Тестирование.

Готовься и исследуй

Важно, чтобы мы правильно понимали, с какими данными мы работаем. Затем мы должны разделить данные на две части, одну для обучения, другую для тестирования. Обычно это соотношение составляет 80% к 20%, но это зависит от вас.

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

import torchvision
import torchvision.transforms as transforms

Затем создаем комплекты и загрузчики

data_dir = './data' # directory of the cifar-10 data you downloaded
transform = transforms.Compose([transforms.RandomHorizontalFlip(), transforms.ToTensor()])

trainset = torchvision.datasets.CIFAR10(root=data_dir, train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(trainset, batch_size=5, shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root=data_dir, train=False, download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(testset, batch_size=5, shuffle=False, num_workers=2)
# The 10 classes in the dataset
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

Теперь давайте исследуем данные для обучающего и тестового наборов ...

# to get the length of the taindata
print(len(trainset))
# get sample of train data and see length
sample = next(iter(trainset))
print(len(sample))
# get the image and it's label
image, label = sample
print(type(image))
print(type(label))
# view image shape
image.shape
# length of test data
print(len(testset))

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

import matplotlib.pyplot as plt
import numpy as np
# train_loader images
dataiter = iter(train_loader)
batch = next(dataiter)
labels = batch[1][0:5]
images = batch[0][0:5]
for i in range(5):
    print(classes[labels[i]])
    image = images[i].numpy()
    plt.imshow(np.rot90(image.T, k=3))
    plt.show()
# test_loader images
dataiter = iter(test_loader)
batch = next(dataiter)
labels = batch[1][0:5]
images = batch[0][0:5]
for i in range(5):
    print(classes[labels[i]])
    image = images[i].numpy()
    plt.imshow(np.rot90(image.T, k=3))
    plt.show()

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

Строй и тренируйся

Теперь, когда мы изучили наши данные, давайте построим нашу модель. Для освежения: nn - это комбинация разных слоев, образующих архитектуру ur. Вы можете изучить различные слои torch.nn здесь. Это действительно длинная тема, если нам придется обсуждать здесь различные уровни и их функции, поэтому я рекомендую вам взглянуть на эту ссылку, чтобы узнать о них больше. В остальном, чтобы не усложнять, я поделюсь с вами своей архитектурой (после некоторых улучшений). Не стесняйтесь изменять и исследовать.

import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, 3)
        self.conv2 = nn.Conv2d(32, 64, 3)
        self.conv3 = nn.Conv2d(64, 128, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(128 * 2 * 2, 128)
        self.fc2 = nn.Linear(128, 64)
        self.fc3 = nn.Linear(64, 32)
        self.fc4 = nn.Linear(32, 10)
        self.dropout1 = nn.Dropout(p=0.2, inplace=False)
    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.dropout1(x)
        x = self.pool(F.relu(self.conv2(x)))
        x = self.dropout1(x)
        x = self.pool(F.relu(self.conv3(x)))
        x = self.dropout1(x)
        x = x.view(-1, 128 * 2 * 2)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = self.fc4(x) #output layer
        
        return x

Теперь давайте создадим экземпляр класса модели.

model = Net()

Я уверен, что вы слышали о GPU. Если не все еще круто. Графический процессор (графический процессор) в основном используется в игровых компьютерах для обеспечения высокой мощности обработки видео. Мы хотим использовать этот мощный ресурс для обучения нашей модели. На самом деле использование графического процессора не является обязательным. ЦП работает, только он намного медленнее и требует больше времени на обучение и тестирование. Такое количество времени также может сказаться на характеристиках модели.

Поэтому мы проверим, присутствует ли GPU на текущей машине, на которой выполняется код, в противном случае мы используем CPU.

device = 'cuda' if torch.cuda.is_available() else 'cpu'
model.to(device)

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

criterion = nn.CrossEntropyLoss()
# Stochastic gradient descent: to perform parameter update for each training sample
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

Теперь можно приступить к обучению модели.

epoch_losses = [] # using this to record the training loss so that we can plot it against the epoch
model.train()
for epoch in range(20):
    running_loss = 0.0
    saved_loss = 0.0
    for i, data in enumerate(train_loader, 0):
        # get inputs and labels and convert to appropriate device
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)
        
        # zero the parameter gradients
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        # print stats
        running_loss += loss.item()
        if i % 2000 == 1999:  # print every 2000 mini-batches
            print('%d, %5d| loss: %.3f' %(epoch+1, i+1, running_loss/2000))
            saved_loss = running_loss
            running_loss = 0.0
    epoch_losses.append(saved_loss/10000)
print('Training done!')  # print when finished training

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

Давайте теперь построим график, показывающий наши потери в обучении и эпоху.

epochs = range(1,21)
plt.plot(epochs, epoch_losses, 'g', label='Training loss')
plt.title('Trainingloss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

Позвольте мне показать вам пример того, как должна выглядеть кривая:

Обратите внимание, как кривая приближается к нулю. Чем меньше убыток, тем лучше модель может предсказать. Обычно самая низкая точка кривой - это место, где модель может хорошо прогнозировать. Если кривая снова начинает расти, это означает, что наша модель переобучена. Обычно в тех случаях, когда кривая начинает расти обратно, мы корректируем количество эпох, чтобы наша модель обучалась только количеству эпох, в которых проклятие наименьшее. Таким образом, мы можем получить хорошую производительность (точность) для нашей модели.

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

Протестируйте модель

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

total = 0
correct = 0
model.eval() # out our model in evaluation mode
with torch.no_grad():
    for data in test_loader:
        images, labels = data
        images, labels = images.to(device), labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
print('Accuracy: %d %%' % (100 * correct / total))

Со своей стороны, после запуска теста я получил точность 75%. Это неплохая точность. Но это обязательно можно улучшить.

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

dataiter = iter(testloader)
images, labels = dataiter.next()
print('Truth: ', ' '.join('%5s' % classes[labels[j]] for j in range(5)))
outputs = net(images)
# Output prediction
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(5)))

Теперь вы можете удобно сохранить модель.

checkpoint = {'model': model,
            'state_dict': model.state_dict(),
            'optimizer' : optimizer.state_dict()}
torch.save(checkpoint, 'checkpoint.pth')

Если вы хотите загрузить модель и повторно использовать в другом месте, вы можете использовать функцию torch.load. Он принимает в качестве параметра файл контрольной точки модели.

Вот и все…

Дополнительный

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

Вы можете подготовить облачный сервер, на котором вы будете загружать свою модель, и у вас есть простая фляга RESTFul или fastapi API, который получает изображения, а затем загружает изображение в вашу модель, получает прогноз и отправляет ответ пользователю.

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

Надеюсь, вы больше не увидите слишком странных вещей в области глубокого обучения.

Большое спасибо за чтение.

ЧАСТЬ 2

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

использованная литература