ML для мобільного розробника: Google Cloud для тренування ML-моделі
Цей текст буде корисний мобільним розробникам, які хочуть тренувати наявні
Чому обчислювальні можливості мого комп’ютера можуть не підійти для тренування ML-моделі?
Якщо ви хочете навчити персептрон, щоб він виконував операцію XOR, можна навчити таку нейромережу навіть на старенькому мобільному.
Але деякі рішення для розпізнавання образів потребують значної обчислювальної потужності. Наприклад, для тренування YOLO (алгоритму розпізнавання об’єктів і їхнього розташування на фото) потрібні тижні (якщо не місяці) тренування на досить потужному CPU. На топових GPU час тренування може зменшитися з декількох днів до кількох годин. Можна, звісно, витратити декілька тисяч доларів на останню модель Nvidia Tesla GPU, але якщо ви не працюєте із цим активно, то, імовірно, таке придбання буде марним. Окрім того, треба зазначити, що інколи щоб пришвидшити обчислення, їх треба «розпаралелити» на декілька таких GPU. Тому досить часто доречно використовувати cloud-потужності.
Від чого залежить час тренування моделі?
Це залежить від багатьох параметрів: від розміру набору даних, на якому тренуватимете модель, від кількості ваг (weights, кількості каліброваних параметрів нейромережі), кількості ітерацій тощо.
Тобто сам процес навчання нейронної мережі можна назвати «калібрацією» ваг, і масив цих ваг + структура самої нейронної мережі формують pre-trained model, яку й завантажуватимуть на мобільний пристрій у нашому прикладі.
Що таке epoch, step, iteration, loss, batch size, tensor shape, over-fitting?
Якщо говорити про код оригінальних репозиторіїв з реалізаціями
Для тренування нейронних мереж широко використовують алгоритми градієнтного спуску й зворотного поширення помилки.
Масив даних, на яких тренуватимемо нашу модель, поділяється на N-ну кількість партій (batches), і розмір кожної з них — це batch size. Далі, коли кожна із цих партій даних передається вперед і назад по обчислювальному графу (розраховуючи зворотне поширення помилки) через нейронну мережу, це і є одна epoch.
Щоб знайти найкраще значення окремої ваги, коли значення помилки найменше, виконують рух уздовж уявного графіка по градієнту (вектору, який указує напрямок до зростання якоїсь величини) через деякий крок (step), і для цього потрібно декілька ітерацій. Тобто iterations — це кількість batches, потрібних для того, щоб закінчити одну epoch.
Loss — це число, яке характеризується loss-функцією, що вказує, наскільки поганим було прогнозування моделі на одному прикладі. Якщо прогноз моделі ідеальний (що завжди малоймовірно), loss дорівнює нулю, інакше — loss більший.
Кожна штучна нейронна мережа має input і output, тому щоб «згодувати» їй дані (та одержати вихідне значення), треба їх привести до відповідного формату, тобто до N-розмірного масиву. І форма (shape) — це кількість елементів у кожній з його розмірностей.
Ранг | Форма | Номер розмірності | Приклад |
0 | [] | Тензор | |
1 | [D0] | Тензор | |
2 | [D0, D1] | Тензор | |
3 | [D0, D1, D2] | Тензор | |
N | [D0, D1...Dn-1] | n-D | Тензор форми N-D [D0, D1...Dn-1] |
Наприклад, таку картинку, створену лише із 4 пікселів (зелений, чорний, синій, червоний), можна представити як N-розмірний масив
[ [ [0, 255, 0], [0, 0, 0] ], [ [0, 0, 255], [255, 0, 0] ] ]з shape [2, 2, 3] (висота, ширина, RGB).
Припустимо, ви намалювали червоними крапками знайомий ще зі школи графік функції y = x. Вийшло не надто рівно, і ви хочете побудувати
Over-fitting — це коли ваша модель ідеально «прилягає» до даних, на яких вона тренується, у цьому прикладі — до всіх нерівностей, червоних крапок, які ви намалювали. Але далі намалювати продовження графіка правильно вона не може, тобто вже на тестових даних робить значні помилки.
Appropriate fitting — це коли ваша модель правильно виокремила закономірності даних, і в цьому прикладі може правильно домалювати графік, тобто добре працює на тестових даних.
Under-fitting — це коли ваша модель погано працює і на даних для тренування, і на даних для тестування.
Але іноді, у реальному житті, припустимо використовувати моделі, які можна назвати over-fitted, які ідеально працюють лише на деякому діапазоні даних (але за умови, що вони спрощують обчислення). Наприклад, формула додавання швидкостей (звичних людині в повсякденному житті) досить проста — просте додавання. Але якщо розглядати досить великий діапазон швидкостей, аж до порівнянних зі швидкістю світла, — вона вже має складніший вигляд. Тобто перша формула працює лише на одному виокремленому діапазоні даних, а друга — на ще ширшому. Але досить часто другою формулою можна знехтувати й заради спрощення обчислень використовувати першу.
Крок 1. Готуємо проект до тренування на Google Cloud
Для прикладу я вибрав проект open-source із розпізнавання об’єктів та їхніх координат на фото — YOLOv3, який використовує Keras.
Спочатку відредагуємо структуру нашого проекту:
trainer # Директорія яка містить train-модуль --- __init__.py --- …. # тут будуть файли нашого open source проекту setup.py # файл з dependencies Вміст setup.py: from setuptools import setup, find_packages setup(name='some_project', version='1.0', packages=find_packages(), include_package_data=True, description='.......', author='…', license='Unlicense', install_requires=[ 'Keras==2.1.5', 'tensorflow-gpu==1.6.0', 'h5py==2.8.0', 'numpy', 'argparse', 'Pillow', 'matplotlib', ])
Для зберігання файлів нашого набору даних, який має обсяг декілька гігабайтів, використовуватимемо Google Cloud Storage. Створимо storage bucket за допомогою команди в консолі Google Cloud:
gsutil mb -p [PROJECT_NAME] -c [STORAGE_CLASS] -l [BUCKET_LOCATION] -b on gs://[BUCKET_NAME]/
Де PROJECT_NAME
— назва нашого проекту в Google Cloud.
STORAGE_CLASS
бувають Multi-Regional Storage, Regional Storage, Nearline Storage і Coldline Storage. Докладніше про storage class можна почитати тут.
BUCKET_LOCATION
— розташування вашого storage bucket — може бути:
Для свого прикладу я використав такі параметри: storageclass — coldline, region — us-east1
.
Далі треба завантажити файли набору даних. Я використав VOC dataset.
Для копіювання цих файлів до Cloud Storage у консолі Google Cloud використаємо команду:gsutil -m cp -R [SOURCE_LOCAL_LOCATION]gs://[BUCKET_NAME]
З іншими командами gsutil можна ознайомитися тут.
Далі бажано всі операції File I/O робити через Bucket I/O, який чудово зреалізували в модулі tensorflow.python.lib.io.
from tensorflow.python.lib.io import file_io # for better file I/O import io from PIL import Image def gs_open(path, mode='r'): return file_io.FileIO(path, mode) def gs_file_exists(path): return file_io.file_exists(path) def gs_copy_file(src, dest): if not file_io.file_exists(src): raise Exception("Src file doesn't exist at %s" % src) file_io.copy(src, dest, overwrite=True) def gs_open_image(path): file = gs_open(path, "rb") image_data = file.read() file.close() return Image.open(io.BytesIO(image_data))
Для цього окремого прикладу — тренування YOLO — нам потрібно ще створити train-file, який містить шляхи до картинок з набору даних, координати об’єктів на них та їхній тип (клас):
path/to/img1.jpg x11,y11,x12,y12,some_class_A x21,y21,x22,y22,some_class_B … path/to/img2.jpg x11,y11,x12,y12,some_class_B … …….
Де x11, y11, x12, y12 — координати «прямокутника» шуканого об’єкта на фото, some_class — клас об’єкта (число, усі класи можна подивитися у файлі classes.txt).
Для автоматизації цього процесу в репозиторії є скрипт voc_annotation.py.
python voc_annotation.py --voc_path gs://[YOUR_BUCKET_NAME]/VOCdevkit --voc_classes_path model_data/voc_classes.txt
Результат — створений файл 2012_train.txt.
Ми одержали таку структуру файлів на Cloud Storage:
Крок 2. Створення ML Cloud Job
Для створення Cloud Job у консолі Google Cloud запустимо команду:
gcloud ai-platform jobs submit training ${job_name} --job-dir ${job_dir} \ --python-version 3.5 \ --runtime-version 1.9 \ --package-path ./trainer `# модуль trainer` \ --module-name trainer.train `# файл train.py` \ --region ${region} \ --scale-tier BASIC_GPU `# single NVIDIA Tesla K80 GPU` \ -- `# Окремо параметри для train.py` \ --weights_stage "${job_dir}/weights_stage_exported_tiny.h5" `# Наша stage pre-trained model, яка повинна створитися наприкінці` \ --weights_final "${job_dir}/weights_final_exported_tiny.h5" `# Наша final pre-trained model, яку маємо створити наприкінці` \ --anchors_file "gs://${bucket_name}/tiny_yolo_anchors.txt" \ --annotation_file "gs://${bucket_name}/2012_train_tiny.txt" \ --classes_file "gs://${bucket_name}/voc_classes_tiny.txt"
Щоб кожного разу не писати команду з параметрами, я вивів її в окремий bash-скрипт.
Якщо зазирнете в логи, то побачите, що значення loss із кожною epoch дещо зменшується:
Найкраще значення loss — це близьке до нуля.
Після закінчення тренування знайдемо наші pre-trained моделі тут:
Повний код можна подивитися в цьому репозиторії.
Крок 3. Завантаження моделі на мобільному пристрої
Розглянемо декілька можливостей:
- Core ML (iOS/Mac);
- Metal Performance Shaders (iOS/Mac).
3.1 Core ML
Для завантаження через Core ML потрібно конвертувати нашу модель до відповідного формату.
Для Keras (*.h5):
#!/usr/bin/env python importcoremltools your_model = coremltools.converters.keras.convert('your_model.h5', input_names=['image'], output_names=['output'], image_input_names='image') your_model.save('your_model_name.mlmodel')
Для TensorFlow (*.pb, *.proto):
import tfcoreml as tf_converter tf_converter.convert(tf_model_path='my_model.pb', mlmodel_path='my_model.mlmodel', input_name_shape_dict=input_tensor_shapes, output_feature_names=output_tensor_names)
Де input_tensor_shapes
— це форма вхідного N-розмірного масиву, у разі YOLO v3 (tiny): [416, 416, 3], формат запису — [height, width, rgb values].
А output_tensor_names
— це назви значень output, у прикладі YOLO це output1, output2, output3, де output1 shape = [13, 13], output2 shape = [26, 26], output3 shape = [52, 52].
Під час додавання моделі Core ML у проект Xcode автогенерується клас Yolov3. Проглянувши його реалізацію, ми бачимо, як і звідки завантажується наша модель.
Як ми бачимо, вона завантажується з директорії Yolov3.mlmodelc (з app bundle), де зберігаються файли, зокрема model.espresso.net (структура моделі), model.espresso.weights (ваги).
Треба зазначити, що файли моделі не зашифровані, тож їх просто можуть «украсти» для використання в іншому застосунку.
Якщо ви хочете дізнатися, як правильно шифрувати й розшифровувати
Для того щоб наша модель опрацювала картинку, треба викликати цей метод з параметром CVPixelBuffer:
Одержати CVPixelBuffer з відеопотоку камери можна за допомогою цього методу в делегаті AVCaptureVideoDataOutputSampleBufferDelegate:
Треба також зазначити, що якщо ви берете CVPixelBuffer з AVCaptureSession (AVFoundation), то він має формат кольорової моделі RGB.
Але якщо ви берете CVPixelBuffer з [ARFrame.capturedImage] (ARKit), то він матиме вже формат YUV.
Ось що може «бачити» ваша нейромережа, коли замість очікуваного RGB ви завантажили в її YUV:
Отже, це може негативно позначитися на результатах якості розпізнавання. Тому раджу завжди конвертувати до відповідного формату, а також до відповідних розмірів зображення.
У YOLO input shape дорівнює [416, 416, 3], тому висота й ширина зображення має бути 416.
Докладніший приклад використання Core ML окремо для YOLOv3 можна знайти в цьому репозиторії.
Core ML може працювати як лише на CPU, так і на GPU. GPU-реалізацію створили на основі Metal Performance Shaders.
3.2 Metal Performance Shaders
Metal Performance Shaders містить колекцію високооптимізованих обчислювальних і графічних шейдерів, розроблених для того, щоб просто та ефективно інтегрувати у ваш застосунок Metal. Вони спеціально налаштовані, щоб скористатися унікальними апаратними характеристиками кожного виду GPU для забезпечення оптимальної обчислювальної потужності.
Якщо ви розглянете стек-трейс майбутнього виконання вашої моделі в Core ML, можете побачити там класи внутрішнього C++
Далі поясню, як можна працювати з Metal Command Buffer.
Обчислювальний граф нашої моделі створюємо послідовно за допомогою обчислювальних нод.
SomeNode1, SomeNode2, … SomeNodeN
— класи нод. Є input- і output-ноди. Серед наявних класів нод присутні MPSCNNPoolingMaxNode
(max pooling), MPSCNNConvolutionNode
(convolution), MPSCNNNeuronReLU
(ReLU activation) тощо.
Граф нод у нашій keras.utils.plot_model
(для Keras).
Наприклад, граф моделі YOLO, яку ми розглядаємо — такий.
device (MTL Device) — інтерфейс Metal для GPU, який можна використовувати для графіки й паралельних обчислень.
Щоб працювати з вхідними даними в GPU-CPU shared memory space, їх треба підготувати для цього за допомогою конвертації в MTLTexture й далі через створення MPSImage.
Після виклику [MPSNNGraph.executeAsync]
весь наш граф кодується в MTL Command Buffer для виконання вже на GPU.
У [MPSNNGraph.executeAsync]
ми можемо одержати outputImage у вигляді MPSImage, з якої можна вже скопіювати масив вихідних значень.
Докладніший приклад використання Metal Performance Shaders окремо для YOLO можна знайти в цьому репозиторії.
Якщо ви не хочете, щоб вашу