nodejs(tensorflow.js)でモデルをトレーニングする方法は?


29

画像分類器を作りたいのですが、Pythonがわかりません。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 ?

}

あなたはどこで問題に直面していますか?tensorflowを読み込んだ場合は、独自のモデルをトレーニングできます
Abhishek Anand

2
tensorflow.jsを使用してモデルをトレーニングできるようですtensorflow.org/js/guide/train_models PythonでTensorFlowを使用しました。TensorFlow.jsがGPUを使用していない場合、トレーニングには長い時間がかかる可能性があります。私にとって、colab.research.google.comは無料で11 GBのGPUを提供するため、便利なリソースでした。
canbax

1
これは広すぎる質問です... docsで指摘されているようにml5を使用してモデルをトレーニングするか、このNode.jsの例のようにTF.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);
}

注意すべきことは、重い処理を行うと、ブラウザのメインスレッドがブロックされる可能性があるということです。ここでWebワーカーが活躍します。


inputShapeの幅と高さは画像の幅と高さに一致している必要がありますか?では、異なる次元の画像を渡すことはできませんか?
アレックス

はい、一致する必要があります。モデルのinputShapeとは異なる幅と高さの画像がある場合は、次を使用して画像のサイズを変更する必要がありますtf.image.resizeBilinear
edkeveked

まあ、それは実際には機能しません。エラーが発生する
Alex

1
@アレックスモデルの概要とロードする画像の形状で質問を更新していただけませんか?すべての画像は同じ形状を持っている必要がありますまたは画像は、トレーニングのためにサイズを変更する必要があるだろう
edkeveked

1
こんにちは@edkeveked、私はオブジェクト検出について話しています、私はここに新しい質問を追加した表情を持ってくださいstackoverflow.com/questions/59322382/...を
Pranoyサルカール

10

例を検討してくださいhttps://codelabs.developers.google.com/codelabs/tfjs-training-classfication/#0

彼らがすることは:

  • BIG png画像(画像の垂直連結)を取得します
  • ラベルを取る
  • データセット(data.js)を構築する

次に訓練する

データセットの構築は次のとおりです。

  1. 画像

大きな画像はn個の垂直チャンクに分割されます。(nはchunkSizeです)

サイズ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つの有望でより高度なもの。厳密な前処理が必要なので、ここだけにしておくと、かなり高度なものになります。

出典:

[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


私の2番目の答えを見てください。それは、どこから始めれば現実にはるかに近いかです。
mico

両方の答えを1つに入れてみませんか?
19

彼らは同じものに対して非常に異なるアプローチを持っています。これは私がコメントしている上記の1つは実際には回避策ですが、もう1つは基本から始めています。これは、後で質問の設定により適していると思います。
mico

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データセットは巨大な画像で、1つのファイルにパズルのタイルのようないくつかの画像があり、xとyの調整テーブルのボックスのように、それぞれが同じサイズで並んでいます。各ボックスには1つのサンプルがあり、labels配列の対応するxとyにはラベルがあります。この例から、それを複数のファイル形式に変換することは大したことではないので、実際には、whileループに一度に1つのpicのみが渡されて処理されます。

ラベル:

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]

損失が少ないほど、モデルは優れています(モデルがトレーニングデータに適合していなければ)。損失は​​トレーニングと検証で計算され、その相互作用は、モデルがこれら2つのセットに対してどの程度適切に機能しているかを示します。精度とは異なり、損失はパーセンテージではありません。これは、トレーニングセットまたは検証セットの各例で発生したエラーの合計です。

..

モデルの精度は通常、モデルのパラメーターが学習および修正され、学習が行われなくなった後で決定されます。次に、テストサンプルがモデルに送られ、真のターゲットと比較した後、モデルで発生したミス(ゼロワンロス)の数が記録されます。


詳しくは:

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.