Если не терпится перейти сразу к коду, то вот мой репозиторий.
Так что да, распознавание лиц — лишь часть приложения, причём самая трудная часть. Так что налейте себе кофе и наслаждайтесь моим рассказом (я старался).
Часто встречаются посвящённые глубокому обучению проекты на Python, но не на Node.js. Причина в том, что под Python гораздо больше библиотек для эффективных вычислений, например, Numpy, Pandas, tensorflow и так далее. И разрыв достаточно велик. Я знаю Node.js, ну, немного знаю, и хотел использовать его в проекте, чтобы освоить получше, пока вожусь с машинным обучением.
Всё началось во время онлайн-соревнования, на котором был вопрос по работе с API AzureML (определение выражения лица). Я увидел, что там есть ещё и API для распознавания лиц, но с некоторыми ограничениями. К тому же сначала нужно загрузить изображение на сервис, затем он вычисляет результат и присылает его мне. По мне слишком медленно. Я хотел с ним поиграться, но в то время сервис был недоступен в моей стране. Так что хочу поблагодарить разработчиков, которые натолкнули меня на идею сделать что-то своё. До той поры я лишь изучал то, что уже сделали другие. У меня были определённые сомнения с точки зрения безопасности, нужна была функция бэкапа. К тому же я прочитал статью моего шефа об изучении нового языка. И подумал, что настала прекрасная возможность применить свои знания в совершенно новом проекте.
Хорошо, и что дальше? Я подключил к своему Macbook шнур питания, потому что понимал, что это будет долгая работа, и взял с собой своего приятеля Google на разгульные поиски лакомых ресурсов, с которых можно начать проект. Оказалось, что люди уже спрашивали в сети, можно ли реализовать то, что я задумал, но толковых ответов не было. В то время я был один на один с этой задачей. Потом наткнулся на отличную серию публикаций Адама Гайтгея. Раньше я читал его блог, но потом как-то увлёкся другими вещами. И тут наткнулся на замечательную статью Адама и узнал, что он создал на Python пакет face_recognition. Я скачал его и протестировал. Ну, работал он не так гладко, как хотелось бы.
Так я себя ощущал, когда устанавливал dlib. Не знаю, почему, но возникла проблема с установкой на самом последнем этапе. Я потратил много часов, пытаясь выяснить причину, даже не пошёл в тот день в спортзал.
Я был на грани того, чтобы забросить проект. Но потом выяснил, что причина была в конфликте из-за пути Python-пакета anaconda, или что-то типа того. Я всё ещё изучал экосистему, поэтому пришлось решать, оставить ли anaconda и забросить проект, или избавиться от «змеи». В конце концов я совсем убрал anaconda, и потратил день на завершение полного удаления разных версий Python, скачанных разными пакетами, оставив только системную версию. Затем с помощью Homebrew корректно скачал Python3, установил dlib, и к концу дня смог её запустить.
Тут возникла новая проблема: как интегрировать библиотеку в Node.js? Снова я встал перед дилеммой: изучать Python-фреймворк вроде Django, Flask и так далее, чтобы продолжать работу над проектом, или ввязаться в потенциально бесконечную задачу, которая может оказаться вообще нерешаемой. Когда-то я прочитал эту фразу и она всплыла у меня в голове:
Правило математики: если это выглядит простым, значит вы делаете неправильно.
Фраза вдохновила меня наравне со статьёй шефа, так что я решил продолжать с Node.js, который немного знал по некоторым веб-проектам.
Поэтому я снова занялся поиском способов интегрирования Python и Node.js. Узнал о дочерних процессах (child process) в Node.js. Но моя ситуация выглядела так:
Читал документацию со скудными примерами. Читал блоги, и везде был одно и то же. Но на этот раз я намеревался завершить проект любой ценой (читай, с помощью стажировки). Поскольку уже близилось начало сезона стажировки, мне нужно было как можно скорее закончить приготовления. К тому же мне необходимо было достаточно времени для создания следующим летом другого такого проекта, но опять самостоятельно, с помощью одного лишь Google. Если вы немного знакомы с машинным обучением, то вам будет это близко:
Если скорость обучения слишком низкая, то достижение оптимума займёт немало времени, или вы застрянете на локальном минимуме. Если скорость слишком высокая, то вы можете промахнуться. Но если скорость правильная, или регулируется в зависимости от условий, то алгоритм достаточно быстро найдёт оптимальную точку.
Для меня, как для алгоритма, стажировка была правильной скоростью обучения. Так что мне нужно было закончить проект.
Так что я усердно трудился, иногда работал до 5.45 утра. Это было удивительное время, я совершил немало дурацких ошибок. Не обновлял вкладку, когда менял код на сервере. Несколько раз менял код на клиенте, но не обновлял окно. Не знаю, почему, может быть, слишком хотелось спать, при комфортной температуре в 22 градуса Цельсия в моей уютной постели. Были удивительные моменты, вроде поиска на Stackoverflow ненужной логической ошибки, которую я позднее пофиксил, просто обновив вкладку.
Наконец я смог подружить Python и Node.js.
После того случая с AzureML я испытывал благоговение при создании полностью оффлайнового веб-приложения, способного делать всё задуманное безо всякого интернета. Мне пришлось находить альтернативные API или делать их самому. Как вы, наверное, знаете, в информатике это в порядке вещей: время обратно пропорционально пространству. Так что я старался минимизировать время, затрачиваемое на обеспечение работы тех или иных вещей. Отправка фото/видео в облачный сервис занимает время и канал, а значит приходилось увеличивать занимаемое пространство. Хотя облачные сервисы гораздо удобнее, но мне нравится писать много кода, если я делаю то, что мне интересно. Вы можете принять это, прочитав инструкции по установке для моего репозитория.
Я нашёл много пакетов, пробовал один за другим, брал в работу, иногда отбрасывал и снова искал. Например, нужно было интегрировать распознавание QR-кодов в качестве запасного варианта на случай сбоя распознавания лица. Существует много пакетов для генерирования кодов, но не для их сканирования. В конце концов я нашёл пакет Instascan. Натерпелся с ним бед. Дело в том, что я использовал OpenCV3 для распознавания лица в Python, и той же камерой (в ноутбуке она единственная) сканировал QR-коды. Для сканирования мне нужен был маленький видеокадр, но при этом изменялся и размер видео для распознавания лица. Ну, это не казалось какой-то проблемой. Я могу остановить процесс распознавания, выполнить сканирование QR-кода и снова запустить. Довольно просто, да? Но если вы читали внимательно, то заметили два момента:
- Если это выглядит простым, значит вы делаете неправильно.
- Я пытаюсь минимизировать время (главная причина, чтобы не ждать, когда станет доступен API).
Так что для решения этой проблемы мне пришлось изучить код пакетов и выполняемые ими процессы.
Парсинг библиотек
Да, я приступал к этой задаче три раза — анализировал библиотеки, чтобы разобраться со всякими сложностями. По крайней мере, сложностями для меня.
- Изучал работу пакета face_recognition, чтобы адаптировать его для своих целей. В то же время помог другим.
- Изучал маленький Instascan чтобы понять, как он работает с камерой, и как вообще работать с камерой веб-приложению. Есть много случаев, которые нужно обрабатывать: что если пользователь каким-то образом остановит работу камеры, например, кликнув мимо модального окна или вообще закрыв его. Я изменял код, много раз запускал его, каждый раз находя несколько багов. Однажды у меня на Mac практически кончилась память, и я подвис на несколько секунд. После многократных попыток я наконец-то добился успеха, но опять нашёл очередной баг.
- На этот раз баг был в модальном окне фреймворка Materialize. Коллбэк не работает. Погуглил, покопался на Github и Stackoverflow — решения не нашёл. Вычислил отвечающий за баг код, попытался в нём разобраться, несколько раз прогнал его с выражениями console.log(), стараясь понять, что происходит, всё ближе подбираясь к багу, по частям изолируя код (почувствовал себя хакером, обходящим пароль). Слышал, что формы в этом фреймворке тоже не слишком хороши, так что поиграюсь с ними в другом веб-приложении.
Event++
Это удивительный баг, возникший из-за jQuery. Количество событий увеличивается с каждым кликом: 1, 2, 3, 4, 5, 6, 7, 8… Мои уведомления в реальном времени полностью забили весь правый столбец, в который они выводились. Выяснилось, что в jQuery я использую on() вместо click(), а также использую события socket.on() вне обработчика.
Наконец, после долгой борьбы, серверная часть начала неплохо работать с клиентской.
Я подумал, что всё готово. Но тут меня посетила идея: что если добавить поддержку базы данных, чтобы пользователь мог выполнять CRUD-операции. Я хотел оставить на усмотрение пользователей, что им использовать: SQL или NoSQL. Думал, что добавлю поддержку и, кто знает, смогу делать на этом деньги (можно слишком рано начать витать в облаках, добившись небольшого успеха). Ещё чуть чуть, и у меня будет полноценная функциональность для автоматического учёта приходящих/уходящих посетителей (распознавание лица, сканирование QR-кода, отсутствие ограничений по API, трёхшаговая аутентификация). Но:
- Я всё узнал от open-source сообщества.
- Не думаю, что могу продавать свой продукт, стучась в разные компании. Для этого я слишком ленив. Я бы предпочёл разработать ещё несколько подобных продуктов.
- Представил сцену из фильма «Социальная сеть» в которой Марк написал приложение и выложил в свободный доступ, даже хотя у него были хорошие предложения о приобретении (думаю, от Microsoft), а тут я такой красивый, с маленьким веб-приложением, которые даже не написал с нуля.
Я попробовал интегрировать MongoDB, потому что был с ней немного знаком, и к тому же я ещё не изучал SQL в колледже. Это будет только в следующем семестре. Так что реализацию этой фичи я оставил для другого проекта, возможно, форка или портирования этого.
В общем, я разработал, интегрировал, потратил несколько дней на изучение, применение, отладку и так далее.
Наконец, я увидел это:
Фронтенд, бэкенд и база данных работают в унисон. Конечно, в центре — серверная часть (объединившая в себе силу Python и Node.js). Вы можете решать и другие задачи, например, обучать модель, потому что я смог интегрировать OpenCV3 (это требует установки бинарников), face_recognition, numpy, pandas с датасетом, и сохраняю результат в .csv-формате в моём Python-процессе. Так что если у вас есть подходящее железо, то можете сделать на основе моего проекта что-то совершенно другое.
Оставляю на ваше усмотрение, кто на гифке фронтенд, а кто база данных.
Signing.Off();
* * *
Ссылка на проект.
Почему я написал этот текст, хотя моя кодовая база не так велика? Что тут такого? Ну, для многих из вас это лишь несколько сотен строк кода, но для меня интегрирование всех частей, планомерное изучение, обновление имеющихся знаний, самостоятельное исправление одного база за другим в этом бессрочном проекте (для меня) — всё это задача, которую никто ещё не решал (машинное обучение на Python и Node.js). Ну, может я и плохо искал. В общем, для меня это большой проект. Надеюсь, кому-нибудь он будет полезен. Кроме того, этот пост я написал, чтобы оживить в памяти моменты разочарования и временного счастья, когда что-то ломалось или работало. Такова жизнь.
Комментарии (9)
GDXRepo
02.08.2017 14:36+5распознавания лиц с точностью 99,38%
Пардон, вопрос, а кто оценку точности проводил?..missing_thing
02.08.2017 16:28-2В репозитории все указано (если туда смотреть, конечно)
GDXRepo
02.08.2017 18:13Смотреть-то я смотрел, поэтому и вопрос задал. Там указано, где проводились тесты, это да, но на странице с этими тестами я нигде не нашел подтверждения, что конкретно ваша библиотека и алгоритм работают именно с такой точностью. Поймите правильно, в заголовке указана вполне конкретная точность — а конкретные цифры требуют конкретного подтверждения, иначе это похоже на цифру, взятую с потолка.
AC130
03.08.2017 10:33+1Насколько я понимаю, нет никакой «его библиотеки и его алгоритма». Автор взял готовый питоновский модуль face_recognition (кстати интересная вещь, надо посмотреть), который основан на плюсовой библиотеке dlib. Некие ребята тестили эту штуку на LFW и получили озвученную точность. А автор просто написал враппер, который позволяет поднять веб-сервер с красивеньким интерфейсом для всего этого.
Aspos
Не обязательно было делать связку Python-Node. Можно было прямо в ноде всё сделать.
И даже сделать так, чтобы работало всё на клиенте чтобы javascript-приложение можно было обернуть в cordova и запускать на мобильном. Более того, на клиенте можно и WebGL использовать, но…, да, молчу-молчу.
Есть целый ряд рабочих библиотек под JS
https://github.com/harthur-org/brain.js
https://github.com/karpathy/convnetjs