Tensorflow.js'deki MNIST görüntü verileriyle nasıl baş edilir?

Veri biliminin yüzde 80'inin verileri temizlediği ve yüzde 20'sinin veri temizliği konusunda şikayette olduğu şakası var. Aslında eğitim modelleri, bir makine öğrencisinin veya veri bilimcisinin yaptığı şeyin tipik olarak nispeten küçük bir kısmıdır (yüzde 10'dan az).

 - Anthony Goldbloom, Kaggle Genel Müdürü

Verileri değiştirmek, herhangi bir makine öğrenmesi sorunu için çok önemli bir adımdır. Bu makalede, Tensorflow.js (0.11.1) için MNIST örneği ele alınacak ve satır satır veri yükleme işleyen kodun içinden geçeceksiniz.

MNIST örneği

18 '* tensorflow / tfjs' den tf olarak içe aktarma;
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';

İlk olarak, kod Tensorflow'u içe aktarır (kodunuzu aktardığınızdan emin olun!) Ve aşağıdakiler dahil bazı sabitler belirler:

  • IMAGE_SIZE - bir resmin boyutu (28x28 = 784 genişlik ve yükseklik)
  • NUM_CLASSES - etiket kategorilerinin sayısı (sayı 0-9 olabilir, bu nedenle 10 sınıf vardır)
  • NUM_DATASET_ELEMENTS - toplam resim sayısı (65.000)
  • NUM_TRAIN_ELEMENTS - eğitim görüntüsü sayısı (55.000)
  • NUM_TEST_ELEMENTS - test görüntüsünün sayısı (10.000, geri kalanlar)
  • MNIST_IMAGES_SPRITE_PATH & MNIST_LABELS_PATH - resimlere ve etiketlere giden yollar

Görüntüler şuna benzeyen tek bir büyük görüntüde birleştirilir:

MNISTData

Daha sonra, 38 satırından başlayarak, aşağıdaki işlevleri ortaya çıkaran bir sınıf olan MnistData:

  • yükle - resmi eşzamansız olarak yüklemek ve verileri etiketlemekle sorumludur
  • nextTrainBatch - bir sonraki eğitim grubunu yükleyin
  • nextTestBatch - sonraki test grubunu yükle
  • nextBatch - antrenman setinde veya test setinde olmasına bağlı olarak bir sonraki partiye geri dönmek için genel bir fonksiyon

Başlamak amacıyla bu makale yalnızca yükleme işlevinden geçecektir.

yük

44 zaman uyumsuz yük () {
45 // MNIST yayılmış görüntüsü için talepte bulunun.
46 const img = yeni Resim ();
47 const canvas = document.createElement ('canvas');
48 const ctx = canvas.getContext ('2d');

async, Javascript'te bir aktarıcıya ihtiyaç duyacağınız nispeten yeni bir dil özelliğidir.

Image nesnesi, bellekteki bir resmi temsil eden yerel bir DOM işlevidir. Görüntünün yüklendiği zaman için, görüntü özniteliklerine erişim için geri aramalar sağlar. canvas, piksel dizilerine kolay erişim sağlayan ve bağlam yoluyla işlem yapan başka bir DOM öğesidir.

Bunların her ikisi de DOM öğeleri olduğundan, Node.js'de (veya bir Web Çalışanı) çalışıyorsanız, bu öğelere erişemezsiniz. Alternatif bir yaklaşım için aşağıya bakınız.

imgRequest

49 const imgRequest = yeni Söz ((çöz, reddet)) => {
50 img.crossOrigin = '';
51 img.onload = () => {
52 img.width = img.naturalWidth;
53 img.height = img.naturalHeight;

Kod, resim başarıyla yüklendikten sonra çözülecek yeni bir söz başlatır. Bu örnek açıkça hata durumunu işlemez.

crossOrigin, görüntülerin etki alanları arasında yüklenmesine izin veren ve DOM ile etkileşime girdiğinde CORS (çapraz kaynaklı kaynak paylaşımı) sorunlarını gideren bir img niteliğidir. naturalWidth ve naturalHeight, yüklenen görüntünün orijinal boyutlarına atıfta bulunur ve hesaplamalar yaparken görüntünün boyutunun doğru olmasını zorunlu kılar.

55 const veri kümesiBytesBuffer =
56 yeni ArrayBuffer (NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
57
58 const chunkSize = 5000;
59 canvas.width = img.width;
60 canvas.height = chunkSize;

Kod, her görüntünün her pikselini içerecek şekilde yeni bir tampon başlatır. Toplam görüntü sayısını, her görüntünün boyutuna, kanal sayısına (4) göre çarpar.

ChunkSize'in kullanıcı arabiriminin bir kerede belleğe çok fazla veri yüklemesini engellemek için kullanıldığına inanıyorum, ancak% 100 emin değilim.

62 (izin i = 0; i 

Bu kod, sprite içindeki her görüntüde dolaşır ve bu yineleme için yeni bir TypedArray başlatır. Ardından, bağlam görüntüsü çizilen görüntünün bir kısmını alır. Son olarak, çizilen görüntü, temel piksel verisini temsil eden bir nesneyi döndüren bağlamdaki getImageData işlevini kullanarak görüntü verilerine dönüştürülür.

72 (j = 0; j 

Pikselleri geçiyoruz ve değerleri 0 ile 1 arasında sabitlemek için 255 (pikselin mümkün olan maksimum değeri) olarak bölüyoruz. Gri tonlu bir resim olduğundan yalnızca kırmızı kanal gereklidir.

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

Bu satır arabelleği alır, piksel verilerimizi tutan yeni bir TypedArray öğesine yeniden yazar ve ardından Sözü çözer. Son satır (src'nin ayarlanması) aslında işlevi başlatan görüntüyü yüklemeye başlar.

İlk başta kafamı karıştıran şeylerden biri, TypedArray'ın temel veri arabelleğine göre davranışıydı. DatasetBytesView'in döngü içinde olduğunu, ancak asla döndürülmediğini fark edebilirsiniz.

Kaputun altında, datasetBytesView, tamponla datasetBytesBuffer (bununla birlikte başlatıldığı) başvuruyor. Kod, piksel verilerini güncellediğinde, dolaylı olarak, satır 78'deki yeni bir Float32Array biçiminde yeniden yapılan tamponun değerlerini düzenler.

Görüntü verilerini DOM dışına alma

DOM’deyseniz, DOM’yi kullanmanız gerekir. Tarayıcı (tuval üzerinden), görüntülerin biçimini belirlemeye ve arabellek verilerini piksellere çevirmeye özen gösterir. Ancak DOM dışında çalışıyorsanız (örneğin, Node.js'de veya bir Web Çalışanı'nda), alternatif bir yaklaşıma ihtiyacınız olacak.

fetch, bir dosyanın temel arabelleğine erişmenizi sağlayan bir mekanizma response.arrayBuffer sağlar. Bunu, baytı manuel olarak okumak, DOM'dan tamamen kaçınarak kullanabiliriz. İşte yukarıdaki kodu yazmaya alternatif bir yaklaşım (bu kod, Düğümde izomorfik-getirme gibi bir şeyle çoklu doldurulabilen getirme gerektirir):

const imgRequest = alma (MNIST_IMAGES_SPRITE_PATH) .then (resp => resp.arrayBuffer ()). sonra (buffer => {
  yeni Promise dön (çözüm => {
    const okuyucu = yeni PNGReader (tampon);
    return reader.parse ((err, png) => {
      const piksel = Float32Array.from (png.pixels) .map (pixel => {
        dönüş pikseli / 255;
      });
      this.datasetImages = piksel;
      çözmek();
    });
  });
});

Bu, belirli görüntü için bir dizi arabelleği döndürür. Bunu yazarken ilk önce gelen tamponu kendim ayrıştırmaya çalıştım, ki bunu tavsiye etmem. (Bunu yapmakla ilgileniyorsanız, burada bir png için bir dizi tamponunu nasıl okuyacağınıza ilişkin bazı bilgiler yer almaktadır.) Bunun yerine, sizin için ayrıştırma işlemini yapan pngjs'yi kullanan pngjs'yi kullanmayı seçtim. Diğer görüntü formatlarıyla uğraşırken, ayrıştırma işlevlerini kendiniz çözmeniz gerekir.

Sadece yüzeyi çiziyorum

Veri manipülasyonunu anlamak, JavaScript'te makine öğrenmenin çok önemli bir bileşenidir. Kullanım durumlarımızı ve gereksinimlerimizi anlayarak, verilerimizi gereksinimlerimiz için doğru şekilde biçimlendirmek için birkaç temel işlevi kullanabiliriz.

Tensorflow.js ekibi, Tensorflow.js'deki temel veri API'sini sürekli olarak değiştiriyor. Bu, API geliştikçe ihtiyaçlarımızın daha fazlasını karşılamaya yardımcı olabilir. Bu aynı zamanda, Tensorflow.js büyümeye ve geliştirilmeye devam ettikçe API'deki gelişmelerden haberdar olmaya değer olduğu anlamına gelir.

Başlangıçta thekevinscott.com yayınlanan

Ari Zilnik'e özel teşekkürler.