Come addestrare un modello in nodejs (tensorflow.js)?


29

Voglio fare un classificatore di immagini, ma non conosco Python. Tensorflow.js funziona con javascript, con cui ho familiarità. I modelli possono essere addestrati con esso e quali sarebbero i passaggi per farlo? Francamente non ho idea di dove cominciare.

L'unica cosa che ho capito è come caricare "mobilenet", che apparentemente è un insieme di modelli pre-addestrati, e classificare le immagini con esso:

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);

che funziona, ma non mi è utile perché voglio formare il mio modello usando le mie immagini con le etichette che creo.

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

Di 'che ho un sacco di immagini ed etichette. Come li uso per addestrare un modello?

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 ?

}

dove stai affrontando il problema. se hai caricato tensorflow, puoi allenare il tuo modello
Abhishek Anand,

2
Sembra che puoi addestrare i modelli con tensorflow.js tensorflow.org/js/guide/train_models che ho usato TensorFlow con Python. Se TensorFlow.js non utilizza la GPU, la formazione potrebbe richiedere molto tempo. Per me, colab.research.google.com è stata una risorsa utile perché è gratuita e fornisce 11 GB di GPU.
canbax,

1
Questa è una domanda troppo ampia ... Come sottolineato nei documenti , puoi usare ml5 per addestrare un modello o usare direttamente TF.js, come in questo esempio Node.js (espandi il codice di esempio per vedere un esempio di addestramento).
jdehesa,

Ma non vedo da nessuna parte in quel codice come passare le immagini e le etichette?
Alex

@Alex Vengono passati al fitmetodo o nel set di dati passato a fitDataset, come mostrato negli esempi.
jdehesa,

Risposte:


22

Prima di tutto, le immagini devono essere convertite in tensori. Il primo approccio sarebbe quello di creare un tensore contenente tutte le caratteristiche (rispettivamente un tensore contenente tutte le etichette). Questo dovrebbe essere il modo di procedere solo se il set di dati contiene poche immagini.

  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])

Le etichette sarebbero una matrice che indica il tipo di ogni immagine

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

È necessario ora creare una codifica a caldo delle etichette

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

Una volta che ci sono i tensori, si dovrebbe creare il modello per l'allenamento. Ecco un modello semplice.

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'}));

Quindi il modello può essere addestrato

model.fit(tensorFeatures, tensorLabels)

Se il set di dati contiene molte immagini, è necessario creare un tfDataset. Questa risposta discute il perché.

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);

E usare model.fitDataset(ds)per addestrare il modello


Quanto sopra è per l'addestramento in nodejs. Per eseguire tale elaborazione nel browser,genFeatureTensor possibile scrivere come segue:

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);
}

Un avvertimento è che eseguire elaborazioni pesanti potrebbe bloccare il thread principale nel browser. È qui che entrano in gioco i lavoratori del web.


la larghezza e l'altezza dall'input La forma deve corrispondere alla larghezza e all'altezza delle immagini? Quindi non riesco a trasmettere immagini con dimensioni diverse?
Alex

Sì, devono corrispondere. Se disponi di immagini di larghezza e altezza diverse dall'inputShape del modello, dovrai ridimensionare l'immagine utilizzandotf.image.resizeBilinear
edkeveked,

Beh, non funziona davvero. Ottengo errori
Alex

1
@Alex Potresti aggiornare la tua domanda con il riepilogo del modello e la forma dell'immagine che stai caricando? Tutte le immagini devono avere la stessa forma o l'immagine dovrebbe essere ridimensionata per l'allenamento
edkeveked,

1
hi @edkeveked, sto parlando di rilevamento di oggetti, ho aggiunto una nuova domanda qui si prega di dare un'occhiata stackoverflow.com/questions/59322382/...
Pranoy Sarkar

10

Considera l'esempio https://codelabs.developers.google.com/codelabs/tfjs-training-classfication/#0

Quello che fanno è:

  • prendere una GRANDE immagine png (una concatenazione verticale di immagini)
  • prendi alcune etichette
  • costruire il set di dati (data.js)

quindi allenarsi

La costruzione del set di dati è la seguente:

  1. immagini

L'immagine grande è divisa in n blocchi verticali. (n essendo chunkSize)

Prendi in considerazione un pezzo di dimensione 2.

Data la matrice di pixel dell'immagine 1:

  1 2 3
  4 5 6

Data la matrice di pixel dell'immagine 2 è

  7 8 9
  1 2 3

La matrice risultante sarebbe 1 2 3 4 5 6 7 8 9 1 2 3 (la concatenazione 1D in qualche modo)

Quindi sostanzialmente alla fine dell'elaborazione, hai un grosso buffer che rappresenta

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

  1. etichette

Questo tipo di formattazione viene fatto molto per problemi di classificazione. Invece di classificarsi con un numero, prendono un array booleano. Per prevedere 7 classi su 10 prenderemo in considerazione [0,0,0,0,0,0,0,1,0,0] // 1 in 7e position, array 0-indexed

Cosa puoi fare per iniziare

  • Prendi la tua immagine (e la sua etichetta associata)
  • Carica la tua immagine sulla tela
  • Estrarre il buffer associato
  • Concatena tutto il buffer dell'immagine come un grande buffer. Questo è tutto per xs.
  • Prendi tutte le etichette associate, mappale come un array booleano e concatenale.

Sotto, sottoclasse MNistData::load (il resto può essere lasciato com'è (tranne in script.js dove è invece necessario creare un'istanza della propria classe)

Genero ancora immagini 28x28, scrivo una cifra su di essa e ottengo una precisione perfetta poiché non includo rumore o etichette volontariamente errate.


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

Ho trovato un tutorial [1] su come utilizzare il modello esistente per formare nuove classi. Parti del codice principale qui:

testa index.html:

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

corpo index.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));

    ....

L'idea principale è quella di utilizzare la rete esistente per fare la sua previsione e quindi sostituire l'etichetta trovata con la propria.

Il codice completo è nel tutorial. Un altro promettente, più avanzato in [2]. Ha bisogno di una pre-elaborazione rigorosa, quindi la lascio solo qui, intendo che è molto più avanzata.

fonti:

[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


Per favore, dai un'occhiata alla mia seconda risposta, è molto più vicino alla realtà, da dove cominciare.
mico,

Perché non mettere entrambe le risposte in una?
edkeveked il

Hanno un approccio così diverso alla stessa cosa. Questo sopra, dove ora commento è in realtà una soluzione alternativa, l'altro sta iniziando dalle basi, che penso che in seguito sia più appropriato per l'impostazione della domanda.
mico,

3

TL; DR

MNIST è il riconoscimento delle immagini Hello World. Dopo averlo appreso a memoria, queste domande nella tua mente sono facili da risolvere.


Impostazione della domanda:

La tua domanda principale scritta è

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

all'interno del tuo blocco di codice. Per quelli ho trovato la risposta perfetta dagli esempi della sezione degli esempi di Tensorflow.js: esempio MNIST. I miei link sottostanti hanno versioni javascript e node.js pure e spiegazione di Wikipedia. Li esaminerò al livello necessario per rispondere alla domanda principale nella tua mente e aggiungerò anche prospettive su come le tue immagini e le tue etichette hanno qualcosa a che fare con il set di immagini MNIST e gli esempi che la utilizzano.

Cominciando dall'inizio:

Frammenti di codice.

dove passare le immagini (esempio 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;
}

Appunti:

Il set di dati MNIST è un'immagine enorme, in cui in un file ci sono diverse immagini come tessere in puzzle, ognuna con le stesse dimensioni, fianco a fianco, come caselle nella tabella di coordinamento xey. Ogni casella ha un campione e la corrispondente xey nell'array di etichette ha l'etichetta. Da questo esempio, non è un grosso problema trasformarlo in diversi formati di file, in modo che in realtà venga fornita solo una foto alla volta al ciclo while da gestire.

etichette:

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;
}

Appunti:

Qui, le etichette sono anche dati di byte in un file. Nel mondo Javascript, e con l'approccio che hai nel tuo punto di partenza, le etichette potrebbero anche essere un array json.

addestrare il modello:

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
  });

Appunti:

Qui model.fit l'attuale riga di codice che fa la cosa: addestra il modello.

Risultati di tutto:

  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)}`);

Nota:

In Data Science, anche questa volta, la parte più faschinante è sapere quanto il modello sopravvive al test di nuovi dati e senza etichette, può etichettarli o no? Per questo è la parte di valutazione che ora ci stampa alcuni numeri.

Perdita e precisione: [4]

Minore è la perdita, migliore è un modello (a meno che il modello non si sia adattato eccessivamente ai dati di allenamento). La perdita viene calcolata sull'addestramento e sulla validazione e la sua interperazione è quanto bene stia andando il modello per questi due set. A differenza della precisione, la perdita non è una percentuale. È una sintesi degli errori commessi per ciascun esempio nei set di addestramento o di validazione.

..

L'accuratezza di un modello è di solito determinata dopo che i parametri del modello sono stati appresi e fissati e non è in corso alcun apprendimento. Quindi i campioni di prova vengono inviati al modello e vengono registrati il ​​numero di errori (zero-one loss) che il modello commette, dopo il confronto con gli obiettivi reali.


Maggiori informazioni:

Nelle pagine di github, nel file README.md, c'è un collegamento al tutorial, dove tutto nell'esempio di github è spiegato in maggior dettaglio.


[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] Come interpretare "perdita" e "accuratezza" per un modello di apprendimento automatico

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.