Многопроцессный доступ к Intel Neural Computer Stick через REST

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Проблема однозадачности

В прошлой серии я поставил на танк Intel Neural Computer Stick 2 и перекинул на него все нейросетевые вычисления, отказавшись от Tensorflow и OpenCV-DNN.
Была проблема, с которой я столкнулся уже тогда — невозможность работать с NCS из нескольких процессов одновременно. Тогда это было не критично, а сейчас пришло время разобраться.
При попытке загрузить модель из второго процесса OpenVino начинало ругаться:

E: [ncAPI] [    926029] resetAll:348     Failed to connect to stalled device, rc: X_LINK_ERROR E: [ncAPI] [    933282] ncDeviceOpen:672        Failed to find suitable device, rc: X_LINK_DEVICE_NOT_FOUND 

Поиском по форуму поддержки Интела удалось найти похожую проблему. Оттуда нас перебросили на документацию где четко сказано:
Single device cannot be shared across multiple processes.

На этом эксперименты можно сворачивать и начинать делать многопроцессный доступ.

NCS Service

Совершенно логично вынести прямую работу с NCS в отдельный сервис, а всем клиентам раздать API через который они и будут работать.

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

NCS API

На низком уровне NCS API очень простой:
— загрузить модель
— запустить расчет
— получить список моделей
— получить свойства модели

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

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

Кроме того, низкий уровень это хорошо, но если поддержать специализированные операции, то это упрощает логику и данные.

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

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

Основной интерфейс

Итак, в основной интерфейс входят методы:

  • POST: /load — загрузить модель
  • POST: /unload/$model — удалить модель (из сервиса, с девайса удалить невозможно)
  • GET: /list — получить список моделей
  • GET: /input/shape/$model — узнать размерность входного тензора
  • POST: /inference/file/$model — сделать расчет с данными из памяти
  • POST: /inference/path/$model — сделать расчет с данными в файловой системе

Здесь два слова про данным из памяти и файловой системы:

Если NCS сервис и его пользователь запущены на одной Raspberry, то есть смысл сэкономить на передаче картинки и вместо этого передать путь, чтобы сервис сам прочитал файл.
Если же картинка уже в памяти (или не существует в файловой системе), то передаем ее прямо оттуда.

Тесты показывают, что передача байтов из памяти существенно медленнее (измерение сделано на 1000 попыток):

Из памяти: 87.5 секунд
Путь к файлу: 63.3150 секунд

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

В целом метод inference принимает на вход картинку в виде numpy array и выдает тензор в этом же формате.
Как трактовать выхлоп — это уже проблема клиента.
Чтобы эту задачу облегчить, сервис поддерживает специализированные методы, которые извлекают из выходного тензора значимую информацию в человеческом виде.

Классификация

Для классификации заводим отдельный REST-метод, который преобразует выходной тензор в набор пар (class, score).

def get_class_tensor(data):     ret = []     thr = 0.01     while(True):         cls = np.argmax(data)         if data[cls] < thr:             break;         logging.debug(("Class", cls, "score", data[cls]))         c = {"class" : int(cls), "score" : int(100 * data[cls])}         data[cls] = 0         ret.append(c)     return ret  def classify(model_id, img):     rc, out = run_inference(model_id, img)     if not rc:         return rc, out     return True, get_class_tensor(out) 

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

  • POST: /classify/file/$model
  • POST: /classify/path/$model

Детектирование

Выходной тензор детектора содержит набор (класс, вероятность, нормированные координаты) и выглядит довольно громоздко.

Превращаем его в понятный вид, одновременно отсекая маловероятные варианты:

def get_detect_from_tensor(t, rows, cols):     score = int(100 * t[2])     cls = int(t[1])     left = int(t[3] * cols)     top = int(t[4] * rows)     right = int(t[5] * cols)     bottom = int(t[6] * rows)     return {"class" : cls, "score" : score, "x" : left, "y" : top, "w" : (right - left), "h" : (bottom - top)}   def build_detection(data, thr, rows, cols):     T = {}     for t in data:         score = t[2]         if score > thr:             cls = int(t[1])             if cls not in T:                 T[cls] = get_detect_from_tensor(t, rows, cols)             else:                 a = T[cls]                 if a["score"] < score:                     T[cls] = get_detect_from_tensor(t, rows, cols)     return list(T.values())   def detect(model_id, img):     rc, out = run_inference(model_id, img)     if not rc:         return rc, out      rows, cols = img.shape[:2]     return True, build_detection(out[0], 0.01, rows, cols) 

Как обычно, поддерживаются оба способа:

  • POST: /detect/file/$model
  • POST: /detect/path/$model

Сегментация

Тензор сегментации содержит вероятности по классам да еще и в размерности нейросети.
Преобразуем это просто в маску классов:

def segment(model_id, img):     rc, out = run_inference(model_id, img)     if not rc:         return rc, out      out = np.argmax(out, axis=0)     out = cv.resize(out, (img.shape[1], img.shape[0]),interpolation=cv.INTER_NEAREST)     return True, out 

  • POST: /segment/file/$model
  • POST: /segment/path/$model

Заключение

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

Опять же, сервис я использую на Raspberry Pi, но он может быть запущен на любой платформе, где есть питон и OpenVino с NCS.

Ссылки


Источник: habr.com

Комментарии: