Создание моделей классификации изображений с нуля с использованием TensorFlow, Keras и Transfer Learning.

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

Постановка задачи и понимание данных

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

Мы будем использовать этот набор данных о животных от Kaggle. Он состоит из 3000 изображений, поровну разделенных между кошками, собаками и пандами.

Сначала мы загрузим набор данных:

! kaggle datasets download -d ashishsaxena2209/animal-image-datasetdog-cat-and-panda
import os
data= ’/content/animals/animals’
folders= os.listdir(data)
print(folders)
[‘cats’, ‘panda’, ‘dogs’]

Теперь давайте разделим данные на обучающие и тестовые наборы данных:

import cv2
from imutils import paths
import numpy as np
image_train=[]
image_labels=[]
image_names=[]
size=64,64
for(i, imgpath) in enumerate(list(paths.list_images(data))):
    img=cv2.imread(imgpath)
    label=imgpath.split(os.path.sep)[-1].split("_")[0]
    img = cv2.resize(img, size)
    image_train.append(img)
    image_labels.append(label)#Train — Test Split

X=np.array(image_train)
Y=np.array(image_labels)
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
import keras
from keras.utils.np_utils import to_categorical
le=LabelEncoder()
Y=le.fit_transform(Y)
Y=to_categorical(Y, num_classes=3)
X_train, X_val, Y_train, Y_val= train_test_split(X,
                                                 Y,
                                                 test_size= 0.25, 
                                                 random_state= 40)

Мы разделили данные на 75% данных обучения и 25% данных тестирования.

Классификатор KNN для классификации изображений

K-ближайшие соседи (KNN) — это контролируемый алгоритм машинного обучения, который определяет класс объекта на основе K ближайших к нему объектов в соответствии с заданной пользователем метрикой расстояния. Параметры метрики расстояния включают евклидово расстояние, расстояние Хэмминга, расстояние Манхэттена и расстояние Минковского. Метки определяются большинством голосов K соседей.

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

from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report
model = KNeighborsClassifier(n_neighbors=5)
model.fit(X_train, Y_train)
acc = model.score(X_val, Y_val)
print("Raw pixel accuracy: {:.2f}%".format(acc * 100))
print(classification_report(Y_val, model.predict(X_val), target_names=le.classes_))
################################################
Raw pixel accuracy: 38.00%
precision recall f1-score support
cats 0.43 0.60 0.50 252
dogs 0.37 0.42 0.39 245
panda 0.89 0.12 0.22 253
micro avg 0.43 0.38 0.40 750
macro avg 0.56 0.38 0.37 750
weighted avg 0.56 0.38 0.37 750
samples avg 0.38 0.38 0.38 750

Наш классификатор достиг точности 38%, а панды правильно идентифицировались почти в 89% случаев. Это может быть связано с тем, что панды бывают только черно-белыми, а собаки и кошки могут быть разных цветов. Похоже, что KNN не смог очень хорошо различать кошек и собак, поскольку каждая из них достигла точности 43% и 37% соответственно.

Tensorflow против Keras для классификации изображений

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

Ниже приведено учебное пособие с использованием только TensorFlow от Shubham Panchal, чтобы стать человеком.ai. Не стесняйтесь следовать за этой записной книжкой Google Colab:

import tensorflow as tf
import tensorflow_datasets as tfds
image_dataset= ”rock_paper_scissors”
dataset= tfds.load(name=image_dataset, split=tfds.Split.TRAIN)
dataset= dataset.shuffle(1024).batch(32)
def conv2d(inputs, filters, stride_size):
    out= tf.nn.conv2d(inputs, 
                      filters, 
                      strides=[1, stride_size, stride_size, 1], 
                      padding='SAME')
    return tf.nn.leaky_relu(out , alpha=0.2)
def maxpool(inputs, pool_size, stride_size):
    return tf.nn.max_pool2d(inputs, 
                            ksize=[1, pool_size, pool_size, 1], 
                            padding='VALID', 
                            strides=[1,stride_size, stride_size, 1])
def dense(inputs, weights):
    x = tf.nn.leaky_relu(tf.matmul(inputs, weights), alpha=0.2)
    return tf.nn.dropout(x, rate=0.5)
output_classes = 3
initializer = tf.initializers.glorot_uniform()
def get_weight(shape, name):
    return tf.Variable(initializer(shape), 
                       name=name, 
                       trainable=True, 
                       dtype=tf.float32)
shapes = [
 [3, 3, 3, 16],
 [3, 3, 16, 16],
 [3, 3, 16, 32],
 [3, 3, 32, 32],
 [3, 3, 32, 64],
 [3, 3, 64, 64],
 [3, 3, 64, 128],
 [3, 3, 128, 128],
 [3, 3, 128, 256],
 [3, 3, 256, 256],
 [3, 3, 256, 512],
 [3, 3, 512, 512],
 [8192, 3600],
 [3600, 2400],
 [2400, 1600],
 [1600, 800],
 [800, 64],
 [64, output_classes],
]
weights = []
for i in range(len(shapes)):
    weights.append(get_weight(shapes[i], 'weight{}'.format(i)))
def model(x):
    x= tf.cast(x, dtype=tf.float32)
    c1= conv2d(x, weights[0], stride_size=1)
    c1= conv2d(c1, weights[1], stride_size=1)
    p1= maxpool(c1 , pool_size=2 ,stride_size=2)
 
    c2= conv2d(p1, weights[2], stride_size=1)
    c2= conv2d(c2, weights[3], stride_size=1)
    p2= maxpool(c2, pool_size=2, stride_size=2)
 
    c3= conv2d(p2, weights[4], stride_size=1)
    c3= conv2d(c3, weights[5], stride_size=1)
    p3= maxpool(c3, pool_size=2, stride_size=2)
 
    c4= conv2d(p3, weights[6], stride_size=1)
    c4= conv2d(c4, weights[7], stride_size=1)
    p4= maxpool(c4, pool_size=2, stride_size=2)
    
    c5= conv2d(p4, weights[8], stride_size=1)
    c5= conv2d(c5, weights[9], stride_size=1)
    p5= maxpool(c5, pool_size=2, stride_size=2)
    
    c6= conv2d(p5, weights[10], stride_size=1)
    c6= conv2d(c6, weights[11], stride_size=1)
    p6= maxpool(c6, pool_size=2, stride_size=2)
    
    flatten= tf.reshape(p6, shape=(tf.shape(p6)[0], -1))
    
    d1= dense(flatten , weights[12])
    d2= dense(d1, weights[13])
    d3= dense(d2, weights[14])
    d4= dense(d3, weights[15])
    d5= dense(d4, weights[16])
    
    logits= tf.matmul(d5, weights[17])
    
    return tf.nn.softmax(logits)
def loss(pred, target):
    return tf.losses.categorical_crossentropy(target, pred)
optimizer= tf.optimizers.Adam(0.001)
def train_step(model, inputs, outputs):
    with tf.GradientTape() as tape:
        current_loss = loss(model(inputs), outputs)
        grads = tape.gradient(current_loss, weights)
        optimizer.apply_gradients(zip(grads, weights))
        print(tf.reduce_mean(current_loss))
num_epochs = 100
for e in range(num_epochs):
    for features in dataset:
    image, label= features['image'], features['label']
    train_step(model, image, tf.one_hot(label, depth=3))

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

Функциональный API Keras с использованием предварительно обученных моделей

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

  1. Предварительно обработать изображение

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

def preprocess_image(sample):
    
    sample['image'] = tf.cast(sample['image'], tf.float32)
    sample['image'] = sample['image'] / 255.
    sample['image'] = tf.image.resize(sample['image'], [150, 150])
    
    return sample
dataset_train= dataset_train.map(preprocess_image)
dataset_test= dataset_test.map(preprocess_image)

2. Преобразование данных изображения в формат NumPy

Теперь, когда наши данные обработаны, мы преобразуем их в массивы NumPy.

import numpy as np
train_numpy = np.vstack(tfds.as_numpy(dataset_train))
test_numpy = np.vstack(tfds.as_numpy(dataset_test))
X_train = np.array(list(map(lambda x: x[0][‘image’], train_numpy)))
y_train = np.array(list(map(lambda x: x[0][‘label’], train_numpy)))
X_test = np.array(list(map(lambda x: x[0][‘image’], test_numpy)))
y_test = np.array(list(map(lambda x: x[0][‘label’], test_numpy)))

3. Загрузите предварительно обученную модель

Мы будем использовать библиотеку Keras для загрузки предварительно обученной модели — InceptionV3 — и использовать ее в качестве базовой модели для обучения нашей модели.

inception_model= tf.keras.applications.InceptionV3(
                                include_top=False,
                                input_shape=(150,150,3))
for layer in inception_model.layers:
    layer.trainable=False
x = inception_model.output
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(1024)(x)
x = layers.BatchNormalization()(x)
x = layers.Activation(“relu”)(x)
x = layers.Dense(5, activation=’softmax’)(x)
model = tf.keras.models.Model(inception_model.input,x)
optimizer= RMSprop(learning_rate=0.001, 
                   rho=0.9, 
                   epsilon=1e-08, 
                   decay=0.0)
model.compile(optimizer,
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

4. Дополнить данные

datagen_train = ImageDataGenerator(
    rotation_range= 10, 
    zoom_range= 0.1,
    width_shift_range= 0.1,
    height_shift_range= 0.1)
train_gen = datagen_train.flow(X_train, y_train, batch_size=256)
datagen_test = ImageDataGenerator()
test_gen = datagen_gen.flow(X_test, y_test, batch_size=256)

5. Обучить модель

history= model.fit(train_gen,
                   epochs=20,
                   validation_data=test_gen,
                   verbose=1,
                   steps_per_epoch=X_train.shape[0]/256)

Создание CNN с помощью Keras Sequential API

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

Какие проблемы возникают при построении моделей машинного обучения в масштабе? Крис Броссман из The RealReal обсуждает сценарий своей команды для больших задач.

Предварительная обработка данных

from tensorflow.keras import layers
IMG_SIZE = 180
resize_and_rescale = tf.keras.Sequential([
    layers.experimental.preprocessing.Resizing(IMG_SIZE, IMG_SIZE),
    layers.experimental.preprocessing.Rescaling(1./255)
])

Здесь мы изменяем размер и масштабируем наши изображения:

import matplotlib.pyplot as plt
image, label = next(iter(dataset_train))
plt.imshow(image)

plt.imshow(resize_and_rescale(image))

dataset_train = dataset_train.map(lambda x, y: 
                                  (resize_and_rescale(x), y))
dataset_test = dataset_test.map(lambda x, y: 
                                (resize_and_rescale(x), y))

Увеличение данных

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

data_augmentation = tf.keras.Sequential([
    layers.experimental.preprocessing.RandomFlip("horizontal_and_vertical"),
    layers.experimental.preprocessing.RandomRotation(0.2),
    layers.experimental.preprocessing.RandomZoom(0.5,0.2),
])

Посмотрим, что он сделает с нашим изображением:

image = tf.expand_dims(image, 0)
plt.figure(figsize=(10, 10))
for i in range(9):
    augmented_image = data_augmentation(image)
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(augmented_image[0])
    plt.axis("off")

Теперь воспользуемся Keras ImageDataGenerator:

datagen_train = ImageDataGenerator(
    rotation_range=10, 
    zoom_range = 0.1,
    width_shift_range=0.1,
    height_shift_range=0.1)
train_gen = datagen_train.flow(x_train)

Определение модели и обучение

Давайте создадим простую модель CNN, используя Keras:

import keras
from keras.models import Sequential
from keras.layers import Dense, Conv2D, MaxPool2D, Flatten, Dropout
model = Sequential()
model.add(Conv2D(32,3,padding=”same”, activation=”relu”, input_shape=(180,180,3)))
model.add(MaxPool2D())
model.add(Conv2D(32, 3, padding=”same”, activation=”relu”))
model.add(MaxPool2D())
model.add(Conv2D(64, 3, padding=”same”, activation=”relu”))
model.add(MaxPool2D())
model.add(Dropout(0.4))
model.add(Flatten())
model.add(Dense(128,activation=”relu”))
model.add(Dense(2, activation=”softmax”))

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

from tensorflow.keras.optimizers import RMSprop
optimizer= RMSprop(learning_rate=0.001,
                   rho=0.9,
                   epsilon=1e-08,
                   decay=0.0)
model.compile(optimizer=optimizer,
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

Теперь давайте обучим нашу модель:

history= model.fit(train_gen,
                   epochs=20,
                   validation_data=test_gen, 
                   verbose=1,
                   steps_per_epoch=X_train.shape[0]/256)

Оценка результатов

Давайте создадим линейный график, чтобы визуализировать производительность нашей модели:

acc = history.history[‘accuracy’]
val_acc = history.history[‘val_accuracy’]
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
epochs_range = np.range(20)
plt.figure(figsize=(15, 15))
plt.subplot(2, 2, 1)
plt.plot(epochs_range, acc, label=’Training Accuracy’)
plt.plot(epochs_range, val_acc, label=’Validation Accuracy’)
plt.legend(loc=’lower right’)
plt.title(‘Training and Validation Accuracy’)
plt.subplot(2, 2, 2)
plt.plot(epochs_range, loss, label=’Training Loss’)
plt.plot(epochs_range, val_loss, label=’Validation Loss’)
plt.legend(loc=’upper right’)
plt.title(‘Training and Validation Loss’)
plt.show()

Трансферное обучение

Обучение с переносом состоит из использования функций, изученных в одной задаче, и их использования в решении новой, похожей задачи. Например, функции модели, которая научилась распознавать енотов, могут быть полезны для запуска модели, предназначенной для идентификации тануки. — Керас

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

  1. Создание базовой модели и загрузка предварительно обученных весов
  2. Заморозить все слои в базовой модели
  3. Создайте новую модель поверх вывода слоев из базовой модели.
  4. Обучите новую модель новому набору данных

Модель MobileNetV2

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

Как видите, MobileNetV2 имеет два блока слоев. Оба блока содержат сверточный слой, за которым следует глубинный слой и еще один сверточный слой. Давайте применим трансферное обучение к модели, чтобы распознать некоторые новые классы.

base_model = tf.keras.applications.MobileNetV2(
    input_shape=(128,128,3),
    include_top=False,
    weights='imagenet',
    pooling='avg')

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

base_model.trainable = False
#assemble the modified layers to the model.
model = tf.keras.models.Sequential()
model.add(base_model)
model.add(tf.keras.layers.Dropout(0.5))
model.add(tf.keras.layers.Dense(units=3,
                                activation=tf.keras.activations.softmax,
                                kernel_regularizer=tf.keras.regularizers.l2(l=0.01)))
rmsprop_optimizer = tf.keras.optimizers.RMSprop(learning_rate=0.001)
model.compile(optimizer=rmsprop_optimizer,
              loss=tf.keras.losses.sparse_categorical_crossentropy,
              metrics=['accuracy'])

Обучение

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

history=model.fit(train_gen,epochs=20,validation_data=test_gen,
 verbose=1,steps_per_epoch=3)

Оценка результатов

Давайте посмотрим, насколько хорошо наша модель способна классифицировать наши изображения:

import matplotlib.pyplot as plt
loss = history.history[‘loss’]
val_loss = history.history[‘val_loss’]
accuracy = history.history[‘accuracy’]
val_accuracy = history.history[‘val_accuracy’]
plt.figure(figsize=(14, 4))
plt.subplot(1, 2, 1)
plt.title(‘Loss’)
plt.xlabel(‘Epoch’)
plt.ylabel(‘Loss’)
plt.plot(loss, label=’Training set’)
plt.plot(val_loss, label=’Test set’, linestyle=’ — ‘)
plt.legend()
plt.grid(linestyle=’ — ‘, linewidth=1, alpha=0.5)
plt.subplot(1, 2, 2)
plt.title(‘Accuracy’)
plt.xlabel(‘Epoch’)
plt.ylabel(‘Accuracy’)
plt.plot(accuracy, label=’Training set’)
plt.plot(val_accuracy, label=’Test set’, linestyle=’ — ‘)
plt.legend()
plt.grid(linestyle=’ — ‘, linewidth=1, alpha=0.5)
plt.show()

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

Последние мысли

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

Ресурсы

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

Независимая от редакции, Heartbeat спонсируется и публикуется Comet, платформой MLOps, которая позволяет специалистам по данным и командам машинного обучения отслеживать, сравнивать, объяснять и оптимизировать свои эксперименты. Мы платим нашим авторам и не продаем рекламу.

Если вы хотите внести свой вклад, перейдите к нашему призыву к участию. Вы также можете подписаться на получение нашего еженедельного информационного бюллетеня (Еженедельник глубокого обучения), заглянуть в блог Comet, присоединиться к нам в Slack и подписаться на Comet в Twitter и LinkedIn для получения ресурсов и событий. и многое другое, что поможет вам быстрее создавать более качественные модели машинного обучения.