كيفية التعامل مع بيانات صور MNIST في Tensorflow.js

هناك نكتة مفادها أن 80٪ من علم البيانات يقوم بتنظيف البيانات و 20٪ يشكون من تنظيف البيانات ... تنظيف البيانات هو نسبة أعلى بكثير من علم البيانات مما يتوقعه شخص غريب. في الواقع ، تعد نماذج التدريب في الواقع نسبة صغيرة نسبيًا (أقل من 10 بالمائة) مما يفعله متعلم الآلة أو عالم البيانات.

 - أنتوني جولد بلوم ، الرئيس التنفيذي لشركة Kaggle

تعد معالجة البيانات خطوة مهمة لأي مشكلة في التعلم الآلي. ستأخذ هذه المقالة مثال MNIST لـ Tensorflow.js (0.11.1) ، وتصفح الكود الذي يعالج تحميل البيانات سطراً.

مثال MNIST

18 استيراد * كـ tf من '@ 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'؛

أولاً ، تستورد الشفرة Tensorflow (تأكد من نقل الشفرة الخاصة بك!) ، وتضع بعض الثوابت ، بما في ذلك:

  • IMAGE_SIZE - حجم الصورة (العرض والارتفاع 28 × 28 = 784)
  • NUM_CLASSES - عدد فئات التصنيفات (يمكن أن يكون الرقم من 0 إلى 9 ، لذلك هناك 10 فئات)
  • NUM_DATASET_ELEMENTS - إجمالي عدد الصور (65000)
  • NUM_TRAIN_ELEMENTS - عدد الصور التدريبية (55000)
  • NUM_TEST_ELEMENTS - عدد صور الاختبار (10000 ، ويعرف أيضًا باسم الباقي)
  • MNIST_IMAGES_SPRITE_PATH & MNIST_LABELS_PATH - مسارات إلى الصور والعلامات

الصور متسلسلة في صورة واحدة ضخمة تبدو كما يلي:

MNISTData

التالي ، بدءًا من السطر 38 ، هو MnistData ، فئة تكشف الوظائف التالية:

  • تحميل - مسؤول عن تحميل الصورة وتسمية البيانات بشكل غير متزامن
  • nextTrainBatch - تحميل دفعة التدريب التالية
  • nextTestBatch - تحميل دفعة الاختبار التالية
  • nextBatch - وظيفة عامة لإرجاع الدُفعة التالية ، اعتمادًا على ما إذا كانت في مجموعة التدريب أو مجموعة الاختبار

لأغراض البدء ، سوف تمر هذه المقالة فقط من خلال وظيفة التحميل.

حمل

44 تحميل غير المتزامن () {
45 // تقديم طلب للحصول على صورة MNIST sprited.
46 const img = new Image ()؛
47 const canvas = document.createElement ('canvas')؛
48 const ctx = canvas.getContext ('2d')؛

async هي ميزة لغة جديدة نسبيًا في Javascript ستحتاج إلى جهاز ترجمة.

كائن الصورة هو دالة DOM أصلية تمثل صورة في الذاكرة. يوفر عمليات رد الاتصال عند تحميل الصورة مع إمكانية الوصول إلى سمات الصورة. canvas هو عنصر DOM آخر يوفر سهولة الوصول إلى صفيفات البيكسل ومعالجتها عن طريق السياق.

نظرًا لأن كلاهما عنصران من عناصر DOM ، إذا كنت تعمل في Node.js (أو عامل ويب) ، فلن تتمكن من الوصول إلى هذه العناصر. للحصول على نهج بديل ، انظر أدناه.

imgRequest

49 const imgRequest = وعد جديد ((العزم ، الرفض) => {
50 img.crossOrigin = ''؛
51 img.onload = () => {
52 img.width = img.naturalWidth؛
53 img.height = img.naturalHeight؛

تهيئة الرمز وعد جديد سيتم حلها بمجرد تحميل الصورة بنجاح. هذا المثال لا يعالج حالة الخطأ بشكل صريح.

crossOrigin هي إحدى سمات img التي تتيح تحميل الصور عبر المجالات ، وتتغلب على مشكلات CORS (مشاركة الموارد عبر المصادر الأصلية) عند التفاعل مع DOM. يشير كل من NaturalWidth و naturalHeight إلى الأبعاد الأصلية للصورة التي تم تحميلها ، وتعمل على فرض أن حجم الصورة صحيح عند إجراء العمليات الحسابية.

55 const datasetBytesBuffer =
56 ArrayBuffer جديد (NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4) ؛
57
58 const chunkSize = 5000 ؛
59 canvas.width = img.width؛
60 canvas. Height = chunkSize؛

تهيئة التعليمات البرمجية مخزن مؤقت جديد لاحتواء كل بكسل لكل صورة. يضاعف إجمالي عدد الصور حسب حجم كل صورة بعدد القنوات (4).

أعتقد أن chunkSize يُستخدم لمنع واجهة المستخدم من تحميل الكثير من البيانات في الذاكرة دفعة واحدة ، على الرغم من أنني لست متأكدًا بنسبة 100٪.

62 لـ (اسمح i = 0 ؛ أنا 

يدير هذا الرمز كل صورة في العفريت ويقوم بتهيئة 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 جديد يحتفظ ببيانات البكسل الخاصة بنا ، ثم يحل الوعد. يبدأ السطر الأخير (تعيين src) فعليًا في تحميل الصورة ، والتي تبدأ الوظيفة.

شيء واحد أربكني في البداية كان سلوك TypedArray بالنسبة إلى المخزن المؤقت للبيانات الأساسي الخاص به. قد تلاحظ أنه تم تعيين datasetBytesView داخل الحلقة ، ولكن لا يتم إرجاعها أبدًا.

تحت الغطاء ، تشير datasetBytesView إلى datasetBytesBuffer المخزن المؤقت (الذي تمت تهيئته به). عندما يقوم الكود بتحديث بيانات البكسل ، فإنه يقوم بتحرير قيم المخزن المؤقت نفسه بشكل غير مباشر ، والذي بدوره يعاد صياغته إلى Float32Array جديد على السطر 78.

جلب بيانات الصورة خارج DOM

إذا كنت في DOM ، يجب عليك استخدام DOM. يعتني المستعرض (من خلال canvas) بمعرفة تنسيق الصور وترجمة البيانات العازلة إلى وحدات بكسل. ولكن إذا كنت تعمل خارج DOM (على سبيل المثال ، في Node.js أو في Web Worker) ، فستحتاج إلى نهج بديل.

يوفر fetch آلية ، response.arrayBuffer ، والتي تمنحك الوصول إلى المخزن المؤقت الأساسي للملف. يمكننا استخدام هذا لقراءة البايتات يدويًا ، مع تجنب DOM بالكامل. إليك طريقة بديلة لكتابة التعليمات البرمجية أعلاه (تتطلب هذه الشفرة إحضارها ، والتي يمكن تعددها في العقدة باستخدام شيء مثل إحضار الشكل المتماثل):

const imgRequest = fetch (MNIST_IMAGES_SPRITE_PATH). ثم (resp => resp.arrayBuffer ()). ثم (buffer => {
  إرجاع وعد جديد (العزم => {
    قارئ const = PNGReader جديد (مخزن مؤقت) ؛
    إرجاع reader.parse ((err، png) => {
      const pixels = Float32Array.from (png.pixels) .map (pixel => {
        إرجاع بكسل / 255 ؛
      })؛
      this.datasetImages = بكسل ؛
      حل()؛
    })؛
  })؛
})؛

هذا إرجاع مخزن مؤقت صفيف للصورة معينة. عند كتابة هذا ، حاولت أولاً تحليل المخزن المؤقت الوارد بنفسي ، والذي لا أوصي به. (إذا كنت مهتمًا بالقيام بذلك ، فإليك بعض المعلومات حول كيفية قراءة مخزن مؤقت لصفيف png.) بدلاً من ذلك ، اخترت استخدام pngjs ، والذي يعالج تحليل png لك. عند التعامل مع تنسيقات الصور الأخرى ، يجب عليك معرفة وظائف التحليل بنفسك.

مجرد خدش السطح

يعد فهم معالجة البيانات مكونًا أساسيًا للتعلم الآلي في جافا سكريبت. من خلال فهم حالات ومتطلبات الاستخدام الخاصة بنا ، يمكننا استخدام بعض الوظائف الرئيسية لتنسيق بياناتنا بشكل أنيق لتلبية احتياجاتنا.

يغير فريق Tensorflow.js واجهة برمجة تطبيقات البيانات الأساسية بشكل مستمر في Tensorflow.js. يمكن أن يساعد هذا في تلبية المزيد من احتياجاتنا مع تطور واجهة برمجة التطبيقات. هذا يعني أيضًا أن الأمر يستحق مواكبة التطورات في واجهة برمجة التطبيقات لأن Tensorflow.js مستمر في النمو والتحسن.

نشرت أصلا في thekevinscott.com

شكر خاص لآري زيلنيك.