О чем говорят руки. 2 место на соревновании Kaggle
Приветствую всех читателей! Меня зовут Артем Топоров, и сегодня я хочу поделиться с вами своим опытом участия в соревновании по распознаванию жестового языка, организованном компанией Google. На этом соревновании, собравшем 1139 команд со всего мира, нам удалось занять 2 место. Расскажу как мы вместе с Николаем Форратом и Xun Zhao разработали ML алгоритм для мобильных устройств, едва не заняли первое место и при чем тут спектрограммы.
leaderboard с призовыми местами
Описание соревнования
Соревнование под названием Google — Isolated Sign Language Recognition проводилось на платформе Kaggle и длилось около трех месяцев. Помимо Google в организации соревнования приняли участие Технологический институт Джорджии и организация D-PAN, которая поддерживает творческих профессионалов с нарушениями слуха. Целью данного соревнования было разработать алгоритмы для распознавания жестового языка. Эта инициатива была направлена на сокращение коммуникационного барьера между глухими и слышащими людьми, предлагая технологическое решение, которое могло бы облегчить процесс обучения языку жестов.
В следующих разделах я расскажу более подробно о технических аспектах соревнования, нашем подходе к решению задачи и о том, какие проблемы возникли у нас на этом пути.
название соревнования
Увы, каждый день в мире рождаются дети с нарушениями слуха, а статистика говорит о том, что 90% из них рождаются у слышащих родителей, многие из которых не владеют жестовым языком. Это создает проблему, так как родители нуждаются в способах общения со своими детьми, ибо использование жестов может предотвратить развитие синдрома языковой депривации. Этот синдром проявляется в отсутствии естественного овладения языком в критический период его изучения, что может оказать серьезное влияние на их жизнь в целом, включая отношения, образование и трудоустройство.
Обучение жестовому языку — задача нетривиальная и затратная по времени, а многие родители просто не обладают достаточными ресурсами для этого. Чтобы упростить задачу для родителей было создано мобильное приложение PopSign — игра, в которой нужно соотносить жесты, показанные на видео, с словами в пузырях, чтобы их лопнуть.
скрины приложения PopSign
Однако организаторы решили расширить функционал приложения, позволяя пользователям не только смотреть видео с жестами, но и активно практиковать их, показывая жесты перед камерой. Эта новая функция требовала алгоритмов классификации жестов, и именно для их разработки и проводилось данное соревнование.
Таким образом нашей целью было разработать алгоритм, который сможет классифицировать различные жесты, при этом его можно будет запустить на мобильном устройстве при всех его ограничениях.
Описание жестового языка
Жестовые языки не только различаются от страны к стране, но и классифицируются на различные категории. В рамках данного соревнования мы сфокусировались на распознавании изолированных жестов, которые, в отличие от дактилологии, не представляют собой отдельные буквы алфавита, а выражают целые слова.
изолированные жесты и алфавит
Данные
В качестве входных данных участникам предоставили не исходный видеоряд, а координаты ключевых точек, полученные c помощью MediaPipe holistic.
пример результатов работы MediaPipe
Конвейер MediaPipe Holistic объединяет отдельные модели для компонентов позы, лица и рук, каждая из которых оптимизирована для своей конкретной области. Сначала определяется поза человека с помощью детектора поз BlazePose. Затем, используя предполагаемые ориентиры позы, выделются три области интереса (кисти рук и лицо) и к ним применяются модели для этих частей тела. Получив все необходимые координаты, они объединяются с координатами позы.
процесс нахождения координат ключевых точек
Таким образом у участников соревнования была информация о 468 точках на лице, 21 на каждой руке и 33 точках, описывающих позу человека. Каждая точка описывалась тремя координатами в каждый момент времени.
Организаторы соревнования предоставили нам около 95 тысяч размеченных примеров, в которых встречалось 250 уникальных жестов, каждый из которых обозначал определенное слово.
cлушай, смотри, молчи
Самыми популярными словами в тренировочных данных оказались «listen», «look» и «shhh» (почти как фраза на латыни).
Для проверки решений в тестовой выборке было предоставлено около 40 тысяч примеров, которые были разделены на равные части: Public и Private. Участники могли получить оценку качества своего алгоритма только на публичной части, в то время как приватная часть оценивалась в конце соревнования и определяла итоговое место в рейтинге. Метрикой оценки качества в данном соревновании была точность (accuracy).
This is a Code Competition
Были времена, когда можно было обучить невообразимое количество моделей на локальной машине, прогнать их все на тестовой выборке и загрузить уже финальный csv файл с ответами на Kaggle, где считалась метрика оценки качества алгоритма. Но эта эпоха уже давно прошла, сейчас у вас уже нет доступа к тестовой выборке. Для оценки решения необходимо подготовить код и веса обученных моделей, загрузить их на Kaggle и дождаться пока ваше решение выдаст ответы для всей тестовой выборки и вы получите значение метрики. Обычно на это участникам даётся ограниченное время — до 9 часов, чтобы избежать чрезмерно сложных и трудоемких решений.
В данном соревновании были установлены строгие ограничения, отличающие его от большинства других. Во-первых, для проверки решений участники должны были предоставить обученную модель в формате TFLite, что потребовало определенных манипуляций для конвертации финального решения. Во-вторых, так как решение предполагалось использовать в мобильном приложении PopSign и время обработки каждого видео было ограничено 100 миллисекундами, вместо обычных 9 часов на Kaggle, решение должно было обрабатывать все тестовые данные за чуть больше часа и потреблять не более 40 МБ памяти.
Описание преобразования данных
В ходе разработки нашего решения нас, конечно, в первую очередь интересовали данные о руках, в то время как информация, полученная с лица, была излишней для решения по данной задаче.
ключевые точки на руке
Поскольку наше окончательное решение представляло собой ансамбль из двух моделей, данные для каждой из них обрабатывались по-разному. В первом решении была использована сверточная модель (Convolutional neural network, далее CNN), во втором Transformer архитектуры.
CNN model
В случае с моделью CNN мы сосредоточились на 80 ключевых точках, включая 42 точки с обеих кистей, а также 18 точек с губ и 20 точек, связанных с руками, плечами, бровями и носом. Это обеспечило нам необходимый объем данных для работы с жестами.
пример отображения ключевых точек в разные моменты времени
Так как у нас был немалый опыт работы с изображениями и звуком, то захотелось переложить задачу в привычный домен.
Как например звук в виде набора амплитуд преобразуют в мел-спектрограмму, и далее уже работают с изображением, применяя всю мощь компьютерного зрения, так и мы решили преобразовать последовательности координат в трех канальные изображения размером 80×160x3, где:
80 это количество выбранных точек;
160 — продолжительность каждого видеоролика, после интерполяции;
3 — количество координат (X, Y, Z).
конвертация входных данных в изображение
Таким образом, каждый видеоролик (а точнее набор данных из MediaPipe) был преобразован в изображение, где каждый пиксель представлял собой значение координаты определенной точки тела в определенный момент времени. Если какая-то часть тела не попадала в кадр или не распознавалась MediaPipe, вместо значений присутствовали NaN, которые мы заменили на 0, чтобы сохранить единый формат данных.
Transformer model
Во втором подходе мы использовали всего 61 точку, включая 40 точек с губ и 21 точку с кисти. Выбиралась только одна кисть, где меньше всего пропусков в данных и при необходимости сохранялась зеркально, чтобы всегда данные принадлежали к левой руке.
Несмотря на более ограниченное количество точек, были добавлены дополнительные признаки для улучшения качества модели:
Motion features, которые отражают длину перемещения точек между кадрами видеоролика. Этот признак позволяет учесть динамику движения руки и позволяет модели лучше понимать, как меняется поза с течением времени. Данные были рассчитаны по отношению к прошлому и будущему кадру.
Дополнительно мы использовали Distance features, которые представляют собой попарное расстояние между всеми точками кисти и некоторыми точками губ. Этот признак помогает модели учитывать пространственные взаимосвязи между различными частями тела.
Еще одним важным признаком были Angle features, которые характеризуют углы между фалангами для каждого пальца. Этот признак позволяет модели учитывать углы и форму пальцев. Для каждого пальца было рассчитано по 3 угла.
Аугментации
Аугментации стали неотъемлемой частью процесса обучения моделей машинного обучения. Они позволяют искажать исходные данные таким образом, чтобы они имели несколько иной вид, тем самым создавая больше уникальных примеров и предотвращая переобучение модели. Существует множество готовых решений, например библиотека albumentations, которая активно используется для обучения моделей компьютерного зрения. Но так как мы работали с довольно специфичными данными, большинство функций было написано самостоятельно.
Применяемые нами аугментации можно разделить на две группы.
1. Аугментации, применяемые к исходным данным:
Random Affine: Это случайные повороты, смещения и изменения масштаба. Они применялись как ко всем точкам, так и к отдельным частям тела.
Random Affine
Random Interpolation: Исходный пример сжимался по времени, а также случайно обрезалось несколько первых или последних секунд.
Random Interpolation
Finger Rotate
2. Аугментации, применяемые только к изображениям:
Time and Frequency Masking: Эти базовые аугментации часто применяются при работе со звуком. Они удаляют (заменяют нулями) случайные диапазоны по времени или частоте (в нашем случае группу точек).
Time and Frequency Masking
MixUp и CutMix: Обе техники предполагают смешивание двух изображений и их меток. В MixUp это осуществляется путем взвешенного смешивания пикселей и меток классов. В CutMix сначала выбирается случайное прямоугольное область на изображении, затем она вырезается и заменяется на соответствующую область с другого изображения, причем метки классов также смешиваются в соответствии с площадями вырезанных областей.
MixUp и CutMix
Replace: Это замена части тела. Например, если два примера из тренировочной выборки относятся к одному и тому же жесту, мы берем значения с левой руки из первого примера и заменяем ими соответствующие значения во втором примере, оставляя остальные значения неизменными.
Архитектуры моделей
В условиях ограничений по времени и памяти, предпочтение было отдано архитектуре EfficientNet-B0. Это быстрая и легковесная модель компьютерного зрения, которая хорошо подходила для наших целей. Для улучшения результатов мы внесли небольшие модификации в виде использования hypercolumn на последних 4 блоках. Hypercolumn — это метод, который позволяет объединить многоуровневые признаки из различных слоев сети для улучшения качества сегментации и распознавания объектов.
Для второго подхода мы использовали модели BERT и DeBERTa, которые широко применяются в области обработки естественного языка (NLP). Сложность этих моделей зависит от параметров, таких как размер скрытого слоя, количество слоев и количество голов внимания. В нашей реализации мы уменьшили значения этих параметров в 3 и более раз относительно значений по умолчанию, чтобы упростить модели и сократить вычислительные затраты.
основные параметры BERT
Основное различие между этими моделями заключается в способе управления вниманием.
BERT (Bidirectional Encoder Representations from Transformers) использует стандартный механизм внимания, который позволяет модели рассматривать оба направления текста одновременно (поэтому «Bidirectional» в названии). Это означает, что BERT может использовать контекст из левой и правой частей предложения для понимания смысла слова или фразы.
DeBERTa-v2 (Decoding-enhanced BERT with disentangled attention), с другой стороны, использует «разделенное внимание» (disentangled attention). Это подход, который позволяет модели более эффективно фокусироваться на различных аспектах текста, разделяя внимание на разные аспекты, такие как отношения между словами и их значимость. Это позволяет улучшить способность модели понимать контекст и взаимосвязи между словами или фразами.
В данных архитектурах текст преобразуется в токены, а затем в эмбеддинги которые характеризуют эти токены в заданном векторном пространстве. Мы пропустили эти операции, так как у нас уже были вектора состоящие из признаков описанных ранее.
Процесс обучения
В процессе обучения много времени занял подбор оптимальных параметров. Подбор осуществлялся с помощью библиотеки Optuna, которая использует несколько алгоритмов оптимизации для поиска наилучших параметров.
Примерно так выглядел список параметров для оптимизации:
param = {
'seed': np.random.randint(20, 10000), #6374,
'aug_prob': trial.suggest_float('aug_prob', 0.15, 0.25), # do_random_affine prob
'invert_prob': trial.suggest_float('invert_prob', 0.25, 0.32), # it flips all points (hands, lips, pose)
'scale_prob': trial.suggest_float('scale_prob', 0.17, 0.3), # prob to rescale some parts (e.g. one hand or hand and lips)
'lr': trial.suggest_float('lr', 2e-3, 2.8e-3), # LR
'train_bs': trial.suggest_categorical('train_bs', [32, 64]), # BS
'drop_rate': 0.2, #trial.suggest_float('drop_rate', 0.18, 0.22), # model dropout
'epochs': trial.suggest_int('epochs', 100, 210), # epochs
'img_masking': 0.98, # prob to use torchaudio masks
'model': 'img_v0', # model name
'dataset': 'img_80_mixup', # img_80_onehand for one hand new_size=(T, 64, 3), img_80_mixup for 2 hands new_size=(T, 80, 3)
'freq_m': trial.suggest_int('freq_m', 66, 80), # mask for time axis (yes name freq but mask time :) )
'time_m': trial.suggest_int('time_m', 3, 12), # mask some points. the max possible masked points
'use_loss_wgt': True, # use wieghted loss
'pw_bad': 1.45, # power to bad predicted classes
'pw_com': 0.81, # power to classes which has common classes (e.g. dad, grandpa, grandma)
"label_smooth": trial.suggest_float('label_smooth', 0.50, 0.65), # loss label smoothing
'shift_prob': trial.suggest_float('shift_prob', 0.12, 0.29), # shift random parts to random value (one or few)
'mixup_prob': trial.suggest_float('mixup_prob', 0.3, 0.42), # mixup prob
'zero_prob': 0., # pixel dropout prob. It didn't work
'rotate_prob': trial.suggest_float('rotate_prob', 0.18, 0.26), # rotate one or few parts
'replace_prob': trial.suggest_float('replace_prob', 0.08, 0.17), # replace one or two parts from another element with same class
'deep_supervision': False, # DSV
'interpol_prob': trial.suggest_float('interpol_prob', 0.15, 0.4), # interploation as in Carno' code
'normalize': True, # mean std normalization
'tree_rot_prob': trial.suggest_float('tree_rot_prob', 0.25, 0.55), # finger tree augmentation from Carno's Code
'interp_nearest_random': trial.suggest_float('interp_nearest_random', 0.35, 0.5),
'lookahed_k':trial.suggest_int('lookahed_k', 2, 7),
'lookahed_alpha':trial.suggest_float('lookahed_alpha', 0.3, 0.6),
}
В результате были получены наилучшие параметры для всех аугментаций, тип оптимайзера, скорость обучения, количество эпох и прочие параметры.
Обучение CNN
Тренировочные данные были разбиты на 8 частей, 1 из них шла в валидацию, остальные для обучения модели. Исходя из метрики на валидации выбирались лучшие параметры.
На финально подобранных параметрах была обучена модель на всех тренировочных данных. Для учета дисбаланса классов и семантических сходств между классами мы применяли взвешенную кросс-энтропию, увеличивая веса для плохо предсказанных классов и классов с семантически похожими парами (например, «kitty» и «cat»).
Обучение Transformer
Аналогично модели CNN, для Transformer сначала подбирались оптимальные параметры с использованием валидационной выборки, затем обучалась финальная модель на полном наборе данных с наилучшими параметрами.
Сложности с конвертацией и ограничением по времени
Существует несколько популярных библиотек для машинного обучения, пожалуй самые популярные TensorFlow и PyTorch.
Для более быстрой и легкой работы с TensorFlow существует высокоуровневый API под названием Keras, а для того чтобы запускать созданные на TensorFlow или Keras модели на мобильных устройствах был создан фреймворк TFLite (TensorFlow Lite).
У PyTorch тоже есть высокоуровневый API под названием PyTorch Lightning, для конвертации и использования на мобильных устройствах обычно используются ONNX и openvino, которые являются универсальными и работают с большинством библиотек машинного обучения.
библиотеки машинного обучения
Преобразование моделей из PyTorch в требуемый TFLite формат представляло собой несколько сложный процесс, требующий несколько этапов конвертации через промежуточные форматы. Это включало конвертацию в формат ONNX, затем в TensorFlow, и, наконец, в TFLite. Этот подход не только требовал дополнительных усилий и времени, но также приводил к потере производительности.
Чтобы упростить этот процесс и сократить время конвертации, было решено создать эквивалентные модели на фреймворке Keras, который позволяет проводить конвертацию в TFLite напрямую. Затем мы перенесли веса из наших PyTorch моделей на эти новые модели на Keras. Это решение оказалось более эффективным. Также после профилирования пришлось переписать функцию DepthwiseConv2D, что также ускорило наше решение.
сравнение конвертаций
Тем не менее, даже после всех этих операций, модель EfficientNet-B0 работала значительно быстрее в формате ONNX по сравнению с TFLite, примерно в 5 раз. Но из за ограничений организаторов на формат подаваемого решения мы не могли загружать модели в другом формате, что не позволило использовать нам другие архитектуры или увеличить число моделей в ансамбле.
Leaderboard
Индивидуально, каждое из наших решений показывало отличные результаты на лидерборде, попадая в топ 10–15 лучших к моменту их объединения. Подход, основанный на изображениях, достиг точности (accuracy) в 0.80, в то время как решение на основе трансформеров демонстрировало показатель в 0.78. Объединение этих двух подходов и позволили нашей команде выйти на первое место и удерживать его до окончания соревнования.
leaderboard на публичной части данных
К сожалению данные значения метрики были рассчитаны только на публичной части тестовых данных и оставалось только надеяться что наше решение будет также хорошо и на приватной части данных. На наши надежды не оправдались.
leaderboard на приватной части данных
По результатам соревнования мы заняли 2 место на Private и 1 место на Public (жалко что не наоборот), получили золотые медали (их раздают не только за первое место, а топ 1%) и призовые. Также я стал в Competition Master, а наш китайский товарищ Xun Zhao получил звание Grandmaster.
Большинство участников соревнования использовали одномерные свертки и Transformer архитектуры, а на первом месте была комбинация двух этих архитектур. Решение с генерацией изображений и использованием моделей компьютерного зрения было единственным в своем роде на соревновании, вызвав удивление и интерес среди других участников.
комментарий к описанию нашего решения
комментарий к описанию нашего решения
Ссылка на наше решение.
Ссылка на описание решения участника занявшего первое место.
Спустя некоторое время стартовало второе соревнование, нацеленное на распознавание жестов, соответствующих отдельным буквам алфавита. К сожалению, на этот раз у меня не было возможности принять в нем участие. В новом соревновании участники активно использовали наш подход к обработке данных , но архитектуры моделей уже были другие. Так выглядело решение которое позволило взять первое место.
лучшее решение второго соревнования
Более подробно можете разобраться с ним по ссылке.
Если у вас есть желание попробовать свои силы в Machine Learning, погрузиться в новый домен или просто посмотреть современные решения нетривиальных задач, то Kaggle отличная платформа для для этих целей.
Благодарю за внимание к моему рассказу! Надеюсь, вы нашли для себя что-то новое и интересное. Если у вас возникли вопросы, буду рад ответить на них в комментариях или на LinkedIn.