ML для мобільного розробника: Google Cloud для тренування ML-моделі
Цей текст буде корисний мобільним розробникам, які хочуть тренувати наявні 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?
Якщо говорити про код оригінальних репозиторіїв з реалізаціями ML-моделей, то для розробника, який не дуже обізнаний із цією темою, може виявитися чимало незнайомих термінів. Розглянємо деякі з них.
Для тренування нейронних мереж широко використовують алгоритми градієнтного спуску й зворотного поширення помилки .
Масив даних, на яких тренуватимемо нашу модель, поділяється на N-ну кількість партій (batches ), і розмір кожної з них — це batch size . Далі, коли кожна із цих партій даних передається вперед і назад по обчислювальному графу (розраховуючи зворотне поширення помилки) через нейронну мережу, це і є одна epoch .
Щоб знайте найкраще значення окремої ваги, коли значення помилки найменше, виконують рух уздовж уявного графіка по градієнту (вектору, який казує напрямок до зростання якоїсь величини) через деякий крок (step), і для цього потрібно декілька ітерацій. Тобто iterations — це кількість batches , потрібних для того, щоб закінчити одну epoch .
Loss — це число, яке характеризується loss-функцією , що вказує, наскільки поганим було прогнозування моделі на одному прикладі. Якщо прогноз моделі ідеальний (що завжди малоймовірно), loss дорівнює нулю, інакше — loss більший.
Кожна штучна нейронна мережа має input і output, того щоб «згодувати» їй дані (та одержати вихідне значення), треба їх привести до відповідного формату, тобто до N-розмірного масиву. І форма (shape ) — це кількість елементів у кожній з його розмірностей.
Ранг | Форма | Номер розмірності | Приклад |
0 | [] | 0-D | Тенозр 0-D. Скаляр |
1 | [D0] | 1-D | Тензор 1-D форми [5] |
2 | [D0, D1] | 2-D | Тензор 2-D форми [3, 4] |
3 | [D0, D1, D2] | 3-D | Тензор 1-D форми [1, 4, 3] |
3 | [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. Вийшло не надто рівно, і ви хочете побудувати ML-модель, щоб вона змогла домалювати продовження цього графіка.
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 Image import 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/python env 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(ваги).
Треба зазначити, що файли моделі не зашифровані , тож їх просто можуть «украсти» для використання в іншому застосунку.
Якщо ви хочете дізнатися, як правильно шифрувати й розшифровувати ML-моделі, я можу написати для цього окрему статтю :)
Для того щоб наша модель опрацювала картинку, треба викликати цей метод з параметром 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++ ML-фреймворку Apple з назвою Espresso й Metal Command Buffer.
Далі поясню, як можна працювати з Metal Command Buffer.
Обчислювальний граф нашої моделі створюємо послідовно за допомогою обчислювальних нод.
SomeNode1, SomeNode2, ... SomeNodeN — класі нсд. Є input і output-ноди. Серед наявних класів нод присутні MPSCNNPoolingMaxNode (max pooling), MPSCNNConvolutionNode (convolution), MPSCNNNeuronReLU (ReLU activation) тощо.
Граф нод у нашій ML-моделі можна подивитися за допомогою python через 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 можна знайте в цьому репозиторії .
Якщо ви не хочете, щоб вашу ML-модель використовував хтось сторонній і міг просто інтегрувати в чужий застосунок, краще застосовувати безпосередньо Metal Performance Shaders, ніж Core ML. Альо щоб розглянути всі методи захисту, краще присвятити цьому окрему статтю.
Опубліковано: 20/08/19 @ 10:00
Розділ Пошуковики
Рекомендуємо:
QA дайджест #38: техніки тестування, генерація реалістичних тестових даних, мобільне тестування
Безсерверні веб-застосунки на Python з використанням Lambda і Flask
Value-Driven Development: досвід трансформації сервісної команди в продуктову
Шлях від QA до Product Owner: як зважитися на зміни в кар'єрі
Мотивація до інновацій у IT-компаніях України. Результати опитування