Играем в Mortal Kombat с TensorFlow.js: перенос обучения и дополнение данных

Перевод статьи Minko Gechev: Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation.


Во время экспериментов по улучшению предсказательной модели в Guess.js я начал обращать внимание на глубокое обучение. До этого я больше фокусировался на рекуррентных нейронных сетях (RNN), особенно на долгой краткосрочной памяти LSTM, из-за их «необъяснимой эффективности» в домене Guess.js. В то же время я начал смотреть в сторону свёрточных нейронных сетей (CNN), которые хотя и менее традиционно, но также часто используются для временных рядов. CNN обычно используют для классификации и распознавания изображений.

Управление MK.js с помощью TensorFlow.js
Исходный код для этой статьи и MK.js вы можете найти на моём GitHub-аккаунте. Там нет данных, на которых я тренировал модель, но вы можете создать свои собственные данные и натренировать модель на них, как это сделать написано ниже. Также не стесняйтесь менять настройки виджетов, чтобы понять, как всё работает.

После того, как я наигрался с CNN, я вспомнил о своём эксперименте, который я сделал в прошлом году, когда браузеры представили API getUserMedia. В этом эксперименте я использовал пользовательскую камеру, чтобы играть в небольшую JavaScript-копию игры Mortal Kombat 3. Вы можете найти игру на моём GitHub-аккаунте. Как часть этого эксперимента я разработал базовый алгоритм для определения позы, классифицирующий изображения по следующим классам:

  • Удар правой или левой рукой
  • Удар правой или левой ногой
  • Проход вправо или влево
  • Приседание
  • Ничего из вышеперечисленного

Алгоритм настолько простой, что его можно описать в несколько предложений:

Алгоритм делает снимок фона за пользователем. Как только пользователь появляется на сцене, алгоритм находит разницу между оригинальным кадром с фоном и текущим кадром с пользователем. Таким образом мы можем определить как расположено тело пользователя. Следующим шагом алгоритм отрисовывает пользователя белым по чёрному холсту. Затем алгоритм создает вертикальную и горизонтальную гистограммы, суммируя значения всех пикселей. Основываясь на суммарных вычислениях, алгоритм определяет, в какой позе сейчас находится пользователь.

На видео демонстрируется работа этого алгоритма. Исходный код находится на моём GitHub-аккаунте.

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

И теперь, учитывая достижения в API веб-платформ, особенно WebGL, я решил ещё раз попробовать решить эту проблему, используя TensorFlow.js.

Вступление

В этом посте я поделюсь своим опытом построения алгоритма по распознаванию позы, используя TensorFlow.js и MobileNet. Мы рассмотрим несколько тем:

  • Сбор тренировочных данных для распознавания изображений
  • Дополнение данных с помощью imgaug
  • Перенос обучения с MobileNet
  • Бинарная классификация и n-арная классификация
  • Тренировка классификационной модели TensorFlow.js с Node.js и использование её в браузере
  • Несколько слов о классифицировании действий с LSTM

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

К концу статьи вы сможете создать модель для игры в MK.js:

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

Сбор данных

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

  • Удары руками
  • Удары ногами
  • Всё остальное

Для этого эксперимента я, с помощью ещё двух волонтеров (lili_vs и gsamokovarov), сделал немного фотографий. Мы записали 5 видео на QuickTime с моего MacBook Pro, каждое из которых содержало 2–4 удара рукой и 2–4 удара ногой.

Теперь у нас есть видео в формате mov, но нужны изображения. Мы будем использовать приложение ffmpeg, чтобы разбить видео на кадры и сохранить их как jpg.

ffmpeg -i video.mov $filename%03d.jpg

Прежде, чем запустить эту команду, установите ffmpeg на ваш компьютер.

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

Таким образом, в каждой папке у нас получится примерно по 200 изображений, похожих на эти:

Заметьте, что в папке «всё остальное» может быть гораздо больше изображений, чем в других, потому что изображений ударов меньше, чем изображений просто стоящих, ходящих или выключающих видеозапись людей. Если при обучении модели у нас в одном классе будет больше данных, чем в остальных, то мы рискуем сделать модель предвзятой к этому классу. Таким образом, даже если мы попытаемся классифицировать образ бьющего ногой человека, нейронная сеть классифицирует его как «всё остальное». Чтобы убрать предвзятость модели, мы можем уменьшить количество фотографий в «всё остальное» категории и сделать так, чтобы в каждой папке было примерно одинаковое количество изображений.

Для удобства, я пронумерую изображения в каждой папке от 1 до 190, так что название первого изображения будет 1.jpg, второго — 2.jpg и т. д.

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

Дополнение данных

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

В нашем случае допустимыми преобразованиями будут: вращения, инвертирование цветов, размытие изображения и т. д. В интернете есть отличные опенсорс-библиотеки для этого. На момент написания, я не нашёл такой библиотеки на JavaScript, поэтому я использовал библиотеку, написанную на Python — imgaug. Она содержит набор параметров, которые могут быть применены с какой-то долью вероятности.

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

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

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

Для каждого изображения из набора данных трансформация создаст еще 16 новых изображений. Это пример того, как выглядят изображения, которыми мы дополняем:

Заметьте, что в коде, приведённом выше, мы уменьшаем изображения до 100x56 пикселей. Мы делаем это для уменьшения объёма данных и, соответственно, для уменьшения количества вычислений, которые наша модель должна будет выполнять во время обучения и оценки.

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

Теперь давайте создадим классификационную модель!

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

Перенос обучения

На рисунке ниже показана VGG-16, популярная CNN, которая используется для классификации изображений.

Сеть VGG-16 может распознавать до 1000 классов изображений. Она имеет 16 слоёв (мы не учитываем выходной и pooling слои). Такую многослойную сеть очень сложно тренировать на практике. Это потребует огромный набор данных и много часов обучения.

Скрытые слои в обученной CNN распознают различные характеристики изображения из тренировочного набора: начиная с распознавания краёв объекта, заканчивая распознаванием отдельных характеристик объекта и распознаванием самого объекта. Обученная CNN, аналогичная VGG-16, распознающая большой набор изображений, будет иметь скрытые слои, которые уже умеют распознавать многие характеристики изображений на основании предыдущего тренировочного сета. Такие характеристики будут общими между большинством изображений и, соответственно, могут использоваться для разных задач.

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

Для наших целей больше всего подходит нейронная сеть MobileNet из @tensorflow-models/mobilenet. MobileNet настолько же хороша как и VGG-16, но она так же и намного меньше, что ускоряет прямое распространение и сокращает время загрузки в браузере. MobileNet была обучена на ILSVRC-2012-CLS наборе классифицированных изображений.

Вы можете попробовать как работает MobileNet сеть в виджете ниже (доступен в оригинальной статье, — прим. пер.). Вы можете загрузить изображение со своей файловой системы или с камеры.

Когда мы разрабатываем модель с использованием перенесённого обучения нам надо принять всего два решения:

  1. Результат с какого слоя будет входными данными для нашей модели
  2. Сколько слоёв в нашей модели мы хотим обучать (и хотим ли мы вообще добавлять новые слои)

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

В нашем случае мы не будем обучать ни один из слоев MobileNet. Мы будем использовать результат работы слоя global_average_pooling2d_1 как входные данные для нашей модели. Как я выбрал именно этот слой? Эмпирическим путем. Я сделал несколько тестов, и этот слой работал достаточно хорошо для моих целей.

Определение модели

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

Этот код определяет простую модель со слоем на 1024 юнита и ReLU активацией, и одним выходным юнитом, который проходит через активационную функцию sigmoid. Функция sigmoid будет возвращать числа от 0 до 1в зависимости от того, насколько вероятно, что человек в кадре делает удар. Почему я выбрал 1024 юнита для второго слоя и 1e-6 для скорости обучения? Я просто попробовал несколько разных настроек и увидел, что 1024и 1e-6 показали лучшие результаты. Возможно «попробовал и увидел» звучит не очень убедительно для вас, но именно так работает настройка гиперпараметров для нейронных сетей глубокого обучения: основываясь на наших знаниях о модели мы используем интуицию, чтобы менять параметры и потом на практике проверять, как работает модель.

Метод compile объединяет слои модели и подготавливает модель для обучения и оценки. В нём мы обозначаем, что хотим использовать оптимизационный алгоритм adam. Мы так же определяем, что мы будем вычислять потери с сигмовидной кросс-энтропией и мы так же указываем, что хотим оценивать точность модели. TensorFlow.js использует следующую формулу для вычисления точности модели:

Точность = (истинно положительные + истинно отрицательные) / (положительные + отрицательные)

Если мы хотим применить перенесённое обучение c MobileNet в качестве исходной модели, мы сперва должны загрузить её. Так как это непрактично обучать нашу модель на трёх тысячах изображений в браузере, мы будем использовать Node.js и загружать сеть из файла.

Вы можете скачать MobileNet от сюда. В папке вы найдёте файл model.json, который содержит описание архитектуры модели - слои, активации и т. д. Остальные файлы содержат параметры модели. Вы можете загрузить модель из файла, используя следующий код:

Заметьте, что в методе loadModel мы возвращаем функцию, принимающую одномерный тензор как входной параметр и возвращающую mn.infer(input, Layer). infer - метод MobileNet, он принимает тензор и слой как входные параметры. Параметр Layer определяет, из какого скрытого слоя мы хотим получать результат. Если вы поищите global_average_pooling2d_1 в model.json, то вы увидите, что это название одного из слоёв.

Теперь, для того чтобы обучить модель, нам надо создать тренировочный набор данных. Для это нам нужно вызвать MobileNet метод infer для каждого изображения и присвоить изображению метку: 1 (если в кадре есть изображение удара) и 0 (если нет).

В этом фрагменте кода мы сначала читаем файлы из папки с изображениями ударов, затем из папки, в которой ударов нет. После этого мы создаем одномерный тензор, содержащий все метки, которые мы получили на выходе. Если у нас было n изображений с ударами и m изображений без ударов, то тензор будет содержать n элементов со значением 1 и m элементов со значением 0.

Результаты исполнения MobileNet метода infer для каждого изображения мы сохраняем в стеке xs. Обратите внимание, что для каждого изображения мы вызываем метод readInput. Давайте посмотрим на его реализацию:

readInput сначала вызывает функцию readImage и после этого вызывает функцию imageToInput. readImage считывает изображение с диска и после этого декодирует буфер как изображение jpg, используя пакет jpeg-js. В imageToInput мы преобразуем изображение в трёхмерный тензор.

После выполнения этого кода, для каждого i от 0 до TotalImages значение ys[i] должно быть 1 (если изображение в xs[i] содержит удар) и 0 (если иначе).

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

Теперь мы готовы приступить к обучению модели. Для этой цели вызовите метод fit на экземпляре модели.

В коде выше мы вызываем fit с тремя параметрами: xs, ys и объектом с настройками. В настройках мы определяем сколько эпох мы хотим тренировать модель, размер партии и колбэк, который будет вызываться после каждой партии.

Размер партии определяет размер подмножеств из xs и ys, на которых мы будем тренировать нашу модель в течение одной эпохи. Для каждой эпохи TensorFlow.js будет выбирать подмножество из xs и соответствующие элементы из ys, затем он применит прямое распространение, получит результат из слоя с sigmoid активацией и затем, на основании потерь, TensorFlow.js выполнит оптимизацию, используя алгоритм adam.

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

Cost: 0.84212, accuracy: 1.00000
eta=0.3 >---------- acc=1.00 loss=0.84 Cost: 0.79740, accuracy: 1.00000
eta=0.2 =>--------- acc=1.00 loss=0.80 Cost: 0.81533, accuracy: 1.00000
eta=0.2 ==>-------- acc=1.00 loss=0.82 Cost: 0.64303, accuracy: 0.50000
eta=0.2 ===>------- acc=0.50 loss=0.64 Cost: 0.51377, accuracy: 0.00000
eta=0.2 ====>------ acc=0.00 loss=0.51 Cost: 0.46473, accuracy: 0.50000
eta=0.1 =====>----- acc=0.50 loss=0.46 Cost: 0.50872, accuracy: 0.00000
eta=0.1 ======>---- acc=0.00 loss=0.51 Cost: 0.62556, accuracy: 1.00000
eta=0.1 =======>--- acc=1.00 loss=0.63 Cost: 0.65133, accuracy: 0.50000
eta=0.1 ========>-- acc=0.50 loss=0.65 Cost: 0.63824, accuracy: 0.50000
eta=0.0 ==========>
293ms 14675us/step - acc=0.60 loss=0.65
Epoch 3 / 50
Cost: 0.44661, accuracy: 1.00000
eta=0.3 >---------- acc=1.00 loss=0.45 Cost: 0.78060, accuracy: 1.00000
eta=0.3 =>--------- acc=1.00 loss=0.78 Cost: 0.79208, accuracy: 1.00000
eta=0.3 ==>-------- acc=1.00 loss=0.79 Cost: 0.49072, accuracy: 0.50000
eta=0.2 ===>------- acc=0.50 loss=0.49 Cost: 0.62232, accuracy: 1.00000
eta=0.2 ====>------ acc=1.00 loss=0.62 Cost: 0.82899, accuracy: 1.00000
eta=0.2 =====>----- acc=1.00 loss=0.83 Cost: 0.67629, accuracy: 0.50000
eta=0.1 ======>---- acc=0.50 loss=0.68 Cost: 0.62621, accuracy: 0.50000
eta=0.1 =======>--- acc=0.50 loss=0.63 Cost: 0.46077, accuracy: 1.00000
eta=0.1 ========>-- acc=1.00 loss=0.46 Cost: 0.62076, accuracy: 1.00000
eta=0.0 ==========>
304ms 15221us/step - acc=0.85 loss=0.63

Обратите внимание, что с течением времени точность увеличивается, а потери уменьшаются.

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

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

Запускаем модель в браузере

В предыдущем разделе мы обучили модель для бинарной классификации. Теперь давайте запустим её в браузере и подключим к MK.js!

В приведённом выше фрагменте мы объявляем несколько переменных:

  • video — содержит ссылку на видеоэлемент HTML5 на странице
  • Layer — содержит имя слоя из MobileNet, результат работы которого мы будем передавать как входные данные в нашу модель
  • mobilenetInfer — это функция, принимающая экземпляр MobileNet и возвращающая другую функцию; возвращённая функция принимает входные данные и возвращает соответствующий результат из указанного слоя MobileNet
  • canvas — указывает на элемент HTML5 canvas, который мы будем использовать для извлечения кадров из видео
  • scale — это ещё один canvas, который мы будем использовать для масштабирования отдельных кадров

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

В качестве следующего шага соединяем модель вместе с MK.js:

В приведённом коде мы сначала загружаем модель, которую мы обучили выше, и после этого загружаем MobileNet. Мы передаём MobileNet в метод mobilenetInfer, чтобы было удобнее вычислять результат скрытого слоя сети. После этого мы вызываем метод startInterval с обеими сетями в качестве аргументов.

startInterval - метод, в котором происходит всё самое интересное. Сначала мы создаём интервал, где каждые 100мс мы вызываем анонимную функцию. В этой функции мы сначала отрисовываем видео поверх canvas, которая будет содержать наш текущий кадр. После этого мы масштабируем кадр до 100x56 и применяем к нему фильтр для оттенков серого.

Следующим шагом мы передаём кадр в MobileNet, получаем результат из желаемого скрытого слоя и передаём его на вход в predict метод прогнозирования нашей модели. Метод predict вернёт тензор с одним элементом. Используя dataSync, мы получаем значение из тензора и сохраняем его в постоянной punching.

Наконец, мы проверяем: если вероятность того, что пользователь делает удар в кадре больше 0.4, мы вызываем метод onPunch у глобального объекта Detect. MK.js предоставляет глобальный объект с тремя методами: onKick, onPunch и onStand, которые мы можем использовать, чтобы контролировать любого из персонажей.

Вот и всё! 🎉 Вот и результат!

Распознавание ударов руками и ногами с помощью n-арной классификации

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

Как и для тренировки предыдущей модели, мы начнём с того, что загрузим папки с изображениями ударов ногой, ударов рукой и изображениями всего остального. После этого мы сформируем двухмерный тензор, а не одномерный как в предыдущем разделе. Если у нас есть n изображений с ударами рукой, m изображений с ударами ногой и k изображений со всем остальным, то тензор ys будет иметь n элементов [1,0,0], m элементов [0,1,0] и k элементов [0,0,1]. Вектор [1,0,0] ассоциируется с изображениями, на которых есть удар рукой, вектор [0,1,0] - с изображениями, на которых есть удар ногой, вектор [0,0,1] - со всеми остальными изображениями.

Вектор с n элементами, в котором n - 1 элемент - это нули, а 1 элемент - это единица, будем называть унитарным кодом.

После этого мы формируем входной тензор xs, сохраняя результаты из MobileNet для каждого изображения.

Для этой цели, нам надо обновить объявление модели:

Есть только два отличия от предыдущей модели:

  • Количество юнитов в выходной модели
  • Активация в выходном слое

Причина, почему мы определили 3 юнита в выходном слое, состоит в том, что у нас есть три разные категории изображений:

  • Удары рукой
  • Удары ногой
  • Всё остальное

Активация Softmax вызывается поверх этих трёх юнитов и преобразует их параметры в тензор с тремя значениями. Почему у нас 3 юнита на выходном слое? Мы знаем, что мы можем представить 3 значения (по одному для каждого класса) с 2 битами: 00, 01, 10. Сумма значений тензора, создаваемых softmax, равна 1, что означает, что мы никогда не получим 00, поэтому мы никогда не сможем классифицировать изображения одного из классов.

После 500 эпох обучения модели, я добился около 92% точности! Это неплохо, и не забывайте, что для обучения использовался небольшой набор данных.

Следующий шаг — запустить модель в браузере! Поскольку логика для этого довольно похожа на то, что мы делали для бинарной модели, давайте посмотрим на последний шаг, на котором мы выбираем действие, основанное на результате работы модели:

Изначально, мы вызываем MobileNet модель с изображениями, которые предварительно прошли через фильтр оттенков серого и были уменьшены, после чего мы передаем полученный результат в нашу обученную модель. Модель возвращает одномерный тензор, который мы приводим к Float32Array с помощью dataSync. Затем, используя Array.from, мы преобразуем типизированный массив к JavaScript-массиву и получаем вероятности того, что на изображении удар рукой, удар ногой или что-то другое.

Если вероятность того, что на изображении не удар рукой и не удар ногой выше 0.4, мы останавливаем выполнение функции. В противном случае, если у нас более высокая вероятность того, что на изображении удар ногой и эта вероятность выше 0.32 мы вызываем команду kick из MK.js. Если вероятность удара рукой выше 0.36 и вероятность удара рукой выше вероятности удара ногой - мы вызываем punch действие.

И это все! Вы можете увидеть результат ниже:

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

Распознавание действия

Если мы соберём большой и разнообразный набор изображений с людьми, бьющими рукой или ногой, мы сможем создать модель, которая отлично работает на отдельных кадрах. Однако достаточно ли этого? Что делать, если мы хотим сделать ещё один шаг и выделить два разных типа ударов ногой: прямой удар и удар с разворотом.

Как видно из приведённых ниже снимков, оба удара будут выглядеть одинаково в конкретный момент времени с определённого угла:

Но если мы посмотрим на выполнение, движения будут совсем другими:

Итак, как же мы научим нашу нейронную сеть изучать последовательность кадров, а не один?

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

  • Обработка естественного языка (NLP), где одно слово зависит от того, что до и после
  • Предсказание, какую страницу посетит пользователь, в зависимости от истории навигации
  • Распознавание действия из последовательности кадров

Реализация такой модели выходит за рамки этой статьи, но давайте взглянем на типовую архитектуру, чтобы мы могли получить представление о том, как всё работает вместе!

Сила RNN

На диаграмме ниже изображена модель распознавания действий:

Мы берём последние n кадров из видео и передаём их в CNN. Результат работы CNN для каждого кадра, мы передаём в качестве входа в RNN. Рекуррентная нейронная сеть определит зависимости между отдельными кадрами и распознает, какое действие они кодируют.

Заключение

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

После этого мы узнали что такое перенос обучения и как мы сможем с его помощью повторно использовать обученную модель MobileNet из @tensorflow-models/mobilenet. Мы загрузили MobileNet из файла в процесс Node.js и обучили дополнительный слой, в который мы передавали выходные данные из скрытого слоя MobileNet. После обучения мы добились более чем 90-процентной точности!

Чтобы использовать модель, которую мы разработали, в браузере, мы загрузили её из файла вместе с MobileNet и затем классифицировали кадры с пользовательской камеры каждые 100мс. Мы подключили модель к игре MK.js и использовали результат работы модели для управления одним из героев.

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

Надеюсь, вам понравился этот небольшой проект так же, как и мне! 🙇


Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.