nodejs (tensorflow.js)에서 모델을 훈련시키는 방법?


29

이미지 분류기를 만들고 싶지만 파이썬을 모르겠습니다. Tensorflow.js는 내가 익숙한 자바 스크립트와 함께 작동합니다. 모델을 학습 할 수 있으며 그렇게하는 단계는 무엇입니까? 솔직히 나는 어디서부터 시작할지 전혀 모른다.

내가 알아 낸 유일한 방법은 "모바일 넷"을로드하는 방법인데, 이것은 사전 훈련 된 모델 집합으로, 이미지를 분류합니다.

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 ?

}

어디에서 문제에 직면하고 있습니까? 당신이로드 tensorflow이있는 경우, 당신은 당신의 자신의 모델을 훈련 할 수
Abhishek 아난드

2
tensorflow.js로 모델을 훈련시킬 수있는 것처럼 보입니다 .tensorflow.org / js / guide / train_models 파이썬에서 TensorFlow를 사용했습니다. TensorFlow.js가 GPU를 사용하지 않는 경우 교육 시간이 오래 걸릴 수 있습니다. 나를 위해, colab.research.google.com는 이 무료이며 GPU의 11기가바이트을 제공하기 때문에 유용한 자원이었다.
canbax

1
이 너무 광범위한 질문입니다 ... 바와 같이 지적 워드 프로세서 , 당신은 사용할 수 있습니다 ml5을 위해 훈련 처럼, 직접 모델 또는 사용 TF.js을 이 Node.js를 예 (훈련의 예를 볼 수 샘플 코드를 확장).
jdehesa

그러나 이미지와 레이블을 전달하는 방법을 해당 코드의 어느 곳에서도 보지 못합니까?
Alex

@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의 너비와 높이가 이미지의 너비와 높이와 일치해야합니까? 크기가 다른 이미지를 전달할 수 없습니까?
Alex

예, 일치해야합니다. 모델의 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 개의 수직 청크로 나뉩니다. (n 청크 크기)

크기가 2 인 chunkSize를 고려하십시오.

이미지 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. 라벨

이러한 종류의 서식은 분류 문제에 대해 많이 수행됩니다. 숫자로 분류하는 대신 부울 배열을 사용합니다. 10 개의 클래스 중 7 개를 예측하려면 [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 헤드 :

   <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


제 두 번째 대답을 살펴보십시오. 처음부터 현실과 훨씬 더 가깝습니다.
mico

두 가지 답변을 모두 하나로 넣지 않겠습니까?
edkeveked

그들은 같은 것에 대한 다른 접근법을 가지고 있습니다. 위의 설명 중 하나는 실제로 해결 방법이며, 다른 하나는 기본 사항에서 시작하여 나중에 질문 설정에 더 적합하다고 생각합니다.
mico December

3

TL; DR

MNIST는 이미지 인식 Hello World입니다. 마음으로 그것을 배우고 나면, 당신의 마음에있는이 질문들은 쉽게 풀 수 있습니다.


질문 설정 :

당신의 주요 질문은

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

코드 블록 내부. 그런 사람들을 위해 Tensorflow.js 예제 섹션의 예제에서 완벽한 대답을 찾았습니다 : MNIST 예제. 아래 링크에는 순수한 javascript 및 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 좌표 테이블의 상자와 같이 같은 크기, 나란히, 같은 크기의 퍼즐 타일과 같은 여러 이미지가있는 거대한 이미지입니다. 각 상자에는 하나의 샘플이 있으며 labels 배열의 해당 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]

손실이 적을수록 모델이 향상됩니다 (모델이 훈련 데이터에 과도하게 적합하지 않은 경우). 손실은 훈련 및 검증에 의해 계산되며 그 상호 작용은 모델이이 두 세트에 대해 얼마나 잘 수행되고 있는지를 나타냅니다. 정확도와 달리 손실은 백분율이 아닙니다. 훈련 또는 검증 세트에서 각 예에 대해 발생한 오류를 요약 한 것입니다.

..

모델의 정확도는 일반적으로 모델 매개 변수를 학습하고 수정 한 후 학습이 수행되지 않은 후에 결정됩니다. 그런 다음 테스트 샘플을 모델에 공급하고 실제 목표와 비교 한 후 모델의 실수 횟수 (0 대 1 손실)를 기록합니다.


추가 정보:

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://ko.wikipedia.org/wiki/MNIST_database

[4] 기계 학습 모델의 "손실"및 "정확도"해석 방법

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.