TensorFlow.js: اصنع "أداة تعليم الآلة" الخاصة بك" باستخدام تعلُّم النقل مع TensorFlow.js

1. قبل البدء

لقد ازداد استخدام نموذج TensorFlow.js بشكل كبير خلال السنوات القليلة الماضية، ويتطلع الآن العديد من مطوّري برامج JavaScript إلى استخدام النماذج المتطوّرة الحالية وإعادة تدريبهم على العمل باستخدام بيانات مخصّصة فريدة في مجالهم. تُعرف عملية أخذ نموذج حالي (غالبًا ما يُشار إليه باسم النموذج الأساسي) واستخدامه على مجال مماثل ولكن مختلف باسم التعلّم الانتقالي.

نقل التعلم له العديد من المزايا مقارنة بالبدء من نموذج فارغ تمامًا. يمكنك إعادة استخدام المعرفة التي تعلمتها بالفعل من نموذج تم تدريبه مسبقًا، وأنت بحاجة إلى عدد أقل من الأمثلة على العنصر الجديد الذي تريد تصنيفه. كذلك، غالبًا ما يكون التدريب أسرع بكثير بسبب الحاجة فقط إلى إعادة تدريب الطبقات القليلة الأخيرة من بنية النموذج بدلاً من الشبكة بأكملها. لهذا السبب، تُعد عملية تعلُّم النقل مناسبة جدًا لبيئة متصفّح الويب حيث قد تختلف الموارد حسب جهاز التنفيذ، ولكنها توفّر أيضًا إمكانية الوصول المباشر إلى أدوات الاستشعار لتسهيل الحصول على البيانات.

يشرح لك هذا الدرس التطبيقي حول الترميز كيفية إنشاء تطبيق ويب من لوحة فارغة، مع إعادة ابتكار تطبيق " Teachable Machine" موقعك الإلكتروني. يتيح لك الموقع الإلكتروني إنشاء تطبيق ويب فعال يمكن لأي مستخدم استخدامه للتعرّف على كائن مخصّص باستخدام بعض الأمثلة على الصور من كاميرا الويب. يتم إبقاء الموقع الإلكتروني في حدّه الأدنى عن قصد حتى تتمكّن من التركيز على جوانب "تعلُّم الآلة" في هذا الدرس التطبيقي حول الترميز. ومع ذلك، كما هو الحال مع الموقع الإلكتروني الأصلي لأداة Teachable Machine، هناك مجالات كثيرة لتطبيق تجربتك الحالية كمطوّر على الويب لتحسين تجربة المستخدم.

المتطلبات الأساسية

تمت كتابة هذا الدرس التطبيقي حول الترميز لمطوّري الويب الذين لديهم دراية نوعًا ما بنماذج TensorFlow.js المعدة مسبقًا واستخدام واجهة برمجة التطبيقات الأساسية، والذين يريدون بدء استخدام تعلُّم النقل inTensorFlow.js.

  • يُفترض في هذا التمرين المعملي الإلمام الأساسي ببرامج TensorFlow.js وHTML5 وCSS وJavaScript.

إذا كنت مستخدمًا جديدًا لمنصة Tensorflow.js، ننصحك بأخذ هذه الدورة التدريبية المجانية من البداية إلى النهاية أولاً، والتي تفترض عدم توفّر أي خلفية عن تعلُّم الآلة أو TensorFlow.js، وستعلمك كل ما تحتاج إلى معرفته بخطوات أصغر.

المعلومات التي ستطّلع عليها

  • ما هو TensorFlow.js ولماذا يجب استخدامها في تطبيق الويب التالي.
  • كيفية إنشاء صفحة ويب HTML/CSS /JS مبسطة تعمل على تكرار تجربة مستخدم Teachable Machine.
  • طريقة استخدام TensorFlow.js لتحميل نموذج أساسي مدرّب مسبقًا، وتحديدًا MobileNet، لإنشاء ميزات صور يمكن استخدامها في تعلُّم النقل.
  • كيفية جمع البيانات من كاميرا الويب الخاصة بالمستخدم لفئات متعددة من البيانات التي تريد التعرف عليها.
  • يشير ذلك المصطلح إلى طريقة إنشاء وتعريف جهاز إدراك متعدّد الطبقات يأخذ خصائص الصورة ويتعلّم تصنيف الأجسام الجديدة باستخدامها.

لنبدأ الاختراق...

المتطلبات

  • يُفضل استخدام حساب Glitch.com للمتابعة، أو يمكنك استخدام بيئة عرض ويب تشعر بالراحة عند تعديلها وتشغيلها بنفسك.

2. ما هو TensorFlow.js؟

54e81d02971f53e8.png

TensorFlow.js هي مكتبة مفتوحة المصدر لتعلُّم الآلة يمكنها العمل في أي مكان يتوفّر فيه JavaScript. وهو يستند إلى مكتبة TensorFlow الأصلية المكتوبة بلغة Python وتهدف إلى إعادة إنشاء تجربة المطوِّر هذه ومجموعة من واجهات برمجة التطبيقات لمنظومة JavaScript المتكاملة.

أين يمكن استخدامها؟

ونظرًا لإمكانية نقل لغة JavaScript، يمكنك الآن الكتابة بلغة واحدة وتنفيذ عملية تعلُّم الآلة على جميع الأنظمة الأساسية التالية بسهولة:

  • من جانب العميل في متصفح الويب باستخدام vanilla JavaScript
  • من جانب الخادم وحتى أجهزة إنترنت الأشياء مثل Raspberry Pi التي تستخدم Node.js
  • تطبيقات الكمبيوتر المكتبي باستخدام Electron
  • تطبيقات متوافقة مع الأجهزة الجوّالة باستخدام React Native

يدعم TensorFlow.js أيضًا العديد من الخلفيات في كل من هذه البيئات (البيئات الفعلية القائمة على الأجهزة التي يمكن تنفيذها داخل مثل وحدة المعالجة المركزية (CPU) أو WebGL، على سبيل المثال. "الواجهة الخلفية" في هذا السياق لا يعني وجود بيئة من جانب الخادم - الواجهة الخلفية للتنفيذ يمكن أن تكون من جانب العميل في WebGL على سبيل المثال) لضمان التوافق وأيضًا الحفاظ على سرعة سير الأمور. في الوقت الحالي، يدعم TensorFlow.js ما يلي:

  • تنفيذ WebGL على بطاقة رسومات الجهاز (GPU) - هذه هي أسرع طريقة لتنفيذ النماذج الأكبر (التي يزيد حجمها عن 3 ميغابايت) مع تسريع وحدة معالجة الرسومات.
  • تنفيذ Web Assembly (WASM) على وحدة المعالجة المركزية (CPU): لتحسين أداء وحدة المعالجة المركزية (CPU) على جميع الأجهزة، بما في ذلك الهواتف الجوّالة من الجيل القديم مثلاً. ويناسب ذلك بشكل أفضل النماذج الأصغر (أقل من 3 ميغابايت) التي يمكنها في الواقع تنفيذها بشكل أسرع على وحدة المعالجة المركزية (CPU) باستخدام WASM مقارنةً بـ WebGL بسبب عبء تحميل المحتوى إلى معالج رسومات.
  • تنفيذ وحدة المعالجة المركزية (CPU) - يجب ألا يكون الجهاز الاحتياطي متاحًا لأي من البيئات الأخرى. هذا الخيار هو الأبطأ من بين ثلاثة، ولكن يمكنك دائمًا الاستماع إليه.

ملاحظة: يمكنك اختيار فرض إحدى هذه الخلفيات إذا كنت تعرف الجهاز الذي سيتم التنفيذ عليه، أو يمكنك ببساطة السماح لمنصة TensorFlow.js باختيار عدم تحديد ذلك.

القوى الخارقة من جانب العميل

يمكن أن يؤدي تشغيل TensorFlow.js في متصفح الويب على جهاز العميل إلى عدة فوائد جديرة بالاهتمام.

الخصوصية

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

السرعة

نظرًا لعدم حاجتك إلى إرسال البيانات إلى خادم بعيد، يمكن أن يكون الاستنتاج (عملية تصنيف البيانات) أسرع. والأفضل من ذلك أنّه يمكنك الوصول مباشرةً إلى أجهزة الاستشعار في الجهاز، مثل الكاميرا والميكروفون ونظام تحديد المواقع العالمي (GPS) ومقياس التسارع وغيرها، في حال منحك المستخدم إذن الوصول.

الوصول إلى الجمهور وتوسيع نطاقه

بنقرة واحدة، يستطيع أي شخص في العالم النقر على الرابط الذي ترسله إليه، وفتح صفحة الويب في متصفحه، والاستفادة من ما قمت بإنشائه. ليست هناك حاجة إلى إعداد Linux معقد من جانب الخادم مع برامج تشغيل CUDA وغير ذلك الكثير لاستخدام نظام التعلم الآلي فقط.

التكلفة

تعني عدم توفّر خوادم أنّ ما عليك سوى الدفع مقابل شبكة توصيل المحتوى (CDN) لاستضافة ملفات HTML وCSS وJS والنماذج. أما تكلفة شبكة توصيل المحتوى (CDN)، فأقل تكلفة بكثير من الاحتفاظ بخادم (من المحتمل أن يكون مع إرفاق بطاقة رسومات) على مدار الساعة طوال أيام الأسبوع.

الميزات من جانب الخادم

بالاستفادة من تنفيذ Node.js في TensorFlow.js يمكن توفير الميزات التالية.

دعم CUDA بالكامل

على الخادم، لتسريع بطاقة الرسومات، يجب تثبيت برامج تشغيل NVIDIA CUDA لتمكين TensorFlow من العمل مع بطاقة الرسومات (على عكس المتصفح الذي يستخدم WebGL ولا يلزم التثبيت). مع ذلك، يمكنك الاستفادة بشكل كامل من القدرات ذات المستوى الأدنى في بطاقة الرسومات من خلال التوافق الكامل مع CUDA، ما يؤدي إلى تدريب واستنتاج أسرع. يتساوى الأداء مع تنفيذ Python TensorFlow حيث إنهما يشتركان في نفس خلفية C++.

مقاس الطراز

بالنسبة إلى النماذج المتطورة من البحث، قد تعمل على نماذج كبيرة جدًا، ربما يصل حجمها إلى غيغابايت. لا يمكن تشغيل هذه النماذج في الوقت الحالي في متصفح الويب بسبب قيود استخدام الذاكرة لكل علامة تبويب في المتصفح. لتشغيل هذه النماذج الأكبر حجمًا، يمكنك استخدام Node.js على خادمك الخاص مع مواصفات الأجهزة التي تحتاجها لتشغيل هذا النموذج بكفاءة.

إنترنت الأشياء (IOT)

تتوافق Node.js مع أجهزة الكمبيوتر الشائعة الأحادية اللوحة، مثل Rspberry Pi، ما يعني أنّه يمكنك تنفيذ نماذج TensorFlow.js على هذه الأجهزة أيضًا.

السرعة

وتمت كتابة Node.js بلغة JavaScript، مما يعني أنها تستفيد من التجميع الزمني فقط. وهذا يعني أنّك قد تلاحظ مكاسب في الأداء عند استخدام Node.js لأنّه سيتم تحسينه في وقت التشغيل، خاصةً لأي عمليات معالجة مسبقة قد تجريها. ومن الأمثلة الرائعة على ذلك دراسة الحالة هذه التي توضّح كيف استخدمت شركة Hugging Face Node.js للحصول على ضعف الأداء لنموذج معالجة اللغة الطبيعية الخاص بها.

بعد أن فهمت أساسيات استخدام TensorFlow.js، ومكان تشغيله، وبعض مزاياه، لنبدأ الآن في استخدامه.

3- نقل التعلّم

ما هو المقصود تحديدًا بالتعلّم الانتقالي؟

يتضمن التعلم المُعدَّل الحصول على المعرفة التي تم تعلمها بالفعل للمساعدة في تعلم شيء مختلف ولكن مشابه.

ونحن نفعل ذلك طوال الوقت. تتضمّن عقلك تجارب حياتية يمكنك استخدامها للمساعدة في التعرّف على أشياء جديدة لم ترها من قبل. خذ شجرة الصفصاف هذه كمثال:

e28070392cd4afb9.png

اعتمادًا على مكانك في العالم، هناك احتمال أنّك لم ترَ هذا النوع من الأشجار من قبل.

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

d9073a0d5df27222.png

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

وبالمثل، إذا كان لديك نموذج تعلُّم آلة تم تدريبه مسبقًا على أحد المجالات، مثل التعرّف على الصور، يمكنك إعادة استخدامه لأداء مهمة مختلفة ولكن ذات صلة.

ويمكنك تحقيق الشيء نفسه مع نموذج متقدم مثل MobileNet، وهو نموذج بحث شائع للغاية يمكنه التعرف على الصور على 1000 نوع مختلف من الكائنات. وقد تم تدريبه على مجموعة بيانات ضخمة تُعرف باسم ImageNet، وتضم ملايين الصور المصنّفة، بدءًا من الكلاب إلى السيارات.

في هذه الحركة، يمكنك مشاهدة العدد الهائل من الطبقات التي تتضمّنها في نموذج MobileNet V1 هذا:

7d4e1e35c1a89715.gif

أثناء التدريب، تعلّم هذا النموذج كيفية استخراج الميزات الشائعة المهمة لجميع العناصر الـ 1,000، ويمكن الاستفادة من العديد من الميزات الأدنى التي يستخدمها لتحديد هذه العناصر لرصد عناصر جديدة لم يرَها من قبل أيضًا. بعد كل شيء، كل شيء في النهاية هو مجرد مزيج من الخطوط والقوام والأشكال.

لنلقِ نظرة على بنية الشبكة العصبية الالتفافية (CNN) التقليدية (المشابهة لـ MobileNet) ونرى كيف يمكن للتعلم عبر النقل الاستفادة من هذه الشبكة المدرَّبة لتعلُّم معلومات جديدة. توضّح الصورة أدناه البنية النموذجية لشبكة CNN التي تم تدريبها في هذه الحالة على التعرّف على الأرقام المكتوبة بخط اليد من 0 إلى 9:

baf4e3d434576106.png

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

369a8a9041c6917d.png

بافتراض أن الشيء الجديد الذي تحاول التعرف عليه يمكنه أيضًا الاستفادة من ميزات الإخراج هذه التي تعلمها النموذج السابق، فإن هناك فرصة جيدة لإعادة استخدامها لغرض جديد.

في الرسم التخطيطي أعلاه، تم تدريب هذا النموذج الافتراضي على الأرقام، لذا ربما يمكن تطبيق ما تم تعلمه عن الأرقام أيضًا على أحرف مثل a وb وc.

والآن، يمكنك إضافة رأس تصنيف جديد يحاول التنبؤ a أو b أو c بدلاً من ذلك، كما هو موضح:

db97e5e60ae73bbd.png

في هذه الحالة، يتم تجميد الطبقات ذات المستوى الأدنى وعدم تدريبها، سيُحدِّث رأس التصنيف الجديد نفسه نفسه للتعلم من الميزات المتوفرة من النموذج المقطّع المدرَّب مسبقًا على اليسار.

ويُعرف هذا الإجراء باسم التعلّم النقلي، وهذا ما تفعله أداة Teachable Machine خلف الكواليس.

كما ترى أيضًا أنه من خلال الاضطرار فقط إلى تدريب جهاز الإدراك متعدد الطبقات في نهاية الشبكة، فإنه يتم تدريبه بشكل أسرع بكثير مما لو اضطررت إلى تدريب الشبكة بأكملها من البداية تمامًا.

ولكن كيف يمكنك الحصول على الأجزاء الفرعية من النموذج؟ توجه إلى القسم التالي لمعرفة المزيد.

4. TensorFlow Hub - النماذج الأساسية

البحث عن نموذج أساسي مناسب لاستخدامه

للحصول على نماذج بحث أكثر تقدمًا ورواجًا مثل MobileNet، يمكنك الانتقال إلى مركز TensorFlow، ثم التصفية بحثًا عن النماذج المناسبة لـ TensorFlow.js التي تستخدم بنية MobileNet v3 للعثور على نتائج مثل تلك المعروضة هنا:

c5dc1420c6238c14.png

تجدر الإشارة إلى أنّ بعض هذه النتائج من النوع "تصنيف الصور" (مفصّلة في أعلى يسار كل نتيجة خاصة ببطاقة النموذج)، والبعض الآخر من النوع "متّجه ميزة الصورة".

نتائج Image Feature Vector هي في الأساس إصدارات MobileNet التي تم اقتصاصها مسبقًا والتي يمكنك استخدامها للحصول على متجهات ميزة الصورة بدلاً من التصنيف النهائي.

تُسمى هذه النماذج غالبًا "النماذج الأساسية"، والتي يمكنك استخدامها بعد ذلك لتنفيذ التعلّم النقلي بالطريقة نفسها كما هو موضّح في القسم السابق عن طريق إضافة عنوان تصنيف جديد وتدريبه ببياناتك الخاصة.

الشيء التالي الذي يجب التحقق منه هو التنسيق الأساسي الذي تم إصدار النموذج به من خلال TensorFlow.js. عند فتح الصفحة لأحد نماذج MobileNet v3 لمتّجه الميزات، سيكون بإمكانك الاطّلاع من مستندات JavaScript على أنّها في شكل نموذج رسم بياني استنادًا إلى مثال مقتطف الرمز في المستندات التي تستخدم tf.loadGraphModel().

f97d903d2e46924b.png

كما تجدر الإشارة إلى أنّه في حال العثور على نموذج بتنسيق الطبقات بدلاً من تنسيق الرسم البياني، يمكنك اختيار الطبقات التي تريد تجميدها وأيها يجب إلغاء تجميدها بغرض التدريب. ويمكن أن يكون ذلك فعالاً للغاية عند إنشاء نموذج لمهمة جديدة، والتي يُشار إليها غالبًا باسم "نموذج النقل". في الوقت الحالي، ستستخدم نوع نموذج الرسم البياني التلقائي لهذا البرنامج التعليمي، والذي يتم نشر معظم نماذج TF Hub به. لمزيد من المعلومات عن العمل باستخدام نماذج الطبقات، اطّلع على الدورة التدريبية من الصفر إلى الجزء الرئيسي TensorFlow.js.

مميزات التعلّم القائم على النقل

ما هي مزايا استخدام التعلّم النقلي بدلاً من تدريب بنية النموذج بالكامل من البداية؟

أولاً، يشكّل وقت التدريب ميزة أساسية لاستخدام منهج التعلّم القائم على النقل لأنّه يتوفّر لديك نموذج أساسي مدرَّب يمكنك الاستفادة منه.

ثانيًا، يمكنك الإفلات من خلال عرض أمثلة أقل بكثير للشيء الجديد الذي تحاول تصنيفه بسبب التدريب الذي تم إجراؤه بالفعل.

يعد هذا أمرًا رائعًا حقًا إذا كان لديك وقت وموارد محدودة لجمع أمثلة بيانات للشيء الذي تريد تصنيفه، وتحتاج إلى إنشاء نموذج أوّلي بسرعة قبل جمع المزيد من بيانات التدريب لجعلها أكثر قوة.

ونظرًا إلى الحاجة إلى بيانات أقل وسرعة تدريب شبكة أصغر، يكون تعلُّم النقل أقل استهلاكًا للموارد. وهذا يجعلها مناسبة جدًا لبيئة المتصفح، إذ تستغرق عشرات الثواني على جهاز حديث بدلاً من ساعات أو أيام أو أسابيع لتدريب النماذج الكاملة.

حسنًا بعد أن تعرفت على جوهر أداة Transfer Learning، حان الوقت لإنشاء نسختك الخاصة من أداة "تعليم الآلة". لِنبدأ.

5- إعداد الرمز

المتطلبات

بدء تعلُّم الترميز

تم إنشاء نماذج نموذجية يمكن البدء منها للموقع Glitch.com أو Codepen.io. ويمكنك ببساطة استنساخ أحد النموذجَين كحالتك الأساسية في هذا الدرس التطبيقي عن الترميز بنقرة واحدة فقط.

في تطبيق Glitch، انقر على الزر إنشاء ريمكس من هذا المحتوى لإنشاء مجموعة جديدة من الملفات التي يمكنك تعديلها.

أو على Codepen، انقر على" fork" في أسفل يسار الشاشة.

يوفر لك هذا الهيكل البسيط جدًا الملفات التالية:

  • صفحة HTML (index.html)
  • ورقة الأنماط (style.css)
  • ملف لكتابة رمز JavaScript (script.js)

ولتسهيل الأمر عليك، هناك استيراد إضافي في ملف HTML للمكتبة TensorFlow.js. تبدو هكذا:

index.html

<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>

الإجراء البديل: استخدام أداة تعديل الويب المفضّلة لديك أو العمل على الجهاز

إذا أردت تنزيل الرمز والعمل على الجهاز أو من خلال محرّر مختلف على الإنترنت، ما عليك سوى إنشاء الملفات الثلاثة المذكورة أعلاه في الدليل نفسه ونسخ الرمز من نص Glitch النموذجي ولصقه في كل ملف.

6- نص HTML النموذجي للتطبيق

من أين أبدأ؟

تتطلب جميع النماذج الأولية بعض عمليات HTML الأساسية التي يمكنك عرض نتائجك عليها. ما عليك سوى إعداد هذه الميزة الآن. أنت بصدد إضافة:

  • تمثّل هذه السمة عنوان الصفحة.
  • بعض النصوص الوصفية.
  • فقرة حالة.
  • فيديو للاحتفاظ بخلاصة كاميرا الويب بمجرد تجهيزها.
  • عدة أزرار لتشغيل الكاميرا أو جمع البيانات أو إعادة ضبط التجربة
  • استيراد ملفات TensorFlow.js وJS التي ستكتبها لاحقًا.

افتح index.html والصِق الرمز الحالي باستخدام ما يلي لإعداد الميزات أعلاه:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Transfer Learning - TensorFlow.js</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Import the webpage's stylesheet -->
    <link rel="stylesheet" href="/style.css">
  </head>  
  <body>
    <h1>Make your own "Teachable Machine" using Transfer Learning with MobileNet v3 in TensorFlow.js using saved graph model from TFHub.</h1>
    
    <p id="status">Awaiting TF.js load</p>
    
    <video id="webcam" autoplay muted></video>
    
    <button id="enableCam">Enable Webcam</button>
    <button class="dataCollector" data-1hot="0" data-name="Class 1">Gather Class 1 Data</button>
    <button class="dataCollector" data-1hot="1" data-name="Class 2">Gather Class 2 Data</button>
    <button id="train">Train &amp; Predict!</button>
    <button id="reset">Reset</button>

    <!-- Import TensorFlow.js library -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js" type="text/javascript"></script>

    <!-- Import the page's JavaScript to do some stuff -->
    <script type="module" src="/script.js"></script>
  </body>
</html>

الفصل

لنفصل بعضًا من رمز HTML أعلاه لإبراز بعض الأشياء الأساسية التي أضفتها.

  • لقد أضفت علامة <h1> لعنوان الصفحة مع علامة <p> برقم تعريف "الحالة". وهو المكان الذي ستطبع فيه المعلومات، أثناء استخدام أجزاء مختلفة من النظام لعرض المخرجات.
  • لقد أضفت عنصر <video> برقم التعريف "كاميرا ويب". لعرض البث عبر كاميرا الويب لاحقًا.
  • لقد أضفت 5 عناصر <button>. والنوع الأول، الذي يحمل معرّف "enableCam"، لتفعيل الكاميرا. يحتوي الزران التاليان على فئة "dataCollector"، التي تتيح لك جمع نماذج صور للكائنات التي تريد التعرّف عليها. سيتم تصميم الكود الذي تكتبه لاحقًا بحيث يمكنك إضافة أي عدد من هذه الأزرار وستعمل على النحو المنشود تلقائيًا.

لاحظ أن هذه الأزرار تحتوي أيضًا على سمة خاصة يحددها المستخدم تسمى data-1hot، مع قيمة عددية تبدأ من 0 للفئة الأولى. هذا هو الفهرس العددي الذي ستستخدمه لتمثيل بيانات فئة معينة. سيتم استخدام الفهرس لترميز فئات الإخراج بشكل صحيح من خلال تمثيل رقمي بدلاً من سلسلة، حيث يمكن أن تعمل نماذج تعلُّم الآلة مع الأرقام فقط.

هناك أيضًا سمة data-name تحتوي على اسم سهل القراءة للمستخدم تريد استخدامه لهذه الفئة، والتي تتيح لك تقديم اسم أكثر معنى للمستخدم بدلاً من قيمة فهرس عددية من الترميز الأكثر تكرارًا 1.

وأخيرًا، لديك زر للتدريب وإعادة الضبط لبدء عملية التدريب بمجرد جمع البيانات، أو لإعادة ضبط التطبيق على التوالي.

  • لقد أضفت أيضًا عمليتي استيراد من <script>. أحدهما لـ TensorFlow.js والآخر لـ script.js الذي ستحدده قريبًا.

7. إضافة نمط

الإعدادات التلقائية للعناصر

أضِف أنماطًا لعناصر HTML التي أضفتها للتو لضمان عرضها بشكل صحيح. في ما يلي بعض الأنماط التي تتم إضافتها إلى عناصر الموضع والحجم بشكل صحيح. لا شيء مميز جدًا. يمكنك بالتأكيد الإضافة إلى ذلك لاحقًا لإنشاء تجربة مستخدم أفضل، كما رأيت في فيديو الجهاز التعليمي.

style.css

body {
  font-family: helvetica, arial, sans-serif;
  margin: 2em;
}

h1 {
  font-style: italic;
  color: #FF6F00;
}


video {
  clear: both;
  display: block;
  margin: 10px;
  background: #000000;
  width: 640px;
  height: 480px;
}

button {
  padding: 10px;
  float: left;
  margin: 5px 3px 5px 10px;
}

.removed {
  display: none;
}

#status {
  font-size:150%;
}

رائع! هذا كل ما تحتاج إليه. في حال معاينة الناتج الآن، من المفترض أن يظهر على النحو التالي:

81909685d7566dcb.png

8. JavaScript: الثوابت الأساسية والمستمعين

تحديد الثوابت الرئيسية

أولاً، أضف بعض الثوابت الأساسية التي ستستخدمها في جميع أنحاء التطبيق. ابدأ باستبدال محتوى script.js بالثوابت التالية:

script.js

const STATUS = document.getElementById('status');
const VIDEO = document.getElementById('webcam');
const ENABLE_CAM_BUTTON = document.getElementById('enableCam');
const RESET_BUTTON = document.getElementById('reset');
const TRAIN_BUTTON = document.getElementById('train');
const MOBILE_NET_INPUT_WIDTH = 224;
const MOBILE_NET_INPUT_HEIGHT = 224;
const STOP_DATA_GATHER = -1;
const CLASS_NAMES = [];

دعنا نحلل ماهية هذه الأشياء:

  • تحتوي الدالة STATUS ببساطة على إشارة إلى علامة الفقرة التي ستكتب تعديلات الحالة إليها.
  • يحتوي VIDEO على مرجع إلى عنصر فيديو HTML الذي سيعرض خلاصة كاميرا الويب.
  • يجلب ENABLE_CAM_BUTTON وRESET_BUTTON وTRAIN_BUTTON مراجع DOM إلى جميع الأزرار الرئيسية من صفحة HTML.
  • يحدد كل من MOBILE_NET_INPUT_WIDTH وMOBILE_NET_INPUT_HEIGHT عرض الإدخال المتوقع وارتفاعه لنموذج MobileNet على التوالي. من خلال تخزين هذا في ثابت بالقرب من أعلى الملف مثل هذا، إذا قررت استخدام إصدار مختلف لاحقًا، فسيكون من السهل تحديث القيم مرة واحدة بدلاً من الاضطرار إلى استبدالها في العديد من الأماكن المختلفة.
  • تم ضبط قيمة STOP_DATA_GATHER على - 1. يخزِّن هذا العمود قيمة الحالة حتى تعرف متى توقّف المستخدم عن النقر على زر لجمع البيانات من خلاصة كاميرا الويب. من خلال إعطاء هذا الرقم اسمًا أكثر وضوحًا، يجعل التعليمة البرمجية أكثر قابلية للقراءة لاحقًا.
  • يعمل CLASS_NAMES كبحث ويحتفظ بالأسماء التي يمكن للمستخدمين قراءتها لتوقّعات الفئة المحتملة. وستتم تعبئة هذا الصفيف لاحقًا.

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

إضافة أدوات معالجة الأحداث الرئيسية

ابدأ بإضافة معالِجات أحداث النقر إلى الأزرار الرئيسية كما هو موضّح:

script.js

ENABLE_CAM_BUTTON.addEventListener('click', enableCam);
TRAIN_BUTTON.addEventListener('click', trainAndPredict);
RESET_BUTTON.addEventListener('click', reset);


function enableCam() {
  // TODO: Fill this out later in the codelab!
}


function trainAndPredict() {
  // TODO: Fill this out later in the codelab!
}


function reset() {
  // TODO: Fill this out later in the codelab!
}

ENABLE_CAM_BUTTON - تستدعي دالة enableCam عند النقر عليها.

TRAIN_BUTTON - تدريب على إجراء استدعاءات للتدريب والتنبؤ عند النقر فوقها.

RESET_BUTTON - تتم إعادة ضبط المكالمات عند النقر عليها.

أخيرًا، يمكنك في هذا القسم العثور على جميع الأزرار التي تحتوي على فئة "dataCollector" باستخدام document.querySelectorAll(). يؤدي ذلك إلى إرجاع صفيف من العناصر التي تم العثور عليها من المستند المطابقة:

script.js

let dataCollectorButtons = document.querySelectorAll('button.dataCollector');
for (let i = 0; i < dataCollectorButtons.length; i++) {
  dataCollectorButtons[i].addEventListener('mousedown', gatherDataForClass);
  dataCollectorButtons[i].addEventListener('mouseup', gatherDataForClass);
  // Populate the human readable names for classes.
  CLASS_NAMES.push(dataCollectorButtons[i].getAttribute('data-name'));
}


function gatherDataForClass() {
  // TODO: Fill this out later in the codelab!
}

شرح الرمز:

يمكنك بعد ذلك التكرار من خلال الأزرار التي تم العثور عليها وربط مستمعين للأحداث بكل منهما. واحدة لـ "الماوس" وأخرى لـ "الماوس". يتيح لك هذا الاحتفاظ بتسجيل العينات طالما تم الضغط على الزر، وهو أمر مفيد لجمع البيانات.

يستدعي كلا الحدثين دالة gatherDataForClass التي ستحددها لاحقًا.

في هذه المرحلة، يمكنك أيضًا إرسال أسماء الفئات التي تم العثور عليها ويمكن لشخص عادي قراءتها من اسم سمة data-name لزر HTML إلى المصفوفة CLASS_NAMES.

بعد ذلك، أضف بعض المتغيرات لتخزين الأشياء الرئيسية التي سيتم استخدامها لاحقًا.

script.js

let mobilenet = undefined;
let gatherDataState = STOP_DATA_GATHER;
let videoPlaying = false;
let trainingDataInputs = [];
let trainingDataOutputs = [];
let examplesCount = [];
let predict = false;

لنتصفحها.

أولاً، لديك متغير mobilenet لتخزين نموذج شبكة الجوّال الذي تم تحميله. اضبط هذا الخيار في البداية على "غير محدّد".

بعد ذلك، لديك متغير يسمى gatherDataState. إذا قامت شركة "dataCollector" فسيتم الضغط على هذا الزر ليكون المعرف الوحيد لهذا الزر بدلاً من ذلك، كما هو محدد في HTML، حتى تعرف فئة البيانات التي تجمعها في تلك اللحظة. في البداية، يتم ضبط هذا الإعداد على STOP_DATA_GATHER كي لا تجمع حلقة جمع البيانات التي تكتبها لاحقًا أي بيانات في حال عدم الضغط على أي أزرار.

يتتبّع "videoPlaying" ما إذا كان قد تم تحميل بث كاميرا الويب وتشغيله بنجاح وهو متاح للاستخدام. في البداية، تم ضبط هذا الإعداد على false لأنّ كاميرا الويب ليست قيد التشغيل إلا بعد الضغط على ENABLE_CAM_BUTTON..

بعد ذلك، حدِّد صفيفَتين، هما trainingDataInputs وtrainingDataOutputs. تقوم هذه بتخزين قيم بيانات التدريب التي تم جمعها، عند النقر على زر "dataCollector" الخاصة بميزات الإدخال التي تم إنشاؤها بواسطة النموذج الأساسي MobileNet، وفئة المخرجات التي تم أخذ عينات منها على التوالي.

يتم بعد ذلك تحديد مصفوفة أخيرة، examplesCount, لتتبُّع عدد الأمثلة المضمَّنة لكل فئة بعد البدء في إضافتها.

وأخيرًا، لديك متغيّر يسمى predict يتحكّم في حلقة التوقّع. وقد تم ضبط هذه السياسة على false في البداية. ولا يمكن حدوث توقعات إلى أن يتم ضبط هذه السياسة على true في وقت لاحق.

والآن بعد أن تم تحديد جميع المتغيرات الرئيسية، لنقم بتحميل النموذج الأساسي للإصدار 3 من MobileNet الذي تم تقطيعه مسبقًا والذي يوفر متجهات ميزات الصورة بدلاً من التصنيفات.

9. تحميل النموذج الأساسي MobileNet

أولاً، حدِّد دالة جديدة تُسمى loadMobileNetFeatureModel كما هو موضّح أدناه. ويجب أن تكون هذه دالة غير متزامنة، حيث تكون عملية تحميل النموذج غير متزامنة:

script.js

/**
 * Loads the MobileNet model and warms it up so ready for use.
 **/
async function loadMobileNetFeatureModel() {
  const URL = 
    'https://tfhub.dev/google/tfjs-model/imagenet/mobilenet_v3_small_100_224/feature_vector/5/default/1';
  
  mobilenet = await tf.loadGraphModel(URL, {fromTFHub: true});
  STATUS.innerText = 'MobileNet v3 loaded successfully!';
  
  // Warm up the model by passing zeros through it once.
  tf.tidy(function () {
    let answer = mobilenet.predict(tf.zeros([1, MOBILE_NET_INPUT_HEIGHT, MOBILE_NET_INPUT_WIDTH, 3]));
    console.log(answer.shape);
  });
}

// Call the function immediately to start loading.
loadMobileNetFeatureModel();

وفي هذا الرمز، يمكنك تحديد URL حيث يقع النموذج المطلوب تحميله من مستندات TFHub.

يمكنك بعد ذلك تحميل النموذج باستخدام await tf.loadGraphModel()، مع تذكُّر ضبط السمة الخاصة fromTFHub على true أثناء تحميل نموذج من موقع Google الإلكتروني هذا. وهذه حالة خاصة فقط عند استخدام النماذج المستضافة على TF Hub حيث يجب ضبط هذه السمة الإضافية.

بعد اكتمال التحميل، يمكنك ضبط العنصر innerText للعنصر STATUS برسالة لكي يظهر أنّه تم تحميله بشكل صحيح ومن أنّك مستعد لبدء جمع البيانات.

إن الشيء الوحيد المتبقي الآن هو إحماء النموذج. مع هذه النماذج الأكبر حجمًا، يمكن أن يستغرق إعداد كل شيء عند استخدام النموذج لأول مرة. وبالتالي فإنه يساعد على تمرير الأصفار من خلال النموذج لتجنب أي انتظار في المستقبل حيث قد يكون التوقيت أكثر أهمية.

يمكنك استخدام دالة tf.zeros() ملفوفة في tf.tidy() للتأكّد من التخلص من قيم العشرات بشكل صحيح، مع حجم الدُفعة 1 والارتفاع والعرض الصحيحَين اللذين حددتهما في الثوابت عند البداية. وأخيرًا، يمكنك أيضًا تحديد قنوات الألوان، والتي تكون في هذه الحالة 3 حيث يتوقع النموذج صورًا بنموذج أحمر أخضر أزرق.

بعد ذلك، سجِّل الشكل الناتج لمنسورة التنس الذي يظهر باستخدام answer.shape() لمساعدتك في فهم حجم ميزات الصورة التي ينتجها هذا النموذج.

وبعد تحديد هذه الدالة، يمكنك استدعاؤها فورًا لبدء تنزيل النموذج عند تحميل الصفحة.

إذا كنت تشاهد المعاينة المباشرة الآن، سترى بعد بضع لحظات تغيير نص الحالة من "في انتظار تحميل TF.js". "تم تحميل الإصدار 3 من MobileNet بنجاح". كما هو موضح أدناه. تأكَّد من تنفيذ هذا الإجراء قبل المتابعة.

a28b734e190afff.png

يمكنك أيضًا الاطلاع على ناتج وحدة التحكم لرؤية الحجم المطبوع لميزات الإخراج التي ينتجها هذا النموذج. بعد تمرير الأصفار في نموذج MobileNet، سيظهر لك شكل [1, 1024]. العنصر الأول هو حجم الدفعة 1 فقط، ويمكنك ملاحظة أنه يعرض في الواقع 1024 ميزة يمكن استخدامها لمساعدتك في تصنيف الكائنات الجديدة.

10. تحديد رأس النموذج الجديد

حان الوقت الآن لتحديد رأس نموذجك، وهو في الأساس من تصورنا الأدنى متعدد الطبقات.

script.js

let model = tf.sequential();
model.add(tf.layers.dense({inputShape: [1024], units: 128, activation: 'relu'}));
model.add(tf.layers.dense({units: CLASS_NAMES.length, activation: 'softmax'}));

model.summary();

// Compile the model with the defined optimizer and specify a loss function to use.
model.compile({
  // Adam changes the learning rate over time which is useful.
  optimizer: 'adam',
  // Use the correct loss function. If 2 classes of data, must use binaryCrossentropy.
  // Else categoricalCrossentropy is used if more than 2 classes.
  loss: (CLASS_NAMES.length === 2) ? 'binaryCrossentropy': 'categoricalCrossentropy', 
  // As this is a classification problem you can record accuracy in the logs too!
  metrics: ['accuracy']  
});

لنتصفح هذه التعليمة البرمجية. تبدأ بتحديد نموذج tf.لإنشاء جدول تسلسلي ستضيف إليه طبقات نموذج.

بعد ذلك، أضف طبقة كثيفة كطبقة إدخال إلى هذا النموذج. على شكل إدخال 1024، سيكون للمخرجات من ميزات الإصدار 3 من MobileNet بهذا الحجم. لقد اكتشفت هذا في الخطوة السابقة بعد تمرير العناصر عبر النموذج. تحتوي هذه الطبقة على 128 خلية عصبية تستخدم وظيفة تفعيل ReLU.

إذا كنت مبتدئًا في مجال وظائف التفعيل وطبقات النماذج، يمكنك المشاركة في الدورة التدريبية المفصّلة في بداية ورشة العمل هذه لفهم كيفية الاستفادة من هذه السمات في الخفاء.

الطبقة التالية التي يمكنك إضافتها هي طبقة الإخراج. يجب أن يساوي عدد الخلايا العصبية عدد الفئات التي تحاول التنبؤ بها. ولإجراء ذلك، يمكنك استخدام CLASS_NAMES.length لمعرفة عدد الصفوف التي تخطّط لتصنيفها، أيّ ما يعادل عدد أزرار جمع البيانات المتوفّرة في واجهة المستخدم. بما أنّ هذه مشكلة تصنيف، فإنّ تفعيل softmax على طبقة الإخراج هذه يجب استخدامه عند محاولة إنشاء نموذج لحل مشاكل التصنيف بدلاً من الانحدار.

يمكنك الآن طباعة model.summary() لطباعة النظرة العامة على النموذج المحدّد حديثًا إلى وحدة التحكّم.

وأخيرًا، قم بتجميع النموذج حتى يكون جاهزًا للتدريب. في هذه الحالة، تم ضبط المحسّن على adam، وستكون الخسارة binaryCrossentropy إذا كانت CLASS_NAMES.length تساوي 2، أو سيستخدم categoricalCrossentropy إذا كان هناك 3 فئات أو أكثر للتصنيف. يتم أيضًا طلب مقاييس الدقة ليتم تتبُّعها في السجلّات لاحقًا لأغراض تصحيح الأخطاء.

من المفترض أن يظهر لك في وحدة التحكّم على النحو التالي:

22eaf32286fea4bb.png

لاحظ أن هذا يحتوي على أكثر من 130 ألف معلمة قابلة للتدريب. ولكن بما أن هذه طبقة بسيطة وكثيفة من الخلايا العصبية المنتظمة، فإنها تتدرب بسرعة كبيرة.

كنشاط تقوم به بمجرد اكتمال المشروع، يمكنك محاولة تغيير عدد الخلايا العصبية في الطبقة الأولى لمعرفة إلى أي مدى يمكنك خفضها مع الاستمرار في الحصول على أداء جيد. غالبًا ما يتوفر مع تعلُّم الآلة مستوى معيّن من التجربة والخطأ لإيجاد القيم المثلى للمعلَمات لمنحك أفضل مقايضة بين استخدام الموارد وسرعتها.

11. تفعيل كاميرا الويب

حان الوقت الآن لتعلُّم دالة enableCam() التي حدّدتها سابقًا. أضِف دالة جديدة باسم hasGetUserMedia() كما هو موضّح أدناه، ثم استبدِل محتوى دالة enableCam() المحدّدة سابقًا بالرمز المقابل أدناه.

script.js

function hasGetUserMedia() {
  return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
}

function enableCam() {
  if (hasGetUserMedia()) {
    // getUsermedia parameters.
    const constraints = {
      video: true,
      width: 640, 
      height: 480 
    };

    // Activate the webcam stream.
    navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
      VIDEO.srcObject = stream;
      VIDEO.addEventListener('loadeddata', function() {
        videoPlaying = true;
        ENABLE_CAM_BUTTON.classList.add('removed');
      });
    });
  } else {
    console.warn('getUserMedia() is not supported by your browser');
  }
}

أولاً، أنشِئ دالة باسم hasGetUserMedia() لمعرفة ما إذا كان المتصفّح يتيح عمل getUserMedia()، وذلك من خلال التحقّق من توفُّر خصائص لواجهات برمجة التطبيقات الرئيسية للمتصفّح.

في الدالة enableCam()، استخدِم الدالة hasGetUserMedia() التي حدّدتها للتو أعلاه للتحقّق مما إذا كانت متوافقة. وإذا لم يكن الأمر كذلك، يمكنك طباعة تحذير لوحدة التحكم.

إذا كان ذلك متاحًا، يُرجى تحديد بعض القيود للمكالمة مع getUserMedia()، مثلاً إذا كنت تريد السماح لبث الفيديو فقط، وتفضل أن يكون حجم width من الفيديو 640 بكسل، على أن يكون height 480 بكسل. لماذا؟ حسنًا، لا فائدة من الحصول على فيديو بحجم أكبر من ذلك حيث يجب تغيير حجمه إلى 224 × 224 بكسل لإضافته إلى نموذج MobileNet. يمكنك أيضًا حفظ بعض موارد الحوسبة من خلال طلب دقة أقل. توفِّر معظم الكاميرات درجة دقة بهذا الحجم.

بعد ذلك، يمكنك الاتصال برقم navigator.mediaDevices.getUserMedia() مع توفير constraints الموضّحة أعلاه، ثم الانتظار إلى أن يتم إرجاع المبلغ stream. بعد إرجاع stream، يمكنك الحصول على العنصر VIDEO لتشغيل stream من خلال ضبطه كقيمة srcObject.

ويجب أيضًا إضافة eventListener على العنصر VIDEO لمعرفة وقت تحميل stream وتشغيله بنجاح.

بعد تحميل البث، يمكنك ضبط videoPlaying على "صحيح" وإزالة ENABLE_CAM_BUTTON لمنع النقر عليه مرة أخرى من خلال ضبط فئته على "removed".

شغِّل الآن الرمز وانقر على زر "تفعيل الكاميرا" واسمح بالوصول إلى كاميرا الويب. إذا كانت هذه المرّة الأولى التي تنفّذ فيها هذه العملية، من المفترض أن ترى نفسك معروضًا في عنصر الفيديو على الصفحة كما هو موضّح:

b378eb1affa9b883.png

حسنًا، حان الوقت الآن لإضافة دالة للتعامل مع النقرات على زر dataCollector.

12. معالِج أحداث زر جمع البيانات

حان الآن وقت ملء الدالة الفارغة الحالية التي تُسمى gatherDataForClass().، وهذه هي الوظيفة التي تم تعيينها كوظيفة معالِج الأحداث لأزرار dataCollector في بداية الدرس التطبيقي حول الترميز.

script.js

/**
 * Handle Data Gather for button mouseup/mousedown.
 **/
function gatherDataForClass() {
  let classNumber = parseInt(this.getAttribute('data-1hot'));
  gatherDataState = (gatherDataState === STOP_DATA_GATHER) ? classNumber : STOP_DATA_GATHER;
  dataGatherLoop();
}

أولاً، تأكَّد من السمة data-1hot على الزر الذي يتم النقر عليه حاليًا من خلال استدعاء this.getAttribute() مع اسم السمة، وفي هذه الحالة data-1hot كمَعلمة. بما أنّها سلسلة، يمكنك استخدام الدالة parseInt() لتحويلها إلى عدد صحيح وتعيين هذه النتيجة لمتغيّر باسم classNumber..

بعد ذلك، اضبط المتغيّر gatherDataState وفقًا لذلك. إذا كانت قيمة gatherDataState الحالية تساوي STOP_DATA_GATHER (التي تم ضبطها على -1)، هذا يعني أنّك لا تجمع حاليًا أي بيانات وأنّه حدث mousedown تم تنشيطه. يجب ضبط قيمة "gatherDataState" على "classNumber" التي عثرت عليها للتو.

بخلاف ذلك، يعني ذلك أنّك تجمع حاليًا البيانات وأنّ الحدث الذي تم تنشيطه كان حدث mouseup، وتريد الآن إيقاف جمع البيانات لهذه الفئة. ما عليك سوى إعادة ضبط السياسة على حالة STOP_DATA_GATHER لإنهاء حلقة جمع البيانات التي ستحدِّدها قريبًا.

وأخيرًا، ابدأ الاتصال بـ "dataGatherLoop()," الذي يتم في الواقع تسجيل بيانات الصف.

13. جمع البيانات

الآن، قم بتعريف الدالة dataGatherLoop(). وتكون هذه الدالة مسؤولة عن أخذ عيّنات من الصور من فيديو كاميرا الويب، وتمريرها عبر نموذج MobileNet، ثم تسجيل مخرجات هذا النموذج (متّجهات الميزة 1024).

وبعد ذلك، يتم تخزينها مع رقم تعريف gatherDataState للزر الذي يتم الضغط عليه حاليًا حتى تعرف الفئة التي تمثلها هذه البيانات.

لنتصفحها:

script.js

function dataGatherLoop() {
  if (videoPlaying && gatherDataState !== STOP_DATA_GATHER) {
    let imageFeatures = tf.tidy(function() {
      let videoFrameAsTensor = tf.browser.fromPixels(VIDEO);
      let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor, [MOBILE_NET_INPUT_HEIGHT, 
          MOBILE_NET_INPUT_WIDTH], true);
      let normalizedTensorFrame = resizedTensorFrame.div(255);
      return mobilenet.predict(normalizedTensorFrame.expandDims()).squeeze();
    });

    trainingDataInputs.push(imageFeatures);
    trainingDataOutputs.push(gatherDataState);
    
    // Intialize array index element if currently undefined.
    if (examplesCount[gatherDataState] === undefined) {
      examplesCount[gatherDataState] = 0;
    }
    examplesCount[gatherDataState]++;

    STATUS.innerText = '';
    for (let n = 0; n < CLASS_NAMES.length; n++) {
      STATUS.innerText += CLASS_NAMES[n] + ' data count: ' + examplesCount[n] + '. ';
    }
    window.requestAnimationFrame(dataGatherLoop);
  }
}

يمكنك مواصلة تنفيذ هذه الدالة فقط في حال ضبط السياسة videoPlaying على "صحيح"، ما يعني أنّ كاميرا الويب نشطة، وأنّ القيمة gatherDataState لا تساوي STOP_DATA_GATHER، ويتم حاليًا الضغط على زر لجمع بيانات الصف.

بعد ذلك، عليك التفاف الرمز في tf.tidy() للتخلص من أي متسّرات تم إنشاؤها في الرمز التالي. يتم تخزين نتيجة تنفيذ رمز tf.tidy() هذا في متغيّر باسم imageFeatures.

يمكنك الآن أخذ إطار من كاميرا الويب VIDEO باستخدام tf.browser.fromPixels(). يتم تخزين أداة تنسر الناتج التي تحتوي على بيانات الصورة في متغير يُدعى videoFrameAsTensor.

بعد ذلك، غيِّر حجم المتغيّر videoFrameAsTensor ليصبح بالشكل الصحيح لإدخال نموذج MobileNet. استخدِم استدعاء tf.image.resizeBilinear() مع عامل التنس الذي تريد إعادة تشكيله كمَعلمة أولى، ثم شكلاً يحدّد الارتفاع والعرض الجديدَين كما هو محدَّد في الثوابت التي سبق لك إنشاؤها. وأخيرًا، اضبط محاذاة الزوايا على "صحيح" بتمرير المعلمة الثالثة لتجنب أي مشكلات في المحاذاة عند تغيير الحجم. يتم تخزين نتيجة تغيير الحجم هذا في متغيّر يسمى resizedTensorFrame.

لاحظ أن تغيير الحجم الأساسي هذا يوسع الصورة، حيث يبلغ حجم صورة كاميرا الويب 640 × 480 بكسل، ويحتاج النموذج إلى صورة مربعة بحجم 224 × 224 بكسل.

لأغراض هذا العرض التوضيحي، من المفترض أن يعمل هذا بشكل جيد. مع ذلك، بعد إكمال هذا الدرس التطبيقي حول الترميز، قد تحتاج إلى اقتصاص مربّع من هذه الصورة بدلاً من ذلك للحصول على نتائج أفضل لأي نظام إنتاج يمكنك إنشاؤه لاحقًا.

بعد ذلك، قم بتسوية بيانات الصورة. تتراوح بيانات الصور دائمًا بين 0 و255 عند استخدام tf.browser.frompixels()، لذلك يمكنك بسهولة قسمة تغيير الحجم على 255 للتأكد من أن جميع القيم تتراوح بين 0 و1 بدلاً من ذلك، وهو ما يتوقعه نموذج MobileNet كمدخلات.

أخيرًا، في القسم tf.tidy() من الرمز، يمكنك دفع هذا المؤشّر الذي تمّت تسويته في النموذج الذي تم تحميله من خلال استدعاء mobilenet.predict()، حيث يمكنك إدخال الإصدار الموسّع من normalizedTensorFrame باستخدام expandDims() بحيث يكون دفعة من العدد 1، لأنّ النموذج يتوقّع مجموعة من الإدخالات للمعالجة.

بعد ظهور النتيجة، يمكنك استدعاء squeeze() على الفور في النتيجة المعروضة لسحبها مرة أخرى إلى مترابط أحادي الأبعاد، والذي يمكنك الرجوع إليه وتخصيصه للمتغيّر imageFeatures الذي يلتقط النتيجة من tf.tidy().

الآن بعد أن حصلت على imageFeatures من نموذج MobileNet، يمكنك تسجيلها من خلال دفعها إلى المصفوفة trainingDataInputs التي حددتها سابقًا.

يمكنك أيضًا تسجيل ما يمثّله هذا الإدخال من خلال دفع gatherDataState الحالية إلى مصفوفة trainingDataOutputs أيضًا.

يُرجى العِلم أنّه تم ضبط المتغيّر gatherDataState على المعرّف الرقمي للفئة الحالية الذي تسجِّل بياناته عند النقر على الزر في دالة gatherDataForClass() المحدَّدة سابقًا.

في هذه المرحلة، يمكنك أيضًا زيادة عدد الأمثلة التي لديك لفئة معينة. لإجراء ذلك، تحقَّق أولاً مما إذا كان قد تم إعداد الفهرس داخل المصفوفة examplesCount من قبل أم لا. إذا كانت القيمة غير معرَّفة، عليك ضبطها على 0 لإعداد العدّاد للمعرّف الرقمي لفئة معيّنة، وبعد ذلك يمكنك زيادة قيمة examplesCount للسمة gatherDataState الحالية.

عدِّل الآن نص العنصر STATUS على صفحة الويب لإظهار الأعداد الحالية لكل صف عند تسجيله. لإجراء ذلك، انتقِل إلى المصفوفة CLASS_NAMES واطبع الاسم الذي يمكن للمستخدمين قراءته مع عدد البيانات في الفهرس نفسه في examplesCount.

أخيرًا، لطلب window.requestAnimationFrame() مع تمرير dataGatherLoop كمَعلمة، لطلب هذه الدالة مرة أخرى بشكل متكرّر. سيؤدي ذلك إلى مواصلة أخذ عيّنات من اللقطات من الفيديو إلى أن يتم رصد mouseup للزر، ويتم ضبط gatherDataState على STOP_DATA_GATHER,، وعندها سينتهي "تكرار جمع البيانات".

إذا قمت بتشغيل التعليمة البرمجية الآن، فيجب أن تتمكن من النقر فوق زر تمكين الكاميرا، وانتظار تحميل كاميرا الويب، ثم النقر مع الاستمرار على كل زر من أزرار جمع البيانات لجمع أمثلة لكل فئة من البيانات. هنا ترى أنني أجمع بيانات هاتفي الجوّال ويدي على التوالي.

541051644a45131f.gif

يُفترض أن تشاهد نص الحالة محدثًا نظرًا لأنه يخزن جميع عناصر العشرات في الذاكرة كما هو موضح في لقطة الشاشة أعلاه.

14. التدريب والتنبؤ

الخطوة التالية هي تنفيذ رمز لدالة trainAndPredict() الفارغة حاليًا، وهو المكان الذي تتم فيه عملية تعلُّم النقل. لنلقِ نظرة على الرمز:

script.js

async function trainAndPredict() {
  predict = false;
  tf.util.shuffleCombo(trainingDataInputs, trainingDataOutputs);
  let outputsAsTensor = tf.tensor1d(trainingDataOutputs, 'int32');
  let oneHotOutputs = tf.oneHot(outputsAsTensor, CLASS_NAMES.length);
  let inputsAsTensor = tf.stack(trainingDataInputs);
  
  let results = await model.fit(inputsAsTensor, oneHotOutputs, {shuffle: true, batchSize: 5, epochs: 10, 
      callbacks: {onEpochEnd: logProgress} });
  
  outputsAsTensor.dispose();
  oneHotOutputs.dispose();
  inputsAsTensor.dispose();
  predict = true;
  predictLoop();
}

function logProgress(epoch, logs) {
  console.log('Data for epoch ' + epoch, logs);
}

أولاً، عليك التأكّد من إيقاف أي توقعات حالية من خلال ضبط السمة predict على false.

بعد ذلك، عليك ترتيب صفائف الإدخال والإخراج بترتيب عشوائي باستخدام tf.util.shuffleCombo() للتأكّد من أنّ الترتيب لا يتسبّب في حدوث مشاكل أثناء التدريب.

حوِّل مصفوفة الإخراج trainingDataOutputs, إلى tensor1d من النوع int32 لكي تكون جاهزة للاستخدام في ترميز واحد سريع. ويتم حفظ هذه القيمة في متغيّر باسم outputsAsTensor.

استخدِم الدالة tf.oneHot() مع المتغيّر outputsAsTensor هذا بالإضافة إلى الحدّ الأقصى لعدد الفئات المطلوب ترميزها، وهو CLASS_NAMES.length فقط. يتم الآن تخزين المخرجات ذات الترميز السريع في موتّر جديد يُسمى oneHotOutputs.

يُرجى العلم أنّ trainingDataInputs حاليًا عبارة عن مصفوفة من موتّرات تم تسجيلها. لاستخدام هذه الأشياء في التدريب، ستحتاج إلى تحويل صفيفة العشرات لكي تصبح متسّعًا ثنائي الأبعاد عاديًا.

ولإجراء ذلك، توجد دالة رائعة ضمن مكتبة TensorFlow.js تُسمى tf.stack()،

الذي يأخذ صفيفًا من متسابقات ويجمعها معًا لإنتاج موتر ذات أبعاد أعلى كمخرج. في هذه الحالة، يتم عرض مؤشر Tenor ثنائي الأبعاد، وهو عبارة عن مجموعة من مدخلات الأبعاد الواحدة يبلغ طول كل منها 1024 ويحتوي على الميزات المسجلة، وهو ما تحتاجه للتدريب.

بعد ذلك، يجب await model.fit() لتدريب رأس النموذج المخصّص. يمكنك هنا تمرير متغيّر inputsAsTensor مع oneHotOutputs لتمثيل بيانات التدريب لاستخدامها في أمثلة على المدخلات والمخرجات المستهدفة على التوالي. في كائن الضبط للمَعلمة الثالثة، اضبط shuffle على true، واستخدِم batchSize من 5، مع ضبط epochs على 10، ثم حدِّد callback لـ onEpochEnd للدالة logProgress التي ستحدّدها قريبًا.

وأخيرًا، يمكنك التخلص من متوترات التي تم إنشاؤها حيث إن النموذج قد تم تدريبه الآن. يمكنك بعد ذلك إعادة ضبط predict على true للسماح بظهور التوقّعات مرّة أخرى، ثم استدعاء الوظيفة predictLoop() لبدء توقّع الصور المباشرة لكاميرا الويب.

يمكنك أيضًا تحديد الدالة logProcess() لتسجيل حالة التدريب، وهي تُستخدم في السمة model.fit() أعلاه والتي تطبع النتائج إلى وحدة التحكّم بعد كل جولة من التدريب.

لم يتم اختيار جميع الإجابات الصحيحة حان الوقت لإضافة الدالة predictLoop() لعمل التنبؤات.

حلقة التوقّع الأساسية

لنفترض هنا أنك تنفذ حلقة التوقع الرئيسية التي تأخذ عينات من الإطارات من كاميرا ويب وتتنبأ باستمرار بما يظهر في كل إطار بنتائج الوقت الفعلي في المتصفح.

هيا نتحقق من التعليمات البرمجية:

script.js

function predictLoop() {
  if (predict) {
    tf.tidy(function() {
      let videoFrameAsTensor = tf.browser.fromPixels(VIDEO).div(255);
      let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor,[MOBILE_NET_INPUT_HEIGHT, 
          MOBILE_NET_INPUT_WIDTH], true);

      let imageFeatures = mobilenet.predict(resizedTensorFrame.expandDims());
      let prediction = model.predict(imageFeatures).squeeze();
      let highestIndex = prediction.argMax().arraySync();
      let predictionArray = prediction.arraySync();

      STATUS.innerText = 'Prediction: ' + CLASS_NAMES[highestIndex] + ' with ' + Math.floor(predictionArray[highestIndex] * 100) + '% confidence';
    });

    window.requestAnimationFrame(predictLoop);
  }
}

أولاً، تأكَّد من أنّ predict صحيح، لكي لا يتم وضع التوقّعات إلا بعد تدريب النموذج وأن يكون متاحًا للاستخدام.

بعد ذلك، يمكنك الحصول على ميزات الصورة للصورة الحالية كما فعلت في دالة dataGatherLoop(). بشكل أساسي، يمكنك أخذ إطار من كاميرا الويب باستخدام tf.browser.from pixels()، ثم تعديل حجمه وتغيير حجمه ليصبح بحجم 224 × 224 بكسل، ثم تمرير تلك البيانات من خلال نموذج MobileNet للحصول على ميزات الصورة الناتجة.

ومع ذلك، يمكنك الآن استخدام رأس النموذج المدرَّب حديثًا لتنفيذ توقُّع من خلال تمرير قيمة imageFeatures الناتجة التي تم العثور عليها للتوّ من خلال دالة predict() في النموذج المدرَّب. يمكنك بعد ذلك الضغط على متوتر الناتج لجعله ذا بُعد واحد مرة أخرى وتعيينه لمتغير يُدعى prediction.

باستخدام الدالة prediction، يمكنك العثور على الفهرس الذي يحتوي على أعلى قيمة باستخدام argMax()، ثم تحويل دالة Tenor الناتجة إلى مصفوفة باستخدام arraySync() للوصول إلى البيانات الأساسية في JavaScript لاكتشاف موضع العنصر الأعلى قيمة. ويتم حفظ هذه القيمة في متغيّر باسم highestIndex.

يمكنك أيضًا الحصول على نتائج الثقة في التوقّع الفعلي بالطريقة نفسها من خلال طلب الدالة arraySync() على مؤشر prediction مباشرةً.

لديك الآن كل ما تحتاج إليه لتعديل النص STATUS ببيانات prediction. للحصول على السلسلة التي يمكن للمستخدمين قراءتها في الفئة، ما عليك سوى البحث عن highestIndex في المصفوفة CLASS_NAMES، ثم الحصول على قيمة الثقة من predictionArray. ولجعلها أكثر قابلية للقراءة كنسبة مئوية، ما عليك سوى ضرب النتيجة في 100 وmath.floor().

أخيرًا، يمكنك استخدام "window.requestAnimationFrame()" للاتصال بـ "predictionLoop()" مجددًا عندما يصبح الفيديو جاهزًا، من أجل تصنيف المحتوى في الوقت الفعلي ضمن الفيديو المضمّن. ويستمر ذلك حتى يتم ضبط predict على false إذا اخترت تدريب نموذج جديد باستخدام بيانات جديدة.

الأمر الذي يقودك إلى الجزء الأخير من اللغز. جارٍ تنفيذ زر إعادة الضبط.

15. تنفيذ زر إعادة الضبط

أوشكت على الانتهاء الجزء الأخير من اللغز هو استخدام زر إعادة تعيين للبدء من جديد. في ما يلي رمز الدالة reset() الفارغة حاليًا. يمكنك المتابعة وتحديثه على النحو التالي:

script.js

/**
 * Purge data and start over. Note this does not dispose of the loaded 
 * MobileNet model and MLP head tensors as you will need to reuse 
 * them to train a new model.
 **/
function reset() {
  predict = false;
  examplesCount.length = 0;
  for (let i = 0; i < trainingDataInputs.length; i++) {
    trainingDataInputs[i].dispose();
  }
  trainingDataInputs.length = 0;
  trainingDataOutputs.length = 0;
  STATUS.innerText = 'No data collected';
  
  console.log('Tensors in memory: ' + tf.memory().numTensors);
}

أولاً، أوقِف أي تكرارات توقّعية قيد التشغيل من خلال ضبط السمة predict على false. بعد ذلك، احذف جميع المحتوى في مصفوفة examplesCount من خلال ضبط طولها على 0، وهي طريقة سهلة لمحو كل المحتوى من مصفوفة.

راجِع الآن كل trainingDataInputs المسجَّلة الحالية وتأكَّد من استخدام dispose() من كل أداة سينسر مضمَّنة لإخلاء مساحة من الذاكرة مرة أخرى، وذلك لأنّه لا يتم محو Tensors بواسطة أداة تجميع البيانات المهملة في JavaScript.

بعد إجراء ذلك، يمكنك الآن ضبط طول المصفوفة بأمان على 0 في كل من الصفيفتين trainingDataInputs وtrainingDataOutputs لمحوهما أيضًا.

وأخيرًا، اضبط النص STATUS على نص منطقي، واطبع ملفات العشرات المتبقية في الذاكرة كفحص صحة.

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

16. لنجربها

حان الوقت لاختبار نسختك الخاصة من Teachable Machine!

توجه إلى المعاينة المباشرة، ومكّن كاميرا الويب، واجمع ما لا يقل عن 30 عينة من الفئة 1 لشيء ما في غرفتك، ثم افعل الشيء نفسه مع الفئة 2 لكائن مختلف، وانقر على "تدريب" وتحقق من سجل وحدة التحكم لترى التقدم. يُفترض أن يتم تدريبه بسرعة كبيرة:

bf1ac3cc5b15740.gif

بعد التدريب، اعرض الأشياء على الكاميرا للحصول على توقعات مباشرة ستتم طباعتها في منطقة نص الحالة على صفحة الويب بالقرب من أعلى الصفحة. إذا واجهتك مشكلة، يمكنك التحقق من رمزي البرمجي الذي يعمل بشكل مكتمل لمعرفة ما إذا كنت قد أخطأت في نسخ أي محتوى.

17. تهانينا

تهانينا! لقد أكملت للتو أول مثال على تعلم النقل باستخدام TensorFlow.js مباشرةً في المتصفح.

جربه واختبره على مجموعة متنوعة من الأشياء، فقد تلاحظ بعض الأشياء التي يصعب التعرف عليها من غيرها، خاصةً إذا كانت متشابهة لشيء آخر. قد تحتاج إلى إضافة المزيد من الصفوف أو بيانات التدريب لتتمكن من التمييز بينها.

الملخّص

في هذا الدرس التطبيقي حول الترميز، تعلمتَ ما يلي:

  1. تعريف النقل ومزاياه مقارنةً بتدريب نموذج كامل.
  2. كيفية الحصول على نماذج لإعادة استخدامها من TensorFlow Hub
  3. طريقة إعداد تطبيق ويب مناسب لنقل التعلّم
  4. كيفية تحميل نموذج أساسي واستخدامه لإنشاء ميزات الصور.
  5. كيفية تدريب رأس تنبؤ جديد يمكنه التعرف على عناصر مخصصة من صور كاميرا الويب.
  6. كيفية استخدام النماذج الناتجة لتصنيف البيانات في الوقت الفعلي.

الخطوات التالية

الآن وبعد أن أصبحت لديك قاعدة عمل يمكن البدء بها، ما هي الأفكار الإبداعية التي يمكنك التوصل إليها لتوسيع هذا النص النموذجي لتعلُّم الآلة لحالة استخدام حقيقية قد تعمل عليها؟ ربما يمكنك إحداث ثورة في الصناعة التي تعمل فيها حاليًا لمساعدة الأشخاص في شركتك على تدريب النماذج لتصنيف الأشياء المهمة في عملهم اليومي؟ بإمكانك الحصول على خيارات لا تُعدّ ولا تُحصى.

لمزيد من التفاصيل، يمكنك أخذ هذه الدورة التدريبية الكاملة مجانًا التي توضّح لك كيفية دمج النموذجَين المتوفّرَين حاليًا في هذا الدرس التطبيقي حول الترميز في نموذج واحد من أجل تحقيق الكفاءة.

إذا أردت معرفة المزيد حول النظرية وراء التطبيق الأصلي للجهاز القابل للتدريس، يمكنك الاطّلاع على هذا الدليل التوجيهي.

مشاركة إبداعاتك معنا

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

ننصحك بالإشارة إلى حسابنا على وسائل التواصل الاجتماعي باستخدام الهاشتاغ #MadeWithTFJS للاستفادة من فرصة عرض مشروعك على مدونة TensorFlow أو حتى الفعاليات المستقبلية. نأمل أن نرى ما يمكنك صنعه.

المواقع الإلكترونية المقترَحة