Bagaimana cara melatih model dalam nodejs (tensorflow.js)?

29

Saya ingin membuat penggolong gambar, tapi saya tidak tahu python. Tensorflow.js bekerja dengan javascript, yang saya kenal. Bisakah model dilatih dengan itu dan apa langkah-langkahnya? Terus terang saya tidak tahu harus mulai dari mana.

Satu-satunya hal yang saya tahu adalah bagaimana memuat "mobilenet", yang tampaknya adalah satu set model pra-terlatih, dan mengklasifikasikan gambar dengan itu:

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

yang berfungsi, tetapi tidak ada gunanya bagi saya karena saya ingin melatih model saya sendiri menggunakan gambar saya dengan label yang saya buat.

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

Katakanlah saya punya banyak gambar dan label. Bagaimana cara menggunakannya untuk melatih model?

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 ?

}
Alex
sumber
di mana Anda menghadapi masalah. jika Anda telah memuat tensorflow, Anda dapat melatih model Anda sendiri
Abhishek Anand
2
Sepertinya Anda dapat melatih model dengan tensorflow.js tensorflow.org/js/guide/train_models Saya menggunakan TensorFlow dengan python. Jika TensorFlow.js tidak menggunakan GPU, pelatihan mungkin memakan waktu lama. Bagi saya, colab.research.google.com adalah sumber yang berguna karena gratis dan menyediakan GPU 11 GB.
canbax
1
Ini adalah pertanyaan yang terlalu luas ... Seperti yang ditunjukkan dalam dokumen , Anda dapat menggunakan ml5 untuk melatih model atau menggunakan TF.js secara langsung, seperti dalam contoh Node.js ini (perluas kode sampel untuk melihat contoh pelatihan).
jdehesa
Tapi saya tidak melihat di mana pun dalam kode itu bagaimana cara mengirimkan gambar dan label?
Alex
@Alex Mereka dilewatkan ke fitmetode, atau dalam dataset dilewatkan ke fitDataset, seperti yang ditunjukkan dalam contoh.
jdehesa

Jawaban:

22

Pertama-tama, gambar perlu dikonversi menjadi tensor. Pendekatan pertama adalah membuat tensor yang berisi semua fitur (masing-masing tensor berisi semua label). Seharusnya cara ini hanya berlaku jika dataset berisi beberapa gambar.

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

Label akan berupa array yang menunjukkan jenis setiap gambar

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

Sekarang kita perlu membuat enkode label panas

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

Begitu ada tensor, seseorang perlu membuat model untuk pelatihan. Ini adalah model sederhana.

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

Maka modelnya bisa dilatih

model.fit(tensorFeatures, tensorLabels)

Jika dataset berisi banyak gambar, seseorang harus membuat tfDataset sebagai gantinya. Jawaban ini membahas mengapa.

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

Dan gunakan model.fitDataset(ds)untuk melatih model


Di atas adalah untuk pelatihan dalam nodejs. Untuk melakukan pemrosesan seperti itu di browser, genFeatureTensordapat ditulis sebagai berikut:

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

Satu kata peringatan adalah bahwa melakukan pemrosesan yang berat dapat memblokir utas di browser. Di sinilah pekerja web ikut bermain.

edkeveked
sumber
lebar dan tinggi dari inputShape harus sesuai dengan lebar dan tinggi gambar? Jadi saya tidak bisa melewatkan gambar dengan dimensi yang berbeda?
Alex
Ya mereka harus cocok. Jika Anda memiliki gambar dengan lebar dan tinggi berbeda dari inputShape model, Anda perlu mengubah ukuran gambar dengan menggunakantf.image.resizeBilinear
edkeveked
Ya, itu tidak benar-benar bekerja. Saya mendapatkan kesalahan
Alex
1
@Alex Bisakah Anda memperbarui pertanyaan Anda dengan ringkasan model dan bentuk gambar yang Anda muat? Semua gambar harus memiliki bentuk yang sama atau gambar harus diubah ukurannya untuk pelatihan
edkeveked
1
hai @edkeveked, saya berbicara tentang deteksi objek, saya telah menambahkan pertanyaan baru di sini, silakan lihat stackoverflow.com/questions/59322382/…
Pranoy Sarkar
10

Pertimbangkan contohnya https://codelabs.developers.google.com/codelabs/tfjs-training-classfication/#0

Apa yang mereka lakukan adalah:

  • mengambil gambar png BESAR (gabungan vertikal gambar)
  • ambil beberapa label
  • membangun dataset (data.js)

lalu latih

Bangunan dataset adalah sebagai berikut:

  1. gambar-gambar

Gambar besar dibagi menjadi n potongan vertikal. (n sedang chunkSize)

Pertimbangkan sepotong Ukuran 2.

Diberi matriks piksel gambar 1:

  1 2 3
  4 5 6

Mengingat matriks pixel dari gambar 2 adalah

  7 8 9
  1 2 3

Array yang dihasilkan adalah 1 2 3 4 5 6 7 8 9 1 2 3(rangkaian 1D entah bagaimana)

Jadi pada dasarnya di akhir pemrosesan, Anda memiliki buffer besar yang mewakili

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

  1. label

Pemformatan semacam itu banyak dilakukan untuk masalah klasifikasi. Alih-alih mengklasifikasikan dengan angka, mereka mengambil array boolean. Untuk memprediksi 7 dari 10 kelas kami akan mempertimbangkan [0,0,0,0,0,0,0,1,0,0] // 1 in 7e position, array 0-indexed

Apa yang dapat Anda lakukan untuk memulai

  • Ambil gambar Anda (dan label terkait)
  • Muat gambar Anda ke kanvas
  • Ekstrak buffer terkait
  • Gabungkan semua buffer gambar Anda sebagai buffer besar. Itu untuk xs.
  • Ambil semua label terkait Anda, petakan sebagai boolean array, dan gabungkan.

Di bawah, saya subkelas MNistData::load(sisanya dapat dibiarkan apa adanya (kecuali dalam script.js di mana Anda harus membuat instance kelas Anda sendiri sebagai gantinya)

Saya masih menghasilkan 28x28 gambar, menulis angka di atasnya, dan mendapatkan akurasi yang sempurna karena saya tidak menyertakan noise atau pelabelan sukarela yang salah.


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

}
grodzi
sumber
8

Saya menemukan tutorial [1] cara menggunakan model yang ada untuk melatih kelas baru. Bagian kode utama di sini:

kepala index.html:

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

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

    ....

Gagasan utama adalah menggunakan jaringan yang ada untuk membuat prediksi dan kemudian mengganti label yang ditemukan dengan yang Anda miliki.

Kode lengkap ada di tutorial. Satu lagi menjanjikan, lebih maju dalam [2]. Ini membutuhkan pemrosesan awal yang ketat, jadi saya tinggalkan hanya di sini, maksud saya ini jauh lebih maju.

Sumber:

[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
sumber
Tolong, lihat jawaban kedua saya, itu jauh lebih dekat dengan kenyataan, di mana untuk memulai.
mico
Mengapa tidak memasukkan kedua jawaban itu menjadi satu?
edkeveked
Mereka memiliki pendekatan yang berbeda untuk hal yang sama. Ini di atas, di mana saya berkomentar sekarang sebenarnya adalah solusi, yang lain mulai dari dasar-dasar, yang saya pikir sekarang nanti lebih tepat untuk pengaturan pertanyaan.
mico
3

TL; DR

MNIST adalah pengenalan gambar Hello World. Setelah mempelajarinya dengan hati, pertanyaan-pertanyaan ini dalam pikiran Anda mudah untuk dipecahkan.


Pengaturan pertanyaan:

Pertanyaan utama Anda yang tertulis adalah

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

di dalam blok kode Anda. Bagi mereka saya menemukan jawaban sempurna dari contoh bagian contoh Tensorflow.js: contoh MNIST. Tautan saya di bawah ini memiliki versi javascript dan node.js murni dan penjelasan Wikipedia. Saya akan membahasnya pada level yang diperlukan untuk menjawab pertanyaan utama dalam pikiran Anda dan saya akan menambahkan juga perspektif bagaimana gambar dan label Anda sendiri ada hubungannya dengan set gambar MNIST dan contoh-contoh yang menggunakannya.

Hal pertama yang pertama:

Cuplikan kode.

tempat untuk mengirimkan gambar (sampel 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;
}

Catatan:

Dataset MNIST adalah gambar besar, di mana dalam satu file ada beberapa gambar seperti ubin dalam puzzle, masing-masing dengan ukuran yang sama, berdampingan, seperti kotak di tabel koordinasi x dan y. Setiap kotak memiliki satu sampel dan sesuai x dan y dalam array label memiliki label. Dari contoh ini, bukan masalah besar untuk mengubahnya ke beberapa format file, sehingga sebenarnya hanya satu gambar pada satu waktu diberikan ke loop sementara untuk menangani.

Label:

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

Catatan:

Di sini, label juga data byte dalam file. Di dunia Javascript, dan dengan pendekatan yang Anda miliki di titik awal, label juga bisa berupa json array.

melatih model:

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

Catatan:

Inilah model.fitbaris kode aktual yang melakukan hal itu: melatih model.

Hasil dari semuanya:

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

catatan:

Dalam Ilmu Data, juga kali ini di sini, bagian yang paling menarik adalah untuk mengetahui seberapa baik model bertahan dari pengujian data baru dan tidak ada label, dapatkah mereka melabelinya atau tidak? Untuk itu adalah bagian evaluasi yang sekarang mencetak beberapa angka.

Kehilangan dan keakuratan: [4]

Semakin rendah kerugiannya, semakin baik suatu model (kecuali jika model tersebut terlalu cocok dengan data pelatihan). Kerugian dihitung pada pelatihan dan validasi dan interperasinya adalah seberapa baik model melakukan untuk dua set ini. Tidak seperti akurasi, kerugian bukanlah persentase. Ini adalah penjumlahan dari kesalahan yang dibuat untuk setiap contoh dalam set pelatihan atau validasi.

..

Keakuratan model biasanya ditentukan setelah parameter model dipelajari dan diperbaiki dan tidak ada pembelajaran yang terjadi. Kemudian sampel uji dimasukkan ke model dan jumlah kesalahan (nol-satu kerugian) yang dibuat model dicatat, setelah dibandingkan dengan target yang sebenarnya.


Informasi lebih lanjut:

Di halaman github, dalam file README.md, ada tautan ke tutorial, di mana semua dalam contoh github dijelaskan secara lebih rinci.


[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] Cara menginterpretasikan "kehilangan" dan "akurasi" untuk model pembelajaran mesin

mico
sumber