วิธีการฝึกอบรมแบบใน nodejs (tensorflow.js)?


29

ฉันต้องการสร้างลักษณนามภาพ แต่ฉันไม่รู้จักหลาม 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 ?

}

คุณประสบปัญหาที่ไหน ถ้าคุณโหลดเทนเซอร์ไหลคุณสามารถฝึกนางแบบของคุณเอง
Abhishek Anand

2
ดูเหมือนว่าคุณสามารถฝึกโมเดลได้ด้วย tensorflow.js tensorflow.org/js/guide/train_modelsฉันใช้ TensorFlow กับ python หาก TensorFlow.js ไม่ได้ใช้ GPU การฝึกอบรมอาจใช้เวลานาน สำหรับฉันcolab.research.google.comเป็นทรัพยากรที่มีประโยชน์เพราะมันฟรีและให้ GPU 11 GB
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);
}

คำเตือนอย่างหนึ่งคือการทำการประมวลผลอย่างหนักอาจบล็อกเธรดหลักในเบราว์เซอร์ นี่คือที่คนทำงานเว็บเข้ามาเล่น


ความกว้างและความสูงจาก inputShape จะต้องตรงกับความกว้างและความสูงของภาพ? ดังนั้นฉันไม่สามารถส่งผ่านภาพที่มีขนาดต่างกันได้หรือไม่
อเล็กซ์

ใช่พวกเขาจะต้องตรงกัน หากคุณมีภาพที่มีความกว้างและความสูงที่แตกต่างจากรูปร่างของแบบจำลองคุณจะต้องปรับขนาดภาพโดยใช้tf.image.resizeBilinear
edkeveked

มันไม่ได้ผลจริงๆ ฉันได้รับข้อผิดพลาด
Alex

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

กำหนดให้ matrix matrix ของภาพ 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 head:

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

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

    ....

แนวคิดหลักคือการใช้เครือข่ายที่มีอยู่เพื่อทำการคาดการณ์และแทนที่ป้ายกำกับที่พบด้วยเครือข่ายของคุณเอง

รหัสที่สมบูรณ์อยู่ในบทช่วยสอน อีกอันหนึ่งที่มีแนวโน้มสูงกว่าใน [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


ได้โปรดดูคำตอบที่สองของฉันมันใกล้เคียงกับความเป็นจริงมากขึ้นเริ่มต้นด้วย
แฟลช

ทำไมไม่ลองใส่คำตอบทั้งคู่ลงไป?
edkeveked

พวกเขามีวิธีการที่แตกต่างกันในสิ่งเดียวกัน อันแรกที่ฉันแสดงความคิดเห็นในตอนนี้เป็นวิธีแก้ปัญหาจริง ๆ และอีกอันเริ่มต้นจากพื้นฐานซึ่งฉันคิดว่าตอนนี้ในภายหลังเหมาะสมกว่าสำหรับการตั้งค่าคำถาม
mico

3

TL; DR

MNIST เป็นการจดจำรูปภาพ Hello World หลังจากเรียนรู้ด้วยใจแล้วคำถามเหล่านี้ในใจของคุณแก้ง่าย


การตั้งค่าคำถาม:

คำถามหลักที่คุณเขียนคือ

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

ภายในบล็อครหัสของคุณ สำหรับผู้ที่ฉันพบคำตอบที่สมบูรณ์จากตัวอย่างของ Tensorflow.js ส่วนตัวอย่าง: ตัวอย่าง MNIST ลิงก์ด้านล่างของฉันมีจาวาสคริปต์และรุ่น node.js ล้วนๆและคำอธิบาย Wikipedia ฉันจะผ่านมันไปในระดับที่จำเป็นเพื่อตอบคำถามหลักในใจของคุณและฉันจะเพิ่มมุมมองว่าภาพและป้ายกำกับของคุณมีส่วนเกี่ยวข้องกับชุดภาพ 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 แต่ละกล่องมีหนึ่งตัวอย่างและ 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)}`);

บันทึก:

ในวิทยาศาสตร์ข้อมูลคราวนี้ที่นี่ส่วนที่น่าสนใจที่สุดคือการรู้ว่าแบบจำลองนี้รอดชีวิตจากการทดสอบข้อมูลใหม่และไม่มีป้ายกำกับหรือไม่มันสามารถติดป้ายชื่อได้หรือไม่? เพราะนั่นคือส่วนการประเมินผลที่จะพิมพ์ตัวเลขจำนวนหนึ่งให้กับเรา

การสูญเสียและความแม่นยำ: [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] วิธีตีความ "การสูญเสีย" และ "ความแม่นยำ" สำหรับโมเดลการเรียนรู้ของเครื่อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.