Fley's flow
121 subscribers
31 photos
1 video
32 links
In this channel I share my experience in CV/ML and what to do and not to do to be successful in the profession

Middle CV/ML Engineer

Contacts:

Telegram: t.me/fleyderer
Github: github.com/fleyderer
LinkedIn: linkedin.com/in/fleyderer/
Download Telegram
❗️ Держу в курсе

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

Однако я-таки развиваюсь и скоро расскажу о следующих вещах:

1️⃣ Кластеризация временных рядов: что это за тип задач, какие методы есть, что реально сработало, какие мысли остались.
2️⃣ Инференс моделей на Triton Inference Server, а в частности реализация асинхронного пайплайна из нескольких моделей с использованием Business Logic Scripting
3️⃣ Как я cтал Golang-разработчиком и не только обучил модель, но и вывел ее в прод. (В процессе, явно не раньше конца января).

Если у вас есть какие-либо пожелания к постам или вопросы, всегда рад обратной связи, мб смогу ответить и раскрыть темы, пропустив через призму личного опыта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
Как идеи из математики и программирования помогают в приготовлении к новогодним праздникам? 🧢

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

1) Решение проблем с нехваткой RAM 🔃

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

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

2) Распределение нагрузки 🍑

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

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

3) Бинарный поиск ↔️

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

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

На самом деле, крайне удивительно то, насколько высока применимость некоторых идей к реальной жизни: как много процессов можно выразить через степенную или же, наоборот, логарифмическую функцию; как часто мы даже неосознанно используем то, что формализовано и активно применяется совершенно в других сферах. На эти мысли меня еще раз натолкнула недавняя статья GPT-like модель «впервые сделала научное открытие», с которой причастным советую ознакомиться.

Всем желаю побольше новых идей в новом году! 🎁

#offtopic
Please open Telegram to view this post
VIEW IN TELEGRAM
😁2❤‍🔥11👍1
🖼Новая аугментация: джипег или шакал — как вам больше нравится.

Сейчас занимаюсь поиском интересных аугментаций изображений для обучения и оценки моделей, наткнулся на настоящий клад: kornia.enhance.jpeg_codec_differentiable

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

Полагаю, многие знакомы с библиотекой Kornia, но все же поясню — это библиотека для работы с изображениями, очень близкая по функциональности к OpenCV, но, во-первых, сделана именно для PyTorch тензоров, а во-вторых, поддерживает работу с GPU и может принимать на вход батчи изображений. Т.е. если у вас перед PyTorch-моделью есть препроцессинг на OpenCV, то скорее всего его можно очень быстро переписать на Kornia, помимо этого перекинув нагрузку на GPU.

И еще один важный момент — эта библиотека заявлена как дифференцируемая. Если говорить корректнее, то дифференцируемы функции, которые реализованы в библиотеке. И новая функция — не исключение (пока все комьюнити поражается тому, на что способны новые генеративные сетки, я поражаюсь тому, что даже шакалов научились дифференцировать🐶).

Вот здесь статья, код и постер, которые легли в основу. В силу того, что работа свежая, аугментация на момент написания этого поста еще не дошла до релиза (ожидается в kornia==0.7.2), поэтому ей можно воспользоваться лишь стянув библиотеку напрямую с GitHub.

P.S. Я уже все опробовал, нашел ошибку, исправил, закинул PR и его уже даже оперативно смерджили, пользуйтесь на здоровье❤️

#papers #hard #code
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥4
Выбор порога принятия решения: идея

Вроде бы очевидная вещь, но почему-то не приходившая мне на ум, пока лично с ней не столкнулся. Представим себе, что мы обучили классификационную модель, получили метрики, которые нас устраивают, тестируем модель на сервере, а там... часть вердиктов адекватна, а часть — совершенно мимо. Изучили ошибки подробнее: уверенность модели в одном классе — 0.25, в другом — 0.24, в третьем — 0.23, а остальные 0.28 распределились между оставшимися классами. То есть, модель нам недвусмысленно говорит: "я не знаю, к какому классу отнести этот объект". Что делать?

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

Здесь можно, конечно, вспомнить про Precision/Recall и пытаться отнести результаты "хз" к False Negative (FN), но мне эта мысль сразу же концептуально не понравилась, потому что отказ от принятия решения не равен допущению ошибки. Важно, что это высказывание может быть истинным или ложным в зависимости от задачи, и в рассматриваемой задаче оно истинно — мы можем отказаться от части вердиктов в угоду точности.

Идея следующая: отказываясь от принятия решения, мы хотим, пожертвовав наименьшей долей всех предсказаний, в награду избавиться от наибольшей доли ошибок. То есть, мы стремимся к ситуации, где мы в 1 из 10 случаев скажем "я не знаю", но среди этих "я не знаю" 9 из 10 были бы ошибками, если бы мы дали уверенный ответ.

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

#hard #cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Выбор порога принятия решения: реализация

Как известно, суровая реальность и нежная виртуальность едва ли когда-нибудь соприкоснутся, однако мы продолжаем оптимистично смотреть в будущее. Описанную идею я применил на практике, и на графике выше можно видеть, что на пороге в 0.55 мы, отказавшись от ~20% данных, потеряли ~80% ошибок — наглядная демонстрация принципа Парето в действии.

Результат хороший, обоснование порога есть, добавили. Но затем видим, что на тестовом сервере вердикты даются дай бог для 2-3 из 10 изображений. Потому что есть разница доменов между обучающим набором данных, на котором считался порог, и изображениями, которые действительно будут в работе. На всякий случай: домен — это множество всех возможных объектов. На тренировочных данных мы имели дело с изображениями с фотостоков 😎, а в продакшене у нас изображения с камер наблюдения — домены разные, потому что в обоих случаях будут различные характерные особенности. Здесь выхода два:
1️⃣ Разметить данные с реальных камер и затем оценивать параметры на них (это возможно где-то в более хорошем и правильном мире, потому что нужно дополнительно организовать процесс разметки данных)
2️⃣ Смириться с реальностью и выбрать порог так, чтобы отсеивалось не более 20% данных.

Выбран был второй вариант — посмотрели, на каком пороге мы отказываемся от ~20% входящих данных, и зафиксировали результат. Решение банальное, временное, потому что стремиться нужно к первому варианту, однако реалистичное и без прикрас. Собственно, чем и хотел поделиться. Если есть похожий опыт, буду рад услышать ваши решения 🙂

#hard #cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
💬 Кластеризация временных рядов (часть 1: описание)

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

Постановка задачи кластеризации

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

Теория

Как правило, этой задачей в вузах вводят в ужас, да и требуют немного, потому что результат – хз. Лично я сидел и думал: "Ну мне-то эта задача никогда не попадется 😊". Кластеризацию нам рассказали, как и пару методов оценки разделимости кластеров (методы локтя и силуэта), дали самим подготовить материал на семинар по этой теме, а мы не будь дураками взяли простые наборы данных и умело выполнили задачу.

Практика

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

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

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

В моем случае есть очень большое количество небольших временных рядов (50-100 значений). Каждый из них представляет из себя результаты замеров датчика во время работы механизма. Задача состоит в том, чтобы на фоне 95% корректных результатов выделить как можно большее число видов некорректной работы механизма, чтобы впоследствии передать результаты специалистам и получить от них пояснение, что это за проблема и проблема ли (получить разметку). Кластеризация здесь нужна для того, чтобы выделить именно группы ошибок, а не единичные странности.

Итого: много небольших рядов близкой длины и похожего поведения. Из особенностей – резкий пик в начале работы механизма, затем возврат к практически константным значениям до конца работы, после чего значения возвращаются к нулю. Очень несбаланстрованные данные – более 95% рядов нормальные. Обрезка данных не идеальная, по времени бывают сдвиги на ± 5 значений.

Предлагаю предположить, на каком подходе я остановился, исходя из этих вводных 👀

#hard #progress #cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
💬 Кластеризация временных рядов (часть 2: решение)

Загрузка

Ряды пришли ко мне в виде дампа базы данных PostgreSQL в количестве ~1 млн. Я радостно экспортировал нужные ряды в .csv, чтобы пандас их смог проглотить, попытался открыть и... разумеется, получил RAM OOM 👨‍🦽. Сейчас, спустя большое количество времени с момента задачи, я уже не удивлен, а тогда это было для меня шоком. Оказывается, в жизни не удастся взять и спокойно загрузить все, что захочешь, в 16 Гбайт оперативной памяти. Приемы с чтением chunk by chunk тоже не помогли, потому что совокупный объем информации, помимо непосредственно ряда, банально не помещался в память. Боль с той битвы уже поутихла, однако много было потрачено сил и крови на предотвращение очередной бесславной смерти ядра Jupyter. Впоследствии при расширении задачи мне так и вовсе пришлось загружать в 10 раз большее число рядов, поэтому сразу озвучу свое решение — Dask. Ещё один важный момент — если создавать индекс, даск попытается загрузить в память весь датасет (хотя необходимости в этом нет), так что здесь повнимательнее.

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

Снижение размерности

В силу того, что ряды, во-первых, небольшие, а во-вторых, имели вполне конкретные визуально отличимые особенности, я решил выделить различные признаки у ряда: позиция пика, высота пика, матожидание/дисперсия бокового движения, 25-75 перцентили и прочее, что только в голову пришло. В результате сокращения размерности удалось из 50 значений получить 15-20 потенциально значимых характеристик, что позволило, как математики любят, вылить воду из чайника и свести задачу к предыдущей — работать с небольшим числом признаков и в ус не дуть.

Предобработка

Разумеется, перед тем, как приступать непосредственно к кластеризации, необходимо отфильтровать мусор. Pairplots, поиск и определение аутлаеров по комбинациям признаков, а также по некоторым эвристикам, касающимся домена рядов (если по-русски, то выкинуть те ряды, которые не могли бы в принципе существовать при нормальной записи данных) — ничего сверхъестественного, рутина, которую надо было кому-то делать. В ходе предобработки выяснилось, что порядка 15% данных не представляют никакого научного интереса, что в пределах адекватного, исходя из моего опыта.

Кластеризация

Когда у нас на руках чистенькие и красивые ряды, выбираем метод кластеризации и в путь. Я выбрал K-means++, потому что мог себе его позволить. Он интуитивно понятный, в моем случае адекватно применимый, и что самое главное — быстрый (1 млн рядов за 7-10 секунд разбивался на кластеры). Для выбора оптимального числа кластеров использовались как банальные предположения, так и методы локтя и силуэта, но в конечном итоге пришлось посмотреть и на иные варианты, потому что порой большее число кластеров выделяло интересную группу рядов, которая при меньшем числе оставалась безликой массой внутри другого кластера.

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

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

#hard #cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2👌2
👥I'ML Conf 2024

Ближайшие два дня принимаю участие в своей первой конференции по ML, которая проходит в онлайн-режиме.

Первые впечатления отличные — послушал доклад от Яндекса на тему компьютерного зрения в складской робототехнике и в ходе рассказа у меня возник вопрос, не думали ли ребята в сторону адаптивной предобработки изображений — например, использования параметров входного изображения, а-ля яркость/контраст, в качестве гиперпараметров модели. Идейно это был просто взгляд с другой стороны на ту проблему, которую подняли в докладе, где они игрались с аугментациями. Ребята подтвердили, что идея огонь, они на нее обязательно посмотрят и поблагодарили за мысль 👉👈. Ну и потом еще часа пол с ними пообщались неформально, несколько не менее интересных тем подняли — например, они думают сейчас в сторону пересмотра метрик, потому что бесконечно гоняться за ними, конечно, хорошо, но возводить их в абсолют неправильно. Короче — классно, интересно, познавательно.

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

А на третьей секции был доклад от Wildberries, за время которого я понял, что ML-ный пайплайн у нас с ребятами очень похож: от хранения эмбеддингов в Milvus до использования Torch -> ONNX -> TensorRT конвертации и инференса в Triton Inference Server. Сам доклад был о выдаче похожих товаров и о том, как они ее улучшили с помощью кропа основного объекта с изображения. Доклад по своей сути был лаконичным и понятным, концептуально все ок, поэтому в дискуссии мы быстро перешли к наболевшему и полчаса обсуждали скорее какие-то локальные вещи.

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

#self #soft
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Классификация временных рядов

Суть задачи

Основная задача состоит в том, чтобы на основе временного ряда, получаемого в ходе работы механизма, выдать диагноз — один из 16 возможных. Разумеется, хороший механизм 95% времени работает и 5% времени ломается, поэтому сразу учитываем большой дисбаланс между первым и остальными 15 классами.

Разметка данных

Изначально разметки данных не было — было только много-много записей в БД, которые слабо отфильтрованы, помимо прочего местами ошибочно размечены характеристики механизмов, а местами записи дублируются под разными айдишниками, и так далее — особенности национального дата-инжиниринга. С целью выделения характерных групп рядов провели кластеризацию, результаты были переданы специалистам, которые через какое-то время вернулись с предположениями о том, что же за диагнозы можно поставить отправленным рядам. На основе этой разметки я прогнал по ней ряды из базы данных, собрав в итоге ~11000 размеченных рядов.

Выглядело это так: три технолога выставляли независимые диагнозы для 10 характерных рядов из каждого кластера, впоследствии их ответы использовались в качестве разметки. Причем порой специалисты могли предположить, что это либо один диагноз, либо другой в связи с тем, что поведение в обоих случаях приблизительно совпадает. В силу этих особенностей пришлось смириться с тем, что задача помимо всего прочего еще и Multiclass-Multilabel.

Модель

Дальше нужно было придумать ML-модель для задачи. На вход поступает массив длины в среднем от 50 до 100, а на выходе нужно отдать массив из 16 чисел с confidence value для каждого из 16 классов. Я подумал-подумал и взял за основу 1d-CNN сетку с VGG-like архитектурой. Если проще, то применил обыкновенную сверточную сетку, где вместо 2d используются 1d-свертки. RNN меня не заинтересовал, потому что 128 значений спокойно ложились в обычную CNN и паттерн они ловили, т.е. вопрос о временной зависимости не стоял. Гипотетически можно было бы к входным рядам докинуть еще 15-20 признаков, которые использовались для кластеризации, но я специально не стал этого делать, чтобы избежать возможного переобучения и посмотреть, насколько удачно сойдется обучение по самим рядам.

Обучение

Оставалось лишь подумать над двумя вещами: препроцессинг ряда перед моделью и нюансы архитектуры (число сверток, их параметры, батчнормы, активации и тд). В качестве препроцессинга — добивка ряда нулями слева до 128 значений (потому что в 112 значений ряды не всегда влезали, а 128 хватало), а также MinMax-нормализацию, потому что выяснилось, что диапазоны значений у разных механизмов очень разные.

В результате всех описанных выше действий удалось достичь точности в 99.8% процентов, что нас вполне устраивало.

Инференс

Далее модель прошла через уже привычный процесс конвертации Torch -> ONNX -> TensorRT, затем я написал коротенький docker compose, чтобы поднимался и автоматически перезапускался Triton Inference Server с моделькой, прокинул порты и вывел таким образом модельку в прод. Скорость работы составила не менее 3000 рядов в секунду на CPU, чего нам хватало с головой.

Итоги

В общем-то, если подумать, недурно вышло для полугодовалого джуна — самостоятельно решить неочевидную задачу, побившись об OOM, кривые данные и регулярные корректировки ТЗ, пройти по классическому пути анализа данных с Pairplots и тд, выделить признаки из временных рядов, построить кластеры и разметить ряды, придумать архитектуру сети, обучить все это на очень приятную точность и в итоге вывести в прод.

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

#progress #hard #cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🌚1
🔫Прогресс

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

За это время я успел пройти путь от а-ля стажера до джуниора (инженер-программист🗿), а затем от джуниора до джуниора+ (старший инженер-программист🗿) – сегодня официально вступил в должность.

В честь такого события напишу о том, что узнал за это время:

🟡Опенсорс-код почти всегда выглядит печально, и со временем учишься понимать даже этот ужас с полуслова полустроки.

🟡Продакшен-код в репозиториях практически никогда не сопровождается комментариями.

С учетом этих двух фактов я придумал подход, как преодолеть барьер экзистенциального ужаса, когда берешься за задачу с чужим репозиторием – планомерно изучать каждую функцию, попутно дописывая комментарии. Это позволяет как "разделить и властвовать", так и к моменту понимания проекта иметь адекватную доку, которую останется лишь слегка поменять.

🟡Память – очень ограниченный ресурс. Что RAM, что GPU обладают ощутимо малыми объемами памяти, поэтому без навыков оптимизации и ленивой подгрузки данных далеко не уедешь. А упереться в RAM на убунте – отдельный кайф 👍

🟡Взять датасет и обучить модель ≠ результат. После окончания ресерча и получения итоговой модели нужно: провести конвертацию -> ONNX -> TRT, проверить работу инференса (про квантование промолчу), установить порог принятия решений (раз и два) и написать полноценный README.

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

🟡Data Science и Machine Learning – ни черта не сексуально. Это такая же рутина, как и любая другая работа, лишь проявляется она иным способом. Результаты работы выглядят круто – да, их часто легко показывать друзьям и знакомым, из-за чего возникает ощущение, что ты просто делаешь-делаешь крутую и интересную задачу и хоба – сделал красоту. Даже я, устроившись и будучи почти в теме, не думал, что будет столько проблем на ровном месте. Одна разметка данных чего стоит, а о проблемах, возникающих на каждом этапе от поиска датасетов вплоть до запуска инференса я и рассказывать не стану.

🟡Целевые метрики очень важны. Я не могу сказать, что сейчас в полной мере ощущаю это, но уже четко осознал, что пресловутое значение Accuracy – хоть макро, хоть микро – очень часто слабый показатель, который мало что говорит (дисбаланс классов передает привет).

🟡На работе можно не работать или важно оценивать свои силы. Первые испытательные три месяца я думал, что все 8 часов рабочего дня нужно работать, нельзя переключиться на какие-то свои мысли, посвятить время изучению статей или же просто сесть на диван и передохнуть. Более того, я использовал на обед лишь 30 минут, чтобы на 30 минут раньше освободиться. А потом как-то раз я вышел на обед на улицу, зашел в парк и понял, что отдых – обязанность, а не право. Обязанность в первую очередь перед самим собой, во вторую – перед близкими людьми и в третью – перед работодателем. Я стал позволять себе иногда сидеть на кухне с коллегами, тратить больше времени на обед или недоработать, когда сильно устал. Потерял ли я в эффективности? Отнюдь, лишь прибавил раза в полтора.

🟡Знает – спорт необходим – Джим

Всю жизнь я обходил стороной тренажерный зал, занимаясь легкой атлетикой, баскетболом, волейболом и иными видами спорта, однако только за время работы понял, что если не буду закачиваться, то ресурса после работы не остается. Сейчас, спустя четыре месяца тренажерки 2-3 раза в неделю помимо волейбола, я ощущаю себя после работы хо-ро-шо. И сам процесс тренировки –  скорее медитация, чем пытка над собой.

Каждый пункт этого списка – опыт, который я хотел бы зафиксировать, поделиться им с вами, и теперь провести черту, за которой будут новые события 👁

#progress
Please open Telegram to view this post
VIEW IN TELEGRAM
👍62❤‍🔥1👏1
👁Большой брат для ритейла: введение

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

Собственно, задача:

Есть крупный сетевик, который делает магазины продуктов – вы все его знаете, формально я никак не связан и мог бы даже его назвать, но лучше не буду. Этот сетевик хочет следить за тем, чтобы работники работали, а не сидели по углам. Мы должны строить тепловые карты на камерах на основе того, в каких зонах работники оказываются чаще. В рамках конкретно демки достаточно было показать, что мы умеем хорошо отделять работников от покупателей. В качестве данных нам отправили несколько видеозаписей одного дня длительностью в 8 часов с разных камер.

Какие сложности возникли сразу же до решения задачи:

Заказчик сказал "отвечаю, у нас жилетки такого-то цвета". В тот же день я зашел в магазин неподалеку от офиса — там жилетки другого-то цвета, а когда прислали видосы с камер, выяснилось — жилетки вообще третьего-то цвета.
Спереди жилетки крайне непримечательные – даже глазами, не зная контекста, не удастся отличить работника от покупателя. Сзади куда ни шло, но тоже спорно.

Пайплайн решения пришел в голову практически сразу: с помощью YOLOv8m-pose находить в кадре людей, выделять кейпоинты их тел, брать 4 точки, относящиеся к торсу, и проецировать (любимый cv2.warpPerspective) на плоскость размера 112х112. В силу отсутствия информации на нижней половине туловища, я остановился на верхней – и информации больше удается сохранить, и соотношение сторон у половины спины куда ближе к 1х1, чем у всей, имеющей форму прямоугольника, а не квадрата. Полученное изображение подаем в легковесную CNN-модель бинарной классификации "не работник/работник" и отдаем ответ.

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

#cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
👁Большой брат для ритейла: решение

Первый этап


Я в быстром режиме отсмотрел первые 1.5 часа видео, отметив моменты входа и выхода работников из кадра. После этого пробежался описанным выше алгоритмом по видосу, и если кадр принадлежал очередному временному отрезку, когда работник был в кадре, вырезанные торсы летели в папочку с работниками, а если вне отрезков — в папочку с покупателями. Итого получилось 5400 кадров с покупателями, 200 кадров с работниками (как вам дисбаланс классов? ☠️). Также я пролистал 200 кадров и перекинул к покупателям лишние изображения (такое происходило, например, когда работник и покупатель были в кадре одновременно и оба торса летели к работникам). Итого какие факты о собранных данных есть:

1️⃣ Очень большой дисбаланс между покупателями и работниками
2️⃣ В разметке точно нет False Positives (когда покупателя отнесли к работникам). Насчет False Negatives (когда работника сочли покупателем) — хз. Наверняка есть, а сколько их, неясно.
3️⃣ Данных мало для полноценного решения задачи, да и test-сета нет, чтобы потом утверждать, что мы хорошо справились с задачей.

Исходя из этого, признаём неидеальность мира, и решаем проблемы по порядку.1️⃣: Выбираем целевую метрику – и это точно не Accuracy, потому что дисбаланс. Нас волнует количество FP и FN – что то, что другое мы хотим максимально сократить, поэтому возьмем F1-score и будем уменьшать learning rate каждый раз, когда F1 для валидационной части будет стопориться. В качестве Loss я взял взвешенную кросс-энтропию, потому что она мне наиболее понятна, есть готовая реализация прямо в торче, ну и проблему с дисбалансом удаётся решить. Хотя, конечно, есть Focal Loss, но я пока его не изучил 🤓. 2️⃣: Выбрав метрики, обучим нашу модель, чтобы поближе рассмотреть ошибочные предсказания и переразметить данные.

Первое обучение – значения метрик около 0.92-0.94. Вроде неплохо, но качество низковато. Смотрим на ошибки модели – она плохо реагирует на то, что логотип оказывается в нестандартных местах: по углам или совсем снизу/сверху, или же что у него нестандартный масштаб в силу небольших ошибок модели детектора-кейпоинтов. Данных мало, а проблему решить нужно, как поступим? Разумеется, аугментации. Сперва с помощью cv2.Canny на всех изображениях с работниками найдем логотипы, сохраним их боксы, а затем реализуем аугментацию, которая будет вырезать этот логотип по боксу и кидать его в случайное место изображения, таким образом подготовив модель к любому возможному случаю. Помимо этого докинем RandomPerspective и RandomRotation, чтобы жизнь медом не казалась.

На практике после 2-3 итераций удалось добиться значений метрики 1.000 и на train, и на val. С одной стороны — вау, круто, а с другой – подозрительно. 3️⃣: Я полагаю, что внимательный читатель уже понял, для чего нам все это было нужно – полученной моделью мы разметим оставшиеся 6.5 часов видеозаписи, чтобы подготовить основную часть датасета.

#cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
👁Большой брат для ритейла: решение и демонстрация

Второй
этап

Разметив моделью оставшиеся данные, получаем 65000 покупателей и 1300 работников. В принципе, быстро прошерстить 1300 изображений проблем не составит, поэтому подчистим FP-кейсы и на этом перейдем к обучению модели на ее же размеченных данных, причем здесь уже сделаем ход конем — поделим авторазметку на Train и Val, а в качестве Test возьмем, логично, ручную разметку🧠. Это данные, в разметке которых мы уже уверены, модель их не увидит в рамках обучения, следовательно, на их основе можно делать выводы о том, как хорошо модель справляется с задачей.

Приступим к обучению и... упремся в RAM OOM. Сколько раз уже об эти грабли бьюсь, но не устану, пожалуй, никогда. Раньше все 5600 картинок влезали одним тензором в RAM, а 66000 — уже нет. Что ж, перепишем быстренько датасет под ленивую загрузку изображений в момент обращения, с помощью Dataloader и пары worker'ов чуть-чуть ускорим их загрузку, но с учетом выросшего количества данных и прилично замедлившейся скорости чтения, процесс совсем уж не быстрый — ждем 💬.

После обучения выяснилось, что на тестовой части модель допускает 4 FP, 2 FN ошибки, причем все FP — это на самом деле работники, но изображения достаточно сложные, поэтому я их изначально убирал к покупателям, чтобы модель не начала галлюцинировать, условно, на любом светлом пятне. Короче говоря, имеем всего 2 действительные ошибки на 5600 кадров, причем сами ошибки из разряда "мы не увидели работника на этом кадре, но увидели сразу же на следующем", что на суть работы не влияет. Для демо-версии задачи результат прямо-таки хороший, поэтому обучение заканчиваем, хотя и можно было бы произвести пару итераций, как в первом этапе, чтобы подчистить ошибки.

Третий
этап

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

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

На выходе имеем рабочую классификационную модель и видеозапись с 8-9 мини-нарезками с камер наблюдения общей длительностью в полторы минуты. Суммарно затрачено 8 рабочих дней, 15 мини-баунти и 2 коробки печенья. Заказчик доволен, программисты сыты, а дальнейшая судьба задачи пока обсуждается, потому что внедрение — это вам не разноцветные рамочки рисовать.

#cases
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Поделюсь с вами наглядным примером того, почему нужно переразмечать данные. 🔄

Как видно на изображении выше, предикт модели — синий цвет, а "правильный" — коричневый.

В первую секунду возникает вопрос, каким образом это произошло, а во вторую ты видишь надпись TonyBrownChevrolet 😨

Лично по своему текущему опыту вывел примерно такой принцип переразметки:
1️⃣ Обучаем модель буквально первые несколько эпох, пока она не успела переобучиться
2️⃣ Этой моделью пробегаем по Train, Val, Test, переразмечаем данные и отправляем на повторный круг.

Самое неприятное здесь еще и то, что можно провести хоть 2-3-4 итерации, а потом увидеть то, что я отправил выше... Поэтому останавливаемся лишь когда метрики на каждой части датасета сходятся примерно к одинаковым значениям.

#cases #tips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Без лишних слов похвастаюсь – стал сегодня наконец магистром по профильному направлению 🥂

#progress #path
Please open Telegram to view this post
VIEW IN TELEGRAM
🫡13👍3🎉3
Всем привет!

Почти все лето не было постов – то одолевала лень что-либо писать, то события поглощали и не оставалось времени. Постараюсь вкратце описать это лето по рабочим и личным аспектам.

Работа:

1) Решал задачу с классификацией автомобилей по цветам. Решил, расскажу детали, было любопытно.

2) Приступил к задаче Car Detection – у нас, оказывается, был предобученный детектор YOLOv8, который часто выдавал кривые типы автомобилей (грузовик превращался в автобус или иное). Там для большей точности потребовались данные в нашем домене (российский транспорт), я их собрал и попробовал заказать разметку со стороны – ждем вместе с тимлидом теперь ответ директора, сверху согласовывают...

3) Сейчас решаю задачу Person Re-identification. Это, пожалуй, первая действительно серьезная задача, буквально неделю ей занимаюсь, расскажу впоследствии во всех подробностях.

Жизнь:

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

2) Переехал в СПб. Вероятно, на какое-то время, но не менее года. Работаю на удаленке, уже обустроился, веду насколько это возможно активную социальную жизнь, даже (тьфу-тьфу) нашел потенциальную команду на ближайший волейбольный сезон. А кто будет в Питере – обязательно пишите, пообщаемся и по работе, и в целом)

Суммируя вышесказанное – все медленно, но верно идет к лучшему, о чем и постараюсь рассказывать в ближайшее время. Пока есть идеи лишь для 2-3 постов, и если будут какие-нибудь пожелания, обязательно предлагайте либо здесь, либо в личку

#self #progress
👍7
😁 квалифицированный алгоритмист*

*По версии Яндекс.Практикума.

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

Если вообще захочется лезть в курсы, важно понимать, какой у тебя бэкграунд. У меня на тот момент уже были за спиной 6 лет вышки + полгода опыта работы. С алгоритмами я был знаком из вузовских курсов, где мы писали условные рекурсии на плюсах, ну и ещё где-то как-то между делом.

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

Кто-то на этом моменте облегченно выдохнет, а я наоборот озабоченно вдохнул, потому что хотелось погрузиться в эту тему с головой, раз уж надо будет уделять курсу время. Но там даже не дали определение функции О "большое" (или Big O notation 🤓). Кому надо – смотрите сами, а мы объяснять не станем. И так в целом можно описать весь курс, снабженный скорее какими-то интуитивными понятиями, примерами на пальцах без возможности открыть доказательства и почитать более подробно, не отходя от кассы.

Если же кайф реально погрузиться в тему – книжку в руки и вперед, а если запутался, можно спросить у ChatGPT или, кстати, у Mistral, который полностью в открытом доступе и терпимо справляется с кодом.

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

#progress #hard #tips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
🗜 Полезные инструменты для работы

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

Большинство этих инструментов я использую каждый день, поэтому и решил о них рассказать.

1) CLI Fuzzy finder

Если дословно, то это "Нечеткий искатель" (А с ним Циклопы, Вулверин, Бандит, Гроза, Зверь и Юбилей).

Он хорошо и, что важно, очень быстро справляется с поиском файлов и ранее выполненных команд в терминале по их примерным названиям. Я лично использую почти всегда именно для команд, поскольку бывает – подключился ты к какому-то очередному серваку или запустил скрипт, там куча параметров, это запомнить просто невозможно, проходит какое-то время и тебе нужно повторить это действие, а искать, где, когда и как – просто мучительно. А если есть fzf – открыл терминал, Ctrl + R, ввел кусочек команды (название скрипта или одного параметра, который помнишь) и сразу нашел её в истории. И вместо нескольких минут прописывания команд или их поиска у себя в сохраненках, за пару секунд удается найти все, что нужно.

2) Code assistants

Будь то бесплатный Codeium, платный Copilot, целая IDE типа Cursor или какое-либо другое решение – помощники в написании кода на основе ИИ – это огромный буст к скорости работы. Причем да, порой косячит, но даже в бесплатном виде как инструмент для дописывания следующих одной-двух строк кода или написания документации – это вещь незаменимая. Кто еще не пробовал, настоятельно советую.

3) LLM

В наше время, особенно программистам, грешно не пользоваться халявным джуном в чатике. Разумеется, лидерами здесь пока являются OpenAI с их моделями, однако есть полностью доступный и бесплатный Mistral, который по моему опыту в кодинге лучше того же ChatGPT 4o.

Эта штука действительно заменяет привычный пока StackOverFlow, поскольку сразу помогает с нужной тебе задачей, являясь end-to-end решением без необходимости в самостоятельном допиливании результата (почти). И что еще важно — она порой закидывает прикольные best practices, которые ты бы сам мог и не встретить никогда.

4) Питон и все сопутствующее

Это уже только для питонистов и только для начинающих, которые, как и я в начале пути, нейросетки делают в jupyter, прости господи. Все просто: вместо ноутбуков – скрипты + argparse. Для работы с путями и директориями вместо os.path – pathlib. Остальное, начиная от timeit, tqdm, pyyaml, заканчивая numpy, pandas, torch, lightning, kornia, jax в отдельном пояснении не нуждаются, потому что их очень много и каждый здесь найдет что-то свое и более подходящее под конкретную задачу, благо выбор есть.

Напоследок скажу, что в зависимостях мне помогает разобраться conda и установленный туда pip. При публикации проекта и conda environment есть, и pip requirements для тех, кому conda неудобна. Но вообще, конечно, в планах потрогать poetry, а docker, возможно, начать применять не только для окончательной сборки проекта. Однако по соотношению минимум гемора / максимум результата в целом пока хватает.

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

#hard #tips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Сказано – сделано

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

Вывод: нельзя закидывать программистам идеи, к осуществлению которых ты можешь быть не готов...✍️

#offtopic
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😁81