Автор статьи: Рустем Галиев

IBM Senior DevOps Engineer & Integration Architect

Глубокое обучение — это набор методов, которые особенно хорошо работают с задачами компьютерного зрения и обработки естественного языка. DL является частью более широкой области, называемой машинным обучением (ML).

В данной практике мы хотим распознавать разные предметы одежды, обученные на наборе данных, содержащем 10 различных типов — по сути, проблема классификации изображений, а не данные, напоминающие что-то вроде набора данных Iris, который мы далее рассмотрим.

Регрессия с использованием нейронной сети

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

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

touch step1.py

Открываем только что созданный файл step1.py.

Начнем с импорта. Здесь мы будем импортировать:

  • tensorflow и назовем его TF для простоты использования.

  • numpy, который помогает нам легко и быстро представлять наши данные в виде массивов.

  • Фреймворк keras для определения нейронной сети как набора последовательных слоев.

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

import tensorflow as tf
import numpy as np
from tensorflow import keras

Настроив нашу среду, мы попытаемся построить нейронную сеть, которая предсказывает цену дома по простой формуле.

Вот как выглядит нейронная сеть:

Гипотетическая модель

Представьте, что у нас есть очень простая модель ценообразования дома 50 000 + 50 000 за спальню, так что дом с 1 спальней стоит 100 000 долларов, дом с 2 спальнями стоит 150 000 долларов и так далее.

Мы можем записать это, используя общую нотацию a + bx, где a равно 50k, b равно 50k, а x — количество спален. Теперь у нас могло бы быть гораздо больше функций, таких как квадратные метры, которые просто расширили бы наше общее обозначение до a + bx + cy, где c — постоянное значение, а y — квадратные метры. Но давайте придерживаться нашей простой модели ценообразования для простоты понимания. Эти неизвестные a и b называются весами модели.

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

Создание данных

Давайте создадим некоторые фиктивные данные по формуле:

bedrooms = np.array([2, 4, 5, 7, 10, 0])
prices = np.array([150, 250, 300, 400, 550, 50])

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

Создание модели

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

Откройте тот же файл: step1.py.

model = tf.keras.Sequential([
        keras.layers.Dense(units=1, input_shape=(1,))
        ])

Sequential() определяет модель с ПОСЛЕДОВАТЕЛЬНОСТЬЮ (стеком) слоев в нейронной сети.
Dense() добавляет слой нейронов/узлов.
По умолчанию, поскольку мы не указываем функцию активации, выход будет линейной комбинацией входных данных — именно то, что мы хотим.

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

Компиляция модели

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

Проигрыш измеряет предполагаемые ответы по сравнению с известными правильными ответами и измеряет, насколько хорошо или плохо они справились.
Оптимизатор угадывает вывод. Основываясь на выводе функции потерь, он попытается минимизировать потери.

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

model.compile(optimizer='sgd', loss='mean_squared_error')

Подгонка модели

После компиляции идет примерка. Процесс обучения нейронной сети, где она изучает взаимосвязь между спальнями и ценами, находится в вызове model.fit. Он итеративно пытается выяснить лучшие “веса”.

model.fit(bedrooms, prices, epochs=500)

Прогнозы с использованием модели

Давайте теперь попробуем предсказать цену дома с восемью спальнями и получить “веса” модели.

print("Price of the 8-bedroom house is: ", model.predict([10.0]))

print('Kernel value:', model.weights[0].numpy()[0,0])
print('Bias value:', model.weights[1].numpy()[0])

Весь код выглядит так:

import tensorflow as tf
import numpy as np
from tensorflow import keras

bedrooms = np.array([2, 4, 5, 7, 10, 0])
prices = np.array([150, 250, 300, 400, 550, 50])

model = tf.keras.Sequential([
        keras.layers.Dense(units=1, input_shape=(1,))
        ])

model.compile(optimizer='sgd', loss='mean_squared_error')

# The number of epochs is the number of passes over the entire dataset done in order to find the best weights.
model.fit(bedrooms, prices, epochs=500)

print("Price of the 8-bedroom house is: ", model.predict([10.0]))

print('Kernel value:', model.weights[0].numpy()[0,0])
print('Bias value:', model.weights[1].numpy()[0])


Наконец, давайте запустим скрипт:

python step1.py

Обратите внимание на значения весов (ядро — это вес, связанный с количеством спален, а смещение — постоянная величина) и на то, что они очень близки к 50!

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

Классификация с использованием нейронной сети

Здесь мы повторим то, что сделали, но с несколькими существенными изменениями:

Большая нейронная сеть: теперь мы добавим один скрытый слой с несколькими узлами.

Проблема классификации. На предыдущем шаге мы реализовали задачу регрессии, включающую непрерывное целевое значение. В задаче классификации целевые значения представляют собой дискретные конечные значения, такие как 0, 1 и 2, или значения в формате горячего кодирования, где 0, 1 и 2 могут быть представлены как [1, 0, 0], [0, 1, 0] и [0, 0, 1] соответственно.

Реальный набор данных: мы будем работать со знаменитым набором данных Iris вместо создания собственного набора данных. Набор данных содержит три класса по 50 экземпляров в каждом, где каждый класс относится к типу ириса. На этот раз у каждого экземпляра также есть 4 характеристики: длина чашелистика, ширина чашелистика, длина лепестка и ширина лепестка. На предыдущем шаге мы использовали только одну функцию: количество спален.

Разделение нашего набора данных на две части: набор тренировочных данных и набор тестовых данных. Мы зарезервируем 80% данных для обучения методу model.fit(), а 20% будем использовать для проверки точности модели. Причина, по которой мы это делаем, заключается в том, что мы хотим протестировать модель на данных, на которых она не обучалась (или не видела), чтобы избежать этой предвзятости на данных обучения.

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

Touch step3.py

Открываем только что созданный файл step3.py.

Начнем с импорта.

import tensorflow as tf
import numpy as np
from tensorflow import keras

bedrooms = np.array([2, 4, 5, 7, 10, 0])
prices = np.array([150, 250, 300, 400, 550, 50])

model = tf.keras.Sequential([
        keras.layers.Dense(units=1, input_shape=(1,))
        ])

model.compile(optimizer='sgd', loss='mean_squared_error')

# The number of epochs is the number of passes over the entire dataset done in order to find the best weights.
model.fit(bedrooms, prices, epochs=500)

print("Price of the 8-bedroom house is: ", model.predict([10.0]))

print('Kernel value:', model.weights[0].numpy()[0,0])
print('Bias value:', model.weights[1].numpy()[0])

Загрузка набора данных

Давайте загрузим набор данных и просмотрим первый экземпляр.

data = load_iris()
X = data['data']
y = data['target']

print('Features of Instance 1:', X[0])
print('Target of Instance 1:', y[0])

Запустим:

python step3.py

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

Давайте разделим наш набор данных на наборы данных для обучения и тестирования.

# Split the data into training and testing sets
X_train, X_test, y_train, y_test  = train_test_split(X,y,test_size=0.2)

# Let's view the shapes of all train and test data.
print('Shape of training data:', X_train.shape)
print('Shape of training targets:', y_train.shape)
print('Shape of test data:', X_test.shape)
print('Shape of test targets:', y_test.shape)

Запустим:

Вы можете заметить, что разделение 80-20 (20% для тестового набора) действительно имеет место! Когда наши данные готовы, давайте поработаем над нашей моделью.

Пришло время построить и скомпилировать нашу модель.

Создание модели

Как упоминалось ранее, теперь мы собираемся использовать один скрытый слой, состоящий из десяти узлов.
Мы будем использовать функцию активации relu для скрытого слоя, популярный выбор в недавней литературе по машинному обучению.
Мы будем использовать функцию активации softmax для вывода позже, потому что нам нужны значения вероятности для каждого класса.
Мы будем использовать три выходных узла, каждый из которых будет иметь значение от 0 до 1, что в сумме даст 1 (из-за softmax). Каждый узел будет указывать вероятность определенного класса в целевых метках.

model = keras.models.Sequential([
    keras.layers.Dense(units=10, activation='relu', input_shape=(4,)),
    keras.layers.Dense(units=3, activation='softmax')
])

# View the Model Summary
print(model.summary())

Выполним код

Python step3.py

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

Компиляция модели

Мы собираемся использовать оптимизатор RMSProp.

Категориальная перекрестная энтропия — это функция потерь для задачи классификации. Он разреженный, потому что наши целевые значения находятся в {0,1,2}, а не в горячем кодировании. Мы добавляем точность в качестве метрики, которую необходимо отслеживать.

model.compile(optimizer='rmsprop',loss='sparse_categorical_crossentropy',metrics=['accuracy'])

Фитинг модели

Мы предоставляем тестовые данные в качестве аргумента validation_data. В результате мы сможем увидеть val_loss и val_accuracy (потери и точность тестового набора) после каждой эпохи.

model.fit(X_train,y_train,epochs=100,validation_data=(X_test,y_test))

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

plt.plot(model.history.history['accuracy'],label='Train Accuracy')
plt.plot(model.history.history['val_accuracy'],label='Test Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.savefig('accuracy_plot.png')

plt.close()
plt.plot(model.history.history['loss'],label='Train Loss')
plt.plot(model.history.history['val_loss'],label='Test Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.savefig('loss_plot.png')

Выполним код

Щелкните по accuracy_plot.png, чтобы визуализировать график точности. Нажмите loss_plot.png, чтобы визуализировать график потерь.

Мы ясно видим улучшение точности и уменьшение потерь — именно то, что мы ожидали.

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



Полный код:

import tensorflow as tf
import numpy as np
from tensorflow import keras

# For plotting the accuracy and loss over the epochs
import matplotlib.pyplot as plt

# Library that has the dataset built-in
from sklearn.datasets import load_iris

# For splitting our entire data into two parts
from sklearn.model_selection import train_test_split

data = load_iris()
X = data['data']
y = data['target']

print('Features of Instance 1:', X[0])
print('Target of Instance 1:', y[0])

# Split the data into training and testing sets
X_train, X_test, y_train, y_test  = train_test_split(X,y,test_size=0.2)

# Let's view the shapes of all train and test data.
print('Shape of training data:', X_train.shape)
print('Shape of training targets:', y_train.shape)
print('Shape of test data:', X_test.shape)
print('Shape of test targets:', y_test.shape)

Computer Vision, Finally!

Загрузка набора данных

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

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

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

Создайте новый файл:

touch step1.py

Открываем только что созданный файл step1.py.

Начнем с импорта библиотек:

import tensorflow as tf
import matplotlib.pyplot as plt

Мы будем обучать нейронную сеть распознавать предметы одежды из общего набора данных под названием Fashion MNIST. Вы можете узнать больше об этом наборе данных здесь: https://www.kaggle.com/datasets/zalando-research/fashionmnist

Он содержит 70 000 предметов одежды в 10 различных категориях. Каждый предмет одежды представлен в виде изображения в оттенках серого 28x28. Вы можете увидеть некоторые примеры здесь: https://github.com/zalandoresearch/fashion-mnist

Этот набор данных также доступен в API наборов данных Keras, давайте загрузим его:

mnist = tf.keras.datasets.fashion_mnist

Точно так же keras (и tensorflow) имеют множество встроенных наборов данных. Далее мы извлечем данные из mnist.

Разделение и нормализация данных. Встроенное разделение набора данных

Вызов метода load_data для объекта mnist даст вам два набора из двух списков, это будут обучающие и проверочные значения для графики, содержащей предметы одежды и их метки.

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

Нормализация данных изображения

Все пиксели в основном имеют значения в диапазоне от 0 до 255. Если мы обучаем нейронную сеть, по разным причинам будет проще, если мы будем рассматривать все значения как от 0 до 1. Следовательно, нам придется нормализовать изображения, разделив каждое из обучающих и тестовых изображений по 255:

print("Few pixel values BEFORE normalization: \n", train_images[0,20:26,20:26])
train_images  = train_images / 255.0
test_images = test_images / 255.0
print("\nFew pixel values AFTER normalization: \n", train_images[0,20:26,20:26])

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

python step1.py

Обратите внимание, что значения пикселей изменились с диапазона 0–255 до 0–1. Это простое масштабирование функций — не единственный способ нормализации, но этот простой способ деления на 255 очень распространен для пикселей изображения.

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

Создание и компиляция модели

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

Откройте тот же файл .py: step1.py

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

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(input_shape=(28,28)),
                                    tf.keras.layers.Dense(256, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

print(model.summary())

Запустим скрипт

Модель изучает более 200 тысяч параметров! Вы можете попробовать изменить количество единиц в скрытом слое (первый плотный слой), чтобы увидеть, как изменится количество параметров. Чем сложнее модель, тем больше времени требуется для ее обучения.

Давайте разберемся с каждым из этих ключевых слов здесь:

  • Sequential: это определяет модель с ПОСЛЕДОВАТЕЛЬНОСТЬЮ (стеком) слоев в нейронной сети, где каждый слой имеет один входной тензор и один выходной тензор.

  • Flatten: этот слой просто берет квадрат и превращает его в одномерный набор. Например, если сглаживание применяется к слою с входной формой (batch_size, 2,2), то выходная форма слоя будет (batch_size, 4).

  • Dense: добавляет слой нейронов.

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

Relu фактически означает «Если X>0 вернуть X, иначе вернуть 0», поэтому он позволяет нам передавать значения 0 или выше на следующий уровень в сети.

Softmax дает нам вероятность для каждого класса, в который должен быть классифицирован экземпляр, и в сумме они составляют 1. Например, если выходные данные последнего слоя равны [0,0, -1,0, 2,0, 3,0], softmax преобразует их в [ 0,03467109 0,01275478 0,25618663 0,69638747].

Теперь, когда модель определена, нужно ее построить. Мы делаем это, компилируя его с оптимизатором и функцией потерь.

model.compile(optimizer = tf.keras.optimizers.Adam(),
              loss = 'sparse_categorical_crossentropy',
              metrics=['accuracy'])

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

Обучим модель, вызвав метод fit().

model.fit(train_images, train_labels, epochs=5, validation_data=(test_images,test_labels))


Давайте построим значения точности и потерь.

plt.plot(model.history.history['accuracy'],label='Train Accuracy')
plt.plot(model.history.history['val_accuracy'],label='Test Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.savefig('accuracy_plot.png')

plt.close()
plt.plot(model.history.history['loss'],label='Train Loss')
plt.plot(model.history.history['val_loss'],label='Test Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.savefig('loss_plot.png')

Оценка модели

Чтобы оценить модель, мы должны знать, как она работает с данными типа unseen. Вот почему у нас есть тестовые изображения. Мы будем использовать model.evaluate() и передавать ей тестовые изображения и метки:

model.evaluate(test_images, test_labels)

Выполним код, чтобы просмотреть выходные данные и графики.

Python step1.py



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

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

Например, tf.keras.layers.Dense(256, активация=tf.nn.relu, kernel_initializer=tf.keras.initializers.GlorotUniform(seed=5)). GlorotUniform — это инициализатор ядра по умолчанию для плотного слоя. Нам не нужно указывать начальное значение для offset_initializer, потому что по умолчанию он устанавливает все значения смещения равными нулю.

Вы должны попытаться увидеть, можете ли вы улучшить производительность, изменив количество эпох или любые другие гиперпараметры, такие как оптимизатор, скорость обучения оптимизатора и т. д. Щелкните по accuracy_plot.png, чтобы визуализировать график точности. Как и ожидалось, точность тренировки и теста увеличивается.Нажмите loss_plot.png, чтобы визуализировать график потерь. Как и ожидалось, потери как в тренировке, так и в тесте уменьшаются.И, как и ожидалось, он, вероятно, не будет так хорошо работать с невидимыми данными, как с данными, на которых он обучался!

Полный код:

import tensorflow as tf
import matplotlib.pyplot as plt

mnist = tf.keras.datasets.fashion_mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

print("Few pixel values BEFORE normalization: \n", train_images[0,20:26,20:26])
train_images  = train_images / 255.0
test_images = test_images / 255.0
print("\nFew pixel values AFTER normalization: \n", train_images[0,20:26,20:26])

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(input_shape=(28,28)),
                                    tf.keras.layers.Dense(256, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

print(model.summary())

model.compile(optimizer = tf.keras.optimizers.Adam(),
              loss = 'sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(train_images, train_labels, epochs=5, validation_data=(test_images,test_labels))

plt.plot(model.history.history['accuracy'],label='Train Accuracy')
plt.plot(model.history.history['val_accuracy'],label='Test Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.savefig('accuracy_plot.png')

plt.close()
plt.plot(model.history.history['loss'],label='Train Loss')
plt.plot(model.history.history['val_loss'],label='Test Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.savefig('loss_plot.png')

model.evaluate(test_images, test_labels)

В заключение приглашаю всех на бесплатный урок курса "Компьютерное зрение", где вы узнаете в чем заключается задача Face Recognition и из каких подзадач она состоит: детекция и выравнивание лиц, распознавание и матчинг лиц. Какие существуют основные подходы по решению задачи детекции лиц (нейросетевые подходы и алгоритмы классического компьютерного зрения). С помощью каких алгоритмов решается задача распознавания лиц (EigenFaces, нейросетевые методы). Как на практике решить задачу распознавания лиц с помощью метода EigenFaces, а также какие существуют датасеты, библиотеки и инструменты, необходимые для решения задачи распознавания лиц.

Комментарии (0)