Это пошаговое руководство по созданию классификатора изображений. Модель AI сможет научиться маркировать изображения. Я использую Python и Pytorch.

Шаг 1. Импортируйте библиотеки

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

Обычно мы импортируем все библиотеки в начале программы.

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

# Import Libraries
import torch
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torchvision.models as models
import torch.nn as nn
import torch.optim as optim
import numpy as np
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

Шаг 2. Определите преобразования

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

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

Эти преобразования выполняются с помощью библиотеки torchvision.transforms. Лучший способ понять преобразования - прочитать документацию здесь. Но я кратко расскажу, что делает каждая команда.

  • transforms.Compose позволяет нам составлять несколько преобразований вместе, поэтому мы можем использовать более одного преобразования.
  • transforms.Resize ((255)) изменяет размер изображения так, чтобы самая короткая сторона имела длину 255 пикселей. Другая сторона масштабируется для сохранения соотношения сторон изображения.
  • transforms.CenterCrop (224) обрезает центр изображения, так что это квадратное изображение размером 224 на 224 пикселя.

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

  • transforms.ToTensor () преобразует наше изображение в числа. Как оно это делает?
  • Он разделяет три цвета, из которых состоит каждый пиксель нашего изображения: красный, зеленый и синий. По сути, это превращает одно изображение в три изображения (одно с красным оттенком, одно зеленое, одно синее).
  • Затем он преобразует пиксели каждого тонированного изображения в яркость их цвета от 0 до 255. Эти значения делятся на 255, поэтому они могут находиться в диапазоне от 0 до 1. Наше изображение теперь является тензором факела (a структура данных, в которой хранится множество чисел).
  • transforms.Normalize (mean = [0,485, 0,456, 0,406], std = [0,229, 0,224, 0,225]) вычитает среднее из каждого значения и затем делит на стандартное отклонение.
  • Мы будем использовать предварительно обученную модель, поэтому нам нужно использовать средства и стандартные отклонения, указанные в Pytorch. Для каждого изображения RGB есть три значения среднего и стандартного отклонения.
# Specify transforms using torchvision.transforms as transforms
# library
transformations = transforms.Compose([
    transforms.Resize(255),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

Шаг 3. Импортируйте наши данные и поместите их в DataLoader.

Наконец, мы можем импортировать наши изображения в программу. Мы используем библиотеку torchvision.datasets.

Об этом читайте здесь.

Мы указываем два разных набора данных: один для изображений, на которых ИИ учится (обучающий набор), а другой - для набора данных, который мы используем для тестирования модели ИИ (набор проверки).

Команда datasets.ImageFolder () ожидает, что наши данные будут организованы следующим образом: root / label / picture.png. Другими словами, изображения следует рассортировать по папкам. Например, все изображения пчел должны быть в одной папке, все изображения муравьев - в другой и т. Д.

Даем команду

  1. путь ко всем папкам и
  2. преобразования, которые мы указали на предыдущем шаге.
# Load in each dataset and apply transformations using
# the torchvision.datasets as datasets library
train_set = datasets.ImageFolder("root/label/train", transform = transformations)
val_set = datasets.ImageFolder("root/label/valid", transform = transformations)

Затем мы хотим поместить наши импортированные изображения в Dataloader.

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

Мы указываем, сколько изображений мы хотим одновременно, в качестве нашего batch_size (поэтому 32 означает, что мы хотим получить 32 изображения одновременно). Мы также хотим перетасовать наши изображения, чтобы они случайным образом вводились в нашу модель ИИ.

Прочтите о DataLoader здесь.

# Put into a Dataloader using torch library
train_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=True)
val_loader = torch.utils.data.DataLoader(val_set, batch_size =32, shuffle=True)

Шаг 4: Создание нашей модели

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

Модели распознавания изображений состоят из двух частей:

  1. сверточная часть и
  2. часть классификатора

Мы хотим сохранить предварительно обученную сверточную часть, но добавить наш собственный классификатор. Вот почему:

Раздел свертки / объединения нашей модели используется для распознавания функций внутри изображения. Сначала он определяет края, затем, используя края, он определяет формы и с помощью форм может идентифицировать объекты.

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

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

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

Мы используем библиотеку torchvision.models для загрузки предварительно обученной модели. Мы можем скачать множество различных моделей, а дополнительную информацию можно найти здесь. Я выбрал модель под названием densenet161 и указал, что мы хотим, чтобы она была предварительно обучена, установив pretrained = True.

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

# Get pretrained model using torchvision.models as models library
model = models.densenet161(pretrained=True)
# Turn off training for their parameters
for param in model.parameters():
    param.requires_grad = False

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

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

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

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

Получив эти данные, мы используем библиотеку torch.nn для создания классификатора. Информацию можно найти здесь.

  • nn.Sequential может помочь нам сгруппировать несколько модулей вместе.
  • nn.Linear определяет взаимодействие между двумя слоями. Мы даем ему 2 числа, определяя количество узлов в двух слоях.
  • Например, в первом nn.Linear первый слой является входным, и мы можем выбрать, сколько чисел мы хотим во втором слое (я выбрал 1024).
  • nn.ReLU - это функция активации скрытых слоев. Функции активации помогают модели изучить сложные отношения между входом и выходом. Мы используем ReLU на всех слоях, кроме вывода.

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

  • nn.LogSoftmax - функция активации вывода. Функция softmax превращает выведенные числа в проценты для каждой метки, а функция журнала применяется для ускорения вычислений. Мы должны указать, что выходной слой является столбцом, поэтому мы устанавливаем размерность равной 1.

После создания собственного классификатора мы заменяем классификатор модели по умолчанию.

# Create new classifier for model using torch.nn as nn library
classifier_input = model.classifier.in_features
num_labels = #PUT IN THE NUMBER OF LABELS IN YOUR DATA
classifier = nn.Sequential(nn.Linear(classifier_input, 1024),
                           nn.ReLU(),
                           nn.Linear(1024, 512),
                           nn.ReLU(),
                           nn.Linear(512, num_labels),
                           nn.LogSoftmax(dim=1))
# Replace default classifier with new classifier
model.classifier = classifier

Теперь модель создана! Далее нам просто нужно его потренировать.

Шаг 5: Обучение и оценка нашей модели

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

# Find the device available to use using torch library
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Move model to the device specified above
model.to(device)

Во время обучения нам нужно определить, насколько наша модель «выключена». Чтобы оценить количество ошибок в нашей модели, мы используем nn.NLLLoss. Эта функция принимает выходные данные нашей модели, для которой мы использовали функцию nn.LogSoftmax.

Чтобы обучить нашу модель, мы берем нашу ошибку и смотрим, как мы можем скорректировать веса, на которые мы умножили наши числа, чтобы получить наименьшую ошибку. Метод расчета того, как мы корректируем наши веса и применяем его к нашим весам, называется Адам. Мы используем библиотеку torch.optim, чтобы использовать этот метод и передать ему наши параметры.

# Set the error function using torch.nn as nn library
criterion = nn.NLLLoss()
# Set the optimizer function using torch.optim as optim library
optimizer = optim.Adam(model.classifier.parameters())

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

Начнем с обучающего набора.

Сначала мы устанавливаем модель в режим обучения и используем цикл for для просмотра каждого изображения.

После перемещения изображений и меток на соответствующее устройство нам нужно очистить настройки весов, объявив optimizer.zero_grad ().

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

Затем мы можем найти необходимые корректировки, чтобы уменьшить эту ошибку, вызвав loss.backward (), и использовать наш оптимизатор для корректировки весов, вызвав optimizer.step () .

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

Переходим к набору валидации.

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

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

Наша модель использовала функцию LogSoftmax для увеличения скорости вычислений, но теперь нам нужны реальные проценты, а не проценты журнала. Поэтому мы используем torch.exp, чтобы отменить функцию журнала. Затем мы хотим увидеть, какой класс модель отнесла к нашим изображениям. .topk дает нам высший класс, который был угадан, и какой процент он угадал - нам важен только класс, поэтому мы можем игнорировать процент.

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

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

epochs = 10
for epoch in range(epochs):
    train_loss = 0
    val_loss = 0
    accuracy = 0
    
    # Training the model
    model.train()
    counter = 0
    for inputs, labels in train_loader:
        # Move to device
        inputs, labels = inputs.to(device), labels.to(device)
        # Clear optimizers
        optimizer.zero_grad()
        # Forward pass
        output = model.forward(inputs)
        # Loss
        loss = criterion(output, labels)
        # Calculate gradients (backpropogation)
        loss.backward()
        # Adjust parameters based on gradients
        optimizer.step()
        # Add the loss to the training set's rnning loss
        train_loss += loss.item()*inputs.size(0)
        
        # Print the progress of our training
        counter += 1
        print(counter, "/", len(train_loader))
        
    # Evaluating the model
    model.eval()
    counter = 0
    # Tell torch not to calculate gradients
    with torch.no_grad():
        for inputs, labels in val_loader:
            # Move to device
            inputs, labels = inputs.to(device), labels.to(device)
            # Forward pass
            output = model.forward(inputs)
            # Calculate Loss
            valloss = criterion(output, labels)
            # Add loss to the validation set's running loss
            val_loss += valloss.item()*inputs.size(0)
            
            # Since our model outputs a LogSoftmax, find the real 
            # percentages by reversing the log function
            output = torch.exp(output)
            # Get the top class of the output
            top_p, top_class = output.topk(1, dim=1)
            # See how many of the classes were correct?
            equals = top_class == labels.view(*top_class.shape)
            # Calculate the mean (get the accuracy for this batch)
            # and add it to the running accuracy for this epoch
            accuracy += torch.mean(equals.type(torch.FloatTensor)).item()
            
            # Print the progress of our evaluation
            counter += 1
            print(counter, "/", len(val_loader))
    
    # Get the average loss for the entire epoch
    train_loss = train_loss/len(train_loader.dataset)
    valid_loss = val_loss/len(val_loader.dataset)
    # Print out the information
    print('Accuracy: ', accuracy/len(val_loader))
    print('Epoch: {} \tTraining Loss: {:.6f} \tValidation Loss: {:.6f}'.format(epoch, train_loss, valid_loss))

Шаг 5: Фактическое использование нашей модели

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

Сначала мы устанавливаем модель в оценочный режим.

model.eval()

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

Мы открываем изображение, изменяем его размер, сохраняя соотношение сторон, но сделав самую короткую сторону всего 255 пикселей, и обрезаем центр 224 на 224 пикселей. Затем мы превращаем изображение в массив и убеждаемся, что количество цветовых каналов является первым измерением, а не последним, путем транспонирования массива. Затем мы преобразуем каждое значение между 0 и 1 путем деления на 255. Затем мы нормализуем значения путем вычитания среднего и деления на стандартное отклонение. Наконец, мы конвертируем массив в тензор Torch и преобразуем значения в float.

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

# Process our image
def process_image(image_path):
    # Load Image
    img = Image.open(image_path)
    
    # Get the dimensions of the image
    width, height = img.size
    
    # Resize by keeping the aspect ratio, but changing the dimension
    # so the shortest size is 255px
    img = img.resize((255, int(255*(height/width))) if width < height else (int(255*(width/height)), 255))
    
    # Get the dimensions of the new image size
    width, height = img.size
    
    # Set the coordinates to do a center crop of 224 x 224
    left = (width - 224)/2
    top = (height - 224)/2
    right = (width + 224)/2
    bottom = (height + 224)/2
    img = img.crop((left, top, right, bottom))
    
    # Turn image into numpy array
    img = np.array(img)
    
    # Make the color channel dimension first instead of last
    img = img.transpose((2, 0, 1))
    
    # Make all values between 0 and 1
    img = img/255
    
    # Normalize based on the preset mean and standard deviation
    img[0] = (img[0] - 0.485)/0.229
    img[1] = (img[1] - 0.456)/0.224
    img[2] = (img[2] - 0.406)/0.225
    
    # Add a fourth dimension to the beginning to indicate batch size
    img = img[np.newaxis,:]
    
    # Turn into a torch tensor
    image = torch.from_numpy(img)
    image = image.float()
    return image

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

# Using our model to predict the label
def predict(image, model):
    # Pass the image through our model
    output = model.forward(image)
    
    # Reverse the log function in our output
    output = torch.exp(output)
    
    # Get the top predicted class, and the output percentage for
    # that class
    probs, classes = output.topk(1, dim=1)
    return probs.item(), classes.item()

Наконец, мы хотим отобразить изображение. Мы превращаем изображение обратно в массив и ненормализуем его, умножая на стандартное отклонение и добавляя среднее значение. Затем мы используем библиотеку matplotlib.pyplot для построения изображения.

# Show Image
def show_image(image):
    # Convert image to numpy
    image = image.numpy()
    
    # Un-normalize the image
    image[0] = image[0] * 0.226 + 0.445
    
    # Print the image
    fig = plt.figure(figsize=(25, 4))
    plt.imshow(np.transpose(image[0], (1, 2, 0)))

Теперь мы можем использовать все эти функции, чтобы распечатать предположение нашей модели и узнать, насколько она достоверна!

# Process Image
image = process_image("root/image1234.jpg")
# Give image to model to predict output
top_prob, top_class = predict(image, model)
# Show the image
show_image(image)
# Print the results
print("The model is ", top_prob*100, "% certain that the image has a predicted class of ", top_class  )

Вот и все!