Як тренувати модель в nodejs (tensorflow.js)?


29

Я хочу зробити класифікатор зображень, але не знаю python. Tensorflow.js працює з javascript, з яким я знайомий. Чи можна з цим навчати моделей і які були б кроки для цього? Чесно кажучи, у мене немає поняття, з чого почати.

Єдине, що я зрозумів - це завантажувати "mobilenet", який, мабуть, є набором попередньо підготовлених моделей, і класифікувати зображення з ним:

const tf = require('@tensorflow/tfjs'),
      mobilenet = require('@tensorflow-models/mobilenet'),
      tfnode = require('@tensorflow/tfjs-node'),
      fs = require('fs-extra');

const imageBuffer = await fs.readFile(......),
      tfimage = tfnode.node.decodeImage(imageBuffer),
      mobilenetModel = await mobilenet.load();  

const results = await mobilenetModel.classify(tfimage);

яка працює, але це не приносить мені користі, тому що я хочу навчити власну модель, використовуючи свої зображення з створеними мітками.

========================

Скажіть, у мене є купа зображень та міток. Як я використовую їх для навчання моделі?

const myData = JSON.parse(await fs.readFile('files.json'));

for(const data of myData){
  const image = await fs.readFile(data.imagePath),
        labels = data.labels;

  // how to train, where to pass image and labels ?

}

де ти стикаєшся з проблемою. якщо ви завантажили tensorflow, ви можете тренувати власну модель
Abhishek Anand

2
Схоже, ви можете тренувати моделі з tensorflow.js tensorflow.org/js/guide/train_models Я використовував TensorFlow з python. Якщо TensorFlow.js не використовує GPU, навчання може зайняти тривалий час. Для мене colab.research.google.com був корисним ресурсом, оскільки він безкоштовний і забезпечує 11 ГБ GPU.
canbax

1
Це занадто широке запитання ... Як зазначалося в документах , ви можете використовувати ml5 для тренування моделі або використання TF.js безпосередньо, як у цьому прикладі Node.js (розгорніть зразок коду, щоб побачити приклад навчання).
jdehesa

Але я ніде в цьому коді не бачу, як передавати зображення та мітки?
Алекс

@Alex Вони передаються до fitметоду або до набору даних, переданого до fitDataset, як показано в прикладах.
jdehesa

Відповіді:


22

Перш за все, зображення потрібно перетворити на тензори. Першим підходом було б створення тензора, що містить усі ознаки (відповідно тензор, що містить усі мітки). Цей шлях повинен відбуватися лише в тому випадку, якщо набір даних містить мало зображень.

  const imageBuffer = await fs.readFile(feature_file);
  tensorFeature = tfnode.node.decodeImage(imageBuffer) // create a tensor for the image

  // create an array of all the features
  // by iterating over all the images
  tensorFeatures = tf.stack([tensorFeature, tensorFeature2, tensorFeature3])

Мітки будуть масивом із зазначенням типу кожного зображення

 labelArray = [0, 1, 2] // maybe 0 for dog, 1 for cat and 2 for birds

Тепер потрібно створити гаряче кодування міток

 tensorLabels = tf.oneHot(tf.tensor1d(labelArray, 'int32'), 3);

Коли будуть тензори, потрібно буде створити модель для тренувань. Ось проста модель.

const model = tf.sequential();
model.add(tf.layers.conv2d({
  inputShape: [height, width, numberOfChannels], // numberOfChannels = 3 for colorful images and one otherwise
  filters: 32,
  kernelSize: 3,
  activation: 'relu',
}));
model.add(tf.layers.flatten()),
model.add(tf.layers.dense({units: 3, activation: 'softmax'}));

Тоді модель можна навчити

model.fit(tensorFeatures, tensorLabels)

Якщо набір даних містить багато зображень, потрібно буде створити набір tfDataset. Ця відповідь обговорює, чому.

const genFeatureTensor = image => {
      const imageBuffer = await fs.readFile(feature_file);
      return tfnode.node.decodeImage(imageBuffer)
}

const labelArray = indice => Array.from({length: numberOfClasses}, (_, k) => k === indice ? 1 : 0)

function* dataGenerator() {
  const numElements = numberOfImages;
  let index = 0;
  while (index < numFeatures) {
    const feature = genFeatureTensor(imagePath) ;
    const label = tf.tensor1d(labelArray(classImageIndex))
    index++;
    yield {xs: feature, ys: label};
  }
}

const ds = tf.data.generator(dataGenerator);

І використовуйте model.fitDataset(ds)для тренування моделі


Викладене вище для тренувань у nodejs. Щоб зробити таку обробку в браузері, genFeatureTensorможна записати так:

function load(url){
  return new Promise((resolve, reject) => {
    const im = new Image()
        im.crossOrigin = 'anonymous'
        im.src = 'url'
        im.onload = () => {
          resolve(im)
        }
   })
}

genFeatureTensor = image => {
  const img = await loadImage(image);
  return tf.browser.fromPixels(image);
}

Одне слово обережності - це те, що велика обробка може блокувати основний потік браузера. Тут і грають веб-працівники.


ширина та висота від входуСама повинна відповідати ширині та висоті зображень? Тож я не можу передавати зображення з різними розмірами?
Олексій

Так, вони повинні відповідати. Якщо у вас є зображення різної ширини та висоти від вхідної форми моделі, вам потрібно буде змінити розмір зображення за допомогоюtf.image.resizeBilinear
edkeveked

Ну, це насправді не працює. Я отримую помилки
Алекс

1
@Alex Чи можете ви, будь ласка, оновити своє запитання за допомогою резюме моделі та форми зображення, яке ви завантажуєте? Усі зображення повинні мати однакову форму, або зображення потрібно змінити для тренувань
edkeveked

1
привіт @edkeveked, я говорю про виявлення об'єкта, я додав новий питання тут , будь ласка, подивіться stackoverflow.com/questions/59322382 / ...
Pranoy Sarkar

10

Розглянемо приклад https://codelabs.developers.google.com/codelabs/tfjs-training-classfication/#0

Що вони роблять:

  • зробити велике зображення PNG (вертикальне з'єднання зображень)
  • візьміть кілька ярликів
  • побудувати набір даних (data.js)

потім тренуйтеся

Побудова набору даних така:

  1. образи

Велике зображення поділено на п ять вертикальних шматок. (n - шматок розміру)

Розглянемо шматок розміру 2.

Дано піксельну матрицю зображення 1:

  1 2 3
  4 5 6

Дана піксельна матриця зображення 2 є

  7 8 9
  1 2 3

Отриманий масив буде 1 2 3 4 5 6 7 8 9 1 2 3(1D конкатенація якось)

Таким чином, в кінці обробки у вас є великий буфер, який представляє

[...Buffer(image1), ...Buffer(image2), ...Buffer(image3)]

  1. етикетки

Такого форматування багато робиться для проблем класифікації. Замість класифікації з числом вони беруть булевий масив. Щоб передбачити 7 з 10 класів, ми б розглядали [0,0,0,0,0,0,0,1,0,0] // 1 in 7e position, array 0-indexed

Що ви можете зробити, щоб почати

  • Створіть своє зображення (та його пов’язану етикетку)
  • Завантажте своє зображення на полотно
  • Витягніть його пов'язаний буфер
  • Об'єднайте весь буфер зображення як великий буфер. Ось це для xs.
  • Візьміть усі пов’язані з вами мітки, складіть їх як булевий масив і з'єднайте їх.

Нижче я підклас MNistData::load(решту можна дозволити так, як є (крім випадків у script.js, де вам потрібно замість власного класу інстанціювати)

Я все-таки генерую 28x28 зображень, пишу цифру на ній і отримую ідеальну точність, оскільки я не включаю шум чи добровільно неправильні маркування.


import {MnistData} from './data.js'

const IMAGE_SIZE = 784;// actually 28*28...
const NUM_CLASSES = 10;
const NUM_DATASET_ELEMENTS = 5000;
const NUM_TRAIN_ELEMENTS = 4000;
const NUM_TEST_ELEMENTS = NUM_DATASET_ELEMENTS - NUM_TRAIN_ELEMENTS;


function makeImage (label, ctx) {
  ctx.fillStyle = 'black'
  ctx.fillRect(0, 0, 28, 28) // hardcoded, brrr
  ctx.fillStyle = 'white'
  ctx.fillText(label, 10, 20) // print a digit on the canvas
}

export class MyMnistData extends MnistData{
  async load() { 
    const canvas = document.createElement('canvas')
    canvas.width = 28
    canvas.height = 28
    let ctx = canvas.getContext('2d')
    ctx.font = ctx.font.replace(/\d+px/, '18px')
    let labels = new Uint8Array(NUM_DATASET_ELEMENTS*NUM_CLASSES)

    // in data.js, they use a batch of images (aka chunksize)
    // let's even remove it for simplification purpose
    const datasetBytesBuffer = new ArrayBuffer(NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
    for (let i = 0; i < NUM_DATASET_ELEMENTS; i++) {

      const datasetBytesView = new Float32Array(
          datasetBytesBuffer, i * IMAGE_SIZE * 4, 
          IMAGE_SIZE);

      // BEGIN our handmade label + its associated image
      // notice that you could loadImage( images[i], datasetBytesView )
      // so you do them by bulk and synchronize after your promises after "forloop"
      const label = Math.floor(Math.random()*10)
      labels[i*NUM_CLASSES + label] = 1
      makeImage(label, ctx)
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
      // END you should be able to load an image to canvas :)

      for (let j = 0; j < imageData.data.length / 4; j++) {
        // NOTE: you are storing a FLOAT of 4 bytes, in [0;1] even though you don't need it
        // We could make it with a uint8Array (assuming gray scale like we are) without scaling to 1/255
        // they probably did it so you can copy paste like me for color image afterwards...
        datasetBytesView[j] = imageData.data[j * 4] / 255;
      }
    }
    this.datasetImages = new Float32Array(datasetBytesBuffer);
    this.datasetLabels = labels

    //below is copy pasted
    this.trainIndices = tf.util.createShuffledIndices(NUM_TRAIN_ELEMENTS);
    this.testIndices = tf.util.createShuffledIndices(NUM_TEST_ELEMENTS);
    this.trainImages = this.datasetImages.slice(0, IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
    this.testImages = this.datasetImages.slice(IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
    this.trainLabels =
        this.datasetLabels.slice(0, NUM_CLASSES * NUM_TRAIN_ELEMENTS);// notice, each element is an array of size NUM_CLASSES
    this.testLabels =
        this.datasetLabels.slice(NUM_CLASSES * NUM_TRAIN_ELEMENTS);
  }

}

8

Я знайшов підручник [1], як використовувати існуючу модель для навчання нових класів. Основні деталі коду тут:

index.html голова:

   <script src="https://unpkg.com/@tensorflow-models/knn-classifier"></script>

body.html тіло:

    <button id="class-a">Add A</button>
    <button id="class-b">Add B</button>
    <button id="class-c">Add C</button>

index.js:

    const classifier = knnClassifier.create();

    ....

    // Reads an image from the webcam and associates it with a specific class
    // index.
    const addExample = async classId => {
           // Capture an image from the web camera.
           const img = await webcam.capture();

           // Get the intermediate activation of MobileNet 'conv_preds' and pass that
           // to the KNN classifier.
           const activation = net.infer(img, 'conv_preds');

           // Pass the intermediate activation to the classifier.
           classifier.addExample(activation, classId);

           // Dispose the tensor to release the memory.
          img.dispose();
     };

     // When clicking a button, add an example for that class.
    document.getElementById('class-a').addEventListener('click', () => addExample(0));
    document.getElementById('class-b').addEventListener('click', () => addExample(1));
    document.getElementById('class-c').addEventListener('click', () => addExample(2));

    ....

Основна ідея - використовувати існуючу мережу, щоб зробити її передбачення, а потім замінити знайдену мітку власною.

Повний код знаходиться в підручнику. Ще один перспективний, більш досконалий у [2]. Йому потрібна сувора попередня обробка, тому я залишаю її лише тут, я маю на увазі, що вона набагато вдосконалена.

Джерела:

[1] https://codelabs.developers.google.com/codelabs/tensorflowjs-teachablemachine-codelab/index.html#6

[2] https://towardsdatascience.com/training-custom-image-classification-model-on-the-browser-with-tensorflow-js-and-angular-f1796ed24934


Будь ласка, погляньте на мою другу відповідь, це набагато ближче до реальності, з чого почати.
mico

Чому б не поставити обидві відповіді в одну?
edkeveked

У них такий різний підхід до однієї речі. Це вище, де я коментую зараз, насправді є вирішенням проблеми, інше - починаючи з основ, які, я думаю, пізніше, є більш підходящим до постановки питання.
mico

3

TL; DR

MNIST - це розпізнавання зображень Hello World. Вивчивши його напам’ять, ці питання у вашій думці легко вирішити.


Постановка питань:

Ваше головне запитання - це

 // how to train, where to pass image and labels ?

всередині блоку коду. Для тих, хто знайшов ідеальну відповідь на прикладах розділу прикладів Tensorflow.js: Приклад MNIST. У моїх нижче посиланнях є чисті версії JavaScript та node.js та пояснення у Вікіпедії. Я розберу їх на рівні, необхідному, щоб відповісти на головне запитання у вашій думці, і додам також перспективи того, як ваші власні зображення та мітки мають відношення до набору зображень MNIST та прикладів його використання.

Насамперед:

Кодові фрагменти

куди передавати зображення (зразок Node.js)

async function loadImages(filename) {
  const buffer = await fetchOnceAndSaveToDiskWithBuffer(filename);

  const headerBytes = IMAGE_HEADER_BYTES;
  const recordBytes = IMAGE_HEIGHT * IMAGE_WIDTH;

  const headerValues = loadHeaderValues(buffer, headerBytes);
  assert.equal(headerValues[0], IMAGE_HEADER_MAGIC_NUM);
  assert.equal(headerValues[2], IMAGE_HEIGHT);
  assert.equal(headerValues[3], IMAGE_WIDTH);

  const images = [];
  let index = headerBytes;
  while (index < buffer.byteLength) {
    const array = new Float32Array(recordBytes);
    for (let i = 0; i < recordBytes; i++) {
      // Normalize the pixel values into the 0-1 interval, from
      // the original 0-255 interval.
      array[i] = buffer.readUInt8(index++) / 255;
    }
    images.push(array);
  }

  assert.equal(images.length, headerValues[1]);
  return images;
}

Примітки:

Набір даних MNIST - це величезне зображення, де в одному файлі є кілька зображень, як плитки в головоломці, кожне з однаковим розміром, поруч, як вікна в координаційній таблиці x і y. Кожне поле має один зразок і відповідні х і у в масиві міток мають мітку. З цього прикладу не дуже важливо повернути його до декількох форматів файлів, так що фактично лише один малюнок одночасно надається циклу while для обробки.

Мітки:

async function loadLabels(filename) {
  const buffer = await fetchOnceAndSaveToDiskWithBuffer(filename);

  const headerBytes = LABEL_HEADER_BYTES;
  const recordBytes = LABEL_RECORD_BYTE;

  const headerValues = loadHeaderValues(buffer, headerBytes);
  assert.equal(headerValues[0], LABEL_HEADER_MAGIC_NUM);

  const labels = [];
  let index = headerBytes;
  while (index < buffer.byteLength) {
    const array = new Int32Array(recordBytes);
    for (let i = 0; i < recordBytes; i++) {
      array[i] = buffer.readUInt8(index++);
    }
    labels.push(array);
  }

  assert.equal(labels.length, headerValues[1]);
  return labels;
}

Примітки:

Тут мітки також є байтовими даними у файлі. У світі Javascript і підході, який ви маєте на початковому етапі, мітки також можуть бути масивом json.

навчіть модель:

await data.loadData();

  const {images: trainImages, labels: trainLabels} = data.getTrainData();
  model.summary();

  let epochBeginTime;
  let millisPerStep;
  const validationSplit = 0.15;
  const numTrainExamplesPerEpoch =
      trainImages.shape[0] * (1 - validationSplit);
  const numTrainBatchesPerEpoch =
      Math.ceil(numTrainExamplesPerEpoch / batchSize);
  await model.fit(trainImages, trainLabels, {
    epochs,
    batchSize,
    validationSplit
  });

Примітки:

Ось model.fitфактичний рядок коду, який робить це: тренує модель.

Результати всієї справи:

  const {images: testImages, labels: testLabels} = data.getTestData();
  const evalOutput = model.evaluate(testImages, testLabels);

  console.log(
      `\nEvaluation result:\n` +
      `  Loss = ${evalOutput[0].dataSync()[0].toFixed(3)}; `+
      `Accuracy = ${evalOutput[1].dataSync()[0].toFixed(3)}`);

Примітка:

У Data Science, також цього разу, найбільш захоплююча частина полягає в тому, щоб знати, наскільки добре модель пережила тест нових даних і жодних міток, може їх маркувати чи ні? Тому що це частина оцінки, яка тепер друкує нам деякі цифри.

Втрата і точність: [4]

Чим менші втрати, тим краща модель (якщо тільки модель не надто підходила до даних тренувань). Втрати обчислюються при навчанні та валідації, і його взаємодія полягає в тому, наскільки добре працює модель для цих двох наборів. На відміну від точності, втрати не є відсотком. Це підсумок помилок, зроблених для кожного прикладу в навчальних наборах чи валідаціях.

..

Точність моделі зазвичай визначається після того, як параметри моделі вивчаються та фіксуються і не відбувається навчання. Потім тестові зразки подаються до моделі і записується кількість помилок (нуль-одна втрата), яку робить модель, після порівняння з справжніми цілями.


Більше інформації:

На сторінках github, у файлі README.md, є посилання на підручник, де все у прикладі github пояснено більш докладно.


[1] https://github.com/tensorflow/tfjs-examples/tree/master/mnist

[2] https://github.com/tensorflow/tfjs-examples/tree/master/mnist-node

[3] https://en.wikipedia.org/wiki/MNIST_database

[4] Як інтерпретувати "втрату" та "точність" для моделі машинного навчання

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.