Распознавание языка жестов с помощью глубокого обучения

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

Полный процесс разработки, включая построение модели, оптимизацию гиперпараметров и развертывание

 

Источник: MNIST-датасет языка жестов от Kaggle.
Источник: MNIST-датасет языка жестов от Kaggle.

Вы можете посмотреть на модель, которую мы собираемся построить, здесь.

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

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

Понимание проблемы

Постановка задачи

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

Данные

Мы будем использовать MNIST-датасет языка жестов от Kaggle, который распространяется под лицензией CC0: Public Domain.

Пара слов об этом наборе данных:

  1. Для букв J и Z нет репрезентаций (Причина: жесты J и Z используют движения).

  2. Изображения черно-белые.

  3. Значения пикселей лежат в диапазоне от 0 до 255.

  4. Каждое изображение содержит 784 пикселя.

  5. Метки кодируются цифрами, которые варьируются от 0 до 25 в соответствии от A до Z.

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

Метрика оценки

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

Моделирование

Сверточная нейронная сеть (изображение автора).
Сверточная нейронная сеть (изображение автора).
  1. Сверточные нейронные сети — лучший выбор для задачи классификации изображений.

  2. Сверточная нейронная сеть (Convolutional Neural Network - CNN) — это искусственная нейронная сеть, состоящая из сверточных слоев.

  3. Сверточные нейронные сети очень хорошо показывают себя в работе с изображениями. Главное отличие сверточных (convolutional) слоев от полносвязных (dense) слоев, заключается в том, что в них каждый нейрон связан только с определенным набором нейронов из предыдущего слоя.

  4. Каждый сверточный слой содержит набор фильтров(filters)/ядер(kernels)/карт признаков(feature maps), которые помогают идентифицировать различные паттерны на изображении.

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

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

Разработка базовой модели

Импорт необходимых модулей и пакетов

Необходимые нам импорты:

from sklearn.preprocessing import LabelBinarizer
from tensorflow import keras
from keras.utils import plot_model

import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pickle

Подготовка данных

Считаем CSV-файл (sign_mnist_train.csv) с помощью pandas и перетасуем все обучающие данные.

Разделение пикселей и меток изображений позволит нам задействовать методы предварительной обработки (preprocessing) категориальных признаков.

Подготовка данных:

train_df = pd.read_csv('data/alphabet/sign_mnist_train.csv')
train_df = train_df.sample(frac=1, random_state=42)
X, y = train_df.drop('label', axis=1), train_df['label']

Нормализация и группировка в батчи

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

Группировка обучающих данных в батчи также сокращает время, необходимое для обучения модели.

Нормализация и группировка в батчи:

X = X/255.0
X = tf.reshape(X, [-1, 28, 28, 1])

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

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

 

Бинаризация меток 

LabelBinarizer (изображение автора).
LabelBinarizer (изображение автора).

LabelBinarizer из библиотеки Scikit-Learn бинаризирует метки по принципу one-vs-all (или one-vs-rest) и возвращает унитарно закодированный (one-hot) вектор.

Бинаризация меток:

label_binarizer = LabelBinarizer()
y = label_binarizer.fit_transform(y)

Отделение данных для валидации

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

Разделение данных:

X_train, X_valid = X[:25000], X[25000:]
y_train, y_valid = y[:25000], y[25000:]

Построение модели

Ниже мы займемся определением модели сверточной нейронной сети.

Для того, чтобы построить сверточную нейронную сеть, нам будет необходимо выбрать несколько типовых элементов:

  1. Выберите набор сверточных и пулинговых слоев. 

  2. Увеличьте количество нейронов в более глубоких сверточных слоях.

  3. Добавьте набор полносвязных слоев после сверточных и пулинговых слоев.

Сверточная нейронная сеть:

model = keras.models.Sequential()
model.add(keras.layers.Conv2D(32, (5, 5), padding='same', activation='relu', input_shape=(28, 28, 1)))
model.add(keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(keras.layers.Conv2D(64, (5, 5), padding='same', activation='relu'))
model.add(keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(keras.layers.Conv2D(128, (5, 5), padding='same', activation='relu'))
model.add(keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(128, activation='relu'))
model.add(keras.layers.Dense(24, activation='softmax'))

Архитектура модели (изображение автора).
Архитектура модели (изображение автора).

Теперь нам необходимо определить ключевые элементы в рамках построения модели:

  1. loss — Определяет функцию потерь, которую мы стремимся минимизировать. Поскольку наши метки приведены к унитарному виду, мы можем выбрать в качестве функции потерь категориальную перекрестную энтропию (categorical cross entropy).

  2. optimizer — Этот алгоритм находит лучшие веса, которые минимизируют функцию потерь. Adam — один из таких алгоритмов, который хорошо работает в большинстве случаев.

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

Определение функции потерь, оптимизатора и метрик:

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

Контрольные точки

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

EarlyStopping — Прерывает обучение, когда на протяжении указанного количества эпох перестает наблюдаться прогресс.

Контрольные точки:

save_best_cb = keras.callbacks.ModelCheckpoint('models/initial-end-to-end', save_best_only = True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience = 5)

Поиск паттернов

Подгонка данных:

history = model.fit(X_train, y_train, epochs=20, validation_data=(X_valid, y_valid), callbacks=[save_best_cb, early_stopping_cb])

Анализ обучения модели

Объект history содержит сведения о функции потерь и указанных метриках, полученные во время обучения модели. Эту информацию можно использовать для формирования кривых обучения и доступа к процессу обучения и показателям модели в каждую эпоху.

Сохранение истории:

with open('models/intial-end-to-end-history', 'wb') as history_file:
    pickle.dump(history.history, history_file)

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

Лучшая модель

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

Лучшая модель:

best_model = keras.models.load_model('models/initial-end-to-end')

Показатели на тестовом наборе

Запуск на тестовом наборе:

def evaluate_model(model, X_test, y_test, label_binarizer):
    X_test_reshape = tf.reshape(X_test, [-1, 28, 28, 1])
    y_test_labels = label_binarizer.transform(y_test)
    results = model.evaluate(X_test_reshape, y_test_labels)
    print(f'Loss: {results[0]:.3f} Accuracy: {results[1]:.3f}')
    
results = evaluate_model(best_model, test_df.drop('label', axis=1), test_df['label'], label_binarizer)

Точность: 94%


Оптимизация гиперпараметров

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

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

Количество сверточных и пулинговых пар

Этот гиперпараметр определяет количество Conv2D и MaxPooling2D пар, которые мы объединяем вместе в рамках нашей сверточной нейронной сети.

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

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

Фильтры

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

Размер фильтра

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

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

Дропаут

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

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

Расширение данных

Расширение данных (изображение автора).
Расширение данных (изображение автора).

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

Например, мы можем ввести небольшой поворот, масштабирование и смещение изображений.

Расширение данных:

data_augmentation = keras.models.Sequential()
data_augmentation.add(keras.layers.RandomRotation(0.1, fill_mode='nearest', input_shape=(28, 28, 1)))
data_augmentation.add(keras.layers.RandomZoom((0.15, 0.2), fill_mode='nearest'))
data_augmentation.add(keras.layers.RandomTranslation(0.1, 0.1, fill_mode='nearest'))

Другие гиперпараметры, которые вы можете попробовать:

  1. Батч-нормализация — нормализует входные данные.

  2. Глубина нейронной сети — например, замена одного сверточного слоя с размером фильтра (5X5) двумя последовательными слоями с размером фильтра (3X3).

  3. Размерность полносвязных слоев и само их количество.

  4. Замена MaxPooling-слоя сверточным слоем сстрайдом > 1

  5. Оптимизаторы

  6. Скорость обучения оптимизатора

Оценка итоговой модели

Лучшая модель после оптимизации гиперпараметров (изображение Sathwick).
Лучшая модель после оптимизации гиперпараметров (изображение Sathwick).

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

def evaluate_model(model, X_test, y_test, label_binarizer):
    # label_binarizer: Used while preprocessing the train data
    X_test_reshape = tf.reshape(X_test, [-1, 28, 28, 1])
    y_test_labels = label_binarizer.transform(y_test)
    results = model.evaluate(X_test_reshape, y_test_labels)
    print(f'Loss: {results[0]:.3f} Accuracy: {results[1]:.3f}')

best_model = keras.models.load_model('models/experiment-dropout-0/')
evaluate_model(best_model, X_test, y_test, label_binarizer)

Точность: 96%

Развертывание модели в Streamlit

Streamlit — это фантастическая платформа, которая избавляет от всех хлопот, связанных с ручным развертыванием.

Со Streamlit все, что от нас требуется, — это GitHub-репозиторий, содержащий скрипт Python, который определяет структуру приложения.

Установка

Вы можете установить библиотеку Streamlit с помощью приведенной ниже команды:

pip install streamlit

Чтобы запустить Streamlit-приложение, используйте streamlit run <script_name>.py

Сборка приложения

Полный код Streamlit-приложения можно найти здесь.

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

@st.cache(allow_output_mutation=True)
def get_best_model():
    best_model = keras.models.load_model('models/experiment-dropout-0')    
    return best_model 
    
@st.cache
def get_label_binarizer():    
    train_df = pd.read_csv('data/alphabet/sign_mnist_train.csv')    
    y = train_df['label']    
    label_binarizer = LabelBinarizer()    
    y = label_binarizer.fit_transform(y)    
    return label_binarizer

best_model = get_best_model()
label_binarizer = get_label_binarizer()

Декоратор @st.cache запускает функцию только один раз, предотвращая излишние перезапуски при повторном отображении страницы.

Прогноз модели

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

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

def preprocess_image(image, image_file, best_model, label_binarizer):
    image = tf.reshape(image, [image.shape[0], image.shape[1], 1])
    image = image/255
    image = tf.image.resize(image, [28, 28],       preserve_aspect_ratio=True)
    preprocessed_image = np.ones((1, 28, 28, 1))
    preprocessed_image[0, :image.shape[0], :image.shape[1], :] = image
    prediction = best_model.predict(preprocessed_image)
    index_to_letter_map = {i:chr(ord('a') + i) for i in range(26)}
    letter = index_to_letter_map[label_binarizer.inverse_transform(prediction)[0]]
    return letter

Предварительная обработка и прогнозирование.

Развертывание приложения в облаке Streamlit

  1. Зарегистрируйте Streamlit-аккаунт здесь.

  2. Подключите свой GitHub-аккаунт к своему Streamlit-аккаунту, предоставив все необходимые разрешения для доступа к вашим репозиториям.

  3. Убедитесь, что репозиторий содержит файл requirements.txt, в котором указаны все зависимости приложения.

  4. Нажмите на кнопку “New App” здесь.

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

  6. Нажмите кнопку “Deploy”. 

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

Заключение

В этом туториале мы разобрали следующее:

  1. Процесс разработки решения с использованием глубокого обучения для решения данной задачи

  2. Предварительная обработка изображений

  3. Обучение сверточной нейронной сети;

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

  5. Оптимизация гиперпараметров

  6. Развертывание

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

Модель можно найти здесь.

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


Приглашаем на открытое занятие «Пишем первую нейронную сеть». Рассмотрим основные этапы создания и обучения своей первой нейронной сети, и попробуем решить известную задачу классификации MNIST полносвязной и сверточной нейронными сетями на примере фреймворка PyTorch. Регистрация по ссылке.

Источник: https://habr.com/ru/company/otus/blog/686428/


Интересные статьи

Интересные статьи

Декораторы в python являются одной из самых часто используемых возможностей языка. Множество библиотек и, особенно, веб-фреймворков предоставляют свой функционал в виде декораторов. У неопытного pytho...
Системы обнаружения Covid-19 на рентгеновских снимках выдают быстрые результаты, в частности информацию о том, насколько серьёзно лёгкие поражены вирусом Covid-19. Традиц...
Привет, Хабр! Представляю вашему вниманию перевод статьи «Demystifying memory management in modern programming languages» за авторством Deepu K Sasidharan. В данной серии статей мне бы хотелос...
Кустикова Валентина, Васильев Евгений, Вихрев Иван, Дудченко Антон, Уткин Константин и Коробейников Алексей. Intel Distribution of OpenVINO Toolkit — набор библиотек для разработки приложений,...
Вступление За годы разработки ML- и DL-проектов у нашей студии накопились и большая кодовая база, и много опыта, и интересные инсайты и выводы. При старте нового проекта эти полезные знания по...