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

Машинное

Перевод Распознавание маски на лице с помощью YOLOv3

19.04.2021 16:09:25 | Автор: admin

YOLO или You Only Look Once это архитектура свёрточных нейронных сетей, которая используется для распознавания множественных объектов на изображении. В 2020, на фоне пандемии, задача детектирования объектов (object detection) на изображении стала как никогда актуальной. Эта статья даёт полное пошаговое руководство для тех, кто хочет научиться распознавать объекты с помощью YOLO на разных данных. Предполагается, что вы уже знаете, как делать распознавание объектов с помощью методов глубокого обучения и, в частности, вы знаете основы YOLO, так что давайте погрузимся в нашу задачу.


Я собираюсь работать с YOLOv3 одной из самых ходовых версий YOLO, которая включает в себя современную, удивительно точную и быструю систему обнаружения объектов в реальном времени. Новые версии, такие как YOLOv4, YOLOv5, могут достичь даже лучших результатов. Этот проект вы найдёте в моём репозитории на Github.

Рабочее окружение

Чтобы реализовать проект, я использовал Google Colab. Первые эксперименты с предварительной обработкой не были вычислительно дорогими, поэтому выполнялись на моём ноутбуке, но модель обучалась на GPU Google Colab.

Активировать GPU в Colab можно в меню Edit->Notebook Settings.Активировать GPU в Colab можно в меню Edit->Notebook Settings.

Набор данных

Для начала, чтобы сделать детектор маски, нужны соответствующие данные. Кроме того, из-за природы YOLO нужны аннотированные данные с ограничивающими прямоугольниками. Один из вариантов создать собственный набор данных, либо собирая изображения из Интернета, либо фотографируя друзей, знакомых и аннотируя фотографии вручную с помощью определённых программ, таких как LabelImg.Однако оба варианта утомительные и трудоёмкие, особенно последний. Есть другой вариант, самый жизнеспособный для моей цели, работать с публичным набором данных.

Я выбрал Набор обнаружения маски на лице от Kaggle и загрузил его прямо в мой Google Drive. Посмотрите здесь, как это можно сделать. Скачанный набор данных это две папки:

  • images, содержит 853 .png файла;

  • annotations, содержит 853 соответствующих аннотации в формате XML.

После загрузки набора данных, чтобы тренировать нашу модель, нам нужно преобразовать файлы .xml в .txt, а если точнее, в формат YOLO. Пример:

<annotation>    <folder>images</folder>    <filename>maksssksksss0.png</filename>    <size>        <width>512</width>        <height>366</height>        <depth>3</depth>    </size>    <segmented>0</segmented>    <object>        <name>without_mask</name>        <pose>Unspecified</pose>        <truncated>0</truncated>        <occluded>0</occluded>        <difficult>0</difficult>        <bndbox>            <xmin>79</xmin>            <ymin>105</ymin>            <xmax>109</xmax>            <ymax>142</ymax>        </bndbox>    </object>    <object>        <name>with_mask</name>        <pose>Unspecified</pose>        <truncated>0</truncated>        <occluded>0</occluded>        <difficult>0</difficult>        <bndbox>            <xmin>185</xmin>            <ymin>100</ymin>            <xmax>226</xmax>            <ymax>144</ymax>        </bndbox>    </object>    <object>        <name>without_mask</name>        <pose>Unspecified</pose>        <truncated>0</truncated>        <occluded>0</occluded>        <difficult>0</difficult>        <bndbox>            <xmin>325</xmin>            <ymin>90</ymin>            <xmax>360</xmax>            <ymax>141</ymax>        </bndbox>    </object></annotation>

Предположим, что это аннотация изображения, содержащего только 3 ограничительных прямоугольника, это видно по количеству <object> ... </object> в XML.

Чтобы создать подходящий текстовый файл, нам нужно 5 типов данных из каждого XML-файла. Для каждого <object> ... </object> в XML-файле извлеките класс, то есть поле <name>...</name> и координаты ограничивающего прямоугольника 4 атрибута в <bndbox>...</bndbox>. Подходящий формат выглядит так:

<class_name> <x_center> <y_center> <width> <height>

Я написал скрипт, который извлекает 5 атрибутов каждого объекта в каждом XML-файле и создаёт соответствующие файлы TXT.Подробные комментарии о подходе к преобразованию вы найдёте в моём скрипте. К примеру, у Image1.jpg должен быть соответствующий Image1.txt, вот такой:

1 0.18359375 0.337431693989071 0.05859375 0.101092896174863390 0.4013671875 0.3333333333333333 0.080078125 0.120218579234972671 0.6689453125 0.3155737704918033 0.068359375 0.13934426229508196

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

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

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

Пока всё хорошо, давайте продолжим.

Разделение данных

Чтобы обучить нашу модель и проверить её на этапе обучения, мы должны разделить данные на два набора набор обучения и набор тестирования. Пропорция составила 90 и 10 % соответственно. Поэтому я создал две новые папки и поместил 86 изображений с аннотациями в test_folder, а остальные 767 изображений в train_folder.

Клонирование фреймворка darknet

Следующий шаг клонировать репозиторий darknet с помощью команды:

!git clone https://github.com/AlexeyAB/darknet

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

!wget https://pjreddie.com/media/files/darknet53.conv.74

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

Последний шаг

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

  1. face_mask.names: создайте содержащий классы задачи файл _.names.

В нашем случае исходный набор данных Kaggle имеет 3 категории: with_mask, without_mask и mask_weared_incorrect [с маской, без маски, маска надета неправильно].

Чтобы немного упростить задачу, я объединил две последние категории в одну. Итак, есть две категории, Good и Bad, на основании того, правильно ли кто-то носит свою маску:

1. Good.2. Bad.
  1. face_mask.data: создайте файл _.data, который содержит релевантную информацию о нашей задаче, с ним будет работать программа:

classes = 2train = data/train.txtvalid  = data/test.txtnames = data/face_mask.namesbackup = backup/

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

3. face_mask.cfg: Этот конфигурационный файл должен быть адаптирован к нашей задаче, а именно нам нужно скопировать yolov3.cfg переименовать его в _.cfg и изменить код, как описано ниже:

  • строку batch на batch=64;

  • строку subdivisions на subdivisions=16. В случае проблемы с памятью увеличьте это значение до 32 или 64;

  • входные размеры на стандартные: width=416, height=416;

  • строку max_batches на (#classes * 2000), это даст 4000 итераций для нашей задачи.

Я начал с разрешения 416x416 и обучил свою модель на 4000 итераций, но, чтобы достичь большей точности, увеличил разрешение и продлил обучение ещё на 3000 итераций. Если у вас есть только одна категория, вы не должны тренировать свою модель только до 2000 итераций. Предполагается, что 4000 итераций это минимум.

  • измените строку steps на 80% и 90% max_batches. В нашем случае 80/100 * 4000 = 3200, 90 / 100 * 4000 = 3600;

  • нажмите Ctrl+F и найдите слово "yolo". Поиск приведёт прямо к yolo_layers, где вы измените количество классов (в нашем случае classes=2) и количество фильтров. Переменная filters это вторая переменная выше строки [yolo].

    Строка должна стать такой: filters=(classes + 5) * 3, в нашем случае это filters = (2 + 5) * 3 = 21. В файле .cfg есть 3 слоя yolo_layers, поэтому упомянутые выше изменения нужно выполнить трижды.

4. Файлы train.txt и test.txt: Эти два файла были включены в файл face_mask.data и указывают абсолютный путь каждого изображения к модели. Например, фрагмент моего файла train.txt выглядит так:

/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss734.png/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss735.png/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss736.png/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss737.png/content/gdrive/MyDrive/face_mask_detection/mask_yolo_train/maksssksksss738.png...

Как я уже говорил, файлы .png должны располагаться в одной папке с соответствующими текстовыми аннотациями.

Это означает, что проект структурирован так:

MyDrivedarknet      ...      backup      ...      cfg            face_mask.cfg      ...      data            face_mask.data            face_mask.names            train.txt            test.txtface_mask_detection      annotations       (contains original .xml files)      images            (contains the original .png images)      mask_yolo_test    (contains .png % .txt files for testing)      mask_yolo_train   (contains .png % .txt files for training)       show_bb.py       xml_to_yolo.py

Начало обучения

После компиляции модели нужно изменить права на папку darknet, вот так:

!chmod +x ./darknet

И начинаем тренировать модель, запустив такую команду:

!./darknet detector train data/face_mask.data cfg/face_mask.cfg backup/face_mask_last.weights -dont_show -i 0 -map

Прописываем флаг -map, чтобы в консоль выводились важные показатели, такие как average Loss, Precision, Recall, AveragePrecision (AP), meanAveragePrecsion (mAP) и т. д.

Однако индикатор mAP в консоли считается лучшей метрикой, чем Loss, поэтому обучайте модель до тех пор, пока mAP возрастает.

В зависимости от различных параметров обучение может занимать часы, это нормально. Мне понадобилось около 15 часов, но первые впечатления от модели я получил примерно после 7 часов, то есть 4000 итераций.

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

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

!./darknet detector test data/face_mask.data cfg/face_mask.cfg backup/face_mask_best.weights

Вы заметили, что мы использовали face_mask_best.weights, а не face_mask_final.weights? К счастью, наша модель сохраняет лучшие веса (mAP достиг 87,16 %) в папке резервного копирования на случай, если мы тренируем её на большем количестве эпох, чем следовало бы (что, возможно, приведёт к переобучению).

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

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

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

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

Я заметил, что относительно стоящего впереди человека модель не столь уверена (38 % в чёткой области) в сравнении с прогнозом для человека сразу за ним (100 % в размытой области). Это может быть связано с качеством обучающего набора данных, таким образом, модель, по-видимому, в определённой степени подвержена влиянию (по крайней мере она не является неточной).

Один последний тест

Конечно, большое преимущество Yolo её скорость. Поэтому я хочу показать вам, как она работает с видео:

!./darknet detector demo data/face_mask.data cfg/face_mask.cfg backup/face_mask_best.weights -dont_show vid1.mp4 -i 0 -out_filename res1.avi

Оптимизировано для HabraStorage, gif с потерями.Оптимизировано для HabraStorage, gif с потерями.

Это был мой первый пошаговый туториал о том, как сделать собственный детектор с помощью YOLOv3 на пользовательском наборе данных. Надеюсь, он был вам полезен. А если хотите научиться создавать собственные нейронные сети и решать задачи с помощью глубокого обучения обратите внимание на курс Machine Learning и Deep Learning.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

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

13.01.2021 14:06:06 | Автор: admin

Приветствую читатель. Когда я был ребёнком и учился в школе, моим любимым предметом была математика, любимым предметом она была из-за того, что я очень люблю решать задачи, в какой-то момент своей жизни я начал составлять сам для себя заведомо нерешаемые задачи и пытался их решить, по полной напрягая свой разум в продумывании подхода для решения нерешаемой задачи, иногда оказывалось, что нерешаемая задача только казалась таковой из-за упущения некоторых неочевидных моментов. Любовь к решению задач сильно повлияла на меня, из-за чего я у себя в голове постоянно решаю какие-либо задачи, не только математические, но и из других сфер. За жизнь у меня накопилось множество идей (решений), от 3d принтера печатающего сталью до способа решения проблемы утилизации радиоактивных отходов атомных электростанций. Наверняка многие идеи на самом деле не реализуемы, по тем или иным причинам, а некоторые наверняка были придуманы до меня, а я просто о них не знал (так уже бывало). В прошлой моей статье я упомянул (сам не знаю зачем) о том, что я придумал новый вид чисел с помощью которых можно обучать нейронные сети. Я хотел открыть сервис по обучению нейронных сетей с помощью этих чисел, но с учётом пандемии и моего плохого состояния здоровья, я подумал, что вдруг я реально первый кто додумался до этих чисел и будет крайне плохо если я умру и знания о этих числах уйдут со мной. Поэтому я и решил написать эту статью, в которой расскажу подробно о этих числах и как их использовать для обучения нейронных сетей. Сразу скажу, что я не прорабатывал все необходимые формулы для работы с такими числами, так как был занят своим языком программирования, это лишь идея, а не готовая реализация.

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

Предположим нужно обучить feedforward нейронную сеть имея некую обучающую выборку, в которой есть примеры того, что подаётся на вход нейронной сети и что ожидается получить на выходе. Для такого случая можно написать функцию, назовём её fitness (как в генетическом алгоритме), на вход такой функции даётся нейронная сеть и обучающая выборка, а функция возвращает число от 0 до 1, число соответствует тому, на сколько данная нейронная сеть обучена данной выборкой, где 0 - максимально не обучена, 1 - идеально обучена. Используя такую fitness функцию, нейронную сеть можно представить как математическую функцию у которой аргументы - это веса нейронной сети, а результатом является результат fitness функции применяемой к нейронной сети с данными весами и обучающей выборкой. Я начал размышлять "как найти максимум такой функции?". В свой голове я представил 3х мерный график функции с 2 аргументами и подумал, что если добавить условие что каждый вес будет ограничен каким либо конечным диапазоном возможных значений, то можно разделить этот график на две части, в одной части графика первый аргумент имеет одни значения из своего возможного диапазона, а вторая часть графика имеет все оставшиеся значения аргумента, затем проанализировать в какой части максимум больше, взять эту часть и делить её таким же образом, но уже опираясь на другой аргумент, после чего полученную в результате второго деления часть, снова нужно разделить на две части опираясь на первый аргумент. Такое деление на части нужно производить до тех пор, пока значения результата функции на полученном от делении участке, будут иметь слишком большие колебания. Любые аргументы из полученной части графика и являются подходящими весами. Для лучшего понимания, поясню вышесказанное на примере.

График функции y = sin xГрафик функции y = sin x

Предположим есть функция y(x) = sin x, x принадлежит множеству [-4, 4], нужно найти максимум, или значение очень близкое к максимуму, данной функции на данном участке. Разделим график на 2 части, в одной части x принадлежит множеству [-4, 0], во второй части x принадлежит множеству [0, 4], как видно на графике максимум находится во второй части, затем делим вторую часть на две части [0, 2] и [2, 4]. В какой-то момент в результате деления получится часть, в которых минимальное и максимальное значение функции очень близко к 1, например [pi * 999999 / 2000000, pi / 2], из этой части любой x будет приемлемым решением. Теперь о том как вышесказанное реализовать на практике. Однажды я сидел и смотрел в интернете научно-популярное видео, касалось оно космоса и в нём вскользь мелькнула информация о суперпозиции, в этот момент я подумал о числах которые не занимают определённую позицию, а находятся во многих местах одновременно. Например представить множество [0, 1], не как множество, а как число одновременно имеющее значение всех чисел из множества. Назовём такие числа "суперпозиционными". Результатом применения любой унарной операции к суперпозиционному числу, это суперпозиционное число, которое охватывает множество чисел, которые могут быть получены в результате применения унарной операции к любому числу из множества, которое охватывает исходное суперпозиционное число. Например: sin([-pi, pi]) = [-1, 1]. Результатом применения любой бинарной операции над суперпозиционными числами, это суперпозиционное число, которое охватывает множество чисел, которые могут быть получены в результате применения бинарной операции к двум любым числам из множеств, которые охватывают исходные суперпозиционные числа. Например: [-3, 6] - [-12, 7] = [-10, 18]. Вещественные числа так же можно представить в виде суперпозиционных как множество с одним числом, например [3, 3]. Что дают такие числа? Если есть некая функция с одним или несколькими неизвестными аргументами, но при этом значения этих аргументов ограничены каким либо конечным множество возможных значений, то подставив в качестве таких аргументов суперпозиционные числа, охватывающие собой множества возможных аргументов, можно узнать диапазон возможных значений функции. Вот как я придумал обучать простые feedforward нейронные сети:

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

  • создать fintess функцию которая принимает указную нейронную сеть и обучающую выборку

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

  • выше указанные действия повторяются для каждого веса

  • выше указанные действия повторяются до тех пор, пока границы всех множеств суперпозиционных чисел в весах отличаются на определённое значение

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

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

Хотя как идея, суперпозиционные числа олицетворяют собой числа которые находятся одновременно в нескольких местах, по факту вышеуказанные алгоритм использует такие числа как олицетворение вещественного числа находящегося в неком промежутке, а в результате получаем промежуток в котором может быть результат действий производимого над числами. Одна из проблем такого подхода в том, что совершенно не учитываются вероятность получения каких либо значений в диапазоне чисел. Например: если есть некое число принадлежащее множеству [1, 2] и есть второе число принадлежащее множеству [4, 5], то если сложить два этих числа, получится число принадлежащее множеству [5, 7], но число 5 можно получить только если сложить 1 и 4, а для числа 6 существует бесконечное количество пар чисел, поэтому вероятность получения числа 6 выше, чем вероятность получения числа 5. Поэтому мною были придуман новый вид чисел, с помощью которых можно учитывать вероятности. Разумеется если взять некое бесконечное множество и некое неизвестное число гарантированно принадлежащее данному множеству, то вероятность того что это число будет равным некому заранее известному числу из этого множества, стремится к 0, поскольку множество бесконечно. Но вот что я придумал сделать, предположим есть множество [a, b] и есть некое случайное число x принадлежащее множеству, предположим что если разделить множество на n равных частей, то x имеет некую вероятность принадлежать одной из частей, для каждой части вероятность может быть различной. Предположим что есть функция f получающая номер части и возвращающая вероятность (от 0 до 1) того, принадлежит ли этой части число x. На основе функции f можно создать функцию f1, f1(x) = f(x) * n. При стремлении n к бесконечности, функция f1 возвращает значение, с помощью которого можно оценить вероятность того, что x находится в окрестностях некого числа. Если f1 возвращает значение 0, то считается, что вероятность существует, но она минимально возможная, а чем выше число, тем выше вероятность. Для создания нового вида чисел, я взял суперпозиционные числа и добавил к ним функцию f1. Характеристику числа, которую описывает функция f1 я изначально назвал концентрацией, но уже после того как я всё придумал, мне в подписки на youtube пришло два видео (первое, второе), в которых рассказывается о вероятности вероятностей и там такую характеристику называли плотностью, поэтому и я тоже буду называть её плотностью, а сами числа - плотностными. Для удобства в дальнейшем областью определения (какому множеству принадлежит x) функции в плотностных числах, будет множество [0, 1], а для получения плотности в конкретной точке, значение получаемое из функции, будет масштабироваться.

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

  • создать нейронную сеть в которой все веса - плотностные числа с множеством, которое ограниченно минимальным и максимальным значением веса и с функцией y(x) = 1

  • создать fintess функцию которая принимает указную нейронную сеть и обучающую выборку

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

  • выше указанные действия повторяются для каждого веса

  • выше указанные действия повторяются до тех пор, пока границы всех множеств плотностных чисел в весах отличаются на определённое значение

  • заменить каждый вес на любое вещественное число из множества охватываемого плотностным числом из соответствующего веса

  • заменить один вес на плотностное число, охватывающее множество ограниченное минимальным и максимальным значением веса

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

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

  • заменить вес на любое вещественное число из множества охватываемого плотностным числом из веса

  • повторить 4 последних действия над каждым весом

  • если в результате последних 5-ти действий нейронная сеть стала более обученной, то повторить эти 5 действий ещё раз

Хотя и в суперпозиционных числах, и в плотностных числах может быть сколь угодно много отрезков возможных чисел (например: суперпозиционное число состоящее из 3 отрезков ([0, 1], [20, 40], [100, 101]) ), поскольку я эти числа использую только в контексте обучения нейронных сетей, то всё что я говорю ниже, относиться к плотностным числам которые состоят из 1 отрезка ограниченного 2-мя вещественными числами. В начале этой статьи я писал о том, что я не проработал все необходимые формулы и вот о каких формулах идёт речь. Если взять два плотностных числа [1, 2, y(x) = 1], [4, 5, y(x) = 1], то при сложении этих чисел получается число [5, 7, y(x) = 1 - |0.5 - x| * 2]. Откуда взялась функция y(x) = 1 - |0.5 - x| * 2? Я её вывел вручную и возможно она не верна, но даже если я не прав с формулой, то в любом случае при сложении двух плотностных чисел, в результирующем числе будет функция которая использует функции из слагаемых. Если в качестве функции активации нейронов использовать функцию y(x) = x, то даже в таком случае для обучения моим алгоритмом, нужно знать как складывать и перемножать 2 плотностных числа. С учётом того, что в результате сложения или перемножения двух плотностных чисел, формулы из 2-х исходных чисел будут храниться и в результирующем числе, то при увеличении количества нейронов и слоёв в сети, количество времени и памяти для обучения растёт экспоненциально. Однако есть одно но, для того чтобы узнать какая функция будет в результате сложения, необходимо знать только функции из слагаемых, т.е. в выражении [a1, b1, y1(x) = f1(x)] + [a2, b2, y2(x) = f2(x)] = [a3, b3, y3(x) = f3(x)], f3 не зависит от a1, b1, a2, b2, а зависит только от f1 и f2, если этим свойством обладает и произведение, то есть несколько оптимизаций с помощью которых можно значительно сократить скорость роста потребления памяти и времени во время обучения. Поскольку статья получилась и так не маленькой, а так же поскольку формулы для сложения и произведения пока не известны, рассказ об оптимизациях отложу на потом.

P. S. Достаточно быстро я остановил разработку компилятора для моего языка, о котором была моя прошлая статья. Кратко о том по чему я это сделал - https://cine-lang.blogspot.com/2020/02/blog-post.html. После прекращения разработки, я начал разработку нового языка, у которого отсутствуют недостатки предыдущего. На момент написания статьи я реализовал новый язык, компилятор и стандартную библиотеку на 80%. Мне очень хочется закончить новый вариант своего языка программирования, а так же реализовать хотя бы, несколько из своих идей (когда закончу ЯП, хочу попробовать реализовать свой алгоритм сжатия изображений, аудио и видео). Но в последнее время жизнь складывается не очень (как в прочем и у многих), из-за совокупности нескольких факторов (включая пандемию), я потерял работу и если осенью я хотя бы грибами питался, то сейчас иногда приходиться и ролтоны есть. Из за того, что в данный момент я много времени трачу на поиск работы (подработок) и иногда еды, а так же из-за психологического давления того факта, что у меня нет средств к существованию, разработка языка продвигается медленно. Если я бы смог фокусироваться на разработке, то компилятор и стандартную библиотеку я закончил бы, скорее всего к концу февраля. Поскольку моя обычная зарплата не превышает 200 долларов США в месяц, даже незначительная финансовая помощь поможет мне значительно. По этому, прошу помощи у всех не равнодушных (на хабре есть кнопка "Задонатить"). Благодарю всех откликнувшихся.

Подробнее..

Рекомендации Друзей ВКонтакте ML на эго-графах

13.04.2021 14:10:30 | Автор: admin

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

Меня зовут Женя Замятин, я работаю в команде Core ML ВКонтакте. Хочу рассказать, как устроены рекомендации, которые делают ближе пользователей самой крупной социальной сети рунета.

Обзор

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

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

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

Ещё один важный метод рекомендаций Adamic/Adar. В его основе лежит всё тот же анализ общих друзей, но с модификацией: авторы предлагают учитывать число друзей у общего друга. Чем больше это значение, тем меньше информации о релевантности он несёт.

Кроме методов на основе анализа общих друзей, довольно распространены рекомендации на базе эмбеддингов. В Лаборатории искусственного интеллекта ВКонтакте в МФТИ мы провели исследование: сравнили эффективность разных подходов к задаче предсказания дружб в VK. Результаты совпали с нашим опытом решения на базе графовых эмбеддингов у нас работают плохо. Учитывая это, мы стали развивать систему отбора кандидатов по пути анализа общих друзей.

EGOML

Общая схема нашего метода продолжает идеи числа общих друзей и Adamic/Adar. Финальная мера релевантности E(u, v), с помощью которой мы будем отбирать кандидатов, всё так же раскладывается в сумму по общим друзьям u и v. Ключевое отличие в форме слагаемого под суммой: в нашем случае это мера ez_c(u, v).

Сначала попробуем понять физический смысл меры ez_c(u, v). Представим, что мы взяли пользователя c и спросили у него: Насколько вероятно, что два твоих друга, u и v, подружатся? Чем больше информации для оценки он учтёт, тем точнее будет его предсказание. Например, если c сможет вспомнить только число своих друзей, его рассуждения могут выглядеть следующим образом: Чем больше у меня друзей, тем менее вероятно, что случайные двое из них знакомы. Тогда оценка вероятность дружбы u и v (с точки зрения c) может выглядеть как 1/log(n), где n число друзей. Именно так устроен Adamic/Adar. Но что если c возьмёт больше контекста?

Прежде чем отвечать на этот вопрос, разберёмся, почему ez_c(u, v) важно определять через пользователя c. Дело в том, что в таком виде очень удобно решать задачу распределённо. Представим, что теперь мы разослали всем пользователям платформы анкету с просьбой оценить вероятность дружбы в каждой паре их друзей. Получив все ответы, мы можем подставить значения в формулу E(u, v). Именно так выглядит вычисление E(u, v) с помощью MapReduce:

  • Подготовка. Для каждого c выделяется тот контекст, который он будет учитывать для вынесения оценок. Например, в Adamic/Adar это будет просто список друзей.

  • Map. Спрашиваем у каждого c, что он думает про возможность дружбы в каждой паре его друзей. По сути, вычисляем ez_c(u, v) и сохраняем в виде (u, v) ez_c(u, v) для всех u, v in N(c). В случае Adamic/Adar: (u, v) 1/log|N(c)|.

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

Таким образом мы получаем все ненулевые значения E(u, v). Заметим: необходимое условие того, что E(u, v) > 0, существование хотя бы одного общего друга у u и v.

Эго-граф ХоппераЭго-граф Хоппера

Контекстом пользователя c в случае меры ez_c будет тот же список друзей, но дополненный информацией о связях внутри этого списка. Такую структуру в науке называют эго-графом. Если более формально, эго-граф вершины x это такой подграф исходного графа, вершинами которого являются все соседи x и сама x, а рёбрами все рёбра исходного графа между этими вершинами. Коллеги из Одноклассников написали подробную статью об эго-графах и затронули в ней вопрос их эффективного построения.

Ключевая идея меры ez_c в том, что её можно сделать обучаемой. Для каждого пользователя c, его эго-графа и всех пар пользователей u, v внутри него мы можем посчитать много разных признаков, например:

  • число общих друзей u и v внутри эго-графа c;

  • число общих друзей u и c;

  • интенсивность взаимодействий между v и c;

  • время, прошедшее с последней дружбы между u и кем-либо из эго-графа c;

  • плотность эго-графа c;

  • и другие.

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

В конечном счёте мы получаем датасет следующего вида:

  • для каждой пары пользователей u и v, а также их общего друга c, посчитаны признаки по эго-графу c;

  • пара пользователей u и v встречается в датасете ровно столько раз, сколько у них общих друзей;

  • все пары пользователей в датасете не являются друзьями на момент времени T;

  • для каждой пары u и v проставлена метка подружились ли они в течение определённого промежутка времени начиная с T.

По такому датасету мы и будем обучать нашу меру ez_c. В качестве модели выбрали градиентный бустинг с pairwise функцией потерь, где идентификатором группы выступает пользователь u.
По сути, мера ez_c(u, v) определяется как предсказание описанной выше модели. Но есть один нюанс: при pairwise-обучении распределение предсказаний модели похоже на нормальное. Поэтому, если в качестве определения меры ez_c(u, v) взять сырое предсказание, может возникнуть ситуация, когда мы будем штрафовать финальную меру E(u, v) за общих друзей, так как значения предсказаний бывают отрицательными. Это выглядит не совсем логично хочется, чтобы с ростом числа общих друзей мера E(u, v) не убывала. Так что поверх предсказания модели мы решили взять экспоненту:

Такой подход хорошо себя показывает на небольших графах. Но чтобы применить его на реальных данных, необходимо выполнить ещё одно действие. Суть проблемы такая: мы не можем вычислять признаки и применять модель для каждой пары пользователей всех эго-графов это слишком долго. Для решения мы придумали специальный трюк. Представим, что наш градиентный бустинг обучился таким образом, что каждое дерево использует признаки только одного пользователя: либо u, либо v. Тогда мы могли бы разделить весь ансамбль на две группы: к группе A мы бы отнесли деревья, которые используют только признаки пользователя u, к B пользователя v. Предсказание такой модели можно представить в виде:

Имея такую модель, мы могли бы получить предсказания для всех пар пользователей одного эго-графа быстрее. Достаточно применить модели A и B для каждого пользователя, а затем сложить соответствующие парам предсказания. Таким образом, для эго-графа из n вершин мы могли бы сократить число применений модели с O(n^2) до O(n). Но как получить такую модель, каждое дерево которой зависит только от одного пользователя? Для этого сделаем следующее:

  1. Исключим из датасета все признаки, которые одновременно зависят и от u и от v. Например, от признака число общих друзей u и v внутри эго-графа c придётся отказаться.

  2. Обучим модель A, используя только признаки на базе u, c и эго-графа c.

  3. Для обучения модели B оставим только признаки на базе v, c и эго-графа c. Также в качестве базовых предсказаний передадим предсказания модели A.

Если объединим модели A и B, получим то что нужно: первая часть использует признаки u, вторая признаки v. Совокупность моделей осмысленна, поскольку B была обучена корректировать предсказания A. Эта оптимизация позволяет ускорить вычисления в сотни раз и делает подход применимым на практике. Финальный вид ez_c(u, v) и E(u, v) выглядит так:

Вычисление меры E в онлайне

Заметим, что E(u, v) можно представить в виде:

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

При построении рекомендаций мы уже вычислили предсказания моделей для всех существующих дружб. Поэтому для каждого пользователя мы можем собрать векторы и сложить их в доступное онлайн key-value хранилище. После этого сможем получать значение E(u, v) для любой пары пользователей в онлайне простой операцией перемножения векторов. Это даёт возможность использовать E(u, v) как лёгкую функцию релевантности в нагруженных местах либо как дополнительный признак финальной модели ранжирования.

Итог

В результате система EGOML позволяет:

  1. Распределённо отбирать кандидатов для каждого пользователя в офлайне. Асимптотическая сложность оптимизированного алгоритма составляет O(|E|) вычислений признаков и применений модели, где |E| число связей в графе. На кластере из 250 воркеров время работы алгоритма составляет около двух часов.

  2. Быстро вычислять меру релевантности E(u, v) для любой пары пользователей в онлайне. Асимптотическая сложность операции O(|N(u)| + |N(v)|).

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

В конечном счёте мы перешли со способа отбора кандидатов с использованием Adamic/Adar к системе EGOML и внедрили в модель второй уровень признаков на основе меры E(u, v). И это позволило увеличить количество подтверждённых дружб со всей платформы на несколько десятков процентов.

Благодарность

Хочу сказать спасибо руководителю команды Core ML Андрею Якушеву за помощь в разработке метода и подготовке статьи, а также всей команде Core ML за поддержку на разных этапах этой работы.

Подробнее..

И чат, и бот, и логопед. Как разработать сервис на основе ML для диагностики дефектов речи у детей

30.04.2021 14:04:59 | Автор: admin

Шла Саша по шоссе, Скажи: рррррыба, Кукушка кукушонку... знаете, да, эти фразы, травмировавшие нас всех в детстве? Это был какой-то бесконечный эксперимент родителей с неоформленным желаемым результатом и главное страхами, что их ребенок растет с дефектом речи.

Привет! Меня зовут Дима Пухов, я технический директор в Cleverbots. Хочу рассказать, как мы учили чат-бот распознавать дефекты речи и добились 80% точности в диагнозах ИИ-логопеда.

Проблема

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

В прошлом году фармкомпания Герофарм, чтобы бороться со страхами и стереотипами о когнитивном развитии, запустила портал ПРО.МОЗГ, на котором можно прочитать очень много полезных и доступных материалов о том, как работает мозг, как действуют болезни и меняется организм. Кроме того, на сайте есть спектрограмма сервис, который помогает родителям в игровом формате протестировать ребенка и определить, есть ли у него дефекты речи.

Коротко о работе сервиса

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

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

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

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

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

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

Теперь самое интересное как мы это реализовывали

Существует много идей и технологий которые можно попробовать для решения:

  • хорошо зарекомендовали себя в голосовых задачах частотные преобразования, например мел-кепстральные коэффициенты (MFCC). Их можно использовать на этапе feature engineering;

  • использовать статистические признаки;

  • Deep Learning, а именно технологии вроде тех, что используются в speech2text. С ними удобно работать, а решения получаются более универсальными, но может потребоваться большее количество данных, чем их есть на самом деле;

  • использовать уже готовые облачные решения для косвенной оценки наличия дефекта. Например, решения от Yandex, Google, AWS для того, чтобы решить задачу speech2text и получить текст, который в дальнейшем будем сравнивать с тем, что должен был произнести ребенок.

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

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

  1. Пустая аудиозапись;

  2. На аудиозаписи нет голоса ребенка (только голос взрослого человека);

  3. Ребенок произнес фразу с дефектом;

  4. Ребенок произнес фразу без дефекта.

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

Ход решения

Предварительная обработка

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

На следующем шаге для сэмплов подсчитали несколько статистических параметров (например, доля пауз и их длительность) и затем применили метод 3Sigma на преобразованных методом Бокса-Кокса распределениях для выявления аномалий и их последующего исключения из датасета. Это позволило нам избавиться от, например, очень коротких/длинных аудиозаписей.

Теперь можно было двигаться дальше.

Определение забракованных аудиозаписей

Для этого из разделов spectral & rhythm features библиотеки librosa мы выбрали ряд переменных, по которым посчитали расширенную статистику. Применение PCA сократило число переменных до адекватных значений, и простая логистическая регрессия показа 0.99 по ROC_AUC.

Определение дефекта речи

Так как пользователи должны произносить заранее определенный текст, решили испытать speech2text. Мы попробовали использовать сервисы для распознавания речи: Yandex, Google, Amazon. Гипотеза, которую мы проверяли, была достаточно простой: если нет дефекта речи, то на выходе из speech2text сервиса получим как раз ту фразу, которую мы просили произнести нашего пользователя.

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

В качестве бейзлана был выбран следующий подход все переменные spectral & rhythm features из библиотеки librosa, tsfresh для калькуляции статистики и PCA (так как переменных получилось очень много). Результат оказался достаточно неплохим: ROC_AUC показал 0.85, но мы решили идти дальше ближе к тому процессу, что проводит логопед при анализе того или иного сэмпла.

Сначала необходимо перейти от общей модели к ряду моделей по каждому таску (пока их 52 штуки; <100 сэмплов в каждой). Так мы хотя бы немного упростим задачу моделирования алгоритм быстрее отберет ряд наиболее важных переменных, представляющих наличие дефекта.

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

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

Как мы реализовали это в production

Технологический стэк

  • Предобученная модель и ее интерфейс в Python;

  • Kafka брокер сообщений;

  • Django для кабинета специалиста.

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

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

Что получилось

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

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

В дальнейшем планируется полный переход от модели human-in-the-loop, когда необходимо участие человека в диагностике, к полной автоматизации процесса благодаря дообученной модели.

Подробнее..

Категории

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

  • Имя: Макс
    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