Cum să tratați datele de imagine MNIST din Tensorflow.js

Există gluma că 80% din știința datelor curăță datele și 20% se plâng de curățarea datelor ... curățarea datelor este o proporție mult mai mare a științei datelor decât se aștepta un străin. Modelele de formare de fapt reprezintă de obicei o proporție relativ mică (mai puțin de 10 la sută) din ceea ce face un cursant sau un om de știință de date.

 - Anthony Goldbloom, CEO al Kaggle

Manipularea datelor este un pas crucial pentru orice problemă de învățare automată. Acest articol va lua exemplul MNIST pentru Tensorflow.js (0.11.1) și va parcurge codul care gestionează încărcarea datelor linie cu linie.

Exemplu MNIST

18 import * ca tf din „@ 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'; `

În primul rând, codul importă Tensorflow (asigurați-vă că transpirați codul!) Și stabilește anumite constante, inclusiv:

  • IMAGE_SIZE - dimensiunea unei imagini (lățimea și înălțimea de 28x28 = 784)
  • NUM_CLASSES - numărul categoriilor de etichete (un număr poate fi 0-9, deci există 10 clase)
  • NUM_DATASET_ELEMENTS - numărul total de imagini (65.000)
  • NUM_TRAIN_ELEMENTS - număr de imagini de antrenament (55.000)
  • NUM_TEST_ELEMENTS - numărul de imagini de testare (10.000, precum restul)
  • MNIST_IMAGES_SPRITE_PATH & MNIST_LABELS_PATH - căi către imagini și etichete

Imaginile sunt concatenate într-o imagine uriașă care arată:

MNISTData

Următorul, începând cu linia 38, este MnistData, o clasă care expune următoarele funcții:

  • încărcare - responsabil pentru încărcarea asincronă a imaginii și a datelor de etichetare
  • nextTrainBatch - încărcați următorul lot de antrenament
  • nextTestBatch - încărcați următorul lot de test
  • nextBatch - o funcție generică pentru a returna următorul lot, în funcție de dacă se află în setul de antrenament sau testul

În scopul de a începe, acest articol va trece doar prin funcția de încărcare.

sarcină

44 async load () {
45 // Efectuați o solicitare pentru imaginea sprită MNIST.
46 const img = Image nou ();
47 const canvas = document.createElement ('pânză');
48 const ctx = canvas.getContext ('2d');

async este o caracteristică de limbaj relativ nouă în Javascript pentru care veți avea nevoie de un transpiler.

Obiectul Image este o funcție DOM nativă care reprezintă o imagine în memorie. Oferă apeluri de apel pentru atunci când imaginea este încărcată de-a lungul, cu acces la atributele imaginii. canvas este un alt element DOM care oferă acces facil la matricele de pixeli și procesare prin context.

Deoarece ambele sunt elemente DOM, dacă lucrați în Node.js (sau un Lucrător Web), nu veți avea acces la aceste elemente. Pentru o abordare alternativă, a se vedea mai jos.

imgRequest

49 const imgRequest = promisiune nouă ((rezolvă, respinge) => {
50 img.crossOrigin = '';
51 img.onload = () => {
52 img.width = img.naturalWidth;
53 img.height = img.naturalHeight;

Codul inițializează o nouă promisiune care va fi rezolvată după ce imaginea va fi încărcată cu succes. Acest exemplu nu tratează în mod explicit starea de eroare.

crossOrigin este un atribut img care permite încărcarea de imagini pe domenii și se confruntă cu problemele CORS (cross-origine sharing sharing resource) atunci când interacționează cu DOM. naturalWidth și naturalHeight se referă la dimensiunile originale ale imaginii încărcate și servesc pentru a impune că dimensiunea imaginii este corectă atunci când se efectuează calcule.

55 const set de dateBytesBuffer =
56 ArrayBuffer nou (NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
57
58 const chunkSize = 5000;
59 canvas.width = img.width;
60 canvas.height = chunkSize;

Codul inițializează un nou tampon care să conțină fiecare pixel din fiecare imagine. Înmulțește numărul total de imagini cu dimensiunea fiecărei imagini cu numărul de canale (4).

Cred că chunkSize este folosit pentru a împiedica interfața de utilizator să încarce prea multe date în memorie, deși nu sunt 100% sigur.

62 pentru (let i = 0; i 

Acest cod se bucle prin fiecare imagine din sprite și inițializează un nou TypedArray pentru acea iterație. Apoi, imaginea de context primește o bucată din imaginea desenată. În cele din urmă, acea imagine desenată este transformată în date de imagine folosind funcția getImageData a contextului, care returnează un obiect reprezentând datele de bază ale pixelilor.

72 pentru (let j = 0; j 

Facem o buclă prin pixeli și împărțim la 255 (valoarea maximă posibilă a unui pixel) pentru a fixa valorile între 0 și 1. Doar canalul roșu este necesar, deoarece este o imagine la scară gri.

78 this.datasetImages = new Float32Array (set de dateBytesBuffer);
79
80 rezolvare ();
81};
82 img.src = MNIST_IMAGES_SPRITE_PATH;
83});

Această linie ia bufferul, o reformulează într-un nou TypedArray care conține datele noastre despre pixeli și apoi rezolvă Promise. Ultima linie (setarea src) începe de fapt încărcarea imaginii, care începe funcția.

Un lucru care m-a confundat la început a fost comportamentul TypedArray în raport cu bufferul său de date de bază. Este posibil să observați că setul de dateBytesView este setat în buclă, dar nu este niciodată returnat.

Sub capotă, setul de date BytesView face referire la setul de date tamponBytesBuffer (cu care este inițializat). Când codul actualizează datele pixelilor, acesta este în mod indirect să editeze valorile bufferului în sine, care la rândul său este reformat într-un nou Float32Array de pe linia 78.

Obținerea datelor de imagine în afara DOM

Dacă sunteți în DOM, ar trebui să utilizați DOM. Browserul (prin pânză) are grijă de a descoperi formatul imaginilor și de a traduce datele bufferului în pixeli. Dar dacă lucrați în afara DOM (de exemplu, în Node.js, sau un Web Worker), veți avea nevoie de o abordare alternativă.

fetch oferă un mecanism, response.arrayBuffer, care vă oferă acces la bufferul de bază al fișierului. Putem folosi acest lucru pentru a citi manual octeții, evitând DOM-ul în întregime. Iată o abordare alternativă pentru scrierea codului de mai sus (acest cod necesită preluare, care poate fi umplut în Nod cu ceva de genul izomorf).

const imgRequest = fetch (MNIST_IMAGES_SPRITE_PATH) .then (resp => resp.arrayBuffer ()). then (buffer => {
  returnați o nouă promisiune (rezolvați => {
    const reader = nou PNGReader (buffer);
    return reader.parse ((err, png) => {
      const pixel = Float32Array.from (png.pixels) .map (pixel => {
        pixel retur / 255;
      });
      this.datasetImages = pixeli;
      rezolva();
    });
  });
});

Aceasta returnează un buffer de matrice pentru imaginea particulară. Când am scris acest lucru, am încercat mai întâi să analizez singur buffer-ul primit, pe care nu l-aș recomanda. (Dacă sunteți interesat să faceți asta, iată câteva informații despre cum puteți citi un buffer de matrice pentru un png.) În schimb, am ales să folosesc pngjs, care se ocupă de analizarea png pentru dvs. Atunci când aveți de-a face cu alte formate de imagine, va trebui să vă descoperiți singur funcțiile de analiză.

Zgârie doar suprafața

Înțelegerea manipulării datelor este o componentă crucială a învățării automate în JavaScript. Înțelegând cazurile și cerințele noastre de utilizare, putem utiliza câteva funcții cheie pentru a ne formata elegant datele corect pentru nevoile noastre.

Echipa Tensorflow.js schimbă continuu API-ul de date de bază în Tensorflow.js. Acest lucru poate ajuta la adaptarea mai multor nevoi noastre pe măsură ce API-ul evoluează. Aceasta înseamnă, de asemenea, că merită să fiți la curent cu evoluția API, deoarece Tensorflow.js continuă să crească și să fie îmbunătățită.

Publicat inițial la thekevinscott.com

Mulțumiri speciale pentru Ari Zilnik.