Kā rīkoties ar MNIST attēlu datiem vietnē Tensorflow.js

Tas ir joks, ka 80 procenti datu zinātnes attīra datus un 20 procenti sūdzas par datu tīrīšanu ... datu tīrīšana ir daudz lielāka datu zinātnes daļa, nekā varētu gaidīt kāds ārējs speciālists. Faktiski apmācības modeļi parasti ir salīdzinoši neliela daļa (mazāk nekā 10 procenti) no tā, ko dara mašīnu apguvējs vai datu zinātnieks.

 - Entonijs Goldblooms, uzņēmuma Kaggle izpilddirektors

Datu manipulēšana ir izšķirošs solis jebkurai mašīnmācīšanās problēmai. Šajā rakstā tiks apskatīts Tensorflow.js (0.11.1) MNIST piemērs un aprakstīts kods, kas apstrādā datu ielādes līnijas pa rindām.

MNIST piemērs

18 imports * kā tf no '@ tensorflow / tfjs';
19
20 konst. IMAGE_SIZE = 784;
21. secība NUM_CLASSES = 10;
22 secība NUM_DATASET_ELEMENTS = 65000;
23
24 secība NUM_TRAIN_ELEMENTS = 55000;
25 secība NUM_TEST_ELEMENTS = NUM_DATASET_ELEMENTS - NUM_TRAIN_ELEMENTS;
26
27. konst. 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'; "

Pirmkārt, kods importē Tensorflow (pārliecinieties, vai esat transpilējis savu kodu!), Un nosaka dažas konstantes, tostarp:

  • IMAGE_SIZE - attēla izmērs (platums un augstums 28x28 = 784)
  • NUM_CLASSES - etiķešu kategoriju skaits (skaitlis var būt no 0 līdz 9, tātad ir 10 klases)
  • NUM_DATASET_ELEMENTS - kopējais attēlu skaits (65 000)
  • NUM_TRAIN_ELEMENTS - apmācību attēlu skaits (55 000)
  • NUM_TEST_ELEMENTS - testa attēlu skaits (10 000, aka atlikušie)
  • MNIST_IMAGES_SPRITE_PATH un MNIST_LABELS_PATH - ceļš uz attēliem un etiķetēm

Attēli ir salikti vienā milzīgā attēlā, kas izskatās šādi:

MNISTData

Nākamais, sākot ar 38. rindu, ir MnistData, klase, kurai ir šādas funkcijas:

  • slodze - atbild par attēla asinhronu ielādi un datu marķēšanu
  • nextTrainBatch - ielādējiet nākamo apmācības partiju
  • nextTestBatch - ielādēt nākamo testa partiju
  • nextBatch - vispārēja funkcija nākamās partijas atgriešanai atkarībā no tā, vai tā atrodas treniņu komplektā vai testa komplektā

Lai sāktu, šajā rakstā tiks apskatīta tikai ielādes funkcija.

slodze

44 asinhronā slodze () {
45 // Iesniedziet pieprasījumu pēc MNIST ieraksta attēla.
46 const img = jauns attēls ();
47 const canvas = document.createElement ('audekls');
48 const ctx = canvas.getContext ('2d');

async ir samērā jauna valodas funkcija Javascript, kurai jums būs nepieciešams transpilators.

Objekts Image ir sākotnējā DOM funkcija, kas attēlo attēlu atmiņā. Tas nodrošina atzvanīšanu tam, kad attēls tiek ielādēts, un piekļuvi attēla atribūtiem. audekls ir vēl viens DOM elements, kas nodrošina ērtu piekļuvi pikseļu masīviem un apstrādi kontekstā.

Tā kā abi šie ir DOM elementi, ja strādājat vietnē Node.js (vai Web Worker), šiem elementiem jums nebūs piekļuves. Par alternatīvu pieeju skatīt zemāk.

imgRequest

49 const imgRequest = jauns solījums ((atrisināt, noraidīt) => {
50 img.crossOrigin = '';
51 img.onload = () => {
52 img.width = img.naturalWidth;
53 img.height = img.naturalHeight;

Kods iniciē jaunu solījumu, kas tiks atrisināts, kad attēls būs veiksmīgi ielādēts. Šajā piemērā nav tieši aprakstīts kļūdas stāvoklis.

crossOrigin ir img atribūts, kas ļauj ielādēt attēlus visos domēnos un mijiedarbojoties ar DOM apiet CORS (dažādu izcelsmju resursu koplietošanas) problēmas. naturalWidth un naturalHeight attiecas uz ielādētā attēla sākotnējiem izmēriem un kalpo, lai pārliecinātos, ka attēla izmērs ir pareizs, veicot aprēķinus.

55 const datasetBytesBuffer =
56 jauni ArrayBuffer (NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
57
58 const chunkSize = 5000;
59 canvas.width = img.width;
60 audekls.augstums = chunkSize;

Kods inicializē jaunu buferi, lai saturētu katra attēla katru pikseļu. Tas tiek reizināts ar kopējo attēlu skaitu ar katra attēla lielumu ar kanālu skaitu (4).

Es uzskatu, ka chunkSize tiek izmantots, lai neļautu lietotāja interfeisam vienlaikus ielādēt pārāk daudz datu atmiņā, lai gan es neesmu 100% pārliecināts.

62 par (ļaujiet i = 0; i 

Šis kods iet cauri katram sprite attēlam un inicializē jaunu itpedāciju TypedArray. Pēc tam konteksta attēls iegūst nogrieztu attēla daļu. Visbeidzot, novilktais attēls tiek pārveidots par attēla datiem, izmantojot konteksta funkciju getImageData, kas atdod objektu, kas attēlo pamatā esošos pikseļu datus.

72 par (ņemiet j = 0; j 

Mēs cilpu cauri pikseļiem un dala ar 255 (maksimālā iespējamā pikseļa vērtība), lai nofiksētu vērtības no 0 līdz 1. Nepieciešams tikai sarkanais kanāls, jo tas ir pelēktoņu attēls.

78 this.datasetImages = jauns Float32Array (datasetBytesBuffer);
79
80 atrisināt ();
81};
82 img.src = MNIST_IMAGES_SPRITE_PATH;
83});

Šī rinda ņem buferi, pārstrādā to jaunā TypedArray, kurā ir mūsu pikseļu dati, un pēc tam atrisina solījumu. Pēdējā rinda (src iestatīšana) faktiski sāk ielādēt attēlu, kas sāk funkciju.

Viena lieta, kas mani sākotnēji mulsināja, bija TypedArray uzvedība attiecībā uz tā pamatā esošo datu buferi. Varētu pamanīt, ka datasetBytesView ir iestatīts cilpā, bet nekad netiek atgriezts.

Zem pārsega datasetBytesView atsaucas uz bufera datu kopuBytesBuffer (ar kuru tas tiek inicializēts). Kad kods atjaunina pikseļu datus, tas netieši rediģē pašas bufera vērtības, kas savukārt tiek pārstrādāts jaunā Float32Array 78. rindā.

Attēla datu iegūšana ārpus DOM

Ja esat DOM, jums vajadzētu izmantot DOM. Pārlūks (izmantojot audekls) rūpējas par attēlu formāta noteikšanu un bufera datu pārveidošanu pikseļos. Bet, ja jūs strādājat ārpus DOM (teiksim, Node.js vai Web Worker), jums būs nepieciešama alternatīva pieeja.

fetch nodrošina mehānismu response.arrayBuffer, kas ļauj piekļūt faila pamatā esošajam buferim. Mēs to varam izmantot, lai manuāli lasītu baitus, pilnībā izvairoties no DOM. Tālāk ir sniegta alternatīva pieeja iepriekšminētā koda rakstīšanai (šim kodam ir nepieciešama atnest, kuru mezglā var aizpildīt ar kaut ko līdzīgu izomorfiskajai atnestībai):

const imgRequest = atnest (MNIST_IMAGES_SPRITE_PATH). Tad (resp => resp.arrayBuffer ()). pēc tam (buferis => {
  atgriezt jaunu solījumu (atrisināt => {
    const lasītājs = jauns PNGReader (buferis);
    atgriezties lasītājs.parse ((kļūda, png) => {
      const pixels = Float32Array.from (png.pixels) .map (pixel => {
        atgriešanās pikselis / 255;
      });
      this.datasetImages = pikseļi;
      atrisināt ();
    });
  });
});

Tas atgriež masīva buferi konkrētajam attēlam. Rakstot to, es vispirms mēģināju pats parsēt ienākošo buferi, ko es neieteiktu. (Ja jūs interesē to izdarīt, šeit ir sniegta informācija par to, kā lasīt masīva buferi png.) Tā vietā es izvēlējos izmantot pngjs, kas apstrādā png parsēšanu jums. Darbojoties ar citiem attēlu formātiem, jums pats būs jāizdomā parsēšanas funkcijas.

Tikai virsmas skrāpēšana

Izpratne par datu manipulācijām ir būtiska Java mašīnmācīšanās sastāvdaļa. Izprotot mūsu lietošanas gadījumus un prasības, mēs varam izmantot dažas galvenās funkcijas, lai eleganti formatētu datus pareizi mūsu vajadzībām.

Tensorflow.js komanda nepārtraukti maina pamata datu API vietnē Tensorflow.js. Tas var palīdzēt apmierināt vairāk mūsu vajadzību, attīstoties API. Tas nozīmē arī to, ka ir vērts sekot līdzi jauninājumiem API, jo Tensorflow.js turpina augt un tikt pilnveidots.

Sākotnēji tas tika publicēts vietnē thekevinscott.com

Īpašs paldies Ārijam Zilņikam.