Tensorflow.js에서 MNIST 이미지 데이터를 처리하는 방법

데이터 과학의 80 %가 데이터를 정리하고 있고 20 %가 데이터 정리에 대해 불평하고 있다는 농담이 있습니다. 데이터 정리는 외부인이 예상하는 것보다 데이터 과학의 비율이 훨씬 높습니다. 실제로 교육 모델은 일반적으로 기계 학습자 또는 데이터 과학자가하는 것의 상대적으로 적은 비율 (10 % 미만)입니다.

 — Kaggle의 CEO Anthony Goldbloom

데이터 조작은 모든 기계 학습 문제의 중요한 단계입니다. 이 기사에서는 Tensorflow.js (0.11.1)에 대한 MNIST 예제를 사용하여 데이터로드를 처리하는 코드를 단계별로 살펴 봅니다.

MNIST 예

18 '@ tensorflow / tfjs'에서 tf로 import *;
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';`

먼저 코드는 Tensorflow를 가져오고 (코드를 변환하고 있는지 확인하십시오!) 다음을 포함하여 몇 가지 상수를 설정합니다.

  • IMAGE_SIZE – 이미지 크기 (가로 및 세로 28x28 = 784)
  • NUM_CLASSES – 라벨 카테고리 수 (숫자는 0 ~ 9 일 수 있으므로 10 개의 클래스가 있음)
  • NUM_DATASET_ELEMENTS – 총 이미지 수 (65,000)
  • NUM_TRAIN_ELEMENTS – 교육 이미지 수 (55,000)
  • NUM_TEST_ELEMENTS – 테스트 이미지 수 (일명 나머지)
  • MNIST_IMAGES_SPRITE_PATH & MNIST_LABELS_PATH – 이미지 및 레이블의 경로

이미지는 다음과 같은 하나의 거대한 이미지로 연결됩니다.

MNIST 데이터

다음으로 38 행부터 다음 기능을 제공하는 클래스 인 MnistData가 있습니다.

  • 로드 – 이미지 및 라벨링 데이터를 비동기 적으로로드하는 역할
  • nextTrainBatch – 다음 교육 배치를로드
  • nextTestBatch – 다음 테스트 배치로드
  • nextBatch – 학습 세트에 있는지 또는 테스트 세트에 있는지에 따라 다음 배치를 리턴하는 일반 함수

시작하기 위해이 기사에서는로드 기능 만 수행합니다.

하중

44 비동기로드 () {
45 // MNIST 스프라이트 이미지를 요청합니다.
46 const img = 새로운 이미지 ();
47 const canvas = document.createElement ( 'canvas');
48 const ctx = canvas.getContext ( '2d');

async는 자바 스크립트에서 비교적 새로운 언어 기능으로 트랜스 파일러가 필요합니다.

Image 객체는 메모리의 이미지를 나타내는 기본 DOM 함수입니다. 이미지가로드 될 때의 이미지 속성에 대한 액세스와 함께 콜백을 제공합니다. 캔버스는 컨텍스트를 통해 픽셀 배열 및 처리에 쉽게 액세스 할 수있는 또 다른 DOM 요소입니다.

둘 다 DOM 요소이므로 Node.js (또는 웹 워커)에서 작업하는 경우 이러한 요소에 액세스 할 수 없습니다. 다른 방법은 아래를 참조하십시오.

imgRequest

49 const imgRequest = 새로운 약속 ((해결, 거부) => {
50 img.crossOrigin = '';
51 img.onload = () => {
52 폭 = img.naturalWidth;
53 img.height = img.naturalHeight;

코드는 이미지가 성공적으로로드되면 해결 될 새로운 약속을 초기화합니다. 이 예제는 명시 적으로 오류 상태를 처리하지 않습니다.

crossOrigin은 여러 도메인에 이미지를로드 할 수있게하는 img 속성이며 DOM과 상호 작용할 때 CORS (cross-origin resource sharing) 문제를 해결합니다. naturalWidth 및 naturalHeight는로드 된 이미지의 원래 크기를 나타내며 계산을 수행 할 때 이미지 크기가 올바른지 확인합니다.

55 const datasetBytesBuffer =
56 개의 새로운 ArrayBuffer (NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
57
const const 청크 크기 = 5000;
59 canvas.width = img.width;
60 canvas.height = 청크 크기;

코드는 모든 이미지의 모든 픽셀을 포함하도록 새 버퍼를 초기화합니다. 총 이미지 수에 각 이미지의 크기를 채널 수로 곱합니다 (4).

chunkSize는 100 % 확실하지 않지만 UI가 한 번에 너무 많은 데이터를 메모리에로드하지 못하게하는 데 사용된다고 생각합니다.

에 대한 62 (i = 0; i 

이 코드는 스프라이트의 모든 이미지를 반복하고 해당 반복에 대한 새 TypedArray를 초기화합니다. 그런 다음 컨텍스트 이미지는 그려진 이미지 덩어리를 가져옵니다. 마지막으로, 그 그려진 이미지는 컨텍스트의 getImageData 함수를 사용하여 이미지 데이터로 변환되어 기본 픽셀 데이터를 나타내는 객체를 반환합니다.

에 대한 72 (j = 0; j 

픽셀을 반복하고 255 (픽셀의 가능한 최대 값)로 나누면 0과 1 사이의 값이 고정됩니다. 회색조 이미지이므로 빨간색 채널 만 필요합니다.

78 this.datasetImages = 새로운 Float32Array (datasetBytesBuffer);
79
80 해결 ();
81};
82 img.src = MNIST_IMAGES_SPRITE_PATH;
83});

이 줄은 버퍼를 가져 와서 픽셀 데이터를 보유한 새로운 TypedArray로 다시 캐스팅 한 다음 Promise를 해결합니다. 마지막 줄 (src 설정)은 실제로 이미지 로딩을 시작하여 기능을 시작합니다.

처음에 나를 혼란스럽게 한 가지는 기본 데이터 버퍼와 관련하여 TypedArray의 동작이었습니다. datasetBytesView가 루프 내에서 설정되었지만 반환되지 않는 것을 알 수 있습니다.

후드 아래에서 datasetBytesView는 버퍼 datasetBytesBuffer (초기화 됨)를 참조합니다. 코드가 픽셀 데이터를 업데이트하면 버퍼 자체의 값을 간접적으로 편집하고 78 번째 줄에서 새로운 Float32Array로 다시 캐스팅됩니다.

DOM 외부에서 이미지 데이터 가져 오기

DOM에 있다면 DOM을 사용해야합니다. 캔버스를 통한 브라우저는 이미지 형식을 파악하고 버퍼 데이터를 픽셀로 변환합니다. 그러나 DOM 외부에서 작업하는 경우 (예 : Node.js 또는 웹 작업자) 다른 방법이 필요합니다.

fetch는 파일의 기본 버퍼에 대한 액세스를 제공하는 response.arrayBuffer 메커니즘을 제공합니다. 이것을 사용하여 DOM을 완전히 피하면서 바이트를 수동으로 읽을 수 있습니다. 위의 코드를 작성하는 다른 방법은 다음과 같습니다 (이 코드에는 페치가 필요합니다. 이는 isomorphic-fetch와 같은 노드로 polyfill 될 수 있습니다).

const imgRequest = fetch (MNIST_IMAGES_SPRITE_PATH) .then (resp => resp.arrayBuffer ()). then (buffer => {
  새로운 약속을 반환 (해결 => {
    const 리더 = 새로운 PNGReader (buffer);
    return reader.parse ((err, png) => {
      const pixels = Float32Array.from (png.pixels) .map (pixel => {
        픽셀 반환 / 255;
      });
      this.datasetImages = 픽셀;
      결의();
    });
  });
});

특정 이미지에 대한 배열 버퍼를 반환합니다. 이 글을 쓸 때 먼저 들어오는 버퍼를 직접 파싱하려고 시도했지만 권장하지 않습니다. (이 작업에 관심이 있다면 png의 배열 버퍼를 읽는 방법에 대한 정보가 있습니다.) 대신 png 구문 분석을 처리하는 pngjs를 사용하기로 결정했습니다. 다른 이미지 형식을 다룰 때는 구문 분석 기능을 직접 파악해야합니다.

그냥 표면을 긁적

데이터 조작 이해는 JavaScript에서 기계 학습의 중요한 구성 요소입니다. 사용 사례와 요구 사항을 이해하면 몇 가지 주요 기능을 사용하여 필요에 따라 데이터를 올바르게 형식화 할 수 있습니다.

Tensorflow.js 팀은 Tensorflow.js에서 기본 데이터 API를 지속적으로 변경하고 있습니다. 이를 통해 API가 발전함에 따라 더 많은 요구를 수용 할 수 있습니다. 이는 또한 Tensorflow.js가 지속적으로 성장하고 개선됨에 따라 API 개발에 대한 최신 정보를 유지할 가치가 있음을 의미합니다.

원래 thekevinscott.com에 게시

Ari Zilnik에게 감사드립니다.