Чому супер-мега-про машинного навчання за 15 хвилин все ж не стати

Вчора я опублікував статті про машинне навчання і 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 to RGB 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]

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

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.