Bagaimana menangani data imej MNIST di Tensorflow.js

Terdapat jenaka bahawa 80 peratus sains data membersihkan data dan 20 peratus mengadu tentang pembersihan data ... pembersihan data adalah sains yang jauh lebih tinggi sains data daripada orang luar yang diharapkan. Sebenarnya model latihan biasanya merupakan bahagian yang agak kecil (kurang daripada 10 peratus) daripada apa yang dilakukan oleh saintis komputer atau saintis data.

 - Anthony Goldbloom, Ketua Pegawai Eksekutif Kaggle

Memanipulasi data merupakan langkah penting untuk sebarang masalah pembelajaran mesin. Artikel ini akan mengambil contoh MNIS untuk Tensorflow.js (0.11.1), dan berjalan melalui kod yang mengendalikan pemuatan data line-by-line.

Contoh MNIST

18 import * sebagai tf dari '@ tensorflow / tfjs';
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'; `

Pertama, kod import Tensorflow (pastikan anda menghantar kod anda!), Dan menetapkan beberapa pemalar, termasuk:

  • IMAGE_SIZE - saiz imej (lebar dan ketinggian 28x28 = 784)
  • NUM_CLASSES - bilangan kategori label (nombor boleh 0-9, jadi terdapat 10 kelas)
  • NUM_DATASET_ELEMENTS - bilangan jumlah imej (65,000)
  • NUM_TRAIN_ELEMENTS - bilangan imej latihan (55,000)
  • NUM_TEST_ELEMENTS - bilangan imej ujian (10,000, yang selebihnya)
  • MNIST_IMAGES_SPRITE_PATH & MNIST_LABELS_PATH - laluan ke imej dan label

Imej-imej tersebut disatukan menjadi satu imej besar yang kelihatan seperti:

MNISTData

Seterusnya, bermula pada baris 38, ialah MnistData, kelas yang mendedahkan fungsi berikut:

  • beban - bertanggungjawab untuk memuatkan imej dan data pelabelan secara asynchronously
  • nextTrainBatch - memuatkan kumpulan latihan seterusnya
  • nextTestBatch - memuatkan batch ujian seterusnya
  • nextBatch - fungsi umum untuk mengembalikan batch seterusnya, bergantung kepada sama ada ia berada dalam set latihan atau set ujian

Untuk tujuan bermula, artikel ini hanya akan melalui fungsi beban.

beban

44 beban async () {
45 / / Membuat permintaan untuk imej MNIST terperanjat.
46 const img = Imej baru ();
47 const canvas = document.createElement ('kanvas');
48 const ctx = kanvas.getContext ('2d');

async adalah ciri bahasa yang agak baru dalam Javascript yang anda perlukan transpiler.

Objek Imej adalah fungsi DOM asli yang mewakili imej dalam ingatan. Ia menyediakan panggilan balik apabila imej dimuat bersama, dengan akses kepada atribut imej. kanvas adalah satu lagi elemen DOM yang menyediakan akses mudah ke array piksel dan pemprosesan melalui konteks.

Oleh kerana kedua-duanya adalah elemen DOM, jika anda bekerja di Node.js (atau Pekerja Web), anda tidak akan dapat mengakses elemen-elemen ini. Untuk pendekatan alternatif, lihat di bawah.

imgRequest

49 const imgRequest = Janjikan baru ((menyelesaikan, menolak) => {
50 img.crossOrigin = '';
51 img.onload = () => {
52 img.width = img.naturalWidth;
53 img.height = img.naturalHeight;

Kod ini memulakan janji baru yang akan diselesaikan setelah imej dimuatkan dengan jayanya. Contoh ini tidak secara eksplisit mengendalikan keadaan ralat.

crossOrigin adalah atribut img yang membolehkan pemuatan imej di seluruh domain, dan mendapatkan sekitar CORS (perkongsian sumber rentas asal) ketika berinteraksi dengan DOM. naturalWidth dan naturalHeight merujuk kepada dimensi asal imej yang dimuatkan, dan berfungsi untuk menguatkan bahawa saiz imej adalah betul apabila melakukan pengiraan.

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

Kod ini memulakan penampan baru untuk mengandungi setiap piksel setiap imej. Ia mengalikan bilangan imej dengan saiz setiap imej dengan bilangan saluran (4).

Saya percaya bahawa chunkSize digunakan untuk menghalang UI daripada memuat terlalu banyak data ke dalam memori sekaligus, walaupun saya tidak 100% pasti.

62 untuk (biarkan i = 0; i 

Kod ini melengkung melalui setiap imej dalam sprite dan memulakan TypedArray baru untuk lelaran itu. Kemudian, imej konteks mendapat sebahagian daripada imej yang ditarik. Akhir sekali, imej yang dilukisnya diubah menjadi data imej menggunakan fungsi getImageData konteks, yang mengembalikan objek yang mewakili data piksel yang mendasarinya.

72 untuk (biarkan j = 0; j 

Kita gelung melalui piksel, dan dibahagikan dengan 255 (nilai maksimum piksel maksimum) untuk menjepit nilai antara 0 dan 1. Hanya saluran merah diperlukan, kerana ia adalah imej skala kelabu.

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

Baris ini mengambil penampan, melancarkannya ke dalam TypedArray baru yang memegang data piksel kami, dan kemudian menyelesaikan Janji. Baris terakhir (menetapkan src) sebenarnya mula memuatkan imej, yang memulakan fungsi.

Satu perkara yang membingungkan saya pada mulanya adalah tingkah laku TypedArray berhubung dengan penyangga data yang mendasarinya. Anda mungkin melihat bahawa datasetBytesView ditetapkan dalam gelung, tetapi tidak pernah dikembalikan.

Di bawah tudung, datasetBytesView merujuk kepada penampan datasetBytesBuffer (yang mana ia diinisialisasi). Apabila kod itu mengemas kini data piksel, ia secara tidak langsung menyunting nilai-nilai penimbal itu sendiri, yang kemudiannya akan kembali ke Float32Array baru pada baris 78.

Mengambil data imej di luar DOM

Jika anda berada di DOM, anda harus menggunakan DOM. Penyemak imbas (melalui kanvas) menjaga memikirkan format imej dan menterjemahkan data penampan kepada piksel. Tetapi jika anda bekerja di luar DOM (katakan, di Node.js, atau Pekerja Web), anda memerlukan pendekatan alternatif.

mengambil menyediakan mekanisme, respon.arrayBuffer, yang memberi anda akses kepada penimbal yang mendasari fail. Kita boleh menggunakan ini untuk membaca bait secara manual, mengelakkan DOM sepenuhnya. Berikut ialah pendekatan alternatif untuk menulis kod di atas (kod ini memerlukan pengambilan, yang boleh di polimer di Node dengan sesuatu seperti isomorphic-fetch):

const imgRequest = fetch (MNIST_IMAGES_SPRITE_PATH) .then (resp => resp.arrayBuffer ()). kemudian (buffer => {
  kembali Janji baru (menyelesaikan => {
    const reader = new PNGReader (buffer);
    kembali reader.parse ((err, png) => {
      const pixels = Float32Array.from (png.pixels) .map (pixel => {
        pulangan pixel / 255;
      });
      this.datasetImages = piksel;
      menyelesaikan ();
    });
  });
});

Ini mengembalikan penampan array untuk imej tertentu. Apabila menulis ini, saya mula-mula cuba mengurai penimbal masuk sendiri, yang saya tidak akan mengesyorkan. (Sekiranya anda berminat untuk melakukan itu, inilah beberapa maklumat mengenai cara membaca penampan array untuk png.) Sebaliknya, saya memilih untuk menggunakan pngjs, yang mengendalikan png parsing untuk anda. Apabila berurusan dengan format imej lain, anda perlu memikirkan fungsi parsing itu sendiri.

Cuma menggaru permukaan

Memahami manipulasi data adalah komponen penting pembelajaran mesin dalam JavaScript. Dengan memahami kes penggunaan dan keperluan kami, kami boleh menggunakan beberapa fungsi utama untuk memformat data kami dengan betul dengan betul untuk keperluan kami.

Pasukan Tensorflow.js sentiasa mengubah API data asas dalam Tensorflow.js. Ini dapat menampung lebih banyak keperluan kami apabila API berkembang. Ini juga bermakna bahawa ia adalah penting untuk terus mengikuti perkembangan API kerana Tensorflow.js terus berkembang dan dipertingkatkan.

Originally diterbitkan di thekevinscott.com

Terima kasih kepada Ari Zilnik.