Kako se nositi s MNIST slikovnim podacima u Tensorflow.js

Postoji šala da 80 posto podatkovnih znanosti čisti podatke, a 20 posto njih žali se na čišćenje podataka ... čišćenje podataka je puno veći udio podataka o podacima nego što bi strani autori očekivali. Zapravo su modeli obuke tipično relativno mali udio (manje od 10 posto) onoga što radi učenik stroja ili znanstvenik podataka.

 - Anthony Goldbloom, izvršni direktor Kagglea

Manipuliranje podataka presudan je korak za bilo koji problem strojnog učenja. Ovaj će članak uzeti primjer MNIST-a za Tensorflow.js (0.11.1) i proći kroz kôd koji obrađuje učitavanje podataka pojedinačno.

Primjer MNIST-a

18 uvoz * kao tf iz '@ 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 con MNIST_IMAGES_SPRITE_PATH =
28 'https://storage.googleapis.com/learnjs-data/model-builder/mnist_images.png';
29 konst. MNIST_LABELS_PATH =
30 'https: //storage.googleapis.com/learnjs-data/model-builder/mnist_labels_uint8'; `

Prvo, kôd uvozi Tensorflow (pazite da translirate svoj kôd!) I uspostavlja neke konstante, uključujući:

  • IMAGE_SIZE - veličina slike (širina i visina 28x28 = 784)
  • NUM_CLASSES - broj kategorija oznaka (broj može biti 0-9, tako da postoji 10 klasa)
  • NUM_DATASET_ELEMENTS - broj slika ukupno (65 000)
  • NUM_TRAIN_ELEMENTS - broj slika s treninga (55 000)
  • NUM_TEST_ELEMENTS - broj testnih slika (10 000, aka ostatak)
  • MNIST_IMAGES_SPRITE_PATH & MNIST_LABELS_PATH - staze do slika i oznaka

Slike su objedinjene u jednu ogromnu sliku koja izgleda kao:

MNISTData

Slijedeće, počevši od retka 38, je MnistData, klasa koja ima sljedeće funkcije:

  • load - odgovoran za asinkrono učitavanje slike i podataka o označavanju
  • nextTrainBatch - učitajte sljedeću seriju treninga
  • nextTestBatch - učitajte sljedeću ispitnu seriju
  • nextBatch - generička funkcija za vraćanje sljedeće serije, ovisno o tome nalazi li se u treningu ili testnom setu

Ovaj će članak, samo za početak, proći kroz funkciju opterećenja.

opterećenje

44 async load () {
45 // Podnesite zahtjev za MNIST spritiranu sliku.
46 const img = nova slika ();
47 const canvas = document.createElement ('platno');
48 const ctx = canvas.getContext ('2d');

async je relativno nova jezična značajka u Javascriptu za koju će vam trebati transpiler.

Objekt Image izvorna je DOM funkcija koja predstavlja sliku u memoriji. Omogućuje povratne pozive za vrijeme učitavanja slike uz pristup atributima slike. canvas je još jedan DOM element koji omogućava jednostavan pristup pikselskim nizovima i obradu u kontekstu.

Budući da su oba elementa DOM, ako radite u Node.js (ili u Web Workeru) nećete imati pristup tim elementima. Alternativni pristup pogledajte u nastavku.

imgRequest

49 const imgRequest = novo Obećanje ((riješi, odbaci) => {
50 img.crossOrigin = '';
51 img.onload = () => {
52 img.width = img.naturalWidth;
53 img.height = img.naturalHeight;

Kôd inicijalizira novo obećanje koje će se riješiti nakon uspješnog učitavanja slike. Ovaj primjer izričito ne obrađuje stanje pogreške.

crossOrigin je atribut img koji omogućava učitavanje slika kroz domene i zaobilazi probleme s CORS (cross-origin dijeljenje resursa) prilikom interakcije s DOM-om. naturalWidth i naturalHeight odnose se na izvorne dimenzije učitane slike i služe da se utvrdi da je veličina slike točna prilikom obavljanja izračuna.

55 const skupu podatakaBytesBuffer =
56 novih ArrayBuffer-a (NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
57
58 const chunkSize = 5000;
59 canvas.width = img.width;
60 canvas.height = chunkSize;

Kôd inicijalizira novi međuspremnik koji sadrži svaki piksel svake slike. Pomnožava ukupni broj slika s veličinom svake slike s brojem kanala (4).

Vjerujem da se chunkSize koristi za sprečavanje UI-a da previše podataka učita u memoriju odjednom, mada nisam 100% siguran.

62 za (neka je i = 0; i 

Ovaj se kôd petlja kroz svaku sliku u spriteu i inicijalizira novi TypedArray za tu iteraciju. Tada kontekstna slika dobiva komad crteža. Konačno, nacrtana slika se pretvara u slikovne podatke pomoću kontekstne funkcije getImageData, koja vraća objekt koji predstavlja temeljne podatke piksela.

72 za (neka je j = 0; j 

Prelazimo kroz piksele i podijelimo sa 255 (najveća moguća vrijednost piksela) da stegnemo vrijednosti između 0 i 1. Potreban je samo crveni kanal, budući da je slika u sivim tonovima.

78 this.datasetImages = novi Float32Array (skup podatakaBytesBuffer);
79
80 riješiti ();
81};
82 img.src = MNIST_IMAGES_SPRITE_PATH;
83});

Ova linija uzima međuspremnik, prebacuje ga u novi TypedArray koji sadrži naše podatke o pikselima i zatim rješava Promise. Posljednji redak (podešavanje srca) zapravo počinje učitavanje slike, čime se pokreće funkcija.

Jedna stvar koja me isprva zbunila je ponašanje TypedArray-a u odnosu na njegov temeljni međuspremnik podataka. Možda ćete primijetiti da je skup podatakaBytesView postavljen unutar petlje, ali se nikada ne vraća.

Pod kapom, skup podatakaBytesView referencira skup podataka međuspremnikaBytesBuffer (s kojim je inicijaliziran). Kad kôd ažurira podatke piksela, posredno uređuje vrijednosti samog međuspremnika, što zauzvrat preinačuje u novi Float32Array u liniji 78.

Dohvaćanje slikovnih podataka izvan DOM-a

Ako ste u domenu, trebali biste ga koristiti. Preglednik (putem platna) brine se za pronalaženje formata slika i prenošenje podataka međuspremnika u piksele. Ali ako radite izvan DOM-a (recimo u Node.js ili u Web Workeru), trebat će vam alternativni pristup.

dohvaćanje pruža mehanizam, response.arrayBuffer, koji vam omogućava pristup podnožnom međuspremniku datoteke. To možemo koristiti za ručno čitanje bajtova, izbjegavajući DOM u potpunosti. Evo alternativnog pristupa pisanju gornjeg koda (ovaj kôd zahtijeva dohvaćanje, koje se u Polju može popuniti s nečim poput izomorfnog-dohvaćanja):

const imgRequest = dohvaćanje (MNIST_IMAGES_SPRITE_PATH) .then (resp => resp.arrayBuffer ()). zatim (buffer => {
  vrati novo obećanje (razriješi => {
    const čitač = novi PNGReader (međuspremnik);
    vrati čitač.parse ((err, png) => {
      const pixels = Float32Array.from (png.pixels) .map (pixel => {
        povratni piksel / 255;
      });
      this.datasetImages = piksela;
      odlučnost();
    });
  });
});

To vraća međuspremnik polja za određenu sliku. Kada pišem ovo, prvo sam pokušao analizirati ulazni međuspremnik, što ne bih preporučio. (Ako ste zainteresirani za to, evo nekoliko informacija o tome kako čitati polje za polje array za png.) Umjesto toga, izabrao sam za upotrebu pngjs koji obrađuje png analizu za vas. Kad se bavite drugim formatima slika, sami ćete morati smisliti funkcije raščlanjivanja.

Samo grebanje po površini

Razumijevanje manipulacije podacima ključna je komponenta strojnog učenja u JavaScript-u. Razumijevajući slučajeve i zahtjeve naše upotrebe, možemo upotrijebiti nekoliko ključnih funkcija kako bismo ispravno formatirali podatke za naše potrebe.

Tensorflow.js tim kontinuirano mijenja API-je za podatke u Tensorflow.js. To može pomoći udovoljavanju većini naših potreba kako API razvija. To također znači da je vrijedno biti u toku s razvojem API-ja jer Tensorflow.js i dalje raste i poboljšava se.

Izvorno objavljeno na thekevinscott.com

Posebna zahvala Ari Zilniku.