Как работать с данными изображения MNIST в Tensorflow.js

1656598690 kak rabotat s dannymi izobrazheniya mnist v tensorflowjs

от Кевина Скотта

0*XDXg44q30kAtG4S8

Существует шутка, что 80 процентов науки о данных очищают данные, а 20 процентов жалуются на очищение данных… очищение данных — это гораздо большая доля науки о данных, чем можно было бы ожидать посторонним. На самом деле, учебные модели, как правило, составляют относительно небольшую долю (менее 10 процентов) того, что делает ученик машинного обучения или ученый по данным.

— Энтони Голдблум, генеральный директор Kaggle

Манипулирование данными является важным шагом для любой проблемы машинного обучения. В этой статье будет рассмотрен пример MNIST для Tensorflow.js (0.11.1) и рассмотрен код, обрабатывающий загрузку данных строку за строкой.

Пример MNIST

18 import * as tf from '@tensorflow/tfjs';1920 const IMAGE_SIZE = 784;21 const NUM_CLASSES = 10;22 const NUM_DATASET_ELEMENTS = 65000;2324 const NUM_TRAIN_ELEMENTS = 55000;25 const NUM_TEST_ELEMENTS = NUM_DATASET_ELEMENTS - NUM_TRAIN_ELEMENTS;2627 const MNIST_IMAGES_SPRITE_PATH =28     ' const MNIST_LABELS_PATH =30     'https://storage.googleapis.com/learnjs-data/model-builder/mnist_labels_uint8';`

Во-первых, код импортирует Tensorflow (убедитесь, что вы транспилируете свой код!) и устанавливаем некоторые константы, в частности:

  • IMAGE_SIZE – размер изображения (ширина и высота 28×28 = 784)
  • NUM_CLASSES – количество категорий меток (число может быть от 0 до 9, поэтому классов 10)
  • NUM_DATASET_ELEMENTS – общее количество изображений (65 000)
  • NUM_TRAIN_ELEMENTS – количество обучающих изображений (55 000)
  • NUM_TEST_ELEMENTS – количество тестовых изображений (10 000, или остаток)
  • MNIST_IMAGES_SPRITE_PATH & MNIST_LABELS_PATH – пути к изображениям и меткам

Изображения объединены в одно огромное изображение, которое выглядит так:

0*zPqagVx10lTbl-c5

MNISTData

Далее, начиная со строки 38, есть MnistDataкласс, который предоставляет следующие функции:

  • load – отвечает за асинхронную загрузку изображения и данных маркировки
  • nextTrainBatch – загрузите следующий учебный пакет
  • nextTestBatch – загрузите следующую тестовую партию
  • nextBatch – общая функция для возвращения следующей партии в зависимости от того, есть ли она в учебном наборе или тестовом наборе

Для начала работы эта статья будет рассмотреть только то load функция.

load

44 async load() {45   // Make a request for the MNIST sprited image.46   const img = new Image();47   const canvas = document.createElement('canvas');48   const ctx = canvas.getContext('2d');

async это относительно новая языковая функция в Javascript, для которой вам понадобится транспиллер.

The Image Объект – это встроенная функция DOM, которая представляет изображение в памяти. Он обеспечивает обратные вызовы, когда вместе загружается изображение с доступом к атрибутам изображения. canvas является еще одним элементом DOM, который обеспечивает легкий доступ к массивам пикселей и обработку с помощью context.

Поскольку оба элемента являются элементами DOM, если вы работаете с Node.js (или веб-воркером), вы не будете иметь доступ к этим элементам. Альтернативный подход см. ниже.

imgRequest

49 const imgRequest = new Promise((resolve, reject) => {50   img.crossOrigin = '';51   img.onload = () => {52     img.width = img.naturalWidth;53     img.height = img.naturalHeight;

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

crossOrigin есть img атрибут, позволяющий загружать изображения между доменами и обходит проблемы CORS (обмен ресурсами между источниками) при взаимодействии с DOM. naturalWidth и naturalHeight ссылаются на исходные размеры загруженного изображения и служат для обеспечения правильности размера изображения при выполнении вычислений.

55     const datasetBytesBuffer =56     new ArrayBuffer(NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);5758     const chunkSize = 5000;59     canvas.width = img.width;60     canvas.height = chunkSize;

Код инициализирует новый буфер, содержащий каждый пиксель каждого изображения. Он умножает общее количество изображений на размер каждого изображения на количество каналов (4).

я верить что chunkSize используется для предотвращения одновременной загрузки пользовательского интерфейса слишком большого количества данных в память, хотя я не уверен на 100%.

62     for (let i = 0; i < NUM_DATASET_ELEMENTS / chunkSize; i++) {63       const datasetBytesView = new Float32Array(64         datasetBytesBuffer, i * IMAGE_SIZE * chunkSize * 4,65         IMAGE_SIZE * chunkSize);66       ctx.drawImage(67         img, 0, i * chunkSize, img.width, chunkSize, 0, 0, img.width,68         chunkSize);6970       const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

Этот код перебирает каждое изображение в спрайте и инициализирует новое TypedArray для этой итерации. Затем контекстное изображение получает часть изображенного изображения. Наконец, это нарисованное изображение преобразуется в данные изображения с помощью контекста getImageData функция, возвращающая объект, представляющий основные пиксельные данные.

72       for (let j = 0; j < imageData.data.length / 4; j++) {73         // All channels hold an equal value since the image is grayscale, so74         // just read the red channel.75         datasetBytesView[j] = imageData.data[j * 4] / 255;76       }77     }

Мы перебираем пиксели и делим на 255 (максимальное возможное значение пикселя), чтобы закрепить значение между 0 и 1. Нужен только красный канал, поскольку это изображение в оттенках серого.

78     this.datasetImages = new Float32Array(datasetBytesBuffer);7980     resolve();81   };82   img.src = MNIST_IMAGES_SPRITE_PATH;83 });

Эта строка принимает буфер, превращает его в новый TypedArray содержащий наши пиксельные данные, а затем решает Promise. Последняя строка (установка src) фактически начинает загрузку изображения, которое запускает функцию.

Одно, что меня поначалу смутило, это поведение TypedArray по отношению к его базовому буферу данных. Вы можете это заметить datasetBytesView устанавливается внутри цикла, но никогда не возвращается.

Под капотом, datasetBytesView ссылается на буфер datasetBytesBuffer (с помощью которого он инициализируется). Когда код обновляет пиксельные данные, он косвенно редактирует значение самого буфера, который, в свою очередь, превращается в новый Float32Array в строке 78.

Получение данных изображения вне DOM

Если вы находитесь в DOM, необходимо использовать DOM. Браузер (через canvas) заботится об определении формата изображений и переводе буферных данных в пикселе. Но если вы работаете вне DOM (скажем, в Node.js или Web Worker), вам понадобится альтернативный подход.

fetch обеспечивает механизм, response.arrayBuffer, что дает вам доступ к основному буферу файла. Мы можем использовать это для чтения байтов вручную, полностью избегая DOM. Вот альтернативный подход к написанию вышеуказанного кода (этот код требует fetchкоторый можно полизаполнить в Node с помощью чего-то вроде isomorphic-fetch):

const imgRequest = fetch(MNIST_IMAGES_SPRITE_PATH).then(resp => resp.arrayBuffer()).then(buffer => {  return new Promise(resolve => {    const reader = new PNGReader(buffer);    return reader.parse((err, png) => {      const pixels = Float32Array.from(png.pixels).map(pixel => {        return pixel / 255;      });      this.datasetImages = pixels;      resolve();    });  });});

Это возвращает буфер массива для конкретного изображения. Сочиняя это, я сначала попытался самостоятельно проанализировать входной буфер, чего я бы не рекомендовал. (Если ты есть заинтересован в этом, вот некоторая информация о том, как прочитать буфер массива для png.) Вместо этого я решил использовать pngjsкоторый обрабатывает png разбор для вас. Имея дело с другими форматами изображений, вам придется самостоятельно разбираться с функциями анализа.

Просто царапая поверхность

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

Команда Tensorflow.js постоянно изменяет базовые API данных в Tensorflow.js. Это может помочь удовлетворить больше наших потребностей по мере развития API. Это также означает, что следует быть в курсе развития API, поскольку Tensorflow.js продолжает развиваться и усовершенствоваться.

Первоначально опубликовано на thekevinscott.com

Особая благодарность Ари Зильнику.

Добавить комментарий

Ваш адрес email не будет опубликован.