Kaip elgtis su MNIST vaizdo duomenimis Tensorflow.js

Juokaujama, kad 80 procentų duomenų mokslo valo duomenis, o 20 procentų skundžiasi dėl duomenų valymo ... duomenų valymas yra daug didesnė duomenų mokslo dalis, nei tikėtųsi pašalinis asmuo. Faktiškai mokymo modeliai paprastai sudaro palyginti nedidelę dalį (mažiau nei 10 procentų) to, ką daro besimokantysis kompiuteriu ar duomenų žinovas.

 - Anthony Goldbloom, „Kaggle“ generalinis direktorius

Manipuliavimas duomenimis yra esminis žingsnis sprendžiant bet kokias mašinų mokymosi problemas. Šiame straipsnyje bus pateiktas „Tensorflow.js“ (0.11.1) MNIST pavyzdys ir pateikiamas kodas, kuris tvarko duomenų įkėlimą iš eilės.

MNIST pavyzdys

18 importuoti * kaip tf iš „@ tensorflow / tfjs“;
19
20 konst. IMAGE_SIZE = 784;
21 konst. NUM_CLASSES = 10;
22 eiga NUM_DATASET_ELEMENTS = 65000;
23
24 eiga NUM_TRAIN_ELEMENTS = 55000;
25 eiga 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'; `

Pirma, kodas importuoja „Tensorflow“ (įsitikinkite, kad perkeliate savo kodą!) Ir nustato kai kurias konstantas, įskaitant:

  • IMAGE_SIZE - vaizdo dydis (28x28 plotis ir aukštis = 784)
  • NUM_CLASSES - etikečių kategorijų skaičius (skaičius gali būti 0–9, taigi yra 10 klasių)
  • NUM_DATASET_ELEMENTS - bendras vaizdų skaičius (65 000)
  • NUM_TRAIN_ELEMENTS - mokymų vaizdų skaičius (55 000)
  • NUM_TEST_ELEMENTS - bandomų vaizdų skaičius (10 000, dar žinomi kaip likusi dalis)
  • MNIST_IMAGES_SPRITE_PATH ir MNIST_LABELS_PATH - keliai į vaizdus ir etiketes

Vaizdai sujungiami į vieną didžiulį vaizdą, kuris atrodo taip:

MNISTData

Kitas, pradedant nuo 38 eilutės, yra „MnistData“, klasė, kurianti šias funkcijas:

  • apkrova - atsakinga už asinchroninį vaizdo įkėlimą ir duomenų ženklinimą
  • nextTrainBatch - įkelkite kitą mokymo paketą
  • nextTestBatch - įkelkite kitą bandymo partiją
  • „NextBatch“ - bendroji funkcija grąžinti sekančią partiją, atsižvelgiant į tai, ar ji yra treniruočių rinkinyje, ar testo rinkinyje

Norėdami pradėti, šiame straipsnyje bus aprašyta tik įkėlimo funkcija.

apkrova

44 asinchroninis įkėlimas () {
45 // Pateikite užklausą dėl MNIST nuotraukos.
46 const img = naujas vaizdas ();
47 const drobė = document.createElement ('drobė');
48 const ctx = canvas.getContext ('2d');

„async“ yra palyginti nauja „Javascript“ kalbos funkcija, kuriai jums reikės perkėlėjo.

Objektas „Image“ yra savaiminė DOM funkcija, vaizduojanti atmintyje esantį vaizdą. Tai suteikia atšaukimus, kai vaizdas įkeliamas kartu, ir prieigą prie vaizdo atributų. drobė yra dar vienas DOM elementas, suteikiantis lengvą prieigą prie pikselių masyvų ir apdorojimą atsižvelgiant į kontekstą.

Kadangi abu jie yra DOM elementai, jei dirbate „Node.js“ (arba žiniatinklio darbuotojui), neturėsite prieigos prie šių elementų. Alternatyvus požiūris pateiktas žemiau.

imgRequest

49 const imgRequest = naujas pažadas ((išspręsti, atmesti) => {
50 img.crossOrigin = '';
51 img.onload = () => {
52 img.width = img.naturalWidth;
53 img.height = img.naturalHeight;

Kodas inicijuoja naują pažadą, kuris bus išspręstas sėkmingai įdėjus vaizdą. Šis pavyzdys aiškiai neapima klaidos būsenos.

„crossOrigin“ yra „img“ atributas, leidžiantis įkelti vaizdus į domenus ir sąveikaujant su DOM, išspręsti CORS (kryžminio šaltinio išteklių bendro naudojimo) problemas. „naturalWidth“ ir „naturalHeight“ nurodo įkeltų vaizdų pradinius matmenis ir jais užtikrinamas, kad atliekant skaičiavimus vaizdas būtų tinkamas.

55 const datasetBytesBuffer =
56 naujos „ArrayBuffer“ (NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
57
58 const chunkSize = 5000;
59 drobė.platumas = img.platumas;
60 drobė.aukštis = chunkSize;

Kodas inicijuoja naują buferį, kuriame yra kiekvienas kiekvieno vaizdo pikselis. Bendras vaizdų skaičius padauginamas iš kiekvieno vaizdo dydžio iš kanalų skaičiaus (4).

Manau, kad „chunkSize“ yra naudojamas siekiant neleisti UI įkelti per daug duomenų į atmintį iš karto, nors nesu visiškai tikra.

62 už (tegul i = 0; i 

Šis kodas pereina per kiekvieną atvaizdą sprite ir inicijuoja naują iteraciją „TypedArray“. Tada kontekstinis vaizdas įgauna paveikslo dalį. Galiausiai tas nupieštas vaizdas paverčiamas vaizdo duomenimis, naudojant konteksto funkciją getImageData, kuri grąžina objektą, vaizduojantį pagrindinius pikselių duomenis.

72 už (tegul j = 0; j 

Mes perlenkiame pikselius ir padalijame iš 255 (didžiausia galima pikselio vertė), kad užfiksuotume reikšmes nuo 0 iki 1. Reikia tik raudono kanalo, nes tai yra pilkos spalvos vaizdas.

78 this.datasetImages = naujas „Float32Array“ (duomenų rinkinysBytesBuffer);
79
80 išspręsti ();
81};
82 img.src = MNIST_IMAGES_SPRITE_PATH;
83});

Ši eilutė paima buferį, perdėsto jį į naują „TypedArray“, kuriame saugomi mūsų taškų duomenys, ir tada išsprendžia pažadą. Paskutinėje eilutėje (nustatant src) iš tikrųjų pradedamas įkelti vaizdas, kuris pradeda funkciją.

Vienas dalykas, kuris mane iš pradžių supainiojo, buvo „TypedArray“ elgesys, atsižvelgiant į jo pagrindinius duomenų buferius. Galbūt pastebėsite, kad datasetBytesView yra nustatytas cikle, bet niekada nebus grąžintas.

Pagal gaubtą „datasetBytesView“ nurodo buferio duomenų rinkinį „BytesBuffer “(su kuriuo jis yra inicijuotas). Kai kodas atnaujina pikselių duomenis, jis netiesiogiai redaguoja paties buferio reikšmes, kurios savo ruožtu 78 eilutėje išdėstomos nauja „Float32Array“.

Vaizdo duomenų gavimas ne DOM

Jei esate DOM, turėtumėte naudoti DOM. Naršyklė (naudodama drobę) rūpinasi vaizdų formato nustatymu ir buferio duomenų pavertimu taškais. Bet jei dirbate ne DOM (tarkime, Node.js ar „Web Worker“), jums reikės alternatyvaus požiūrio.

„fetch“ pateikia mechanizmą „response.arrayBuffer“, kuris suteikia prieigą prie pagrindinio failo buferio. Tai galime naudoti baitų skaitymui rankiniu būdu, visiškai išvengdami DOM. Čia pateiktas alternatyvus būdas aukščiau aprašytam kodui rašyti (šiam kodui reikia atsisiųsti, kurį galima užpildyti mazge, pavyzdžiui, izomorfiniu atsisiuntimu):

const imgRequest = gauti (MNIST_IMAGES_SPRITE_PATH). Tada (resp => resp.arrayBuffer ()), tada (buferis => {
  grąžinti naują pažadą (išspręsti => {
    const skaitytuvas = naujas PNGR skaitytuvas (buferis);
    grąžinti skaitytoją.parse ((klaidinga, png) => {
      const pixels = „Float32Array.from“ (png.pikseliai) .map (pixel => {
        grįžimo taškas / 255;
      });
      this.datasetImages = pikseliai;
      ryžtis ();
    });
  });
});

Tai grąžina masyvo buferį konkrečiam vaizdui. Rašydamas tai pirmiausia bandžiau pats parsisiųsti gaunamą buferį, kurio nerekomenduočiau. (Jei jus domina tai padaryti, čia yra keletas informacijos apie tai, kaip nuskaityti png masyvo buferį.) Vietoj to, aš pasirinkau naudoti pngjs, kuris tvarko png analizę už jus. Dirbdami su kitais vaizdo formatais, patys turėsite išsiaiškinti analizės funkcijas.

Tiesiog subraižyti paviršių

Manipuliavimas duomenimis yra esminis kompiuterio mokymosi „JavaScript“ komponentas. Suprasdami naudojimo atvejus ir reikalavimus, galime naudoti keletą pagrindinių funkcijų, kad elegantiškai suformatuotume savo duomenis.

„Tensorflow.js“ komanda nuolat keičia pagrindinių duomenų API Tensorflow.js. Tai gali padėti patenkinti daugiau mūsų poreikių, tobulėjant API. Tai taip pat reiškia, kad verta neatsilikti nuo API patobulinimų, nes Tensorflow.js toliau auga ir tobulėja.

Iš pradžių paskelbta thekevinscott.com

Ypatinga padėka Ari Zilnik.