Статья о том, как мы шаг за шагом строили наш AI. Время чтения 10+ минут.
Введение. Стартап в области компьютерного зрения, используемый low-cost разработку в качестве базовой концепции. Команда вполне соответствует духу: 3 — 5студентов разработчиков разного уровня и направления, в зависимости от дня недели и времени суток (от 0.25 до 1.25 ставки). Мой опыт игры в пятнашки здесь очень пригодился.
Коротко о продукте (пк + софт) — интеллектуальная система видео наблюдения, подключаемая к локальной сети и производящая обработку видео своими силами. Два обязательных условия: наличие интерфейса пользователя с различными правами и максимальная автономность алгоритмов.
С технической стороны ограничений на железо не было, главное — чтоб работало хорошо; а вот с финансовой были. На все про все ~500$. Разумеется только новые и современные комплектующие. Выбор их не велик, но есть!
С железом определились, далее софт. Выбор пал на микросервисную архитектуру с использованием докера по некоторым, достаточно весомым причинам.
Разработка фичей шла от простых и необходимых (работа с потоками и видео файлами) к сложным, с периодическим review. Собрали MVP, несколько спринтов оптимизации заметно приблизили нас к заветной цели — выполнять все 4 пункта одновременно, а не по отдельности:
Немного о стеке, выбор пал на: С/С+, Python + TensorFlow, PHP, NodeJS, TypeScript, VueJS, PostgreSQL + Socket.io и прочие мелочи.
Реализованные фичи были сознательно скрыты, чтоб подробнее остановится, пожалуй, на самой интересной и восхитительной фичи из области CV и в некоторой степени — ML.
Пример использования — собирать историю визитов каждого конкретного посетителя, причем сотрудников учитывать отдельно, даже если мы не знаем что это сотрудник (Пример — ТРЦ).
И казалось бы, вроде как эта задача решена 100500+ раз и телефоны и все что угодно уже умеет распознавать лица и запоминать их, отправлять куда то, сохранять. Но 95% решений используются в СКУД, где сам пользователь стараясь быть распознанным, стоит перед камерой 5Мп на расстоянии 30-50см в течении нескольких секунд, пока его лицо не сверится с одним или несколькими лицами из БД.
В нашем случае такие условия были роскошью. Пользователи двигались хаотично, смотря в свой смартфон на достаточном расстоянии от камеры, установленной на потолке. Дополнительно, сложности вносили сами камеры, чаще всего — это бюджетные камеры с 1.3-2Мп и какой то непонятной цветопередачей, всегда разной.
Частично, эта проблема решилась формированием ТЗ на условия установки камер, но в целом система должна была уметь распознавать и в таких условиях (естественно — хуже).
Подход к решению: задача была декомпозированна на 2 задачи + структура БД.
Отдельный сервис, где в основном протекает real-time процесс, на входе кадр с камеры (на самом деле другого сервиса), на выходе — http запрос с нормированным 512-и мерным Х-вектором (face-id) и некоторыми мета-данными, например time stamp.
Внутри нее множество интересных решений в области логики и оптимизации, но на этом всё; пока что всё…
Отдельный сервис, где требования к real-time не стоит остро, но в некоторых случая это важно (например — человек из стоп листа). В целом ограничились 3 секундами на обработку.
На входе в сервис — http от краткосрочной памяти с 512-и мерным вектором внутри; на выходе — Id посетителя.
Первые мысли очевидны, решение задачи достаточно просто: получил http > сходил в БД, взял, что есть > сравнил с начинкой http, если есть такой, то он и есть; если нет, то новый.
Плюсов такого решения не счесть, а минус только один — оно не работает.
Задачу решили, хоть и пошли путем самурая, пробуя различные подходы, периодически посматривая просторы интернета. В общем решение оказалось в меру лаконичным. Концепция достаточно проста и основывается на кластеризации:
Примечание: т.к. мы использовали нормированные вектора, то расстояние между ними было гарантированно от 0 до 2. Далее о алгоритме реализации концепции.
Х-вектор, полученный от краткосрочной памяти сравнивается с имеющимися в БД центроидами кластеров (А-вектор) на предмет близости, далекие, где range[X,A] > 1 — отбрасывались. Если никого не оставалось — создается новый кластер.
Далее ищется минимум между Х-вектором и всеми оставшимися а-векторами (min_range[X,a])
Вычисляется собственный range_A кластера, чей вектор самый близкий к Х-вектору. Здесь используется обратная линейная функция от количества векторов (N), уже находящихся в этом кластере (const*(1 — N/2M)); из коробки const =0,67).
Если range_А > min_range[X,a], то Х-вектор помечается как принадлежащий к А-кластеру. Если нет — то… Ох… Это чем то похоже на описание математической модели недопонимания.
Определились с тем, что в этом случае будем создавать новый кластер, тем самым сознательно шли на ошибку 1-го рода «Пропуск цели».
Субъективный опыт — это когда данные становятся инструментом. Ранее мы распознали, но возможно с ошибкой. Стоит ли доверять Х-вектору, чтоб использовать его в следующем матчинге !? Проверяем! Х-вектор должен:
Выполняя эти условия, Х-вектор попадает в состав кластера А ( до этого он просто принадлежал User). Если векторов в кластере становится больше допустимого, то убираем самый центральный (min_range[А,a]) — он вносит меньше всего разнообразия и является лишь функцией остальных; к тому же центроид и так участвует в матчинге.
В каждом сложном выборе мы делали шаг в сторону ошибки «Пропуск цели» — создавали новый кластер и User. Пришло время пересмотреть их… все. После #4 мы имеем модифицированный кластер А. Далее мы пересчитываем его центроид (А-вектор) и ищем минимальное расстояние ко всем имеющимися центроидам в нашем 512-ти мерном пространстве. В этом случае расстояние считается более сложно, но это сейчас не так важно. Когда расстояние min_range[A,B] будет меньше, чем некоторая величина (из коробки range_unity=0,25) мы объединяем два множества, считаем новый центроид и избавляемся от менее «полезных» векторов, если их слишком много.
Другими словами: если существует 2+ кластера, в действительности, принадлежащих одному User, то они, спустя некоторую серию детекций, станут близки и объединяться в один вместе со своими историями.
Здесь стоит определить новый термин в этой статье. Фантомный вектор — вектор, который был получен не в результате деятельности краткосрочной памяти, а в результате функции над N-шт векторов кластера (a1,a2,a3,a4…). Разумеется, полученные таким образом вектора хранятся и учитываются отдельно и не представляют из себя ни какой ценности до тех пор, пока в результате матчинга не будут определены, как ближайшие (см #3). Основная польза фантомных векторов — ускорение обучения кластера на его ранних этапах.
Система уже запущена в продакшен. Результат был получен на реальных данные вне тестовой среды на 5000+ User; так же там была замечена пачка «слабых» мест, которые были усилены и учтены в этом тексте.
Интересно то, что такой подход ни имеет пользовательских настроек и вся работа ни как не управляется, все абсолютно автономно. Дополнительно, анализ временных рядов позволяет классифицировать User на различные, свои категории аналогичным методом, тем самым строить ассоциаций. Именно так и решился вопрос — кто есть сотрудники, а кто — посетители.
Роль пользователя системы проста — нужно периодически проверять почту или интерфейс системы на наличие новых отчетов о работе.
Величина близости при распознании, основанном на долгосрочной памяти ~0,12-0,25 у умеренно обученного кластера (содержит 6-15 а-векторов). Далее обучение замедляется по причине повышения вероятности «копий векторов», но в долгосрочной перспективе близость стремится к величинам ~0,04-0,12, когда кластер содержит уже 20+ а-векторов. Замечу, что внутри краткосрочной памяти, от кадра к кадру, этот же параметр имеет значение ~0,5-1,2, что звучит примерно как: «Человек больше похож* на себя в очках 2 года назад, чем 100мс назад». Такие возможности открывает использование кластеризации в долгосрочной памяти.
В результате одного из тестов получилось интересное наблюдение.
Начальные условия:
Действие:
Результат:
Жаль, что о многом я не могу написать здесь, но возможно, кое-что я смогу описать так же подробно в другой статье. Возможно, все это уже описано в какой-то замечательной методичке, но к моему сожалению, я такую так и не нашел.
В заключении скажу, что наблюдать изнутри, как автономная AI система классифицирует окружающее себя пространство, реализуя по пути различные заложенные в нее фичи — очень интересно. Люди много чего не замечают в силу наличия накопленного опыта восприятия (шаг #4).
Очень надеюсь, что эта статья будет полезна кому либо в его проекте.
Введение. Стартап в области компьютерного зрения, используемый low-cost разработку в качестве базовой концепции. Команда вполне соответствует духу: 3 — 5
Коротко о продукте (пк + софт) — интеллектуальная система видео наблюдения, подключаемая к локальной сети и производящая обработку видео своими силами. Два обязательных условия: наличие интерфейса пользователя с различными правами и максимальная автономность алгоритмов.
С технической стороны ограничений на железо не было, главное — чтоб работало хорошо; а вот с финансовой были. На все про все ~500$. Разумеется только новые и современные комплектующие. Выбор их не велик, но есть!
С железом определились, далее софт. Выбор пал на микросервисную архитектуру с использованием докера по некоторым, достаточно весомым причинам.
Разработка фичей шла от простых и необходимых (работа с потоками и видео файлами) к сложным, с периодическим review. Собрали MVP, несколько спринтов оптимизации заметно приблизили нас к заветной цели — выполнять все 4 пункта одновременно, а не по отдельности:
- 16+ ip-камер (FHD/25fps) трансляция, воспроизведение по событию или времени и запись
- Параллельная работа всех имеющихся CV алгоритмов
- Пользователь интенсивно пользуется интерфейсом без задержек — смотрит стримы
- Загрузка ЦП менее 90% и все работает (!)
Немного о стеке, выбор пал на: С/С+, Python + TensorFlow, PHP, NodeJS, TypeScript, VueJS, PostgreSQL + Socket.io и прочие мелочи.
Реализованные фичи были сознательно скрыты, чтоб подробнее остановится, пожалуй, на самой интересной и восхитительной фичи из области CV и в некоторой степени — ML.
«Уникальные User»
Пример использования — собирать историю визитов каждого конкретного посетителя, причем сотрудников учитывать отдельно, даже если мы не знаем что это сотрудник (Пример — ТРЦ).
И казалось бы, вроде как эта задача решена 100500+ раз и телефоны и все что угодно уже умеет распознавать лица и запоминать их, отправлять куда то, сохранять. Но 95% решений используются в СКУД, где сам пользователь стараясь быть распознанным, стоит перед камерой 5Мп на расстоянии 30-50см в течении нескольких секунд, пока его лицо не сверится с одним или несколькими лицами из БД.
В нашем случае такие условия были роскошью. Пользователи двигались хаотично, смотря в свой смартфон на достаточном расстоянии от камеры, установленной на потолке. Дополнительно, сложности вносили сами камеры, чаще всего — это бюджетные камеры с 1.3-2Мп и какой то непонятной цветопередачей, всегда разной.
Частично, эта проблема решилась формированием ТЗ на условия установки камер, но в целом система должна была уметь распознавать и в таких условиях (естественно — хуже).
Подход к решению: задача была декомпозированна на 2 задачи + структура БД.
Краткосрочная память
Отдельный сервис, где в основном протекает real-time процесс, на входе кадр с камеры (на самом деле другого сервиса), на выходе — http запрос с нормированным 512-и мерным Х-вектором (face-id) и некоторыми мета-данными, например time stamp.
Внутри нее множество интересных решений в области логики и оптимизации, но на этом всё; пока что всё…
Долгосрочная память
Отдельный сервис, где требования к real-time не стоит остро, но в некоторых случая это важно (например — человек из стоп листа). В целом ограничились 3 секундами на обработку.
На входе в сервис — http от краткосрочной памяти с 512-и мерным вектором внутри; на выходе — Id посетителя.
Первые мысли очевидны, решение задачи достаточно просто: получил http > сходил в БД, взял, что есть > сравнил с начинкой http, если есть такой, то он и есть; если нет, то новый.
Плюсов такого решения не счесть, а минус только один — оно не работает.
Задачу решили, хоть и пошли путем самурая, пробуя различные подходы, периодически посматривая просторы интернета. В общем решение оказалось в меру лаконичным. Концепция достаточно проста и основывается на кластеризации:
- Каждый вектор (а-вектор) будет принадлежать какому либо User; каждый кластер (не более М векторов, из коробки M =30) принадлежит какому либо User. Принадлежит ли а-вектор кластеру А — не факт. Вектора в кластере определяют взаимодействие кластера, вектора в User определяют только историю User.
- Каждый кластер будет иметь центроид (по сути — А-вектор) и собственный радиус (далее — range) взаимодействия с другими векторами или кластерами.
- Центроид и range будут функцией кластера, а не статикой.
- Близость векторов определяется квадратом евклидова расстояния (в особых случаях — иначе). Хотя здесь есть еще несколько других достойных методов, но мы просто остановились на этом.
Примечание: т.к. мы использовали нормированные вектора, то расстояние между ними было гарантированно от 0 до 2. Далее о алгоритме реализации концепции.
#1 Круг подозреваемых. Центроид, как хэш-функция
Х-вектор, полученный от краткосрочной памяти сравнивается с имеющимися в БД центроидами кластеров (А-вектор) на предмет близости, далекие, где range[X,A] > 1 — отбрасывались. Если никого не оставалось — создается новый кластер.
Далее ищется минимум между Х-вектором и всеми оставшимися а-векторами (min_range[X,a])
#2 Уникальные свойства кластера. Саморегулируемая сущность
Вычисляется собственный range_A кластера, чей вектор самый близкий к Х-вектору. Здесь используется обратная линейная функция от количества векторов (N), уже находящихся в этом кластере (const*(1 — N/2M)); из коробки const =0,67).
#3 Валидация и непонимание. Если ни кто — то кто !?
Если range_А > min_range[X,a], то Х-вектор помечается как принадлежащий к А-кластеру. Если нет — то… Ох… Это чем то похоже на описание математической модели недопонимания.
Определились с тем, что в этом случае будем создавать новый кластер, тем самым сознательно шли на ошибку 1-го рода «Пропуск цели».
#4 Дообучение. Как циферки формируют признаки
Субъективный опыт — это когда данные становятся инструментом. Ранее мы распознали, но возможно с ошибкой. Стоит ли доверять Х-вектору, чтоб использовать его в следующем матчинге !? Проверяем! Х-вектор должен:
- быть достаточно близок к центроиду А (range_А > range[X, А])
- быть полезным и разнообразным, ведь с одной стороны мы минимизируем риск ошибки, с другой — копии нам тоже не нужны (Config_Max[0,35] > range[X,a] > Config_Max[0,125]). Тем самым, конфиги определяют скорость и правильность «обучения».
Выполняя эти условия, Х-вектор попадает в состав кластера А ( до этого он просто принадлежал User). Если векторов в кластере становится больше допустимого, то убираем самый центральный (min_range[А,a]) — он вносит меньше всего разнообразия и является лишь функцией остальных; к тому же центроид и так участвует в матчинге.
#5 Работа над ошибками. Превращаем недостатки в достоинства
В каждом сложном выборе мы делали шаг в сторону ошибки «Пропуск цели» — создавали новый кластер и User. Пришло время пересмотреть их… все. После #4 мы имеем модифицированный кластер А. Далее мы пересчитываем его центроид (А-вектор) и ищем минимальное расстояние ко всем имеющимися центроидам в нашем 512-ти мерном пространстве. В этом случае расстояние считается более сложно, но это сейчас не так важно. Когда расстояние min_range[A,B] будет меньше, чем некоторая величина (из коробки range_unity=0,25) мы объединяем два множества, считаем новый центроид и избавляемся от менее «полезных» векторов, если их слишком много.
Другими словами: если существует 2+ кластера, в действительности, принадлежащих одному User, то они, спустя некоторую серию детекций, станут близки и объединяться в один вместе со своими историями.
#6 Комбинаторика признаков. Когда машина… думает !?
Здесь стоит определить новый термин в этой статье. Фантомный вектор — вектор, который был получен не в результате деятельности краткосрочной памяти, а в результате функции над N-шт векторов кластера (a1,a2,a3,a4…). Разумеется, полученные таким образом вектора хранятся и учитываются отдельно и не представляют из себя ни какой ценности до тех пор, пока в результате матчинга не будут определены, как ближайшие (см #3). Основная польза фантомных векторов — ускорение обучения кластера на его ранних этапах.
Система уже запущена в продакшен. Результат был получен на реальных данные вне тестовой среды на 5000+ User; так же там была замечена пачка «слабых» мест, которые были усилены и учтены в этом тексте.
Интересно то, что такой подход ни имеет пользовательских настроек и вся работа ни как не управляется, все абсолютно автономно. Дополнительно, анализ временных рядов позволяет классифицировать User на различные, свои категории аналогичным методом, тем самым строить ассоциаций. Именно так и решился вопрос — кто есть сотрудники, а кто — посетители.
Роль пользователя системы проста — нужно периодически проверять почту или интерфейс системы на наличие новых отчетов о работе.
Результат
Величина близости при распознании, основанном на долгосрочной памяти ~0,12-0,25 у умеренно обученного кластера (содержит 6-15 а-векторов). Далее обучение замедляется по причине повышения вероятности «копий векторов», но в долгосрочной перспективе близость стремится к величинам ~0,04-0,12, когда кластер содержит уже 20+ а-векторов. Замечу, что внутри краткосрочной памяти, от кадра к кадру, этот же параметр имеет значение ~0,5-1,2, что звучит примерно как: «Человек больше похож* на себя в очках 2 года назад, чем 100мс назад». Такие возможности открывает использование кластеризации в долгосрочной памяти.
Загадка
В результате одного из тестов получилось интересное наблюдение.
Начальные условия:
- На двух абсолютно одинаковых ПК развернута абсолютно одинаковая система видео наблюдения с абсолютно одинаковыми настройками. Они подключены к одной единственной ip-камере, расположенной грамотно, согласно ТЗ.
Действие:
- Системы запускают в одно и то же время и оставляют на неделю в покое со всеми работающими алгоритмами. Трафик соответствует нормальному трафику без изменений.
Результат:
- Количество созданных User, кластеров и а-векторов одинаково, а центроиды разные, не значительно — но разные. Вопрос — почему? Кто знает — пишите в комментариях или сюда)
Жаль, что о многом я не могу написать здесь, но возможно, кое-что я смогу описать так же подробно в другой статье. Возможно, все это уже описано в какой-то замечательной методичке, но к моему сожалению, я такую так и не нашел.
В заключении скажу, что наблюдать изнутри, как автономная AI система классифицирует окружающее себя пространство, реализуя по пути различные заложенные в нее фичи — очень интересно. Люди много чего не замечают в силу наличия накопленного опыта восприятия (шаг #4).
Очень надеюсь, что эта статья будет полезна кому либо в его проекте.
gofat
Самая простая история, почему центроиды могут различаться — не зафиксирована случайность инициализации.