Nodejs'de bir model nasıl eğitilir (tensorflow.js)?


29

Bir görüntü sınıflandırıcısı yapmak istiyorum, ancak python bilmiyorum. Tensorflow.js, aşina olduğum javascript ile çalışır. Modeller bununla eğitilebilir mi ve bunun için atılacak adımlar ne olurdu? Açıkçası nereden başlayacağım konusunda hiçbir fikrim yok.

Anladığım tek şey, görünüşe göre önceden eğitilmiş bir dizi model olan "mobilenet" in nasıl yükleneceği ve resimlerin onunla nasıl sınıflandırılacağı:

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

Bu işe yaramaz, ama benim için bir yararı yok çünkü kendi modellerimi oluşturduğum etiketlerle resimlerimi kullanarak eğitmek istiyorum.

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

Diyelim ki bir sürü resimim ve etiketlerim var. Onları bir model eğitmek için nasıl kullanabilirim?

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 ?

}

problemin neresi ile karşı karşıyasınız. tensorflow yüklediyseniz, kendi modelinizi eğitebilirsiniz
Abhishek Anand

2
Görünüşe göre tensorflow.js ile modelleri eğitebilirsiniz. Tensorflow.org/js/guide/train_models TensorFlow'u python ile kullandım. TensorFlow.js GPU kullanmıyorsa, eğitim uzun sürebilir. Benim için colab.research.google.com ücretsiz ve 11 GB GPU sağladığı için faydalı bir kaynaktı.
canbax

1
Bu çok geniş bir soru ... gibi işaret dokümanlar , kullanabileceğiniz Ml5 için eğitmek olduğu gibi doğrudan bir model ya da kullanım TF.js bu node.js örneğin (bir eğitim örnek görmek için örnek kod genişletmek).
jdehesa

Ama bu kodda hiçbir yerde resim ve etiketleri nasıl geçireceğimi görmüyorum?
Alex

@Alex Örneklerde gösterildiği gibi fityönteme veya geçirilen veri kümesinde geçirilir fitDataset.
jdehesa

Yanıtlar:


22

Her şeyden önce, görüntülerin tensörlere dönüştürülmesi gerekir. İlk yaklaşım, tüm özellikleri içeren bir tensör oluşturmaktır (sırasıyla tüm etiketleri içeren bir tensör). Bu, yalnızca veri kümesinde çok az resim varsa gidilecek yol olmalıdır.

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

Etiketler, her bir resmin türünü gösteren bir dizi olabilir

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

Şimdi etiketlerin sıcak kodlamasını oluşturmak için

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

Tensörler olduğunda, eğitim için bir model oluşturulması gerekir. İşte basit bir model.

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

Daha sonra model eğitilebilir

model.fit(tensorFeatures, tensorLabels)

Veri kümesi çok fazla resim içeriyorsa, bunun yerine bir tfDataset oluşturmanız gerekir. Bu cevap nedenini tartışıyor.

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

Ve model.fitDataset(ds)modeli eğitmek için kullanın


Yukarıda nodej'lerde eğitim içindir. Tarayıcıda böyle bir işlem yapmak için genFeatureTensoraşağıdaki gibi yazılabilir:

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

Dikkat edilmesi gereken bir nokta, ağır işlem yapmanın tarayıcıdaki ana iş parçacığını engelleyebileceğidir. Web çalışanları burada devreye giriyor.


inputShape öğesinin genişliği ve yüksekliği görüntülerin genişliği ve yüksekliğiyle eşleşmelidir? Bu yüzden farklı boyutlarda görüntüler iletemiyorum?
Alex

Evet, eşleşmeleri gerekiyor. Modelin girişinden farklı genişlik ve yükseklikteki resimleriniz varsa, görüntüyü yeniden boyutlandırmanız gerekirtf.image.resizeBilinear
edkeveked

Gerçekten işe yaramıyor. Hata alıyorum
Alex

1
@Alex Sorunuzu, model özeti ve yüklediğiniz görüntünün şekli ile güncelleyebilir misiniz? Tüm görüntülerin aynı şekle sahip olması veya görüntünün eğitim için yeniden boyutlandırılması gerekir
edkeveked

1
merhaba @edkeveked, nesne algılama hakkında konuşuyorum, buraya yeni bir soru ekledim lütfen bir göz atın stackoverflow.com/questions/59322382/…
Pranoy Sarkar

10

Örnek https://codelabs.developers.google.com/codelabs/tfjs-training-classfication/#0 düşünün

Yaptıkları şey:

  • BÜYÜK bir png resmi al (resimlerin dikey birleşimi)
  • bazı etiketler al
  • veri kümesini oluşturma (data.js)

sonra eğit

Veri kümesinin yapısı aşağıdaki gibidir:

  1. Görüntüler

Büyük görüntü n dikey parçaya bölünmüştür. (n chunkSize olarak)

2 boyutunda bir yığın düşünün.

Resim 1'in piksel matrisi verildiğinde:

  1 2 3
  4 5 6

Şekil 2'nin piksel matrisi verildiğinde

  7 8 9
  1 2 3

Sonuçta elde edilen dizi 1 2 3 4 5 6 7 8 9 1 2 3 (1D birleşimi bir şekilde)

Temel olarak işlemenin sonunda, temsil eden büyük bir tamponunuz var.

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

  1. etiketler

Bu tür biçimlendirme, sınıflandırma problemleri için çok fazla yapılır. Bir sayı ile sınıflandırmak yerine, bir boole dizisi alırlar. 10 sınıftan 7'sini tahmin etmek için [0,0,0,0,0,0,0,1,0,0] // 1 in 7e position, array 0-indexed

Başlamak için neler yapabilirsiniz?

  • Resminizi (ve ilişkili etiketini) alın
  • Resminizi tuvale yükleyin
  • İlişkili tamponu çıkar
  • Resminizin arabelleğini büyük bir arabellek olarak birleştirin. Xs için bu kadar.
  • İlişkili tüm etiketlerinizi alın, bir boole dizisi olarak eşleyin ve birleştirin.

Aşağıda, ben alt sınıf MNistData::load (geri kalanı olduğu gibi izin verilebilir (bunun yerine kendi sınıfınızı başlatmanız gereken script.js hariç)

Hala 28x28 görüntü oluşturuyorum, üzerine bir rakam yazıyorum ve gürültü veya gönüllü olarak yanlış etiketler eklemediğim için mükemmel bir doğruluk elde ediyorum.


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

Yeni sınıflar yetiştirmek için mevcut modelin nasıl kullanılacağına dair bir eğitim buldum. Burada ana kod parçaları:

index.html kafa:

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

index.html gövde:

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

    ....

Ana fikir, tahminini yapmak ve daha sonra bulunan etiketi kendi ağınızla değiştirmek için mevcut ağı kullanmaktır.

Kodun tamamı eğitimde. Bir başka umut verici, daha gelişmiş [2]. Sıkı ön işlemeye ihtiyacı var, bu yüzden sadece burada bırakıyorum, yani çok daha gelişmiş bir şey.

Kaynaklar:

[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


Lütfen, ikinci cevabıma bir bakın, nereden başlayacağınız gerçeğe çok daha yakın.
mico

Neden her iki cevabı tek bir cevapta vermiyorsunuz?
edkeveked

Aynı şeye çok farklı yaklaşımları var. Şimdi yorum yaptığım bu, aslında bir geçici çözüm, diğeri ise, daha sonra soru ayarına daha uygun olduğunu düşündüğüm temellerden başlıyor.
mico

3

TL; DR

MNIST görüntü tanıma Merhaba Dünya olduğunu. Kalbinizi öğrendikten sonra, aklınızdaki bu soruları çözmek kolaydır.


Soru belirleme:

Yazılan ana sorunuz:

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

kod bloğunuzun içinde. Bunlar için Tensorflow.js örnekler bölümü örneklerinden mükemmel cevap buldum: MNIST örneği. Aşağıdaki linklerimin saf javascript ve node.js sürümleri ve Wikipedia açıklaması var. Onları zihninizdeki ana soruyu cevaplamak için gereken seviyede geçireceğim ve kendi resimlerinizin ve etiketlerinizin MNIST görüntü kümesi ve onu kullanan örneklerle nasıl bir ilgisi olduğunu da perspektiflere ekleyeceğim.

Her şey sırayla:

Kod parçacıkları.

görüntülerin nereye aktarılacağı (Node.js örneği)

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

Notlar:

MNIST veri seti, bir dosyada, x ve y koordinasyon tablosundaki kutular gibi, her biri aynı boyutta, yan yana, her biri aynı boyutta, bulmacadaki fayanslar gibi birkaç görüntünün bulunduğu büyük bir görüntüdür. Her kutunun bir örneği vardır ve etiket dizisindeki karşılık gelen x ve y etiketine sahiptir. Bu örnekten, birkaç dosya biçimine dönüştürmek büyük bir şey değildir, böylece işlemek için while döngüsüne bir seferde sadece bir resim verilir.

Etiketler:

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

Notlar:

Burada, etiketler bir dosyadaki bayt verileridir. Javascript dünyasında ve başlangıç ​​noktanızdaki yaklaşımla etiketler bir json dizisi de olabilir.

modeli eğit:

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

Notlar:

İşte model.fitişi yapan gerçek kod satırı: modeli eğitir.

Her şeyin sonuçları:

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

Not:

Veri Biliminde, bu sefer de burada, en büyüleyici kısım, modelin yeni verilerin testinden ne kadar iyi kurtulduğunu ve etiketsiz kaldığını bilmek, bunları etiketleyebilir mi, değil mi? Çünkü şimdi bize bazı rakamlar basan değerlendirme bölümü.

Kayıp ve doğruluk: [4]

Kayıp ne kadar düşükse, bir model o kadar iyi olur (model eğitim verilerine fazla oturmamışsa). Kayıp, eğitim ve validasyonda hesaplanır ve yorumu, modelin bu iki set için ne kadar iyi olduğunu gösterir. Doğruluğun aksine, kayıp bir yüzde değildir. Eğitim veya doğrulama setlerinde her örnek için yapılan hataların bir toplamıdır.

..

Bir modelin doğruluğu genellikle model parametreleri öğrenilip sabitlendikten ve hiçbir öğrenme yapılmadığında belirlenir. Daha sonra test örnekleri modele beslenir ve gerçek hedeflerle karşılaştırıldıktan sonra modelin yaptığı hata sayısı (sıfır bir kayıp) kaydedilir.


Daha fazla bilgi:

Github sayfalarında, README.md dosyasında, github örneğindeki tüm bilgilerin daha ayrıntılı olarak açıklandığı öğretici bir bağlantı vardır.


[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] Bir makine öğrenme modeli için "kayıp" ve "doğruluk" nasıl yorumlanır

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.