Tensorflow.jsでMNIST画像データを処理する方法

データサイエンスの80%がデータのクリーニングを行っており、20%がデータのクリーニングについて不平を言っているという冗談があります。実際、トレーニングモデルは通常、機械学習者またはデータサイエンティストが行うことの比較的小さな割合(10%未満)です。

 — Angony Goldbloom、KaggleのCEO

データの操作は、機械学習の問題にとって重要なステップです。この記事では、Tensorflow.js(0.11.1)のMNISTの例を取り上げ、データのロードを行ごとに処理するコードを説明します。

MNISTの例

18 * '@ tensorflow / tfjs'からtfとしてインポート。
19
20 const IMAGE_SIZE = 784;
21 const NUM_CLASSES = 10;
22 const NUM_DATASET_ELEMENTS = 65000;
23
24 const NUM_TRAIN_ELEMENTS = 55000;
25 const NUM_TEST_ELEMENTS = NUM​​_DATASET_ELEMENTS-NUM_TRAIN_ELEMENTS;
26
27 const MNIST_IMAGES_SPRITE_PATH =
28 'https://storage.googleapis.com/learnjs-data/model-builder/mnist_images.png';
29 const MNIST_LABELS_PATH =
30 'https://storage.googleapis.com/learnjs-data/model-builder/mnist_labels_uint8'; `

最初に、コードはTensorflowをインポートし(コードをトランスコンパイルしていることを確認してください!)、以下を含むいくつかの定数を確立します。

  • IMAGE_SIZE –画像のサイズ(28x28の幅と高さ= 784)
  • NUM_CLASSES –ラベルカテゴリの数(0〜9の数値が可能なため、10クラスあります)
  • NUM_DATASET_ELEMENTS –合計画像数(65,000)
  • NUM_TRAIN_ELEMENTS –トレーニング画像の数(55,000)
  • NUM_TEST_ELEMENTS –テスト画像の数(10,000、別名残り)
  • MNIST_IMAGES_SPRITE_PATH&MNIST_LABELS_PATH –画像とラベルへのパス

画像は、次のような1つの巨大な画像に連結されます。

MNISTData

次に、38行目から始まるMnistDataは、次の関数を公開するクラスです。

  • ロード–画像の非同期ロードとデータのラベル付けを行います
  • nextTrainBatch –次のトレーニングバッチを読み込む
  • nextTestBatch –次のテストバッチをロードする
  • nextBatch –トレーニングセットまたはテストセットにあるかどうかに応じて、次のバッチを返す汎用関数

はじめに、この記事ではロード機能のみを説明します。

負荷

44 async load(){
45 // MNISTスプリットイメージをリクエストします。
46 const img = new Image();
47 const canvas = document.createElement( 'canvas');
48 const ctx = canvas.getContext( '2d');

非同期は、JavaScriptの比較的新しい言語機能であり、トランスパイラーが必要になります。

Imageオブジェクトは、メモリ内の画像を表すネイティブDOM関数です。画像がロードされるときのコールバックと、画像属性へのアクセスを提供します。 canvasは、コンテキストを介してピクセル配列と処理に簡単にアクセスできる別のDOM要素です。

これらは両方ともDOM要素であるため、Node.js(またはWeb Worker)で作業している場合、これらの要素にアクセスすることはできません。別のアプローチについては、以下を参照してください。

imgRequest

49 const imgRequest = new Promise((resolve、reject)=> {
50 img.crossOrigin = '';
51 img.onload =()=> {
52 img.width = img.naturalWidth;
53 img.height = img.naturalHeight;

このコードは、イメージが正常にロードされると解決される新しいプロミスを初期化します。この例では、エラー状態を明示的に処理しません。

crossOriginは、ドメイン間で画像をロードできるimg属性であり、DOMと対話する際にCORS(クロスオリジンリソース共有)の問題を回避します。 naturalWidthとnaturalHeightは読み込まれた画像の元の寸法を参照し、計算を実行するときに画像のサイズが正しいことを強制するのに役立ちます。

55 const datasetBytesBuffer =
56新しいArrayBuffer(NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
57
58 const chunkSize = 5000;
59 canvas.width = img.width;
60 canvas.height = chunkSize;

コードは、すべての画像のすべてのピクセルを含むように新しいバッファーを初期化します。画像の合計数に各画像のサイズとチャネル数を掛けます(4)。

chunkSizeは、UIが一度に大量のデータをメモリにロードするのを防ぐために使用されると思いますが、100%確実ではありません。

62(let i = 0; i 

このコードは、スプライト内のすべての画像をループし、その反復のために新しいTypedArrayを初期化します。次に、コンテキスト画像は描画された画像のチャンクを取得します。最後に、描画された画像は、コンテキストのgetImageData関数を使用して画像データに変換されます。この関数は、基になるピクセルデータを表すオブジェクトを返します。

72 for(let j = 0; j 

ピクセルをループし、255(ピクセルの可能な最大値)で除算して0〜1の値を固定します。これはグレースケール画像であるため、赤色のチャンネルのみが必要です。

78 this.datasetImages = new Float32Array(datasetBytesBuffer);
79
80 resolve();
81};
82 img.src = MNIST_IMAGES_SPRITE_PATH;
83});

この行はバッファを取得し、ピクセルデータを保持する新しいTypedArrayに再キャストしてから、Promiseを解決します。最後の行(srcの設定)は実際に画像の読み込みを開始し、関数を開始します。

最初に混乱したことの1つは、基礎となるデータバッファに関連するTypedArrayの動作です。 datasetBytesViewはループ内で設定されますが、返されることはありません。

内部的には、datasetBytesViewはバッファーdatasetBytesBuffer(初期化に使用される)を参照しています。コードがピクセルデータを更新すると、バッファー自体の値が間接的に編集され、78行目の新しいFloat32Arrayに再キャストされます。

DOMの外部で画像データを取得する

DOMを使用している場合は、DOMを使用する必要があります。ブラウザは(キャンバスを介して)画像の形式を把握し、バッファデータをピクセルに変換します。ただし、DOMの外部(Node.jsやWeb Workerなど)で作業している場合は、別のアプローチが必要になります。

fetchは、ファイルの基礎となるバッファにアクセスできるメカニズムresponse.arrayBufferを提供します。これを使用して、バイトを手動で読み取り、DOMを完全に回避できます。上記のコードを記述する別のアプローチを次に示します(このコードはフェッチを必要とします。これはNodeでisomorphic-fetchのようなものでポリフィルできます)。

const imgRequest = fetch(MNIST_IMAGES_SPRITE_PATH).then(resp => resp.arrayBuffer())。then(buffer => {
  新しいPromise(resolve => {
    const reader = new PNGReader(buffer);
    return reader.parse((err、png)=> {
      const pixel = Float32Array.from(png.pixels).map(pixel => {
        return pixel / 255;
      });
      this.datasetImages = pixel;
      resolve();
    });
  });
});

これは、特定の画像の配列バッファーを返します。これを書くとき、私は最初に着信バッファーを自分で解析しようとしましたが、これはお勧めしません。 (それを行うことに興味がある場合は、pngの配列バッファーを読み取る方法について説明します。)代わりに、png解析を処理するpngjsを使用することにしました。他の画像形式を扱う場合、解析関数を自分で理解する必要があります。

表面をひっかくだけ

データ操作の理解は、JavaScriptの機械学習の重要な要素です。ユースケースと要件を理解することで、いくつかの主要な機能を使用して、ニーズに合わせてデータをエレガントに正しくフォーマットできます。

Tensorflow.jsチームは、Tensorflow.jsの基になるデータAPIを継続的に変更しています。これにより、APIが進化するにつれて、より多くのニーズに対応できます。これは、Tensorflow.jsの成長と改善が続けられているため、APIの開発に遅れずについていく価値があることも意味します。

もともとthekevinscott.comで公開

Ari Zilnikに感謝します。