Это пошаговое руководство по созданию классификатора изображений. Модель 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. Другими словами, изображения следует рассортировать по папкам. Например, все изображения пчел должны быть в одной папке, все изображения муравьев - в другой и т. Д.
Даем команду
- путь ко всем папкам и
- преобразования, которые мы указали на предыдущем шаге.
# 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 изображения одновременно). Мы также хотим перетасовать наши изображения, чтобы они случайным образом вводились в нашу модель ИИ.
# 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: Создание нашей модели
Чтобы модели ИИ были эффективными, их нужно обучать на большом количестве данных. Поскольку у нас не так много данных, мы хотим взять предварительно обученную модель (модель, которая ранее была обучена на многих изображениях), но адаптировать ее для распознавания наших конкретных изображений. Этот процесс называется трансферным обучением.
Модели распознавания изображений состоят из двух частей:
- сверточная часть и
- часть классификатора
Мы хотим сохранить предварительно обученную сверточную часть, но добавить наш собственный классификатор. Вот почему:
Раздел свертки / объединения нашей модели используется для распознавания функций внутри изображения. Сначала он определяет края, затем, используя края, он определяет формы и с помощью форм может идентифицировать объекты.
Но для обучения этого раздела требуется МНОГО данных - вероятно, больше, чем у нас - поэтому вместо этого мы можем использовать предварительно обученные сверточные слои по умолчанию. Эти предварительно обученные сверточные слои были обучены очень хорошо определять эти особенности, независимо от того, какое у вас изображение.
Между сверточными слоями также есть объединяющие слои, которые сокращают изображение до меньшего размера, чтобы его можно было легко ввести в наш классификатор.
Последняя часть модели - это классификатор. Классификатор берет всю информацию, извлеченную из фотографии в части свертки, и использует ее для идентификации изображения. Это часть предварительно обученной модели, которую мы хотим заменить и обучить на наших собственных изображениях. Это делает модель адаптированной для идентификации изображений, которые мы ей даем.
Мы используем библиотеку 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 )
Вот и все!