[Перевод] Машинное обучение с Node.js при помощи библиотеки Tensorflow.js
Привет всем, коллеги!
Возможно, поклонники библиотеки Tensorflow, уже заметившие у нас в предзаказе эту книгу, также присматривались к возможностям машинного и глубокого обучения в браузере, тем более, что тему не обошел вниманием и сам Франсуа Шолле. Интересующихся приглашаем под кат, где рассказано, как при помощи библиотеки Tensorflow.js распознаются изображения.
TensorFlow.js — новая версия популярной опенсорсной библиотеки, обогащающей язык JavaScript возможностями глубокого обучения. Теперь разработчики могут определять, обучать и запускать модели при помощи высокоуровневого библиотечного API.
Благодаря предобученным моделям разработчики теперь могут с легкостью решать такие сложные задачи, как распознавание образов, генерация музыки или определение человеческих поз всего в нескольких строках JavaScript.
Tensorflow.js начиналась как фронтендовая библиотека для работы в браузере, но в этом году в нее была добавлена экспериментальная поддержка Node.js. Таким образом, TensorFlow.js можно использовать и в бэкендовых приложениях на JavaScript, что совершенно избавляет нас от необходимости прибегать к Python.
Читая об этой библиотеке, я решил испробовать ее на простой задаче…
Использовать TensorFlow.js для визуального распознавания образов на изображениях при применении JavaScript из Node.js
К сожалению, документация и примеры кода в основном описывают использование этой библиотеки в браузере, Проектные утилиты, призванные упростить загрузку и использование предобученных моделей на момент написания статьи еще не поддерживали Node.js. Мне пришлось потратить немало времени, чтобы хорошенько прочитать исходники на Typescript для этой библиотеки.
Однако, через несколько дней долбежки я все-таки это сделал! Ура!
Прежде чем перейти к подробному разбору кода, давайте поговорим о других реализациях библиотеки TensorFlow.
TensorFlow
TensorFlow — это свободно распространяемая программная библиотека для приложений из области машинного обучения. TensorFlow можно применять для создания нейронных сетей и реализации других алгоритмов глубокого обучения.
Это библиотека, выпущенная Google в ноябре 2015, исходно была написана на Python. Для обучения и оценки создаваемых моделей в ней применяются вычисления на CPU или GPU. Изначально эта библиотека создавалась для работы на высокопроизводительных серверах с использованием ресурсозатратных GPU.
Последние обновления позволили оптимизировать эту библиотеку и использовать в средах с более ограниченными ресурсами — например, на мобильных устройствах и в веб-браузерах.
TensorFlow Lite
Tensorflow Lite, облегченная версия этой библиотеки для мобильных устройств и встраиваемых систем, была выпущена в мае 2017 года. Вместе с ней предоставляется новый набор предобученных глубоких моделей для задач, связанных с распознаванием образов; эта коллекция именуется MobileNet. Модели MobileNet были разработаны специально для эффективной работы в окружениях с ограниченным количеством ресурсов, например, на мобильных устройствах.
TensorFlow.js
Вслед за Tensorflow Lite в марте 2018 года была анонсирована TensorFlow.js. Эта версия библиотеки предназначена для работы в браузере и базируется на более раннем проекте под названием deeplearn.js. WebGL обеспечивает GPU-доступ к библиотеки. Разработчики используют API на JavaScript для обучения, загрузки и запуска моделей.
Позже TensorFlow.js была расширена для работы с Node.js, для этого применяется библиотечное дополнение tfjs-node
.
Импорт имеющихся моделей в TensorFlow.js
Готовые модели TensorFlow и Keras можно выполнять при помощи библиотеки TensorFlow.js. Перед выполнением модели необходимо перевести в новый формат при помощи этого инструмента. Предобученные и преобразованные модели для классификации изображений, определения поз и обнаружения k-ближайших соседей доступны на Github.
Использование TensorFlow.js с Node.js
Установка библиотек TensorFlow
TensorFlow.js можно установить из реестра NPM.
npm install @tensorflow/tfjs @tensorflow/tfjs-node
// или...
npm install @tensorflow/tfjs @tensorflow/tfjs-node-gpu
В обоих расширениях для Node.js используются нативные зависимости, которые будут компилироваться по запросу.
Загрузка библиотек TensorFlow
API на JavaScript для Tensorflow предоставляется из core-библиотеки. В модулях-расширениях, обеспечивающих поддержку Node.js, дополнительные API не предоставляются.
const tf = require('@tensorflow/tfjs')
// Загружаем привязку (вычисления CPU)
require('@tensorflow/tfjs-node')
// Или загружаем привязку (вычисления GPU)
require('@tensorflow/tfjs-node-gpu')
Загрузка моделей TensorFlow
В TensorFlow.js предоставляется библиотека NPM (tfjs-models
), упрощающая загрузку предобученных и преобразованных моделей для классификации изображений, определения поз и обнаружения k-ближайших соседей.
Модель MobileNet для классификации изображений — это глубокая нейронная сеть, обученная различать 1000 различных классов изображений.
В файле README к проекту в качестве примера приведен следующий код, используемый для загрузки модели.
import * as mobilenet from '@tensorflow-models/mobilenet';
// загрузить модель
const model = await mobilenet.load();
Одна из первых проблем, с которыми мне довелось столкнуться — оказывается, этот код не работает с Node.js.
Error: browserHTTPRequest is not supported outside the web browser.
Изучив исходный код, видим, что библиотека mobilenet — это обертка для класса tf.Model
. При вызове метод load()
автоматически загружает нужные файлы моделей, расположенные по внешнему HTTP-адресу, и инстанцирует модель TensorFlow.
Расширение Node.js на момент написания статьи еще не поддерживало HTTP-запросы для динамического извлечения моделей. Оставалось только вручную загружать модели в файловую систему.
Однако, вчитавшись в исходный код библиотеки, я нашел обходной путь…
Загрузка моделей из файловой системы
В случае, если класс MobileNet создается вручную, можно не вызывать метод load
модуля, а перезаписать автоматически генерируемую переменную path
, содержащую HTTP-адрес модели, заменив этот адрес на локальный путь в файловой системе. После этого при вызове метода load
в экземпляре класса будет срабатывать класса загрузчика файловой системы; в таком случае мы отказываемся от использования браузерного HTTP-загрузчика.
const path = "mobilenet/model.json"
const mn = new mobilenet.MobileNet(1, 1);
mn.path = `file://${path}`
await mn.load()
Круто, все работает!
Но откуда же берутся файлы моделей?
Модели MobileNet
Модели для TensorFlow.js состоят из файлов двух типов: файл конфигурации модели, хранимый в формате JSON, и веса моделей, хранимые в двоичном формате. Веса моделей зачастую фрагментируются на множество частей для оптимизации кэширования в браузерах.
Рассмотрев автоматический код загрузки для моделей MobileNet, видим, что модели, их конфигурации и весовые фрагменты извлекаются из общедоступного контейнера по следующему адресу.
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v${version}_${alpha}_${size}/
Шаблонные параметры в URL описывают версии моделей, перечисленные здесь. Результирующая точность классификации также выводится на той же странице.
В исходном коде указано, что только модели версии MobileNet v1 можно загружать при помощи библиотеки tensorflow-models/mobilenet
.
Код извлечения по HTTP загружает файл model.json
из места хранения, а затем рекурсивно выбирает все фрагменты моделей с весовыми коэффициентами, на которые стоят ссылки. Это файлы в формате groupX-shard1of1
.
Скачивание моделей вручную
Если нужно сохранить все файлы моделей в файловой системе, то можно поступить так: извлечь конфигурационный файл модели, разобрать синтаксис всех весовых файлов, на которые стоят ссылки в конфигурационном файле, после чего скачать каждый весовой файл вручную.
Я собирался использовать модуль MobileNet V1 с альфа-значением 1.0 и изображение размером 224 пиксела. Так я получаю следующий URL для конфигурационного файла модели.
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/model.json
Как только этот файл будет скачан локально, можно воспользоваться инструментом jq
для синтаксического разбора имен всех весовых файлов.
$ cat model.json | jq -r ".weightsManifest[].paths[0]"
group1-shard1of1
group2-shard1of1
group3-shard1of1
...
При помощи инструмента sed
можно поставить перед именем каждого элемента HTTP URL, чтобы сгенерировать URL для каждого весового файла.
$ cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//'
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group1-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group2-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group3-shard1of1
...
Команды parallel
и curl
позволяют затем скачать все эти файлы в мой локальный каталог.
cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//' | parallel curl -O
Классификация изображений
Этот пример кода, предоставляемый с TensorFlow.js, демонстрирует, как вернуть результат классификации изображения.
const img = document.getElementById('img');
// Классифицируем изображение
const predictions = await model.classify(img);
Это не работает в Node.js из-за отсутствия поддержки DOM.
Метод classify
принимает разнообразные элементы DOM (canvas
, video
, image
) и автоматически извлекает и преобразует «картиночные» байты из этих элементов в класс tf.Tensor3D
, используемый в качестве ввода модели. В качестве альтернативы входную информацию tf.Tensor3D
можно передавать напрямую.
Я решил не пытаться использовать внешний пакет для имитации DOM-элемента вручную, а обнаружил, что tf.Tensor3D
проще собрать вручную.
Генерируем Tensor3D из изображения
Читая исходный код метода, применяемого для преобразования элементов DOM в классы Tensor3D, находим, что для генерации класса Tensor3D используются следующие входные параметры.
const values = new Int32Array(image.height * image.width * numChannels);
// заполняем пикселы информацией пиксельных каналов, взятой с картинки
const outShape = [image.height, image.width, numChannels];
const input = tf.tensor3d(values, outShape, 'int32');
pixels
— это двухмерный массив типа (Int32Array)
, содержащий последовательный список канальных значений для каждого пиксела. numChannels
— это количество канальных значений на пиксел.
Создание входных значений для JPEG
Библиотека jpeg-js
— это JPEG-кодировщик/декодировщик для Node.js, написанный на чистом JavaScript. При помощи этой библиотеки можно извлечь RGB-значения для каждого пиксела.
const pixels = jpeg.decode(buffer, true);
В результате получим Uint8Array с четырьмя канальными значениями (RGBA
) на каждый пиксел (width * height
). В модели MobileNet для классификации используется всего три цветовых канала (RGB
), альфа-канал игнорируется. Этот код преобразует четырехканальный массив в верную трехканальную версию.
const numChannels = 3;
const numPixels = image.width * image.height;
const values = new Int32Array(numPixels * numChannels);
for (let i = 0; i < numPixels; i++) {
for (let channel = 0; channel < numChannels; ++channel) {
values[i * numChannels + channel] = pixels[i * 4 + channel];
}
}
Требования ко входным значениям для моделей MobileNet
Используемая здесь модель MobileNet классифицирует изображения высотой и шириной по 224 пиксела. Входные тензоры должны содержать значения с плавающей точкой в диапазоне от -1 до 1 для каждого из трех канальных значений каждого пиксела.
Входные значения для изображений с другой размерностью перед классификацией нужно пересчитать в правильный размер. Кроме того, пиксельные значения, получаемые от JPEG-декодера, находятся в диапазоне 0 — 255, а не -1 — 1. Эти значения также необходимо преобразовать перед классификацией.
В TensorFlow.js есть библиотечные методы, упрощающие этот процесс, но, что еще лучше, есть специальная библиотека tfjs-models/mobilenet
, автоматически решающая эту проблему!
Разработчик может передавать входные Tensor3D типа int32
, а также различные размерности методу classify
, который перед классификацией переводит входные значения в правильный формат. То есть, нам здесь ничего делать не приходится. Супер!
Получение прогнозов
Модели MobileNet в Tensorflow обучаются распознаванию объектов из 1000 важнейших классов из множества данных ImageNet. На выходе модели дают вероятностные значения, характеризующие, каковы шансы найти данные объекты на классифицируемом изображении.
Полный список обученных классов для используемой модели находится в этом файле.
Библиотека tfjs-models/mobilenet
предлагает метод classify
в классе MobileNet
, возвращающий топ-X наиболее вероятных классов, исходя из того, что изображено на картинке.
const predictions = await mn_model.classify(input, 10);
predictions
— это массив из X классов и вероятностей в следующем формате.
{
className: 'panda',
probability: 0.9993536472320557
}
Пример
Итак, мы разобрались, как использовать библиотеку TensorFlow.js и модели MobileNet в Node.js, а теперь рассмотрим, как этот скрипт классифицирует изображение, заданное в качестве аргумента командной строки.
Исходный код
Сохраните этот файл скрипта и дескриптор пакета в локальных файлах.
{
"name": "tf-js",
"version": "1.0.0",
"main": "script.js",
"license": "MIT",
"dependencies": {
"@tensorflow-models/mobilenet": "^0.2.2",
"@tensorflow/tfjs": "^0.12.3",
"@tensorflow/tfjs-node": "^0.1.9",
"jpeg-js": "^0.3.4"
}
}
const tf = require('@tensorflow/tfjs')
const mobilenet = require('@tensorflow-models/mobilenet');
require('@tensorflow/tfjs-node')
const fs = require('fs');
const jpeg = require('jpeg-js');
const NUMBER_OF_CHANNELS = 3
const readImage = path => {
const buf = fs.readFileSync(path)
const pixels = jpeg.decode(buf, true)
return pixels
}
const imageByteArray = (image, numChannels) => {
const pixels = image.data
const numPixels = image.width * image.height;
const values = new Int32Array(numPixels * numChannels);
for (let i = 0; i < numPixels; i++) {
for (let channel = 0; channel < numChannels; ++channel) {
values[i * numChannels + channel] = pixels[i * 4 + channel];
}
}
return values
}
const imageToInput = (image, numChannels) => {
const values = imageByteArray(image, numChannels)
const outShape = [image.height, image.width, numChannels];
const input = tf.tensor3d(values, outShape, 'int32');
return input
}
const loadModel = async path => {
const mn = new mobilenet.MobileNet(1, 1);
mn.path = `file://${path}`
await mn.load()
return mn
}
const classify = async (model, path) => {
const image = readImage(path)
const input = imageToInput(image, NUMBER_OF_CHANNELS)
const mn_model = await loadModel(model)
const predictions = await mn_model.classify(input)
console.log('classification results:', predictions)
}
if (process.argv.length !== 4) throw new Error('incorrect arguments: node script.js ')
classify(process.argv[2], process.argv[3])
Тестирование
Скачайте файлы модели в каталог mobilenet, следуя вышеизложенным инструкциям.
Установите зависимости проекта при помощи NPM
npm install
Скачайте образец JPEG-файла для классификации
wget http://bit.ly/2JYSal9 -O panda.jpg
Запустите скрипт, аргументами которого послужат файл модели и входное изображение.
node script.js mobilenet/model.json panda.jpg
Если все сработало верно, то в консоли должен появиться следующий вывод.
classification results: [ {
className: 'giant panda, panda, panda bear, coon bear',
probability: 0.9993536472320557
} ]
Изображение верно классифицировано как содержащее панду с вероятностью 99.93%!
Заключение
Библиотека TensorFlow.js открывает перед JavaScript-разработчиками возможности глубокого обучения. Использование предобученных моделей с библиотекой TensorFlow.js позволяет без труда надстраивать в JavaScript-приложениях новые возможности для решения сложных задач машинного обучения, обходясь минимальными усилиями и лаконичным кодом.
Библиотека TensorFlow.js создавалась сугубо для работы в браузере, но сейчас уже взаимодействует и с Node.js, хотя, не все инструменты и утилиты поддерживают эту новую среду исполнения. Повозившись с библиотекой несколько дней, я научился использовать ее с моделями MobileNet для визуального распознавания изображений из локального файла.