Русский
Русский
English
Статистика
Реклама

Cv

Главное мотивированность и настойчивость как в Deutsche Telekom IT Solutions растят своих специалистов

25.05.2021 18:15:50 | Автор: admin

Как выглядит типичный процесс отбора на стажировку? Есть ли у выпускников гуманитарных вузов шансы на трудоустройство в IT? Как найти способного стажёра и вырастить из него настоящего специалиста? Сотрудники Т Дмитрий Балахонов и Сергей Морозов набирали ребят в свои команды, преследуя разные цели, но в итоге пришли к схожим выводам. Этими выводами, а также интересными историями из практики, коллеги готовы поделиться с читателями Хабра.

День карьеры на факультете прикладной математики и механики Воронежского госуниверситетаДень карьеры на факультете прикладной математики и механики Воронежского госуниверситета

Вы оба набирали стажёров. Почему возникла такая потребность?

Сергей: В компании я являюсь руководителем трайба Test Management SSC Russia. Одна из моих задач это поиск стажёров для различных проектов, не только для себя, но и для коллег. Часть стажёров приходит к нам непосредственно через HR-отдел. Например, они могут увидеть вакансию на корпоративном сайте или на HH.ru и откликнуться. Других мы находим через программу рекомендаций, в рамках которой действующие сотрудники могут предлагать кандидатуры своих знакомых. В случае прохождения стажировки и успешного трудойствойства кандидата такой сотрудник получает бонус. Наконец, кого-то мне приходилось искать и самостоятельно.

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

Дмитрий: У меня немного другая специфика. Сергей набирает людей про запас, чтобы сначала выучить, а уже потом выбрать для них проект. Мне нужны были стажёры в конкретную команду в проект Open Telekom Cloud. Задача была важная, требующая оперативного решения, поэтому я сам был замотивирован заниматься поиском.

По какой причине чаще всего отсеиваются кандидаты?

Сергей: Самый большой отсев происходит из-за английского. Причём у нас не такие уж высокие требования. В идеале, конечно, B2. Но на практике можно и чуть-чуть снизить планку. Если говорить о стажёрском уровне, то вполне достаточно, чтобы человек мог понять вопрос на чужом языке и ответить на него, пусть с ошибками, но чтобы основная мысль была понятна.

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

Дмитрий: Справедливо. Грубо говоря, проще взять выпускника или выпускницу факультета романо-германской филологии и помочь освоить тестирование, чем взять вроде бы продвинутого человека с факультета компьютерных наук, но с нуля учить его английскому или немецкому.

Сергей: Часто в резюме написано B2, С1, а человек этому уровню не соответствует. История из реальной практики. Приходит ко мне кандидат. В резюме указано, что уровень владения английским С1, т.е. продвинутый. Спрашиваю: Could you please briefly tell us about yourself?. Отвечает на русском: Извините, я сегодня рассказ о себе не готовил. То есть даже эту фразу произнести на английском он не смог.

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

По каким ещё критериям проходит отбор?

Сергей МорозовСергей Морозов

Сергей: Если речь идёт именно о стажёрах, то завышенных требований мы не выдвигаем. Мы просто хотим увидеть, что у человека есть элементарная техническая база (SQL, REST, понимание протокола HTTP и т.п.). Можем давать простейшие задачи на программирование. Важно, чтобы человек, помимо упомянутого владения языком, просто обладал логическим мышлением и был способен к обучению.

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

А что важно указывать в CV?

Сергей: Я всегда говорю обязательно пишите ключевые слова по вашим компетенциям, даже если вы не очень глубоко в чём-то разбираетесь. Речь ведь идёт о стажировке, а не о полноценной занятости. Если читали книжку по профильной теме, проходили курс, даже видео на Youtube смотрели, пишите об этом в CV. Интервьюеру это позволит найти темы для разговора и в итоге максимально раскрыть кандидата. А так мы порой вынуждены сами буквально выуживать из человека информацию.

Дмитрий: Да, это очень типично. Как-то перед собеседованием я просматривал резюме девушки, которую мне рекомендовали как хорошего специалиста в Python. Так у неё ни разу слово Python в CV не мелькнуло. Если бы я изначально об этом её навыке не знал, то как должен был догадаться?

Такое ощущение, что сейчас бум платных курсов по программированию и тестированию. Часто их указывают в CV?

Сергей: Да, они встречаются. Но у меня к ним скептическое отношение. Конечно, если платные курсы есть в CV, это лучше, чем ничего. Но при прочих равных условиях я бы предпочёл видеть там стажировку в конкурирующей IT-компании. Обучение в таких компаниях, как ни крути, выгоднее для человека, чем платные курсы. Во-первых, не нужно платить деньги. Во-вторых, IT-компании реально заинтересованы в том, чтобы вырастить специалиста и устроить его в свой проект, а не заработать на обучении. А платные школы это всё-таки коммерческая история, и нередко бывает так, что за красивой картинкой там ничего не стоит. Не буду обвинять всех подряд, но такое действительно бывает. Поэтому важно читать отзывы о школах и образовательных курсах, общаться с теми, кто их уже проходил, чтобы иметь релевантную информацию.

Но если человек прошёл стажировку в другой компании и его не взяли на младшего инженера, значит, он недостаточно хорош. Разве не так?

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

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

Как проходит стажировка? Ваши подходы чем-то отличаются?

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

Далее обязательно держать руку на пульсе и не оставлять стажёра наедине с собой. Ментор должен регулярно давать ему задания, проверять, разбирать ошибки, отвечать на вопросы. Задания могут быть взяты из реальных проектов, а могут быть придуманы самим ментором. Разница не очень существенна. Главное, чтобы стажёру была обозначена конечная цель что он должен знать и уметь по истечении трёх месяцев.

Дмитрий: Я уже говорил, что мы берём людей в конкретный проект. Люди сразу работают над реальными задачами. Что касается подбора менторов, то здесь есть два варианта развития событий. В каких-то командах Open Telekom Cloud много специалистов из России, а в каких-то единицы. Может сложиться, что стажёр попадёт в команду, где вообще никого из России нет. Ментором тогда станет иностранец и задания будут исходить от него. Но в таком случае мы всё равно подыскиваем и русскоязычного наставника. Его можно найти в другой команде, которая выполняет похожие задачи.

Приходят ли на стажировку люди с абсолютно не профильным образованием и бэкграундом? Насколько они бывают успешны в профессии?

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

А какой вообще процент стажёров в итоге становится специалистами?

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

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

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

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

Подробнее..

Управляем звуком ПК от активности пользователя с помощью Python

17.06.2021 14:15:17 | Автор: admin

Настройка программного обеспечения

Без промедления начнём. Нам нужно установить следующее ПО:

  • Windows 10

  • Anaconda 3 (Python 3.8)

  • Visual Studio 2019 (Community) - объясню позже, зачем она понадобится.

Открываем Anaconda Prompt (Anaconda3) и устанавливаем следующие пакеты:

pip install opencv-pythonpip install dlibpip install face_recognition

И уже на этом моменте начнутся проблемы с dlib.

Решаем проблему с dlib

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

Итак, первая же ошибка говорит о том, что у нас не установлен cmake.

ERROR: CMake must be installed to build dlib
ERROR: CMake must be installed to build dlibERROR: CMake must be installed to build dlib

Не закрывая консоль, вводим следующую команду:

pip install cmake
Проблем при установке быть не должно

Пробуем установить пакет той же командой (pip install dlib), но на этот раз получаем новую ошибку:

Отсутствуют элементы Visual Studio

Ошибка явно указывает, что у меня, скорее всего, стоит студия с элементами только для C# - и она оказывается права. Открываем Visual Studio Installer, выбираем "Изменить", в вкладке "Рабочие нагрузки" в разделе "Классические и мобильные приложения" выбираем пункт "Разработка классических приложений на С++":

Пошагово
"Изменить""Изменить"Разработка классических приложений на С++Разработка классических приложений на С++Ждем окончания установкиЖдем окончания установки

Почему важно оставить все галочки, которые предлагает Visual Studio. У меня с интернетом плоховато, поэтому я решил не скачивать пакет SDK для Windows, на что получил следующую ошибку:

Не нашли компилятор

Я начал искать решение этой ошибки, пробовать менять тип компилятора (cmake -G " Visual Studio 16 2019"), но только стоило установить SDK, как все проблемы ушли.

Я пробовал данный метод на двух ПК и отмечу ещё пару подводных камней. Самое главное - Visual Studio должна быть 2019 года. У меня под рукой был офлайн установщик только 2017 - я мигом его поставил, делаю команду на установку пакета и получаю ошибку, что нужна свежая Microsoft Visual C++ версии 14.0. Вторая проблема была связана с тем, что даже установленная студия не могла скомпилировать проект. Помогла дополнительная установка Visual C++ 2015 Build Tools и Microsoft Build Tools 2015.

Открываем вновь Anaconda Prompt, используем ту же самую команду и ждём, когда соберется проект (около 5 минут):

Сборка
Всё прошло успешноВсё прошло успешно

Управляем громкостью

Вариантов оказалось несколько (ссылка), но чем проще - тем лучше. На русском язычном StackOverflow предложили использовать простую библиотеку от Paradoxis - ей и воспользуемся. Чтобы установить её, нам нужно скачать архив, пройти по пути C:\ProgramData\Anaconda3\Lib и перенести файлы keyboard.py, sound.py из архива. Проблем с использованием не возникало, поэтому идём дальше

Собираем события мыши

Самым популярным модулем для автоматизации управления мышью/клавиатурой оказался pynput. Устанавливаем так же через (pip install dlib). У модуля в целом неплохое описание - https://pynput.readthedocs.io/en/latest/mouse.html . Но у меня возникли сложности при получении событий. Я написал простую функцию:

from pynput import mousedef func_mouse():        with mouse.Events() as events:            for event in events:                if event == mouse.Events.Scroll or mouse.Events.Click:                    #print('Переместил мышку/нажал кнопку/скролл колесиком: {}\n'.format(event))                    print('Делаю половину громкости: ', time.ctime())                    Sound.volume_set(volum_half)                    break

Самое интересное, что если раскомментировать самую первую строчку и посмотреть на событие, которое привело выходу из цикла, то там можно увидеть Move. Если вы заметили, в условии if про него не слово. Без разницы, делал я только скролл колесиком или только нажатие любой клавиши мыши - все равно просто движение мыши приводит к выходу из цикла. В целом, мне нужно все действия (Scroll, Click, Move), но такое поведение я объяснить не могу. Возможно я где-то ошибаюсь, поэтому можете поправить.

А что в итоге?

Adam Geitgey, автор библиотеки face recognition, в своём репозитории имеет очень хороший набор примеров, которые многие используют при написании статей: https://github.com/ageitgey/face_recognition/tree/master/examples

Воспользуемся одним из них и получим следующий код, который можно скачать по ссылке: Activity.ipynb, Activity.py

Код
# Подключаем нужные библиотекиimport cv2import face_recognition # Получаем данные с устройства (веб камера у меня всего одна, поэтому в аргументах 0)video_capture = cv2.VideoCapture(0) # Инициализируем переменныеface_locations = []from sound import SoundSound.volume_up() # увеличим громкость на 2 единицыcurrent = Sound.current_volume() # текущая громкость, если кому-то нужноvolum_half=50  # 50% громкостьvolum_full=100 # 100% громкостьSound.volume_max() # выставляем сразу по максимуму# Работа со временем# Подключаем модуль для работы со временемimport time# Подключаем потокиfrom threading import Threadimport threading# Функция для работы с активностью мышиfrom pynput import mousedef func_mouse():        with mouse.Events() as events:            for event in events:                if event == mouse.Events.Scroll or mouse.Events.Click:                    #print('Переместил мышку/нажал кнопку/скролл колесиком: {}\n'.format(event))                    print('Делаю половину громкости: ', time.ctime())                    Sound.volume_set(volum_half)                    break# Делаем отдельную функцию с напоминаниемdef not_find():    #print("Cкрипт на 15 секунд начинается ", time.ctime())    print('Делаю 100% громкости: ', time.ctime())    #Sound.volume_set(volum_full)    Sound.volume_max()        # Секунды на выполнение    #local_time = 15    # Ждём нужное количество секунд, цикл в это время ничего не делает    #time.sleep(local_time)        # Вызываю функцию поиска действий по мышке    func_mouse()    #print("Cкрипт на 15 сек прошел")# А тут уже основная часть кодаwhile True:    ret, frame = video_capture.read()        '''    # Resize frame of video to 1/2 size for faster face recognition processing    small_frame = cv2.resize(frame, (0, 0), fx=0.50, fy=0.50)    rgb_frame = small_frame[:, :, ::-1]    '''    rgb_frame = frame[:, :, ::-1]        face_locations = face_recognition.face_locations(rgb_frame)        number_of_face = len(face_locations)        '''    #print("Я нашел {} лицо(лица) в данном окне".format(number_of_face))    #print("Я нашел {} лицо(лица) в данном окне".format(len(face_locations)))    '''        if number_of_face < 1:        print("Я не нашел лицо/лица в данном окне, начинаю работу:", time.ctime())        '''        th = Thread(target=not_find, args=()) # Создаём новый поток        th.start() # И запускаем его        # Пока работает поток, выведем на экран через 10 секунд, что основной цикл в работе        '''        #time.sleep(5)        print("Поток мыши заработал в основном цикле: ", time.ctime())                #thread = threading.Timer(60, not_find)        #thread.start()                not_find()        '''        thread = threading.Timer(60, func_mouse)        thread.start()        print("Поток мыши заработал.\n")        # Пока работает поток, выведем на экран через 10 секунд, что основной цикл в работе        '''        #time.sleep(10)        print("Пока поток работает, основной цикл поиска лица в работе.\n")    else:        #все хорошо, за ПК кто-то есть        print("Я нашел лицо/лица в данном окне в", time.ctime())        Sound.volume_set(volum_half)            for top, right, bottom, left in face_locations:        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)        cv2.imshow('Video', frame)        if cv2.waitKey(1) & 0xFF == ord('q'):        breakvideo_capture.release()cv2.destroyAllWindows()

Суть кода предельно проста: бегаем в цикле, как только появилось хотя бы одно лицо (а точнее координаты), то звук делаем 50%. Если не нашёл никого поблизости, то запускаем цикл с мышкой.

Тестирование в бою

Ожидание и реальность

Если вы посмотрели видео, то поняли, что результат ещё далёк от реальной эксплуатации.

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

Так же возникает закономерный вопрос - а если вместо живого человека поставить перед монитором картинку? Да, она распознает, что, скорее всего, не совсем верно. Мне попался очень хороший материал по поводу определения живого лица в реальном времени - https://www.machinelearningmastery.ru/real-time-face-liveness-detection-with-python-keras-and-opencv-c35dc70dafd3/ , но это уже немного другой уровень и думаю новичкам это будет посложнее. Но эксперименты с нейронными сетями я чуть позже повторю, чтобы тоже проверить верность и повторяемость данного руководства.

Немаловажным фактором на качество распознавания оказывает получаемое изображение с веб-камеры. Предложение использовать 1/4 изображения (сжатие его) приводит только к ухудшению - моё лицо алгоритм распознать так и не смог. Для повышения качества предлагают использовать MTCNN face detector (пример использования), либо что-нибудь посложнее из абзаца выше.

Другая интересная особенность - таймеры в Питоне. Я, опять же, признаю, что ни разу до этого не было нужды в них, но все статьях сводится к тому, чтобы ставить поток в sleep(кол-во секунд). А если мне нужно сделать так, чтобы основной поток был в работе, а по истечению n-ое количества секунд не было активности, то выполнялась моя функция? Использовать демонов (daemon)? Так это не совсем то, что нужно. Писать отдельную программу, которая взаимодействует с другой? Возможно, но единство программы пропадает.

Заключение

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

P.S. Предлагаю вам, читатели, обсудить в комментариях статью - ваши идеи, замечания, уточнения.

Подробнее..

Из песочницы AI, который не просит хлеба

17.11.2020 14:16:28 | Автор: admin
Статья о том, как мы шаг за шагом строили наш AI. Время чтения 10+ минут.



Введение. Стартап в области компьютерного зрения, используемый low-cost разработку в качестве базовой концепции. Команда вполне соответствует духу: 3 5 студентов разработчиков разного уровня и направления, в зависимости от дня недели и времени суток (от 0.25 до 1.25 ставки). Мой опыт игры в пятнашки здесь очень пригодился.

Коротко о продукте (пк + софт) интеллектуальная система видео наблюдения, подключаемая к локальной сети и производящая обработку видео своими силами. Два обязательных условия: наличие интерфейса пользователя с различными правами и максимальная автономность алгоритмов.

С технической стороны ограничений на железо не было, главное чтоб работало хорошо; а вот с финансовой были. На все про все ~500$. Разумеется только новые и современные комплектующие. Выбор их не велик, но есть!

С железом определились, далее софт. Выбор пал на микросервисную архитектуру с использованием докера по некоторым, достаточно весомым причинам.

Разработка фичей шла от простых и необходимых (работа с потоками и видео файлами) к сложным, с периодическим review. Собрали MVP, несколько спринтов оптимизации заметно приблизили нас к заветной цели выполнять все 4 пункта одновременно, а не по отдельности:

  1. 16+ ip-камер (FHD/25fps) трансляция, воспроизведение по событию или времени и запись
  2. Параллельная работа всех имеющихся CV алгоритмов
  3. Пользователь интенсивно пользуется интерфейсом без задержек смотрит стримы
  4. Загрузка ЦП менее 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, если есть такой, то он и есть; если нет, то новый.
Плюсов такого решения не счесть, а минус только один оно не работает.

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

  1. Каждый вектор (а-вектор) будет принадлежать какому либо User; каждый кластер (не более М векторов, из коробки M =30) принадлежит какому либо User. Принадлежит ли а-вектор кластеру А не факт. Вектора в кластере определяют взаимодействие кластера, вектора в User определяют только историю User.
  2. Каждый кластер будет иметь центроид (по сути А-вектор) и собственный радиус (далее range) взаимодействия с другими векторами или кластерами.
  3. Центроид и range будут функцией кластера, а не статикой.
  4. Близость векторов определяется квадратом евклидова расстояния (в особых случаях иначе). Хотя здесь есть еще несколько других достойных методов, но мы просто остановились на этом.

Примечание: т.к. мы использовали нормированные вектора, то расстояние между ними было гарантированно от 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).


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

Как мы сделали акселератор инференса нейронных сетей для ЦОД с 64 чипами Intel Movidius

11.05.2021 10:09:42 | Автор: admin

Некоторое время назад мы искали оптимальное аппаратное и программное обеспечение для исполнения нейронных сетей в ЦОД и "на краю" (edge computing). В рамках нашего исследования мы протестировали множество устройств, от процессоров до встроенной графики iGPU и GPGPU различных производителей. С результатами исследования можно ознакомиться по ссылке.

В рамках этого исследования нас заинтересовал VPU Intel Movidius (MyriadX). При вычислениях "на краю" и использовании фреймворка Intel OpenVINO он позволял нам увеличивать число потоков или каналов путем дооснащения существующих устройств без какой-либо модификации аппаратной и программной базы. По умолчанию мы использовали встроенную графику, например, Intel HD или Iris Plus 655, но если FPS и число потоков необходимо было увеличивать, то промышленные ПК можно было дооснастить VPU. Это давало возможность сохранить единообразие множества устройств при изменяемом числе потоков. В качестве примера можно привести транспортную отрасль и подсчет пассажиров на борту автобусов. Автобусы бывают с 2, 3 и 4 дверьми. И если для двух дверей достаточно встроенной графики, то для четырех необходимо увеличение FPS, что достигалось расширением готового решения при помощи VPU формата M.2.

Вот так выглядело наше устройство для исполнения нейронных сетей "на краю" с Intel Movidius:

ComBox Outdoor Box SquaredComBox Outdoor Box Squared

Сегодня для инференса "на краю" интерес представляют решения от компании AAEON, в частности VPC-3350S, VPC-3350AI:

AAEON VPC-3350SAAEON VPC-3350S

Они отличаются расширенным температурным диапазоном эксплуатации -20+70 градусов, наличием возможности расширения двумя VPU Movidius, широкой линейкой поддерживаемых процессоров от Intel Atom x5 E3940 до Pentium N4200 или Atom x7 E3950, а также наличием 4 PoE Ethernet портов для подключения камер или иного оборудования.

С использованием Movidius в IoT все было более или менее понятно, но нас заинтересовала хотя бы теоретическая возможность масштабирования и применения этих компактных энергоэффективных чипов в ЦОД в виде ускорителей инференса PCIe формата.

Суммарный объем рынка публичных и частных облаков в России по данным IDC с 2019 года растет минимум на 25% в год, что на 2019 год составляло $1,72 млрд., а на 2020 год увеличилось до $2,2 млрд. Доля публичных облаков в общем объеме рынка в 2019 году 84,6%. Несмотря на то, что облачный рынок претерпел ряд структурных изменений в 2020 году, рост продолжается с частичным, но постоянным увеличением объемов облачных вычислений в системах искусственного интеллекта прикладного уровня, например, видеоаналитике для обработки ранее сформированных видеоархивов.

После предварительной оценки рынка мы провели поиск имеющихся решений в формате PCIe. Все найденные на тот момент устройства содержали 4 или 8 Movidius на одну плату. Например, решения от AAEON:

AAEON AI CORE XP4/ XP8AAEON AI CORE XP4/ XP8

Общее назначение имеющихся устройств - инференса "на краю". И вот здесь родилась идея реализации собственного ускорителя инференса нейронных сетей для ЦОД с чипами Movidius высокой плотности.

Сейчас в этой сфере используются два основных устройства: GPGPU nVidia Tesla T4 и ускорители инференса Huawei Atlas 300. Альтернатив по производительности от компании Intel для дооснащения существующих систем или внедрения новых серверных решений нет. Возможное решение, сопоставимое по производительности и стоимости - это ускоритель на основе VPU Movidius (MyriadX) высокой плотности в форм-факторе PCIe с плотностью не менее 64 Movidius на каждой несущей плате.

Требования:

  • плотность чипов Movidius не менее 64 штук на каждую плату

  • наличие возможности изменения числа VPU на плате

  • минимально возможное энергопотребление

  • форм-фактор PCIe x4/x8

  • работа конечного устройства под управлением фреймворка Intel OpenVINO без каких-либо значимых доработок

  • исполнение под использование в серверных платформах

Концепт не заставил себя долго ждать:

ComBox x64 Movidius Blade BoardComBox x64 Movidius Blade BoardComBox x64 Movidius Blade BoardComBox x64 Movidius Blade Board

Результатом проектирования платы получилось устройство PCIe с размещенными на несущей плате кастомными разъемами для подключения дочерних плат с нанесенными на них VPU. Таким образом конечную плату можно использовать с числом VPU до 64 штук, кратно 8. На каждый разъем отведена 1 линия PCIe, а в рамках каждой дочерней платы устройства подключены через USB хаб.

Охлаждение - пассивные радиаторы вдоль каждой дочерней платы, которые продуваются мощными вентиляторами серверных платформ.

Первые образцы прототипа:

ComBox x64 Movidius Blade BoardComBox x64 Movidius Blade Board

Дочерние платы (по 8 Movidius на каждой):

x8 Movidius blades for ComBox x64 Movidius boardx8 Movidius blades for ComBox x64 Movidius board

Для тестирования и отладки мы использовали платформу Supermicro SYS-1029TRT и рекомендуем ее по следующим причинам:

  • хорошее соотношения цена/качество

  • форм-фактора 1U (занимает 1 место в стойке)

  • наличие 4 портов PCIe x8/x16

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

Supermicro SYS-1029TRT с установленной платой ComBox x64 Movidius Blade BoardSupermicro SYS-1029TRT с установленной платой ComBox x64 Movidius Blade Board

На картинке выше у нас установлено 4 дочерних платы с 32 Movidius, что отображается на обратной стороне ускорителя 4 зелеными диодами.

Вид готового изделия:

ComBox x64 Movidius Blade BoardComBox x64 Movidius Blade Board

И первые первые промышленные образцы платы:

Каких итогов мы добились:

  1. Максимальная плотность VPU Movidius на одной плате в мире.

  2. Показатель в инференсе сверточных нейронных сетей (на примере Mobilenet v.2 SSD) - 2800 FPS.

  3. Энергопотребление платы не более 120 Вт при полной загрузке.

  4. Возможность использовать произвольное число дочерних плат и устанавливать по 8, 16, 24 и т.д. VPU в рамках одной несущей платы.

  5. Возможность запуска инференса под управлением фреймворка Intel OpenVINO с использованием MDL и HDDL плагинов.

Следующие планируемые шаги:

  1. Выпуск несущих плат с интегрируемым аппаратным ключом Senselock для защиты моделей нейронных сетей в процессе их исполнения.

  2. Предоставление облачных мощностей для инференса в аренду на базе ComBox x64 Movidius Blade board.

Подробнее..

Перевод 20 самых популярных блогов и новостных сайтов по компьютерному зрению в 2020 г

25.09.2020 16:06:58 | Автор: admin
Всем привет. В преддверии старта курса Компьютерное зрение подготовили для вас полезный перевод.





1. Learn OpenCV Сатья Маллик



Сан-Диего, Калифорния

О блоге: Этот блог предназначен для программистов, инженеров, хакеров, ученых, студентов и энтузиастов, которые интересуется компьютерным зрением и машинным обучением. Здесь вы можете освоить компьютерное зрение, машинное обучение и обработку изображений с помощью руководств по OpenCV, CUDA, Caffe и примеров написанных на C и Python.

Частота публикаций: 2 поста в неделю

Блог: Learnopencv.com

Подписчиков в Twitter 10,7 тыс. Вовлеченность в соцсетях 51 Авторитетность сайта 48 Рейтинг Alexa 81,7 тыс.

2. Piekniewski's Blog (Блог Пикневски)



Сан-Диего, Калифорния

О блоге: Филип Пикневски ученый-исследователь, занимающийся компьютерным зрением и искусственным интеллектом. Главная цель его исследовательских работ привнести динамику в машинное обучение, где сейчас безраздельно доминирует статистика.

Частота публикаций: 1 пост в квартал

Блог: blog.piekniewski.info

Подписчиков в Twitter 3.5K Вовлеченность в соцсетях 64 Авторитетность сайта 45 Рейтинг Alexa 1.5 млн.

3. Zbigatron Збигнев Здзярски



Сидней, Новый Южный Уэльс

О блоге: Zbigatron это блог об интересных вещах, происходящих в мире компьютерного зрения.

Частота публикаций: 2 поста в квартал

Блог: zbigatron.com

Подписчиков в Twitter 138 Авторитетность сайта 21

4. OpenCV library



О блоге: Фонд OpenCV это некоммерческий фонд, целью которого является развитие компьютерного зрения с помощью программного обеспечения (в основном посредством библиотеки OpenCV) и остальных непрограммных методов, например, организации мероприятий.

Блог: opencv.org

Подписчиков в Facebook 5,2 тыс. Подписчиков в Twitter 10,2 тыс. Вовлеченность в соцсетях 314 Авторитетность сайта 68 Рейтинг Alexa 17,5 тыс.

5. Computer Vision: doing stuff with pixels | Reddit (Компьютерное зрение: делаем что-то с пикселями)



Сан-Франциско, Калифорния

О блоге: Сабреддит про компьютерное зрение.

Частота публикаций: 6 постов в день

Блог: old.reddit.com/r/computervision

Подписчиков в Facebook 1,3 млн Подписчиков в Twitter 674,7 тыс. Подписчики в Instagram 462,1 тыс. Вовлеченность в соцсетях 1 Авторитетность сайта 91 Рейтинг Alexa 17

6. CV-Tricks.com




Бангалор, Индия

О блоге: CV-Tricks фокусирует свое внимание на различных приемах и хитростях в компьютерном зрении. В этом блоге вы найдете практические советы, приемы и методы, используемые для создания решений компьютерного зрения мирового уровня.

Частота публикаций: 1 пост месяц

Блог: cv-tricks.com

Подписчиков в Twitter 552 Вовлеченность в соцсетях 43 Авторитетность сайта 41 Рейтинг Alexa 552,4 тыс.

7. LDV Capital Blog



Нью-Йорк, штат Нью-Йорк

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

Частота публикаций: 1 пост в неделю
Блог: ldv.co/blog

Подписчиков в Facebook 268 Подписчиков в Twitter 4 тыс. Вовлеченность в соцсетях 17 Авторитетность сайта 45 Рейтинг Alexa 1,5 млн.

8. Блог Machine Vision



Тейм, Великобритания

О блоге: Будьте в курсе последних новостей отрасли, релизов продуктов машинного зрения и улучшений их систем. ClearView Imaging ведущий поставщик компонентов, решений, систем и услуг машинного зрения. Не имеет значения, являетесь ли вы производителем, разрабатывающим систему технического зрения с нуля, или конечным пользователем, который хочет получить готовую систему.

Частота публикаций: 1 пост в квартал

Блог: clearviewimaging.co.uk/blog

Подписчиков в Facebook 16 Подписчиков в Twitter 136 Авторитетность сайта 25 Рейтинг Alexa 8,6 млн.

9. UCF CRCV | Youtube




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

Частота публикаций: 5 видео в неделю

Блог: youtube.com/user/UCFCRCV
Подписчиков в Facebook 646 Подписчиков в Twitter 80 Авторитетность сайта 100 Рейтинг Alexa 2

10. The Xiris Blog



Берлингтон, Онтарио, Канада

О блоге: Xiris Automation Inc. специализируется на разработке оптического метрологического оборудования (от камер до умных компьютеров), используемого для контроля процессов и качества в ряде специализированных отраслей.

Частота публикаций: 2 поста в месяц

Блог: blog.xiris.com/blog

Подписчиков в Facebook 58 Подписчиков в Twitter 303 Подписчиков в Instagram 465 Вовлеченность в соцсетях 1 Авторитетность сайта 25

11. the Serious Computer Vision Blog (серьезный блог о компьютерном зрении)



Тайвань

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

Частота публикаций: 1 пост в неделю

Блог: computervisionblog.wordpress

Подписчиков в Twitter 163 Вовлеченность в соцсетях 4 Авторитетность сайта 28

12. Eastern European Conference on Computer Vision (EECVC Восточно-европейская конференция по компьютерному зрению)



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

Частота публикаций: 1 пост в месяц

Блог: eecvc.com/blog-1-column

Подписчиков в Facebook 265 Вовлеченность в соцсетях 87 Авторитетность сайта 24

13. Embedded Vision Alliance



Уолнат-Крик, Калифорния

О блоге: Основная цель Embedded Vision Alliance вдохновлять и расширять возможности создателей аппаратного и программного обеспечения для использования технологий компьютерного зрения.

Частота публикаций: 17 постов в год

Блог: embedded-vision.com/industry

Подписчиков в Facebook 2,1 тыс. Вовлеченность в соцсетях 4 Авторитетность сайта 49

14. Dov Katz: Computer Vision (Дов Кац: Компьютерное зрение)



О блоге: Блог Дова Каца о компьютерном зрении, машинном обучении и человеческом восприятии.

Блог: dovkatz.wordpress.com

Подписчиков в Twitter 11 Авторитетность сайта 3

15. RSIP Vision



О блоге: RSIP Vision, управляемый Роном Соферманом, является признанным лидером в области компьютерного зрения и обработки изображений. Здесь приведен раздел со статьями RSIP Vision о проектах и работах в области компьютерного зрения и обработки изображений.

Частота публикаций: 10 постов в год

Блог: rsipvision.com/category/rsip

Подписчиков в Twitter 1,5 тыс. Вовлеченность в соцсетях 13 Авторитетность сайта 42 Рейтинг Alexa 1,3 млн.

16. Блог Kairos | Последние новости в области человеческой аналитики



Майами, Флорида

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

Блог: kairos.com/blog

Подписчиков в Twitter 4.6 тыс. Авторитетность сайта 55 Рейтинг Alexa 219.3 тыс.

17. iMerit



Сан Франциско, Калифорния

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

Частота публикаций: 2 поста в месяц

Блог: imerit.net/blog

Подписчиков в Facebook 1,6 тыс. Подписчиков в Twitter 955 Авторитетность сайта 37 Рейтинг Alexa 273,7 тыс.

18. Computer Vision News (Новости компьютерного зрения)



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

Блог: computervisionnews.wordpress

Подписчиков в Twitter 6,2 тыс. Авторитетность сайта 16

19. Блог Playment



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

Частота публикаций: 2 поста в год

Также присутствует в: Блоги по искусственному интеллекту

Блог: blog.playment.io

Подписчиков в Facebook 1,6 тыс. Подписчиков в Twitter 896 Авторитетность сайта 35 Рейтинг Alexa 144,1 тыс.

20. Cortexica



Лондон / Сан Франциско

О блоге: Cortexica, базирующаяся в Лондоне глобальный поставщик решений B2B AI для сферы цифровой трансформации (Digital Transformation). Внося ясность и понимание в шум вокруг ИИ, Cortexica работает с ведущими мировыми брендами, предоставляющими программные системы компьютерного зрения, машинного обучения и видеоаналитики, предоставляя спектр отраслевых профессиональных услуг.

Частота публикаций: 5 постов в год

Блог: cortexica.com

Подписчиков в Facebook 1,1 тыс. Подписчиков в 3,7 тыс. Авторитетность сайта 43

21. Alex Kendall Blog (Блог Алекса Кендалла)




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

Блог: alexgkendall.com/blog

Подписчиков в Twitter 3,9 тыс. Вовлеченность в соцсетях 93 Авторитетность сайта 33 Рейтинг Alexa 4,3 млн.
Подробнее..

Компьютерное зрение в промышленной дефектоскопии Часть 1 Как мы заставляли нейронку пялиться на ржавчину

11.02.2021 14:07:02 | Автор: admin


Сегодня мы хотим поделиться опытом решения задачи детекции дефектов на снимках промышленных объектов методами современного компьютерного зрения.


Наш рассказ будет состоять из нескольких частей:


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

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


Заметка от партнера IT-центра МАИ и организатора магистерской программы VR/AR & AI компании PHYGITALISM.


Описание задачи



Рис 1. Схематичное изображение рассматриваемого проекта.


Машинное обучение (machine learning / ML) в общем и компьютерное зрение (computer vision / CV) в частности находят сегодня все больше применений в решение задач из промышленной области (пример). Начиная от задач нахождения бракованных деталей на конвейере и заканчивая управлением беспилотным транспортом везде используются глубокие архитектуры, позволяющие детектировать многочисленные объекты разных категорий, предсказывать пространственное расположение объектов друг относительно друга и многое другое.


Сегодня мы рассмотрим кейс (проект Defects detector CV) по созданию прототипа программного обеспечения (ПО), которое использует нейронные сети, для того, чтобы на фото или видеопотоке детектировать объекты заданных категорий. В качестве предметной области, в данном проекте, выступила дефектоскопия промышленных труб.



Рис 2.Схематичное изображение парового котла, аналогичного тому, что рассматривался в проекте.


Кратко опишем предметную постановку задачи:


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


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


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


Мы постараемся осветить все основные этапы при разработке проекта, связанного с машинным


  • обучением;
  • сбор данных;
  • разметка данных;
  • дополнение данных (аугментация);
  • обучение модели;
  • оценка качества работы модели;
  • подготовка к внедрению.

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


Набор данных



Рис. 3 Пример объекта обучающей выборки фото, сделанное при осмотре внутренности остановленного котла.


Данные в нашей задачи представлены классическим для CV образом в виде цветных RGB снимков. На каждом снимки может как присутствовать сразу несколько дефектов, так и не присутствовать вовсе.


Ниже постараемся ответить на следующие основные вопросы о данных, которые возникают почти во всех проектах, связанных с компьютерным зрением:


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

Виды задач распознавания образов на изображениях



Рис.4 Пример разметки для задачи детекции объектов на изображениях из датасета MS COCO. Иллюстрация из репозитория detectron2.


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


  • выделения области интереса;
  • классификация объекта в области интереса.

В зависимости от формы области интереса, задачи детекции делят на детектирование в виде ограничивающих прямоугольников (bounding boxes), в виде многоугольников, в виде битовых масок, в виде точек интереса и пр.


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


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


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


Про устройство формата MS COCO и формы предсказаний можно узнать здесь.


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



Рис. 5 Демонстрация работы CVAT для разметки дефекта трубы в виде полигональной маски.


Для того, чтобы разметить сырые данные существует несколько основных способов. Во-первых, можно воспользоваться услугами сервисов облачной распределенной разметки данных, вроде Amazon Mechanical Turk или Яндекс.Толока, во-вторых, если данные приходят не из реального мира, а генерируются искусственно, то разметку можно генерировать вместе с данными, ну и самым доступным способом является использование специализированного ПО для разметки данных.


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


Некоторые альтернативные инструменты разметки:



Усиление обобщающей способности


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



Рис. 5.1 Изображение из сообщества Memes on Machine Learning for Young Ladies. Там же можно увидеть пример того как не нужно делать аугментации.


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


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


Метрики качества


Для того, чтобы измерять качество работы алгоритма после процесса обучения на тестовой выборке в задачах детекции объектов на изображениях традиционно используют метрику mean average precision (mAP), рассчитанную для каждого класса по отдельности и усредненную для всех классов. Значение этой метрики рассчитываются при разных уровнях характеристики Intersection over Union (IoU). Здесь мы не будем подробно останавливаться на разъяснении устройства этих функций, всем заинтересованным предлагаем пройти по ссылкам ниже на статьи и заметки, которые помогут освоится в данном вопросе, но все же поясним некоторые основные моменты оценки качества работы алгоритмов в нашей задаче.


Для оценки результатов были выбраны следующие метрики:


  • mAP (mean average precision) среднее значение точности по всем классам (поскольку у термина могут быть разные трактовки, рекомендуем ознакомится с различными вариантами здесь).
  • AP (средняя точность) средняя точность по каждому отдельному классу.
  • Precision Recall кривая.
  • Число случаев когда дефект был обнаружен и он на самом деле был (TP).
  • Число случаев когда дефект был обнаружен, но его не было на самом деле (FP) т. е. ложное срабатывание.

Расчет метрик производился следующим образом. Так как целевым объектом для оценки был ограничивающий прямоугольник (bounding box), то интерес для оценки представляет три свойства:


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

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


Рассмотрим пример вычисления метрик на основе примера из рис. ниже. Для этого определяется величина IoU, которая равна отношению площади пересечения прямоугольников (серый прямоугольник) к площади их объединения. Она принимает значения в отрезке [0;1]. Можно выбрать определённый порог и считать, что при превышении этого порога прямоугольники совпадают.


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


Определение FP:
Если IoU меньше определенного порога и метки классов совпадают, то предсказание считается ложным.


На основании этих показателей рассчитывается точность и полнота. Если кратко, то точность показывает насколько хорошо модель предсказывает дефекты определенного класса из тех которые были обнаружены вообще. Чем больше значение, тем меньше ошибок совершается. Значение в отрезке от [0;1].



Рис. 6 Пример рассчитанной Precision-Recall кривой для одного из классов дефектов для архитектуры DetectoRS.


Полнота показывает способность модели найти все дефекты определенного класса. Чем выше значение тем больше будет обнаруживаться дефектов, но не все могут быть точно предсказаны Эти величины взаимосвязаны. Как правило, чем больше точность, тем меньше полнота и наоборот. Т. к. предсказания зависят от некоторого порога принятия решения, то строят графики точности-полноты при разных значениях порога принятия решения для его оптимального определения и оценки возможностей модели.



Рис. 7 Сравнение технических метрик для выбранных архитектур для процесса обучения.


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


Заметку с примерами и объяснениями всей терминологии про метрики качества в CV можно посмотреть здесь.


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



*Рис. 8 Сравнение технических метрик для выбранных архитектур для процесса использования.* тестирование проводилось на видеокарте RTX 2080 Ti, тестирование проводилось на CPU AMD Ryzen 7 2700X Eight-Core Processor.*


Деление данных на обучающую и тестовую выборку


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


Рассмотрим пример, изображенный на рис. 9 случай, когда каждый объект принадлежит одному классу:

Рис. 9 Деление данных на тестовую и обучающую выборку.


Особой проблемы здесь нет и разделение на две выборке можно сделать достаточно просто. В нашем же случае картина с распределением объектов и классов будет такая:

Рис. 10 Каждый объект может содержать иметь несколько классов.


Для решения проблемы разделения такого вида данных на подвыборки была использована библиотека scikit-multilearn и метод iterative_train_test_split. На вход подавалась матрица из нулей и единиц. Каждая строка обозначала изображение. Единицы стояли в столбцах с номерами соответствующими номерам классов. Если применить функцию к объектам на рис. выше, то получится следующее разделение:

Рис. 11 Результат разделения на тестовую и обучающую выборку.


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


Выбор архитектуры


На основе результатов анализа известных архитектур собранных на paperwithcode (на момент 3 квартала 2020 года) для детектирования дефектов были выбраны две архитектуры:




Рис. 12 Сравнение архитектур на бенчмарке MS COCO object detection с сайта papesrwithcode (3 квартал 2020 года).



Рис.13 Сравнение архитектур на бенчмарке MS COCO, полученное авторами архитектуры YOLOv4.


YOLOv4 попала в этот список из-за своей скорости работы. Мы хотели посмотреть на результаты разных моделей и выбрать итоговый вариант в зависимости от требований. Потенциально могла быть потребность в обработке видео и для этого случая планировалось использовать YOLOv4. Более точная, но медленная работа ожидалась от DetectoRS.


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


DetectoRS


Данная архитектура базируется на использовании специального типа сверток (Switchable Atrouse Convolution / SAC) и в верхнем уровне устроена в виде рекурсивной пирамиды (Recursive Feature Pyramid / RFP), объединяющий локальные и глобальные признаки.



Рис. 14 Основные нововведения, используемые авторами архитектуры DetectoRS: (a) рекурсивная пирамида признаковых описаний, используемая для объединения глобальных признаков на изображении; (b) переключательные свертки типа atrouse для работы с локальными признаками.


Реализация данной модели присутствует внутри фреймворка MMDetection.
Об устройстве архитектуры можно прочитать здесь.


YOLOv4


Эта модель делает свои предсказания на основе анализа изображения, разбивая его на квадратную сетку как на рис. ниже:

Рис. 15 Принцип работы из оригинальной статьи про YOLO.


Видео с объяснением работы можно найти здесь.


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


Фреймворки для обучения моделей в задачах CV


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


Для разработки под Python, двумя наиболее популярными фреймворками являются MMdetection (open source) и Detectron2 (Facebook research). Для разработки под C, существует фреймворк Darknet (open source). Подробнее про то, как использовать данные фреймворки можно прочитать в заметках один, два, три.


MMDetection


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


Устройство фреймворка


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


  • подготовить веса при тренированной модели в формате .pth или прописать самостоятельно инициализацию новых весов;
  • написать конфигурационный файл в виде Python скрипта со всеми настройками для сети и описание хода обучения, валидации и пр.;
  • выбрать способ логирования процесса обучения (доступна запись в текстовый файл и логирование с помощью tensorboard);
  • организовать данные в файловой системе согласно выбранному типу разметки (доступны MS COCO, Pascal VOC и поддерживается возможность внедрение пользовательских форматов);
  • написать основный скрипт, собирающий воедино все перечисленные выше составные части.

После тестов локально на компьютере, на котором была установлена и развернута среда для работы с MMDetection, аналогично YOLOv4, сборка была перенесена внутрь NVidia Docker контейнера.


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


Наш конфигурационный файл для обучения на данных с трубами для 9 классов дефектов:
# Обучения на 9 классах без масокmodel = dict(    type='CascadeRCNN',    pretrained='torchvision://resnet50',    backbone=dict(        type='DetectoRS_ResNet',        depth=50,        num_stages=4,        out_indices=(0, 1, 2, 3),        frozen_stages=1,        norm_cfg=dict(type='BN', requires_grad=True),        norm_eval=True,        style='pytorch',        conv_cfg=dict(type='ConvAWS'),        sac=dict(type='SAC', use_deform=True),        stage_with_sac=(False, True, True, True),        output_img=True),    neck=dict(        type='RFP',        in_channels=[256, 512, 1024, 2048],        out_channels=256,        num_outs=5,        rfp_steps=2,        aspp_out_channels=64,        aspp_dilations=(1, 3, 6, 1),        rfp_backbone=dict(            rfp_inplanes=256,            type='DetectoRS_ResNet',            depth=50,            num_stages=4,            out_indices=(0, 1, 2, 3),            frozen_stages=1,            norm_cfg=dict(type='BN', requires_grad=True),            norm_eval=True,            conv_cfg=dict(type='ConvAWS'),            sac=dict(type='SAC', use_deform=True),            stage_with_sac=(False, True, True, True),            pretrained='torchvision://resnet50',            style='pytorch')),    rpn_head=dict(        type='RPNHead',        in_channels=256,        feat_channels=256,        anchor_generator=dict(            type='AnchorGenerator',            scales=[8],            ratios=[0.5, 1.0, 2.0],            strides=[4, 8, 16, 32, 64]),        bbox_coder=dict(            type='DeltaXYWHBBoxCoder',            target_means=[0.0, 0.0, 0.0, 0.0],            target_stds=[1.0, 1.0, 1.0, 1.0]),        loss_cls=dict(            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),        loss_bbox=dict(            type='SmoothL1Loss', beta=0.1111111111111111, loss_weight=1.0)),    roi_head=dict(        type='CascadeRoIHead',        num_stages=3,        stage_loss_weights=[1, 0.5, 0.25],        bbox_roi_extractor=dict(            type='SingleRoIExtractor',            roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0),            out_channels=256,            featmap_strides=[4, 8, 16, 32]),        bbox_head=[            dict(                type='Shared2FCBBoxHead',                in_channels=256,                fc_out_channels=1024,                roi_feat_size=7,                num_classes=9,                bbox_coder=dict(                    type='DeltaXYWHBBoxCoder',                    target_means=[0.0, 0.0, 0.0, 0.0],                    target_stds=[0.1, 0.1, 0.2, 0.2]),                reg_class_agnostic=True,                loss_cls=dict(                    type='CrossEntropyLoss',                    use_sigmoid=False,                    loss_weight=1.0),                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,                               loss_weight=1.0)),            dict(                type='Shared2FCBBoxHead',                in_channels=256,                fc_out_channels=1024,                roi_feat_size=7,                num_classes=9,                bbox_coder=dict(                    type='DeltaXYWHBBoxCoder',                    target_means=[0.0, 0.0, 0.0, 0.0],                    target_stds=[0.05, 0.05, 0.1, 0.1]),                reg_class_agnostic=True,                loss_cls=dict(                    type='CrossEntropyLoss',                    use_sigmoid=False,                    loss_weight=1.0),                loss_bbox=dict(type='SmoothL1Loss', beta=1.0,                               loss_weight=1.0)),            dict(                type='Shared2FCBBoxHead',                in_channels=256,                fc_out_channels=1024,                roi_feat_size=7,                num_classes=9,                bbox_coder=dict(                    type='DeltaXYWHBBoxCoder',                    target_means=[0.0, 0.0, 0.0, 0.0],                    target_stds=[0.033, 0.033, 0.067, 0.067]),                reg_class_agnostic=True,                loss_cls=dict(                    type='CrossEntropyLoss',                    use_sigmoid=False,                    loss_weight=1.0),                loss_bbox=dict(type='SmoothL1Loss', beta=1.0, loss_weight=1.0))        ],        train_cfg=[            dict(                assigner=dict(                    type='MaxIoUAssigner',                    pos_iou_thr=0.5,                    neg_iou_thr=0.5,                    min_pos_iou=0.5,                    match_low_quality=False,                    ignore_iof_thr=-1),                sampler=dict(                    type='RandomSampler',                    num=512,                    pos_fraction=0.25,                    neg_pos_ub=-1,                    add_gt_as_proposals=True),                pos_weight=-1,                debug=False),            dict(                assigner=dict(                    type='MaxIoUAssigner',                    pos_iou_thr=0.6,                    neg_iou_thr=0.6,                    min_pos_iou=0.6,                    match_low_quality=False,                    ignore_iof_thr=-1),                sampler=dict(                    type='RandomSampler',                    num=512,                    pos_fraction=0.25,                    neg_pos_ub=-1,                    add_gt_as_proposals=True),                pos_weight=-1,                debug=False),            dict(                assigner=dict(                    type='MaxIoUAssigner',                    pos_iou_thr=0.7,                    neg_iou_thr=0.7,                    min_pos_iou=0.7,                    match_low_quality=False,                    ignore_iof_thr=-1),                sampler=dict(                    type='RandomSampler',                    num=512,                    pos_fraction=0.25,                    neg_pos_ub=-1,                    add_gt_as_proposals=True),                pos_weight=-1,                debug=False)        ],        test_cfg=dict(            score_thr=0.05, nms=dict(type='nms', iou_thr=0.5),            max_per_img=100)))train_cfg = dict(    rpn=dict(        assigner=dict(            type='MaxIoUAssigner',            pos_iou_thr=0.7,            neg_iou_thr=0.3,            min_pos_iou=0.3,            match_low_quality=True,            ignore_iof_thr=-1),        sampler=dict(            type='RandomSampler',            num=256,            pos_fraction=0.5,            neg_pos_ub=-1,            add_gt_as_proposals=False),        allowed_border=0,        pos_weight=-1,        debug=False),    rpn_proposal=dict(        nms_across_levels=False,        nms_pre=2000,        nms_post=2000,        max_num=2000,        nms_thr=0.7,        min_bbox_size=0),    rcnn=[        dict(            assigner=dict(                type='MaxIoUAssigner',                pos_iou_thr=0.5,                neg_iou_thr=0.5,                min_pos_iou=0.5,                match_low_quality=False,                ignore_iof_thr=-1),            sampler=dict(                type='RandomSampler',                num=512,                pos_fraction=0.25,                neg_pos_ub=-1,                add_gt_as_proposals=True),            pos_weight=-1,            debug=False),        dict(            assigner=dict(                type='MaxIoUAssigner',                pos_iou_thr=0.6,                neg_iou_thr=0.6,                min_pos_iou=0.6,                match_low_quality=False,                ignore_iof_thr=-1),            sampler=dict(                type='RandomSampler',                num=512,                pos_fraction=0.25,                neg_pos_ub=-1,                add_gt_as_proposals=True),            pos_weight=-1,            debug=False),        dict(            assigner=dict(                type='MaxIoUAssigner',                pos_iou_thr=0.7,                neg_iou_thr=0.7,                min_pos_iou=0.7,                match_low_quality=False,                ignore_iof_thr=-1),            sampler=dict(                type='RandomSampler',                num=512,                pos_fraction=0.25,                neg_pos_ub=-1,                add_gt_as_proposals=True),            pos_weight=-1,            debug=False)    ])test_cfg = dict(    rpn=dict(        nms_across_levels=False,        nms_pre=1000,        nms_post=1000,        max_num=1000,        nms_thr=0.7,        min_bbox_size=0),    rcnn=dict(        score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100))dataset_type = 'CocoDataset'data_root = 'data/coco/'classes = ('ПРМУ_поперечная трещина на изгибе', 'ПРМУ_выход трубы из ряда',           'ПРМУ_Крип', 'ПРМУ_свищи', 'ПРМУ_разрыв трубы',           'ПРМУ_поперечная трещина в околошовной зоне',           'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',           'ПРМУ_Цвета побежалости')img_norm_cfg = dict(    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)train_pipeline = [    dict(type='LoadImageFromFile'),    dict(type='LoadAnnotations', with_bbox=True),    dict(type='Resize', img_scale=(1280, 720), keep_ratio=True),    dict(type='RandomFlip', flip_ratio=0.5),    dict(        type='Normalize',        mean=[123.675, 116.28, 103.53],        std=[58.395, 57.12, 57.375],        to_rgb=True),    dict(type='Pad', size_divisor=32),    dict(type='DefaultFormatBundle'),    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])]test_pipeline = [    dict(type='LoadImageFromFile'),    dict(        type='MultiScaleFlipAug',        img_scale=(1280, 720),        flip=False,        transforms=[            dict(type='Resize', keep_ratio=True),            dict(type='RandomFlip'),            dict(                type='Normalize',                mean=[123.675, 116.28, 103.53],                std=[58.395, 57.12, 57.375],                to_rgb=True),            dict(type='Pad', size_divisor=32),            dict(type='ImageToTensor', keys=['img']),            dict(type='Collect', keys=['img'])        ])]data = dict(    samples_per_gpu=2,    workers_per_gpu=1,    train=dict(        type='CocoDataset',        classes=('ПРМУ_поперечная трещина на изгибе',                 'ПРМУ_выход трубы из ряда', 'ПРМУ_Крип', 'ПРМУ_свищи',                 'ПРМУ_разрыв трубы',                 'ПРМУ_поперечная трещина в околошовной зоне',                 'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',                 'ПРМУ_Цвета побежалости'),        ann_file='data/coco/annotations/instances_train.json',        img_prefix='data/coco/train/',        pipeline=[            dict(type='LoadImageFromFile'),            dict(type='LoadAnnotations', with_bbox=True),            dict(type='Resize', img_scale=(1280, 720), keep_ratio=True),            dict(type='RandomFlip', flip_ratio=0.5),            dict(                type='Normalize',                mean=[123.675, 116.28, 103.53],                std=[58.395, 57.12, 57.375],                to_rgb=True),            dict(type='Pad', size_divisor=32),            dict(type='DefaultFormatBundle'),            dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])        ]),    val=dict(        type='CocoDataset',        classes=('ПРМУ_поперечная трещина на изгибе',                 'ПРМУ_выход трубы из ряда', 'ПРМУ_Крип', 'ПРМУ_свищи',                 'ПРМУ_разрыв трубы',                 'ПРМУ_поперечная трещина в околошовной зоне',                 'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',                 'ПРМУ_Цвета побежалости'),        ann_file='data/coco/annotations/instances_val.json',        img_prefix='data/coco/val/',        pipeline=[            dict(type='LoadImageFromFile'),            dict(                type='MultiScaleFlipAug',                img_scale=(1280, 720),                flip=False,                transforms=[                    dict(type='Resize', keep_ratio=True),                    dict(type='RandomFlip'),                    dict(                        type='Normalize',                        mean=[123.675, 116.28, 103.53],                        std=[58.395, 57.12, 57.375],                        to_rgb=True),                    dict(type='Pad', size_divisor=32),                    dict(type='ImageToTensor', keys=['img']),                    dict(type='Collect', keys=['img'])                ])        ]),    test=dict(        type='CocoDataset',        classes=('ПРМУ_поперечная трещина на изгибе',                 'ПРМУ_выход трубы из ряда', 'ПРМУ_Крип', 'ПРМУ_свищи',                 'ПРМУ_разрыв трубы',                 'ПРМУ_поперечная трещина в околошовной зоне',                 'ПРМУ_трещина в основном металле', 'ПРМУ_продольные трещины',                 'ПРМУ_Цвета побежалости'),        ann_file='data/coco/annotations/instances_val.json',        img_prefix='data/coco/val/',        pipeline=[            dict(type='LoadImageFromFile'),            dict(                type='MultiScaleFlipAug',                img_scale=(1280, 720),                flip=False,                transforms=[                    dict(type='Resize', keep_ratio=True),                    dict(type='RandomFlip'),                    dict(                        type='Normalize',                        mean=[123.675, 116.28, 103.53],                        std=[58.395, 57.12, 57.375],                        to_rgb=True),                    dict(type='Pad', size_divisor=32),                    dict(type='ImageToTensor', keys=['img']),                    dict(type='Collect', keys=['img'])                ])        ]))evaluation = dict(interval=1, metric='bbox')optimizer = dict(type='SGD', lr=0.0001, momentum=0.9, weight_decay=0.0001)optimizer_config = dict(grad_clip=None, type='OptimizerHook')lr_config = dict(    policy='step',    warmup=None,    warmup_iters=500,    warmup_ratio=0.001,    step=[8, 11],    type='StepLrUpdaterHook')total_epochs = 12checkpoint_config = dict(interval=-1, type='CheckpointHook')log_config = dict(    interval=10,    hooks=[dict(type='TextLoggerHook'),           dict(type='TensorboardLoggerHook')])dist_params = dict(backend='nccl')log_level = 'INFO'load_from = './checkpoints/detectors_cascade_rcnn_r50_1x_coco-32a10ba0.pth'resume_from = Noneworkflow = [('train', 1)]work_dir = './logs'seed = 0gpu_ids = range(0, 1)

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


Обучение модели занимало порядка 2 часов на RTX 2080 Ti. Прогресс можно было отслеживать с помощью запущенного tensorboard. Ниже приведены графики эволюции метрик и функции ошибки в процессе обучения на реальных данных.



Рис. 16 Зависимость mAP от числа итераций обучения для архитектуры DetectoRS для датасета дефектов труб по 9 классам на валидационном датасете.



Рис. 17 Зависимость функции потерь (multiclass cross entropy) от числа итераций обучения для архитектуры DetectoRS для датасета дефектов труб по 9 классам.


Подготовка модели для использования


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


Стоит отметить, что, несмотря на то, что данная архитектура обучается в несколько раз быстрее чем YOLOv4, она занимает в 2 раза больше памяти (500 MB для DetectoRS против 250 MB для YOLOv4 для хранения весов модели) и работает на порядок медленнее (1 с. для DetectoRS против 10 мс. для YOLOv4).


Малое время обучения DetectoRS отчасти объясняется тем, что веса базовых слоев сети (backbone и neck) были взяты из претренированной на ImageNet датасете аналогичной архитектуры и в процессе обучения не изменялись. Такой прием называется transfer learning. Про него вы можете подробнее прочитать в этой заметке.


Darknet


Оригинальная реализация YOLOV4 написана на C c использованием CUDA C. Было принято решение использовать оригинальную реализацию модели, хотя обычно доминируют эксперименты на Python. Это накладывало свои ограничения и риски, связанные с необходимость разбираться в коде на C, в случае каких-то проблем или переделки частей под свои нужды. Подробной документации с примерами не было, поэтому пришлось в некоторых местах смотреть исходный код на C.
Для успешного запуска нужно было решить несколько проблем:


  1. Собрать проект.
  2. Понять что необходимо для обучения модели.
  3. Обучить модель.
  4. Подготовить код для использования.

Сборка проекта


Первая сборка проекта происходила на Windows. Для сборки использовался CMake, поэтому особых проблем с этим не возникло. Единственная проблема была с компиляцией динамической библиотеки для обёртки на Python. Пришлось редактировать файл с проектом для Visual Studio, чтобы включить поддержку CUDA. Динамическая библиотека была нужна т. к. это позволяло использовать код на Python для запуска модели.


После было принято решение перенести сборку внутрь Docker контейнера. Для того чтобы оставить возможность использовать видеокарту был установлен NVIDIA Container Toolkit. Это позволяет достаточно просто организовать перенос проекта на другую машину при необходимости, а также упрощает дальнейшее использование. Благодаря наличию различных образов nvidia/cuda на Docker Hub, можно достаточно просто менять конфигурации. Например, переключение между различными версиями CUDA или cuDNN.


Необходимые файлы для обучения


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


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


Обучение модели производилось в контейнере NVIDIA Docker. Занимало порядка 12 часов на RTX 2080 Ti. Прогресс можно было отслеживать периодически копируя график функции потерь из контейнера на хост, где запущен контейнер. Красным показано значение mAP на тестовой выборке.



Рис. 18 График обучения YOLOv4.


Подготовка модели для использования


После обучения модели была необходимость в ее использовании для визуализации результатов. От кода на Python пришлось отказаться т. к. были проблемы с неожиданным завершением программы. Некоторые части кода пришлось дописать на языке С под собственные нужды.


Результаты


После всех экспериментов были выявлены следующие зависимости:


  • Обучение на смеси данных (реальные + искусственные) приводит к небольшому ухудшению обобщающей способности (это связано с тем, что в данной итерации генератора синтетических данных присутствуют недостаточно разнообразные данные и однотипное освещение);
  • После недолгого подбора гиперпараметров для архитектуры DetectoRS удалось добиться показателя $mAP (IoU=0.5) = 0.85$, а для архитектуры YOLOv4 $mAP(IoU=0.5) = 0.74$ ;
  • Некоторые типы дефектов, такие как разного рода трещины или выход труб из ряда, детектируются лучше чем иные типы дефектов, такие как вздутие труб. Это можно объяснить не только дисбалансом и малым количеством примеров, но и тем, что для определения некоторых типов дефектов, нужно больше пространственной информации (аналогично этому, в реальной детекции дефектов, некоторые дефекты определяются на глаз, а некоторые требуют специальных измерительных приборов). Потенциально, использование помимо RGB каналов с камеры также еще и канала глубины (RGB-D) могло бы помочь с детектированием этих сложных пространственных дефектов: в этом случае мы смогли бы прибегнуть к методам и алгоритмама 3D ML.


Рис. 19 Сравнение работы архитектур, обученных на разных датасетах: Real только реальные изображение, Mixed обучении на смеси реальных и синтетических изображений, Mask обучения на реальных изображениях, с многоугольной разметкой областей.



Рис. 20 Пример некорректной детекции модели DetectoRS, обученной на синтетических данных отсутствие посторонних предметов в синтетическом датасете приводит к определению куска деревянной балки как трещины.



Рис. 21 Пример детекции дефектов в виде многоугольных масок модели DetectoRS, обученной на датасете реальных изображений с соответствующей разметкой.



Рис. 22 Пример детекции дефектов в виде прямоугольников для одного и того же изображения с помощью разных архитектур.


Финальное решение с интерфейсом


В итоге, был получен работающий прототип ПО с интерфейсом, который можно было бы просто использовать как со стороны пользователя так и для разработчиков. Так как проекты на основе ML часто требуют вычислительных мощностей видеокарт NVIDIA для своей работы, то было принято решение сделать клиент-серверное приложение. При необходимости всё можно развернуть на одном компьютере. Если же есть в наличии свободный сервер с видеокартой, то основную логику можно перенести туда, оставив возможность любым пользователям использовать сервис даже на слабом по вычислительным возможностям компьютере.


Для архитектуры ПО была выбрана следующая схема:
Финальное решение с интерфейсом
В итоге, был получен работающий прототип ПО с интерфейсом, который можно было бы просто использовать как со стороны пользователя так и для разработчиков. Так как проекты на основе ML часто требуют вычислительных мощностей видеокарт NVIDIA для своей работы, то было принято решение сделать клиент-серверное приложение. При необходимости всё можно развернуть на одном компьютере. Если же есть в наличии свободный сервер с видеокартой, то основную логику можно перенести туда, оставив возможность любым пользователям использовать сервис даже на слабом по вычислительным возможностям компьютере.


Для архитектуры ПО была выбрана следующая схема:

Рис. 23 Схема архитектуры прототипа ПО для детектирования дефектов на изображениях.


Все модели для детектирования дефектов работают внутри NVIDIA Docker. Остальные части, кроме Web-интерфейса внутри обычных контейнеров Docker. Логика работы следующая:


  1. Пользователь загружает изображение и выбирает нужную модель вместе с порогом принятия решения.
  2. Изображение отправляется на сервер. Оно сохраняется на диске и сообщение с заданием на обработку попадает в RabbitMQ, в соответствующую очередь.
  3. Модель берёт сообщение с заданием из очереди, когда готова выполнить предсказание. Выполняет предсказание и сохраняет необходимые файлы на диск. Отправляет сообщение в RabbitMQ о готовности результат.
  4. Когда результат готов он отображается в web-интерфейсе.

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


RabbitMQ также позволяет изолировать систему друг от друга и начать разработку независимо. При разработке сервера не надо дожидаться реализации ML моделей и наоборот. Каждая система общается только с RabbitMQ. В случае падения какого-то сервиса сообщение не потеряется, если соответствующим образом настроить RabbitMQ.



Рис. 24 Демонстрация работы созданного прототипа ПО для детекции дефектов на трубах.


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


Основные источники


Статьи описывающие SOTA глубокие архитектуры в задаче детекции:

Статьи, посвященные выбору метрик качества в задачах детекции объектов на изображениях:

Фундаментальные монографии на тему современного компьютерного зрения:
  • Szeliski, R., 2010. Computer vision: algorithms and applications. Springer Science & Business Media.
  • Nixon, M. and Aguado, A., 2019. Feature extraction and image processing for computer vision. Academic press.
  • Jiang, X. ed., 2019. Deep Learning in Object Detection and Recognition. Springer.
  • Pardo, A. and Kittler, J. eds., 2015. Progress in Pattern Recognition, Image Analysis, Computer Vision, and Applications: 20th Iberoamerican Congress, CIARP 2015, Montevideo, Uruguay, November 9-12, 2015, Proceedings (Vol. 9423). Springer.
Подробнее..

Ошибки, вода в описаниях, опыт работы на что на самом деле рекрутеры обращают внимание в резюме разработчиков

27.09.2020 00:06:12 | Автор: admin

Летом 2013 известный американский tech-рекрутер Алин Лернер (Aline Lerner) опубликовала в своем блоге материал с анализом факторов, которые оказывают наибольшее влияние на решение о найме разработчика. За период около года Лернер проинтервьюировала около 300 человек на позицию back-end/full-stack разработчика.

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

Результаты 2013 года

В ходе своего анализа Алин Лернер сфокусировалась на изучении следующих факторов:

  • Наличие диплома в сфере Computer Science и уровень университета.

  • Наивысший достигнутый уровень образования.

  • Число грамматических и пунктуационных ошибок в резюме.

  • Частота использования базвордов (названия языков программирования, фреймворки, ОС, софтверные пакеты и т.п.)

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

  • Длина резюме.

  • Наличие персональных проектов.

  • Опыт работы в топовой компании.

  • Балл аттестата (GPA).

Целью было понять, какие из этих факторов оказывают наибольшее влияние на рекрутеров и шансы быть нанятым, а какие почти ни на что не влияют. Вот, как распределились результаты.

1. Наличие ошибок в резюме

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

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

Источник: blog.alinelerner.comИсточник: blog.alinelerner.com

2. Опыт работы в топ-компании

Здесь обошлось без сюрпризов, кроме того, что по логике этот пункт должен быть первым. К элитным компаниям рекрутер отнесла: Amazon, Apple, Evernote, Facebook, Google, LinkedIn, Microsoft, Oracle, стартапы из наборов Y Combinator, Yelp и Zynga. Напомним, что анализ проводился в 2013 году, сегодня, очевидно, набор компаний бы чуть изменился.

3. Насколько легко понять, что кандидат делал на прошлой работе

Лернер собрала резюме тех кандидатов, которые получили офферы, и тех, кто не получил, и составила так называемые word clouds для каждого кластера. В них вошли слова, которые чаще всего встречались в резюме.

Вот как выглядит облако для тех, кто получил оффер (чем больше и ярче слово, тем чаще оно использовалось):

Источник: blog.alinelerner.comИсточник: blog.alinelerner.com

А вот как для тех, кто не получил:

Истоничник: blog.alinelerner.comИстоничник: blog.alinelerner.com

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

4. Наивысший уровень образования

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

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

5. Персональные проекты

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

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

Что изменилось в 2020 году

Мы решили посмотреть, насколько изменилась ситуация, и какие факторы оказывают влияние на карьеру инженеров сегодня. Для этого мы проанализировали данные бота g-mate и рекрутингового агентства gms. Вот, что у нас получилось.

Главная причина отказа: недостаток опыта

Самая распространенная причина отказа кандидату на техническую позицию недостаток опыта. По этой причине отказывают примерно 50% кандидатов, прошедшим через нашу экосистему.

Что еще смущает работодателей: нестабильный опыт работы, некорректные описания

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

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

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

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

Итого

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

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

Используйте бот g-mate (@g_jobbot), чтобы получать вакансии по своему профилю с возможностью релокации или удаленной работы прямо в Telegram. Компании могут опубликовать первые 3 вакансии бесплатно переходитепо ссылке.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru