Почему супер-мега-про машинного обучения за 15 минут всё же не стать

  • Tutorial
Вчера я опубликовал статью про машинное обучение и NVIDIA DIGITS. Как и обещал, сегодняшняя статья — почему всё не так уж и хорошо + пример выделения объектов в кадре на DIGITS.

NVIDIA подняла волну пиара по поводу разработанной и имплиментированной в DIGITS сетки DetectNet. Сетка позиционируется как решение для поиска одинаковых/похожих объектов на изображении.



Что это такое


В начале года я несколько раз упоминал про забавную сетку Yolo. В целом, весь народ, с которым я общался, отнеслись к ней скорее негативно, со словами, что Faster-RCNN куда быстрее и проще. Но, инженеры NVIDIA ею вдохновились и собрали свою сетку на Caffe, назвав её DetectNet.
Принцип сетки такой же как и в Yolo. Выходом сети для изображения (N*a*N*a) является массив N*N*5, в котором для каждого региона исходного изображения размером a*a вводиться 5 параметров: наличие объекта и его размер:

image

Плюс сетки:

  • Быстро считает. У меня получалось по 10-20ms на кадр. В то время, когда Faster-RCNN тратил по 100-150.
  • Просто обучается и настраивается. С Faster-RCNN нужно было долго возиться.

Минус один: есть решения с более качественным детектированием.

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


В отличие от распознавания категорий, про которое я писал вчера, детектирование объектов сделано плохо. Не user friendly. Большая часть статьи будет на тему того, как всё же это чудо запустить. К сожалению, такой подход убивает изначальную идею DIGITS, что можно сделать что-то не разбираясь в логике системы и её математике.
Но если всё же запустили — пользоваться удобно.

Что будем распознавать


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

Я решил воспользоваться частью наработок и подетектировать номера через DIGITS. Так что их-то и будем использовать.

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

Поехали


Выбрав в главном меню «New Dataset->Images->Object Detection» мы попадаем в меню создания датасета. Здесь нужно обязательно указать:

  • Training image folder — папку с изображениями
  • Training label folder — папку с текстовичками-подписями к изображениям
  • Validation image folder — папку с изображениями для проверки
  • Validation label folder — папку с текстовичками-подписями к ним
  • Pad image — Если изображение меньше указанного тут, то оно будет дополнено чёрным фоном. Если больше — создание базы упадёт ¯ \ _ (ツ) _ / ¯
  • Resize image — к какому размеру ресайзнуть изображение
  • Minimum box size — лучше всего установить это значение. Это минимальный размер объекта при валидации

Тут есть сложность. Как делать текстовик-подпись к изображению с его описанием? Пример на ГитХабе от NVIDIA в официальном репозитории DIGITS скромно об этом умалчивает, упоминая лишь, что он такой же, как в датасете kitti. Меня несколько удивил такой подход к пользователям готового из коробки фреймворка. Но ок. Пошёл, скачал базу и доки к ней, прочитал. Формат файла:

Car 0.00 0 1.95 96.59 181.90 405.06 371.40 1.52 1.61 3.59 -3.49 1.62 7.68 1.53
Car 0.00 0 1.24 730.55 186.66 1028.77 371.36 1.51 1.65 4.28 2.61 1.69 8.27 1.53
Car 0.00 0 1.77 401.35 177.13 508.22 249.68 1.48 1.64 3.95 -3.52 1.59 16.82 1.57

Описание файла:

#Values    Name      Description
----------------------------------------------------------------------------
   1    type         Describes the type of object: 'Car', 'Van', 'Truck',
                     'Pedestrian', 'Person_sitting', 'Cyclist', 'Tram',
                     'Misc' or 'DontCare'
   1    truncated    Float from 0 (non-truncated) to 1 (truncated), where
                     truncated refers to the object leaving image boundaries
   1    occluded     Integer (0,1,2,3) indicating occlusion state:
                     0 = fully visible, 1 = partly occluded
                     2 = largely occluded, 3 = unknown
   1    alpha        Observation angle of object, ranging [-pi..pi]
   4    bbox         2D bounding box of object in the image (0-based index):
                     contains left, top, right, bottom pixel coordinates
   3    dimensions   3D object dimensions: height, width, length (in meters)
   3    location     3D object location x,y,z in camera coordinates (in meters)
   1    rotation_y   Rotation ry around Y-axis in camera coordinates [-pi..pi]
   1    score        Only for results: Float, indicating confidence in
                     detection, needed for p/r curves, higher is better.

Естественно, большая часть параметров тут не нужна. Реально можно оставить только параметр «bbox», остальное всё равно не будет использоваться.

Как выяснилось позже, для DIGITS был ещё второй тьюториал, где формат файла всё же подписывался. Но был он не в репозитории DIGITS ¯ \ _ (ツ) _ / ¯

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

image

Начинаем обучать


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

  • Subtract Mean в None
  • base learning rate в 0.0001
  • ADAM solver
  • Выбрать вашу базу
  • Выбрать вкладку «Custom Network». Скопировать в неё текст из файла "/caffe-caffe-0.15/examples/kitti/detectnet_network.prototxt" (это в форке caffe от nvidia, понятно).
  • Так же, рекомендуется скачать предварительно натренированную модель GoogleNet вот тут. Указать её в «Pretrained model(s)»

Так же, я сделал следующее. Для скопированной сетки «detectnet_network.prototxt» все значения размера изображения «1248, 352» я заменил на размеры изображений из своей базы. Без этого обучение падало. Ну, естественно, ни в одном тьюторивале этого нет… ¯ \ _ (ツ) _ / ¯

График Loss падает, обучение пошло. Но… График точности стоит на нуле. Что такое?!
Ни один из двух тьюториалов которые я нашел не отвечал на этот вопрос. Пошёл копаться в описание сетки. Где копаться, было понятно сразу. Раз падают loss — обучение идёт. Ошибка в validation пайплайне. И действительно. В конфигурации сети есть блок:

layer {
  name: "cluster"
  type: "Python"
  bottom: "coverage"
  bottom: "bboxes"
  top: "bbox-list"
  python_param {
    module: "caffe.layers.detectnet.clustering"
    layer: "ClusterDetections"
    param_str: "1024, 640, 16, 0.05, 1, 0.02, 5, 1"
  }
}

Выглядит подозрительно. Открыв описание слоя clustering можно найти комментарий:

# parameters - img_size_x, img_size_y, stride,
# gridbox_cvg_threshold,gridbox_rect_threshold,gridbox_rect_eps,min_height,num_classes

Становится понятно, что это пороги. Зарандомил там 3 числа не вникая в суть. Обучение пошло + начал расти validation. Часов за 5 достиг каких-то разумных порогов.



Но вот облом. При успешном обучении 100% картинок не распонзавалось. Пришлось копаться и разбираться, что этот слой значит.

Слой реализует сбор полученных гипотез в единое решение. Как основной инструмент тут применяется OpenCV модуль «cv.groupRectangles». Это функция, которая ассоциирует группы прямоугольников в один прямоугольник. Как вы помните, у сети такая структура, что в окрестности объекта — должно быть много срабатываний. Их нужно собрать в единое решение. У алгоритма сбора есть куча параметров.

  • gridbox_cvg_threshold (0.05) — порог детектирования объекта. По сути достоверность того, что мы нашли номер. Чем меньше — тем больше детекций.
  • gridbox_rect_threshold (1) — сколько детекторов должно сработать, чтобы было принято решение «есть номер»
  • gridbox_rect_eps (0.02) — во сколько раз могут отличаться размеры прямоугольников, чтобы объединить их в одну гипотезу
  • min_height — минимальная высота объекта

Теперь их достаточно просто подобрать, чтобы всё заработало. А теперь юмор. Таки был ещё и третий тьюториал, где часть всего этого дела описана.
Но не вся ¯ \ _ (ツ) _ / ¯

Что в итоге


В итоге можно посмотреть что сетка выделила:


Работает неплохо. На первый взгляд лучше, чем Хаар, который мы использовали. Но сразу стало понятно, что маленькая обучающая база (~1500 кадров) — даёт о себе знать. В базе не учли грязные номера => они не детектируются. В базе не учли сильную перспективу номера => они не детектируются. Не учли слишком крупные/слишком мелкие. Ну, вы поняли. Короче нужно не полениться и разметить тысяч 5 номеров нормально.

При распознавании можно посмотреть прикольные картинки с картами активации (1,2,3). Видно, что на каждом следующем уровне номер виден всё чётче и чётче.

Как запустить


Приятный момент — результат можно запустить кодом из ~20 строчек. И это будет готовый детектор номеров:

import numpy as np
import sys
caffe_root = '../'  # путь в корень каффе
sys.path.insert(0, caffe_root + 'python')
import caffe
caffe.set_mode_cpu() # Если на проце. Иначе:
#caffe.set_device(0)
#caffe.set_mode_gpu()
model_def = caffe_root + 'models/DetectNet/deploy.prototxt' #описание сети
model_weights = caffe_root + 'models/DetectNet/DetectNet.caffemodel' #веса сети
net = caffe.Net(model_def,      # defines the structure of the model
                model_weights,  # contains the trained weights
                caffe.TEST)     # use test mode (e.g., don't perform dropout)

#Как преобразовывать картинки перед отправкой в сеть
mean=np.array([128.0,128.0,128.0])
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
transformer.set_transpose('data', (2,0,1))  # move image channels to outermost dimension
transformer.set_mean('data', mean)            # subtract the dataset-mean value in each channel
transformer.set_raw_scale('data', 255)      # rescale from [0, 1] to [0, 255]
transformer.set_channel_swap('data', (2,1,0))  # swap channels from RGB to BGR
# Вход сети на всякий случай поставим корректный
net.blobs['data'].reshape(1,        # batch size
                          3,         # 3-channel (BGR) images
                          640, 1024)  # image size is 227x227
image = caffe.io.load_image('/media/anton/Bazes/ReInspect/CARS/test/0.jpg')# тестовое изображение загружаем
transformed_image = transformer.preprocess('data', image)# подготовим дял укладывания в сеть
output = net.forward() # распознаем
output_prob = output['bbox-list'][0] # массив результатов в формате нужном нам
print output_prob[0]

Вот тут вот я выложил деплой файл для сетки и веса обученой сети, если кому надо.
Recognitor
78.44
Computer Vision and Machine Learning
Share post

Comments 36

    0
    Передайте центру личностного и духовного развития, что силой мысли можно не только ложки гнуть, но и мягкие знаки правильно расставлять.
      0
      Спасибо за статью.
      Быстро считает. У меня получалось по 10-20ms на кадр. В то время, когда Faster-RCNN тратил по 100-150.
      А что за видеокарта?
        +1
        1080, так что да, это не очень репредентативно для простых устройств
          0
          Понятно, а более быстрые сети в природе существуют? Насколько реально получить такие скорости на видеокарте среднего уровня типа 950?
            +1

            Упомянуый в статье YOLO можно разогнать вплоть до 100fps.

              +1
              Я думаю, что на 950 максимум раза в 2 упадёт. А так должна работать.
                0
                Ну работать это само собой, просто интересно, насколько подобные сети сегодня шагнули вперед по скорости.

                Кстати, какая зависимость размера кадра и скорости? Уменьшения кадра и падения качества распознавания? Точных цифр не надо, достаточно одним словом охарактеризовать.
                  +1
                  Ну работать это само собой, просто интересно, насколько подобные сети сегодня шагнули вперед по скорости.

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

                    +1
                    В DetectNet вычислений на верхнем уровне практически нет, как я понимаю. А для свёрточной сети объём вычислений будет прямо пропорционален площади. Ускорения тут скорее от железа будут зависить. Вроде NVIDIA сделало нативную поддержку ядер 3*3. Но каких-то тестов не от NVIDIA не видел.
                  +2
                  Не могу не заметить, что в новый Titan X и Теслы подвезли INT8 инструкции оптимизированные для машинного обучения. Также хочу услышать комментарий автора, относительно них.
                    +1
                    На новых не работал. Видел тесты NVIDIA, что они просто разрывают всех по производительности. Но реально не читал про опыт использования. И сам не тестировал.
                      0
                      Тесл еще в продаже нет, только Титан(в P100 поддержки INT8 нет). Еще нагуглил, что Intel Xeon Phi уже поддерживают часть инструкций, запихивая по 8 штук за раз в AVX-512. Видимо будет отчаянная грызня до последней капли крови за нейросети.
                        0
                        То что я видел у интела до сих пор — было на редкость убого. Но по крайней мере они стали это направление направленно развивать — что, конечно, очень хорошо.
                  0
                  А ATI какие нибудь интересные решения для своих железок не предлагает? И у кого из этих производителей больше взаимопонимания с юзерами и разработчиками под GPU?
                    +3
                    У NVIDIA. Считайте что они захватили рынок. Нет, какая-то поддержка ATI есть. Но обычно кривая и бажная. В Theano вроде есть, в TensorFlow. Но ATI реагирует на желания пользователей лишь после того как на них прореагирует NVIDIA. Или даже позже.
                    А так, даже Intel прикрутил для своей IntelPHI какой-то форк Caffe. Но вот стоит ли этим пользоваться? Не думаю.
                      0
                      Спасибо за развернутый ответ. Стало быть, хоть жефорсовые флопсы и дороже радеоновских, зато это какие надо флопсы)
                      0
                      В Torch есть поддержка OpenCL, но оно работает заментно медленее чем CUDA/cuDNN.
                        0
                        спасибо, помогли дилетанту определиться с выбором)
                        0
                        AMD что-то не видно и не слышно. Топовое решение у них S9300 x2, судя по ттх FP32 считает как боженька, а FP64 плохо. Да и ориентируются, судя по сайту на облачные игры. Поддерживает только C++AMP и OpenCL.
                          +1

                          У AMD долгов столько, что некогда им заниматься венчурными вложениями в Deep Learning :(

                    +2
                    Быстро считает. У меня получалось по 10-20ms на кадр. В то время, когда Faster-RCNN тратил по 100-150.

                    Чет мне кажется, что Faster тоже полетит, если у него в качестве feature extractor будет использоваться GoogLeNet вместо VGG-16...

                      +1
                      Кстати, тут есть любопытный вопрос. Если YOLO заапдейтить верхушку вот таким образом (назвается SSD). То у него качество распознавания раза в 1.2 вырастает. Но этот апдейт, по сути, просто напросто Residual Connection.
                      Возникает логичный вопрос: где сетки для детектирования на базе полноценного ResNet??? Они должны хорошо работать. Почему никто на базе ResNet не пересобрал Faster-RCNN? Или всё это уже давно сделано в недрах крупных компаний, просто не публикуются результаты?

                      Чет мне кажется, что Faster тоже полетит, если у него в качестве feature extractor будет использоваться GoogLeNet вместо VGG-16...

                      Как я понял YOLO на VGG16 всё же даёт ~50 fps. По крайней мере вот эти ребята недавно эксперементировали вроде. А. Вы чуть выше это тоже упомянули. Но 100fps я не видел сам:)
                      так что не думаю, что Faster прямо таки в 10 раз ускориться. Но раза в 2 может.

                        +1
                        Топовый результат в object detection на ImageNet как раз продемонстрирован ResNet, воткнутым в Faster-RCNN
                          +1
                          А там опубликована статья? Ссылочкой не поделитесь? Я переодически табличку по VOC2012 отслеживаю — http://host.robots.ox.ac.uk:8080/leaderboard/displaylb.php?cls=mean&challengeid=11&compid=6&submid=8822#KEY_RRR-ResNet152-COCO-MultiScale
                          Но там по тем сеткам, где архитектура открыта она какая-то мутная. То марковские модели впилят, то какие-то карты фич полу-вручную разгребают.
                            +1

                            А чего там публиковать то? Ну воткнули и воткнули, в faster'е же feature extraction впиливается достаточно прямолинейно. А вообще вот, например, статейка про то, как чего-нибудь сильно кастомное приделать к faster'у: https://arxiv.org/pdf/1608.08021.pdf

                              0
                              В изначальной статье про ResNets это обсуждается в Appendix: https://arxiv.org/abs/1512.03385
                            +1
                            Как я понял YOLO на VGG16 всё же даёт ~50 fps. По крайней мере вот эти ребята недавно эксперементировали вроде. А. Вы чуть выше это тоже упомянули. Но 100fps я не видел сам:)

                            С VGG-16 у меня получалось ~90мс на кадр (на 980Ti), а вот при использовании упрощенных фич 100fps он действительно выдает, но детектирует совсем плохо.

                          0
                          Спасибо за обзоры, с помощью чего приводили базу к стандарту описания KITTY?
                            0
                            Ручками не то в C# не то в Python за 10 строчек.
                            0
                            А зачем вы удалили приложение по распознаванию номеров из App Store? Где его можно скачать?
                              0
                              То что там GoogLeNet используется несет какой то смысл или можно любую воткнуть?
                                0
                                Можно любую. Более того, DIGITS можно поверх любой более-менее адекватной современной версии caffe зацепить. Просто перед стартом указать переменную в папку где Caffe.
                                Конечно, после этого DetectNet и прочие свистелки-перделки перестают работать, но графички потерь и всё такое он кажет.
                                +1
                                Спасибо за статью. Я новичок в этой области, и в попытках повторить все шаги автора, наступил на некоторые грабли:
                                • Долго разбирался с форматом KITTY. Было не очевидно, что начало координат: левый верхний угол с нулевого индекса. Готовую базу можно посмотреть и проверить через KITTI Object development kit
                                • Для новых Pascal видеокарт необходима cuda 8, на 7.5 будет ошибка.

                                В итоге застрял на самом обучении. По разному изменял параметры сети, график остаётся на нуле. На моей GTX 1060 6Гб памяти, поэтому использовал Batch size 5 и Batch Accumulation 2. В параметрах ClusterDetections: 720, 480, 16, 0.05, 1, 0.02, 10, 1

                                При этом график обучения выглядит вот так
                                image

                                В какую сторону нужно копать, чтобы заставить его обучаться?
                                  +1
                                  Видите, у вас падают loss. Тобишь обучение всё-таки идёт.
                                  Нужно менять эти 4 параметра:
                                  gridbox_cvg_threshold,gridbox_rect_threshold,gridbox_rect_eps,min_height,num_classes
                                  А как — не знаю. Попробуйте порандомить или подумать какие параметры больше подходят под вашу задачу.
                                    +1
                                    Разобрался и заставил ползти график вверх. Похоже, дело было в грязной среде. Решилось всё тем, что я взял nvidia-docker, на нём запустил Digits 5.1, в котором обучение пошло. Стандартный docker образ от nvidia, содержащий digits, не подходит для новых видеокарт. (там cuda 7.5)

                                    Мои шаги для установки digits в docker, быть может кому-то пригодится
                                    Сначала нужно установить nvidia-docker по инструкции: https://github.com/NVIDIA/nvidia-docker/wiki/Installation

                                    Потом запустить контейнер nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04 командой:
                                    sudo nvidia-docker run --name digits -p 5000:5000 -v /media:/media -d -t -i nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04 /bin/bash
                                    


                                    Присоединиться к запущенному контейнеру:
                                    sudo nvidia-docker attach digits
                                    


                                    Внутри контейнера следовать инструкциям по установке caffe и digits:
                                    https://github.com/NVIDIA/DIGITS/blob/master/docs/BuildCaffe.md
                                    https://github.com/NVIDIA/DIGITS/blob/master/docs/BuildDigits.md

                                    В инструкциях указаны не все пакеты, при возникновение ошибок нужно смотреть и доустанавливать. История не сохранилась, из того что помню:
                                    sudo apt-get install python-setuptools python-bs4
                                    

                                    0
                                    На будущее. Вдруг кому-то понадобиться. Ещё точно такую же ошибку встретил, когда строки в файлах DIGITS разделял не как \n, а как \n\r (Windows-стандаром).
                                    Судя по всему сетка не может спарсить файлы, обучение падает но она об этом молчит.

                                  Only users with full accounts can post comments. Log in, please.