Làm thế nào để đào tạo một mô hình trong nodejs (tenorflow.js)?


29

Tôi muốn tạo một bộ phân loại hình ảnh, nhưng tôi không biết python. Tensorflow.js hoạt động với javascript, mà tôi quen thuộc. Các mô hình có thể được đào tạo với nó và các bước để làm như vậy là gì? Thành thật mà nói tôi không có đầu mối bắt đầu từ đâu.

Điều duy nhất tôi tìm ra là làm thế nào để tải "Mobilenet", mà rõ ràng là một tập hợp các mô hình được đào tạo trước và phân loại hình ảnh với nó:

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

Nó hoạt động, nhưng nó không hữu dụng với tôi vì tôi muốn đào tạo mô hình của riêng mình bằng cách sử dụng hình ảnh của tôi với nhãn mà tôi tạo.

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

Nói rằng tôi có một loạt các hình ảnh và nhãn hiệu. Làm thế nào để tôi sử dụng chúng để đào tạo một mô hình?

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 ?

}

bạn đang đối mặt với vấn đề ở đâu nếu bạn đã tải dòng chảy căng, bạn có thể đào tạo mô hình của riêng mình
Abhishek Anand

2
Có vẻ như bạn có thể huấn luyện các mô hình với tenorflow.js tenorflow.org/js/guide/train_models Tôi đã sử dụng TensorFlow với python. Nếu TensorFlow.js không sử dụng GPU, việc đào tạo có thể mất nhiều thời gian. Đối với tôi, colab.research.google.com là một tài nguyên hữu ích vì nó miễn phí và cung cấp 11 GB GPU.
canbax

1
Đây là một câu hỏi quá rộng ... Như đã chỉ ra trong các tài liệu , bạn có thể sử dụng ml5 để huấn luyện một mô hình hoặc sử dụng trực tiếp TF.js, như trong ví dụ Node.js này (mở rộng mã mẫu để xem ví dụ đào tạo).
jdehesa

Nhưng tôi không thấy bất cứ nơi nào trong mã đó làm thế nào để vượt qua hình ảnh và nhãn?
Alex

@Alex Chúng được truyền cho fitphương thức, hoặc trong tập dữ liệu được truyền tới fitDataset, như trong các ví dụ.
jdehesa

Câu trả lời:


22

Trước hết, các hình ảnh cần phải được chuyển đổi thành tenxơ. Cách tiếp cận đầu tiên sẽ là tạo ra một tenxơ chứa tất cả các tính năng (tương ứng là một tenxơ chứa tất cả các nhãn). Đây chỉ là cách để đi nếu tập dữ liệu chứa một vài hình ảnh.

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

Các nhãn sẽ là một mảng chỉ ra loại của mỗi hình ảnh

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

Bây giờ bạn cần tạo một mã hóa nóng của các nhãn

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

Một khi có các tenxơ, người ta sẽ cần tạo ra mô hình để đào tạo. Đây là một mô hình đơn giản.

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

Sau đó, mô hình có thể được đào tạo

model.fit(tensorFeatures, tensorLabels)

Nếu tập dữ liệu chứa nhiều hình ảnh, người ta sẽ cần phải tạo một tfDataset thay thế. Câu trả lời này thảo luận tại sao.

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

Và sử dụng model.fitDataset(ds)để đào tạo mô hình


Trên đây là để đào tạo trong nodejs. Để thực hiện xử lý như vậy trong trình duyệt,genFeatureTensor có thể được viết như sau:

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

Một lưu ý là việc xử lý nặng có thể chặn luồng chính trong trình duyệt. Đây là nơi các nhân viên web đi vào chơi.


chiều rộng và chiều cao từ inputShape phải phù hợp với chiều rộng và chiều cao của hình ảnh? Vì vậy, tôi không thể vượt qua hình ảnh với kích thước khác nhau?
Alex

Vâng, họ phải phù hợp. Nếu bạn có hình ảnh có chiều rộng và chiều cao khác nhau từ inputShape của mô hình, bạn sẽ cần thay đổi kích thước hình ảnh bằng cách sử dụngtf.image.resizeBilinear
edkeveked

Chà, nó không thực sự hoạt động. Tôi gặp lỗi
Alex

1
@Alex Bạn có thể vui lòng cập nhật câu hỏi của bạn với tóm tắt mô hình và hình dạng của hình ảnh bạn đang tải không? Tất cả các hình ảnh cần phải có cùng hình dạng hoặc hình ảnh sẽ cần được thay đổi kích thước cho khóa đào tạo
edkeveked

1
hi @edkeveked, tôi đang nói về phát hiện đối tượng, tôi đã thêm một câu hỏi mới ở đây xin vui lòng xem stackoverflow.com/questions/59322382/
Lỗi

10

Hãy xem xét mẫu mực https://codelabs.developers.google.com/codelabs/tfjs-training- / # 0

Những gì họ làm là:

  • chụp ảnh png LỚN (ghép hình dọc)
  • lấy một số nhãn
  • xây dựng bộ dữ liệu (data.js)

sau đó đào tạo

Việc xây dựng bộ dữ liệu như sau:

  1. hình ảnh

Hình ảnh lớn được chia thành n khối dọc. (n là chunkSize)

Xem xét một kích thước kích thước 2.

Cho ma trận pixel của hình ảnh 1:

  1 2 3
  4 5 6

Cho ma trận pixel của ảnh 2 là

  7 8 9
  1 2 3

Mảng kết quả sẽ là 1 2 3 4 5 6 7 8 9 1 2 3 (cách ghép 1D bằng cách nào đó)

Vì vậy, về cơ bản khi kết thúc quá trình xử lý, bạn có một bộ đệm lớn đại diện cho

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

  1. nhãn

Đó là loại định dạng được thực hiện rất nhiều cho các vấn đề phân loại. Thay vì phân loại với một số, họ lấy một mảng boolean. Để dự đoán 7 trên 10 lớp chúng tôi sẽ xem xét [0,0,0,0,0,0,0,1,0,0] // 1 in 7e position, array 0-indexed

Bạn có thể làm gì để bắt đầu

  • Lấy hình ảnh của bạn (và nhãn liên quan của nó)
  • Tải hình ảnh của bạn vào khung vẽ
  • Trích xuất bộ đệm liên quan của nó
  • Ghép tất cả bộ đệm của hình ảnh của bạn như một bộ đệm lớn. Đó là cho xs.
  • Lấy tất cả các nhãn liên quan của bạn, ánh xạ chúng thành một mảng boolean và nối chúng.

Dưới đây, tôi phân lớp MNistData::load (phần còn lại có thể được để nguyên (ngoại trừ trong script.js nơi bạn cần khởi tạo lớp của riêng mình)

Tôi vẫn tạo ra hình ảnh 28x28, viết một chữ số trên đó và có được độ chính xác hoàn hảo vì tôi không bao gồm nhiễu hoặc tự ý dán nhãn sai.


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

Tôi tìm thấy một hướng dẫn [1] cách sử dụng mô hình hiện có để đào tạo các lớp mới. Phần mã chính ở đây:

chỉ mục.html

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

chỉ mục cơ thể:

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

    ....

Ý tưởng chính là sử dụng mạng hiện có để đưa ra dự đoán và sau đó thay thế nhãn tìm thấy bằng nhãn của riêng bạn.

Mã hoàn chỉnh là trong hướng dẫn. Một triển vọng khác, tiên tiến hơn trong [2]. Nó cần xử lý trước nghiêm ngặt, vì vậy tôi chỉ để nó ở đây, ý tôi là nó tiên tiến hơn nhiều.

Nguồn:

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

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


Xin vui lòng, hãy xem câu trả lời thứ hai của tôi, nó gần với thực tế hơn, bắt đầu từ đâu.
mico

Tại sao không đặt cả hai câu trả lời vào một?
edkeveked

Họ có cách tiếp cận rất khác nhau cho cùng một điều. Cái này ở trên, nơi tôi nhận xét bây giờ thực sự là một cách giải quyết, cái khác đang bắt đầu từ những điều cơ bản, mà tôi nghĩ bây giờ sau này là phù hợp hơn với việc đặt câu hỏi.
mico

3

TL; DR

MNIST là nhận dạng hình ảnh Hello World. Sau khi học nó bằng trái tim, những câu hỏi trong tâm trí của bạn rất dễ giải quyết.


Đặt câu hỏi:

Câu hỏi chính của bạn được viết là

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

bên trong khối mã của bạn. Đối với những người tôi đã tìm thấy câu trả lời hoàn hảo từ các ví dụ của phần ví dụ Tensorflow.js: ví dụ MNIST. Các liên kết bên dưới của tôi có các phiên bản javascript và node.js thuần túy và giải thích Wikipedia. Tôi sẽ xem xét chúng ở mức độ cần thiết để trả lời câu hỏi chính trong đầu bạn và tôi cũng sẽ thêm các quan điểm về cách hình ảnh và nhãn của riêng bạn có liên quan gì đến bộ ảnh MNIST và các ví dụ sử dụng nó.

Điều đầu tiên trước tiên:

Đoạn mã.

nơi để truyền hình ảnh (mẫu 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;
}

Ghi chú:

Bộ dữ liệu của MNIST là một hình ảnh khổng lồ, trong đó trong một tệp có một số hình ảnh giống như các ô trong câu đố, mỗi hình có cùng kích thước, cạnh nhau, giống như các hộp trong bảng phối hợp x và y. Mỗi hộp có một mẫu và x và y tương ứng trong mảng nhãn có nhãn. Từ ví dụ này, việc chuyển nó sang định dạng một số tệp không phải là vấn đề lớn, do đó thực tế chỉ có một hình ảnh tại một thời điểm được đưa vào vòng lặp while để xử lý.

Nhãn:

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

Ghi chú:

Ở đây, nhãn cũng là dữ liệu byte trong một tệp. Trong thế giới Javascript và với cách tiếp cận bạn có trong điểm xuất phát của mình, nhãn cũng có thể là một mảng json.

đào tạo người mẫu:

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

Ghi chú:

Đây model.fit là dòng mã thực tế thực hiện công việc: đào tạo mô hình.

Kết quả của toàn bộ:

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

Ghi chú:

Trong Khoa học dữ liệu, lần này cũng ở đây, phần khó hiểu nhất là để biết mô hình sống sót qua thử nghiệm dữ liệu mới và không có nhãn như thế nào, nó có thể gắn nhãn cho chúng hay không? Vì đó là phần đánh giá hiện in cho chúng tôi một số con số.

Mất và chính xác: [4]

Mất mát càng thấp, mô hình càng tốt (trừ khi mô hình đã quá phù hợp với dữ liệu đào tạo). Mất mát được tính toán trên đào tạo và xác nhận và sự can thiệp của nó là mô hình hoạt động tốt như thế nào đối với hai bộ này. Không giống như độ chính xác, mất không phải là một tỷ lệ phần trăm. Đây là tổng hợp các lỗi được tạo cho từng ví dụ trong tập huấn luyện hoặc xác nhận hợp lệ.

..

Độ chính xác của một mô hình thường được xác định sau khi các tham số mô hình được học và cố định và không có việc học nào diễn ra. Sau đó, các mẫu thử được đưa vào mô hình và số lỗi (mất không một) mà mô hình tạo ra được ghi lại, sau khi so sánh với các mục tiêu thực sự.


Thêm thông tin:

Trong các trang github, trong tệp README.md, có một liên kết đến hướng dẫn, trong đó tất cả trong ví dụ về github được giải thích chi tiết hơn.


[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] Cách diễn giải "mất" và "chính xác" cho mô hình học máy

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.