1. قبل از شروع
استفاده از مدل TensorFlow.js در چند سال گذشته بهطور تصاعدی رشد کرده است و بسیاری از توسعهدهندگان جاوا اسکریپت اکنون به دنبال استفاده از مدلهای پیشرفته موجود و آموزش مجدد آنها برای کار با دادههای سفارشی منحصر به فرد در صنعت خود هستند. عمل گرفتن یک مدل موجود (که اغلب به عنوان مدل پایه از آن یاد می شود) و استفاده از آن در یک دامنه مشابه اما متفاوت به عنوان یادگیری انتقال شناخته می شود.
یادگیری انتقالی مزایای زیادی نسبت به شروع از یک مدل کاملاً خالی دارد. شما می توانید از دانشی که قبلاً از یک مدل آموزش دیده قبلی آموخته اید مجددا استفاده کنید و به نمونه های کمتری از آیتم جدیدی که می خواهید طبقه بندی کنید نیاز دارید. همچنین، به دلیل نیاز به آموزش مجدد چند لایه نهایی معماری مدل به جای کل شبکه، آموزش اغلب بسیار سریعتر است. به همین دلیل، یادگیری انتقال برای محیط مرورگر وب که منابع ممکن است بر اساس دستگاه اجرا متفاوت باشد بسیار مناسب است، اما همچنین دسترسی مستقیم به حسگرها برای کسب آسان داده ها دارد.
این کد لبه به شما نشان می دهد که چگونه می توانید یک برنامه وب را از روی بوم خالی بسازید و وب سایت محبوب Google Teachable Machine را بازسازی کنید. این وبسایت به شما امکان میدهد یک برنامه وب کاربردی ایجاد کنید که هر کاربر میتواند از آن برای تشخیص یک شی سفارشی فقط با چند نمونه تصویر از وبکم خود استفاده کند. این وبسایت عمداً به حداقل میرسد تا بتوانید بر جنبههای یادگیری ماشینی این کد لبه تمرکز کنید. با این حال، مانند وبسایت اصلی Teachable Machine، فضای زیادی برای اعمال تجربه توسعهدهنده وب موجود برای بهبود UX وجود دارد.
پیش نیازها
این کد لبه برای توسعه دهندگان وب نوشته شده است که تا حدودی با مدل های از پیش ساخته شده TensorFlow.js و استفاده اولیه از API آشنا هستند و می خواهند با آموزش انتقال inTensorFlow.js شروع کنند.
- آشنایی اولیه با TensorFlow.js، HTML5، CSS و JavaScript برای این آزمایشگاه فرض شده است.
اگر با Tensorflow.js تازه کار هستید، ابتدا این دوره رایگان از صفر تا قهرمان را بگذرانید ، که در آن هیچ پیش زمینه ای در زمینه Machine Learning یا TensorFlow.js وجود ندارد و هر آنچه را که باید در مراحل کوچکتر بدانید به شما آموزش می دهد.
چیزی که یاد خواهید گرفت
- TensorFlow.js چیست و چرا باید از آن در برنامه وب بعدی خود استفاده کنید.
- چگونه یک صفحه وب HTML/CSS/JS ساده بسازیم که تجربه کاربر Teachable Machine را تکرار کند.
- نحوه استفاده از TensorFlow.js برای بارگذاری یک مدل پایه از پیش آموزشدیده، بهویژه MobileNet، برای تولید ویژگیهای تصویری که میتوانند در یادگیری انتقال استفاده شوند.
- نحوه جمعآوری دادهها از وبکم کاربر برای چندین کلاس از دادههایی که میخواهید تشخیص دهید.
- نحوه ایجاد و تعریف یک پرسپترون چندلایه که ویژگی های تصویر را می گیرد و می آموزد که اشیاء جدید را با استفاده از آنها طبقه بندی کند.
بیا هک کنیم...
آنچه شما نیاز دارید
- یک حساب کاربری Glitch.com ترجیح داده میشود که همراه با آن دنبال شود، یا میتوانید از یک محیط سرویسدهی وب استفاده کنید که خودتان راحت ویرایش و اجرا میکنید.
2. TensorFlow.js چیست؟
TensorFlow.js یک کتابخانه یادگیری ماشین منبع باز است که می تواند در هر جایی که جاوا اسکریپت می تواند اجرا شود. این بر اساس کتابخانه اصلی TensorFlow نوشته شده در پایتون است و هدف آن ایجاد دوباره این تجربه توسعهدهنده و مجموعهای از APIها برای اکوسیستم جاوا اسکریپت است.
کجا میشه ازش استفاده کرد؟
با توجه به قابلیت حمل جاوا اسکریپت، اکنون می توانید به 1 زبان بنویسید و یادگیری ماشینی را در تمامی پلتفرم های زیر به راحتی انجام دهید:
- سمت کلاینت در مرورگر وب با استفاده از وانیلی جاوا اسکریپت
- سمت سرور و حتی دستگاه های اینترنت اشیا مانند Raspberry Pi با استفاده از Node.js
- برنامه های دسکتاپ با استفاده از Electron
- برنامه های موبایل بومی با استفاده از React Native
TensorFlow.js همچنین از چندین پشتیبان در هر یک از این محیطها پشتیبانی میکند (محیطهای مبتنی بر سختافزار واقعی که میتواند در داخل آن مانند CPU یا WebGL اجرا کند. یک "بکاند" در این زمینه به معنای یک محیط سمت سرور نیست - پشتیبان برای اجرا. به عنوان مثال می تواند سمت مشتری در WebGL باشد) تا از سازگاری اطمینان حاصل کند و همچنین کارها را سریع نگه دارد. در حال حاضر TensorFlow.js پشتیبانی می کند:
- اجرای WebGL روی کارت گرافیک دستگاه (GPU) - این سریعترین راه برای اجرای مدلهای بزرگتر (با اندازه بیش از 3 مگابایت) با شتاب GPU است.
- اجرای Web Assembly (WASM) بر روی CPU - برای بهبود عملکرد CPU در سراسر دستگاه ها از جمله تلفن های همراه نسل قدیمی تر. این برای مدلهای کوچکتر (با حجم کمتر از 3 مگابایت) که در واقع میتوانند در CPU با WASM سریعتر از WebGL اجرا شوند، مناسبتر است، زیرا بارگذاری محتوا در یک پردازنده گرافیکی زیاد است.
- اجرای CPU - نسخه بازگشتی نباید هیچ یک از محیط های دیگر در دسترس باشد. این کندترین از این سه است اما همیشه برای شما آماده است.
توجه: اگر میدانید در چه دستگاهی اجرا میکنید، میتوانید یکی از این پشتیبانها را مجبور کنید، یا اگر این مورد را مشخص نکردهاید، به سادگی میتوانید به TensorFlow.js اجازه دهید برای شما تصمیم بگیرد.
قدرت های فوق العاده سمت مشتری
اجرای TensorFlow.js در مرورگر وب روی دستگاه مشتری می تواند به چندین مزیت منجر شود که ارزش در نظر گرفتن دارد.
حریم خصوصی
شما می توانید بدون ارسال داده ها به وب سرور شخص ثالث، هم داده ها را در دستگاه مشتری آموزش دهید و هم طبقه بندی کنید. ممکن است مواقعی برای رعایت قوانین محلی، مانند GDPR، یا هنگام پردازش هرگونه داده ای که کاربر بخواهد در دستگاه خود نگه دارد و به شخص ثالث ارسال نشود، الزامی باشد.
سرعت
از آنجایی که مجبور نیستید داده ها را به یک سرور راه دور ارسال کنید، استنتاج (عمل طبقه بندی داده ها) می تواند سریعتر باشد. حتی بهتر از آن، دسترسی مستقیم به حسگرهای دستگاه مانند دوربین، میکروفون، GPS، شتاب سنج و موارد دیگر در صورتی که کاربر به شما اجازه دسترسی بدهد، دارید.
رسیدن و مقیاس
با یک کلیک هر کسی در جهان می تواند روی پیوندی که برایشان ارسال می کنید کلیک کند، صفحه وب را در مرورگر خود باز کند و از آنچه ساخته اید استفاده کند. بدون نیاز به راه اندازی پیچیده لینوکس سمت سرور با درایورهای CUDA و خیلی بیشتر فقط برای استفاده از سیستم یادگیری ماشین.
هزینه
بدون سرور به این معنی است که تنها چیزی که باید برای آن هزینه کنید یک CDN برای میزبانی فایل های HTML، CSS، JS و مدل شماست. هزینه CDN بسیار ارزان تر از نگه داشتن سرور (به طور بالقوه با کارت گرافیک متصل) در حال اجرا 24/7 است.
ویژگی های سمت سرور
استفاده از اجرای Node.js از TensorFlow.js ویژگی های زیر را فعال می کند.
پشتیبانی کامل از CUDA
در سمت سرور، برای شتاب کارت گرافیک، باید درایورهای NVIDIA CUDA را نصب کنید تا TensorFlow با کارت گرافیک کار کند (برخلاف مرورگری که از WebGL استفاده می کند - نیازی به نصب نیست). با این حال، با پشتیبانی کامل از CUDA، میتوانید به طور کامل از تواناییهای سطح پایینتر کارت گرافیک استفاده کنید، که منجر به آموزش سریعتر و زمانهای استنتاج میشود. عملکرد با پیادهسازی Python TensorFlow برابری میکند، زیرا هر دوی آنها از یک باطن ++C به اشتراک میگذارند.
سایز مدل
برای مدل های پیشرفته از تحقیقات، ممکن است با مدل های بسیار بزرگ، شاید گیگابایتی کار کنید. این مدلها در حال حاضر به دلیل محدودیتهای استفاده از حافظه در هر برگه مرورگر نمیتوانند در مرورگر وب اجرا شوند. برای اجرای این مدلهای بزرگتر، میتوانید از Node.js در سرور خود با مشخصات سختافزاری مورد نیاز برای اجرای کارآمد چنین مدلی استفاده کنید.
IOT
Node.js در رایانه های تک بردی محبوب مانند Raspberry Pi پشتیبانی می شود، که به نوبه خود به این معنی است که می توانید مدل های TensorFlow.js را در چنین دستگاه هایی نیز اجرا کنید.
سرعت
Node.js با جاوا اسکریپت نوشته شده است که به این معنی است که از کامپایل به موقع سود می برد. این بدان معنی است که شما اغلب هنگام استفاده از Node.js شاهد افزایش عملکرد خواهید بود، زیرا در زمان اجرا بهینه می شود، به خصوص برای هر پیش پردازشی که ممکن است انجام دهید. یک مثال عالی از این را می توان در این مطالعه موردی مشاهده کرد که نشان می دهد چگونه Hugging Face از Node.js برای افزایش عملکرد 2 برابری برای مدل پردازش زبان طبیعی خود استفاده کرد.
اکنون اصول اولیه TensorFlow.js، جایی که میتواند اجرا شود و برخی از مزایای آن را میدانید، بیایید شروع کنیم به انجام کارهای مفید با آن!
3. انتقال یادگیری
یادگیری انتقالی دقیقا چیست؟
یادگیری انتقالی شامل گرفتن دانشی است که قبلاً آموخته شده است تا به یادگیری یک چیز متفاوت اما مشابه کمک کند.
ما انسان ها همیشه این کار را انجام می دهیم. شما یک عمر تجربیات در مغز خود دارید که می توانید از آنها برای تشخیص چیزهای جدیدی که قبلاً ندیده اید استفاده کنید. به عنوان مثال این درخت بید را در نظر بگیرید:
بسته به اینکه در کجای دنیا هستید، این احتمال وجود دارد که قبلاً این نوع درخت را ندیده باشید.
با این حال، اگر از شما بخواهم که به من بگویید آیا درختان بید در تصویر جدید زیر وجود دارد یا خیر، احتمالاً می توانید آنها را خیلی سریع تشخیص دهید، حتی اگر در زاویه متفاوتی قرار داشته باشند، و کمی متفاوت از آن چیزی که به شما نشان دادم.
شما در حال حاضر تعداد زیادی نورون در مغز خود دارید که می دانند چگونه اجسام درخت مانند را شناسایی کنند و نورون های دیگری که در یافتن خطوط مستقیم طولانی خوب هستند. شما میتوانید از این دانش برای طبقهبندی سریع درخت بید استفاده کنید، که یک شی درخت مانند است که شاخههای عمودی مستقیم زیادی دارد.
به طور مشابه، اگر یک مدل یادگیری ماشینی دارید که قبلاً در یک دامنه آموزش دیده است، مانند تشخیص تصاویر، می توانید از آن برای انجام یک کار متفاوت اما مرتبط دوباره استفاده کنید.
شما می توانید همین کار را با یک مدل پیشرفته مانند MobileNet انجام دهید، که یک مدل تحقیقاتی بسیار محبوب است که می تواند تشخیص تصویر را روی 1000 نوع شی مختلف انجام دهد. از سگها گرفته تا ماشینها، روی مجموعه داده عظیمی به نام ImageNet که میلیونها تصویر برچسبدار دارد، آموزش داده شد.
در این انیمیشن می توانید تعداد بسیار زیادی از لایه های موجود در این مدل MobileNet V1 را مشاهده کنید:
این مدل در طول آموزش خود یاد گرفت که چگونه ویژگیهای مشترکی را استخراج کند که برای همه آن 1000 شی مهم است و بسیاری از ویژگیهای سطح پایینتر که برای شناسایی چنین اشیایی استفاده میکند میتواند برای شناسایی اشیاء جدیدی که قبلاً هرگز ندیده است نیز مفید باشد. به هر حال، همه چیز در نهایت فقط ترکیبی از خطوط، بافت ها و اشکال است.
بیایید نگاهی به معماری سنتی شبکه عصبی کانولوشنال (CNN) بیندازیم (شبیه به MobileNet) و ببینیم که چگونه یادگیری انتقال می تواند این شبکه آموزش دیده را برای یادگیری چیزهای جدید اهرمی کند. تصویر زیر معماری مدل معمولی یک CNN را نشان می دهد که در این مورد برای تشخیص ارقام دست نویس از 0 تا 9 آموزش دیده است:
اگر میتوانید لایههای سطح پایینتر از قبل آموزشدیدهشده یک مدل آموزشدیده موجود را مانند این نشان داده شده در سمت چپ، از لایههای طبقهبندی نزدیک به انتهای مدل نشاندادهشده در سمت راست جدا کنید (گاهی اوقات به عنوان سر طبقهبندی مدل از آن یاد میشود)، میتوانید از لایههای سطح پایینتر برای تولید ویژگیهای خروجی برای هر تصویر داده شده بر اساس دادههای اصلی که روی آن آموزش داده شده است استفاده کنید. در اینجا همان شبکه ای است که سر طبقه بندی حذف شده است:
با فرض اینکه چیز جدیدی که میخواهید تشخیص دهید میتواند از چنین ویژگیهای خروجی که مدل قبلی آموخته است نیز استفاده کند، در این صورت شانس خوبی وجود دارد که بتوان از آنها برای یک هدف جدید استفاده مجدد کرد.
در نمودار بالا، این مدل فرضی بر روی ارقام آموزش داده شده است، بنابراین شاید آنچه در مورد ارقام آموخته شده است را بتوان برای حروفی مانند a، b و c نیز اعمال کرد.
بنابراین اکنون می توانید یک سر طبقه بندی جدید اضافه کنید که سعی می کند a، b یا c را پیش بینی کند، همانطور که نشان داده شده است:
در اینجا لایههای سطح پایینتر منجمد شدهاند و آموزش داده نمیشوند، فقط سر طبقهبندی جدید خود را بهروزرسانی میکند تا از ویژگیهای ارائهشده از مدل خرد شده از پیش آموزشدیده در سمت چپ یاد بگیرد.
عمل انجام این کار به عنوان یادگیری انتقالی شناخته می شود و همان کاری است که Teachable Machine در پشت صحنه انجام می دهد.
همچنین می توانید ببینید که تنها با آموزش پرسپترون چند لایه در انتهای شبکه، بسیار سریعتر از زمانی که مجبور باشید کل شبکه را از ابتدا آموزش دهید، تمرین می کند.
اما چگونه می توانید به بخش های فرعی یک مدل دست پیدا کنید؟ برای اطلاع از این موضوع به بخش بعدی بروید.
4. TensorFlow Hub - مدل های پایه
یک مدل پایه مناسب برای استفاده پیدا کنید
برای مدلهای تحقیقاتی پیشرفتهتر و محبوبتر مانند MobileNet، میتوانید به TensorFlow hub بروید و سپس مدلهای مناسب برای TensorFlow.js که از معماری MobileNet v3 استفاده میکنند را فیلتر کنید تا نتایجی مانند آنچه در اینجا نشان داده شده است را بیابید:
توجه داشته باشید که برخی از این نتایج از نوع "طبقه بندی تصویر" هستند (مفصل در سمت چپ بالای هر نتیجه کارت مدل)، و برخی دیگر از نوع "بردار ویژگی تصویر" هستند.
این نتایج Image Feature Vector اساساً نسخههای از پیش خرد شده MobileNet هستند که میتوانید از آنها برای دریافت بردارهای ویژگی تصویر به جای طبقهبندی نهایی استفاده کنید.
مدلهایی مانند این اغلب «مدلهای پایه» نامیده میشوند، که سپس میتوانید با افزودن یک سر طبقهبندی جدید و آموزش آن با دادههای خود، برای انجام یادگیری انتقال به همان روشی که در بخش قبل نشان داده شده است، استفاده کنید.
مورد بعدی که باید بررسی شود این است که برای یک مدل پایه مورد علاقه، مدل با چه فرمتی TensorFlow.js منتشر شده است. اگر صفحه یکی از این مدلهای بردار ویژگی MobileNet v3 را باز کنید، میتوانید از مستندات JS ببینید که به شکل یک مدل نمودار بر اساس قطعه کد مثال در مستندات است که از tf.loadGraphModel()
استفاده میکند.
همچنین لازم به ذکر است که اگر مدلی را در قالب لایه ها به جای قالب نمودار پیدا کردید، می توانید انتخاب کنید که کدام لایه ها را فریز کنید و کدام را برای آموزش از حالت انجماد خارج کنید. این می تواند هنگام ایجاد یک مدل برای یک کار جدید، که اغلب به عنوان "مدل انتقال" نامیده می شود، بسیار قدرتمند باشد. با این حال، در حال حاضر، شما از نوع مدل نمودار پیش فرض برای این آموزش استفاده خواهید کرد، که اکثر مدل های TF Hub به عنوان آن استفاده می شوند. برای کسب اطلاعات بیشتر در مورد کار با مدلهای لایهها، دوره Zero to Hero TensorFlow.js را بررسی کنید.
مزایای یادگیری انتقالی
مزایای استفاده از آموزش انتقال به جای آموزش کل معماری مدل از ابتدا چیست؟
اولاً، زمان آموزش یک مزیت کلیدی برای استفاده از رویکرد یادگیری انتقالی است، زیرا شما قبلاً یک مدل پایه آموزش دیده برای ساخت دارید.
ثانیاً، میتوانید با نشان دادن نمونههای بسیار کمتری از چیز جدیدی که میخواهید طبقهبندی کنید، به دلیل آموزشهایی که قبلاً انجام شده، خلاص شوید.
اگر زمان و منابع محدودی برای جمعآوری دادههای نمونه از چیزهایی که میخواهید طبقهبندی کنید دارید، واقعاً عالی است، و قبل از جمعآوری دادههای آموزشی بیشتر برای قویتر کردن آن، باید به سرعت یک نمونه اولیه بسازید.
با توجه به نیاز به داده های کمتر و سرعت آموزش شبکه کوچکتر، یادگیری انتقال منابع کمتری نیاز دارد. این باعث می شود که برای محیط مرورگر بسیار مناسب باشد، به جای ساعت ها، روزها یا هفته ها برای آموزش کامل مدل، فقط ده ها ثانیه در یک ماشین مدرن وقت صرف می کند.
باشه! اکنون شما ماهیت آموزش انتقالی را میدانید، زمان آن رسیده است که نسخه خود را از Teachable Machine ایجاد کنید. بیایید شروع کنیم!
5. برای کدگذاری تنظیم شوید
آنچه شما نیاز دارید
- یک مرورگر وب مدرن
- دانش اولیه HTML، CSS، JavaScript و Chrome DevTools (مشاهده خروجی کنسول).
بیایید کد نویسی کنیم
قالبهای Boilerplate برای شروع برای Glitch.com یا Codepen.io ایجاد شدهاند. شما به سادگی می توانید هر یک از الگوها را به عنوان حالت پایه خود برای این آزمایشگاه کد، تنها با یک کلیک کلون کنید.
در Glitch، روی دکمه " remix this" کلیک کنید تا آن را فورک کنید و مجموعه جدیدی از فایل ها را ایجاد کنید که می توانید ویرایش کنید.
از طرف دیگر، در Codepen، روی " fork" در پایین سمت راست پایین صفحه کلیک کنید.
این اسکلت بسیار ساده فایل های زیر را در اختیار شما قرار می دهد:
- صفحه HTML (index.html)
- صفحه سبک (style.css)
- فایل برای نوشتن کد جاوا اسکریپت ما (script.js)
برای راحتی شما، یک import اضافه شده در فایل 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>
جایگزین: از وب سایت دلخواه خود استفاده کنید یا به صورت محلی کار کنید
اگر میخواهید کد را دانلود کنید و به صورت محلی یا روی یک ویرایشگر آنلاین دیگر کار کنید، به سادگی 3 فایل نامگذاری شده در بالا را در همان دایرکتوری ایجاد کنید و کد را از Glitch boilerplate ما در هر یک از آنها کپی و جایگذاری کنید.
6. برنامه HTML boilerplate
از کجا شروع کنم؟
همه نمونههای اولیه به داربستهای اولیه 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 & 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 برای کلاس اول شروع می شود. این شاخص عددی است که برای نمایش داده های یک کلاس خاص استفاده می کنید. این شاخص برای رمزگذاری صحیح کلاس های خروجی با نمایش عددی به جای رشته استفاده می شود، زیرا مدل های ML فقط می توانند با اعداد کار کنند.
همچنین یک ویژگی data-name وجود دارد که حاوی نام قابل خواندن برای انسان است که میخواهید برای این کلاس استفاده کنید، که به شما امکان میدهد به جای یک مقدار شاخص عددی از کدگذاری 1 داغ، نام معنادارتری برای کاربر ارائه دهید.
در نهایت، یک دکمه قطار و تنظیم مجدد برای شروع فرآیند آموزش پس از جمعآوری دادهها یا به ترتیب تنظیم مجدد برنامه دارید.
- شما همچنین 2 واردات
<script>
اضافه کردید. یکی برای TensorFlow.js و دیگری برای script.js که به زودی تعریف خواهید کرد.
7. اضافه کردن سبک
پیش فرض عنصر
برای اطمینان از نمایش صحیح عناصر HTML که به تازگی اضافه کرده اید، سبک اضافه کنید. در اینجا چند سبک وجود دارد که به درستی به عناصر موقعیت و اندازه اضافه شده اند. هیچ چیز خیلی خاصی نیست. مطمئناً می توانید بعداً به این اضافه کنید تا یک UX حتی بهتر بسازید، همانطور که در ویدیوی ماشین قابل آموزش دیدید.
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%;
}
عالیه این تمام چیزی است که شما نیاز دارید. اگر در حال حاضر پیش نمایش خروجی را مشاهده کنید، باید چیزی شبیه به این باشد:
8. جاوا اسکریپت: ثابت های کلیدی و شنوندگان
ثابت های کلیدی را تعریف کنید
ابتدا چند ثابت کلیدی را که در سراسر برنامه استفاده خواهید کرد، اضافه کنید. با جایگزین کردن محتویات 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
- با کلیک روی trainAndPredict تماس می گیرد.
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!
}
توضیح کد:
سپس از طریق دکمههای پیدا شده تکرار میکنید و 2 شنونده رویداد را به هر کدام مرتبط میکنید. یکی برای "Download" و دیگری برای "Mouseup". این به شما امکان می دهد تا زمانی که دکمه فشرده است، نمونه ها را ضبط کنید، که برای جمع آوری داده ها مفید است.
هر دو رویداد یک تابع gatherDataForClass
را فراخوانی می کنند که بعداً تعریف خواهید کرد.
در این مرحله، میتوانید نامهای کلاسهای قابل خواندن انسان پیدا شده را از ویژگی دکمه HTML data-name به آرایه 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
متغیر برای ذخیره مدل mobilenet بارگذاری شده دارید. ابتدا این را روی undefined تنظیم کنید.
بعد، شما یک متغیر به نام gatherDataState
دارید. اگر یک دکمه 'dataCollector' فشار داده شود، به جای آن، همانطور که در HTML تعریف شده است، به عنوان 1 شناسه داغ آن دکمه تغییر می کند، بنابراین شما می دانید که در آن لحظه چه دسته ای از داده ها را جمع آوری می کنید. در ابتدا، این روی STOP_DATA_GATHER
تنظیم میشود تا حلقه جمعآوری دادهای که بعداً مینویسید، وقتی هیچ دکمهای فشار داده نمیشود، دادهای را جمعآوری نکند.
videoPlaying
پیگیری می کند که آیا جریان وب کم با موفقیت بارگیری و پخش شده است و برای استفاده در دسترس است یا خیر. در ابتدا، این روی false
تنظیم میشود، زیرا وبکم روشن نیست تا زمانی که ENABLE_CAM_BUTTON.
بعد، 2 آرایه، trainingDataInputs
و trainingDataOutputs
تعریف کنید. این مقادیر دادههای آموزشی جمعآوریشده را ذخیره میکنند، همانطور که روی دکمههای 'dataCollector' برای ویژگیهای ورودی تولید شده توسط مدل پایه MobileNet و کلاس خروجی نمونهگیری شده به ترتیب کلیک میکنید.
یک آرایه نهایی، examplesCount,
برای پیگیری تعداد مثالهای موجود برای هر کلاس پس از شروع اضافه کردن آنها تعریف میشود.
در نهایت، شما متغیری به نام predict
دارید که حلقه پیش بینی شما را کنترل می کند. این در ابتدا روی false
تنظیم شده است. هیچ پیشبینی نمیتواند انجام شود تا زمانی که بعداً به true
تنظیم شود.
اکنون که همه متغیرهای کلیدی تعریف شده اند، بیایید برویم و مدل پایه MobileNet v3 را که از قبل خرد شده است بارگذاری کنیم که به جای طبقه بندی، بردارهای ویژگی تصویر را ارائه می دهد.
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()
بارگیری کنید، و به یاد داشته باشید که هنگام بارگیری یک مدل از این وبسایت Google، ویژگی خاص fromTFHub
را روی true
تنظیم کنید. این یک مورد خاص فقط برای استفاده از مدلهای میزبانی شده در TF Hub است که در آن ویژگی اضافی باید تنظیم شود.
هنگامی که بارگیری کامل شد، می توانید innerText
عنصر STATUS
را با یک پیام تنظیم کنید تا بتوانید به صورت بصری مشاهده کنید که به درستی بارگیری شده است و آماده شروع جمع آوری داده ها هستید.
اکنون تنها کاری که باید انجام دهید گرم کردن مدل است. با مدلهای بزرگتر مانند این، اولین باری که از مدل استفاده میکنید، تنظیم کردن همه چیز ممکن است یک لحظه طول بکشد. بنابراین به عبور صفرها از مدل کمک می کند تا از هرگونه انتظار در آینده که زمان بندی ممکن است حیاتی تر باشد جلوگیری شود.
می توانید از tf.zeros()
پیچیده شده در tf.tidy()
استفاده کنید تا مطمئن شوید که تانسورها به درستی دور ریخته می شوند، با اندازه دسته ای 1، و ارتفاع و عرض صحیحی که در ابتدا در ثابت های خود تعریف کرده اید. در نهایت، کانال های رنگی را نیز مشخص می کنید که در این حالت 3 است زیرا مدل انتظار تصاویر RGB را دارد.
سپس، شکل حاصل از تانسور را با استفاده از answer.shape()
ثبت کنید تا به شما در درک اندازه ویژگی های تصویری که این مدل تولید می کند کمک کند.
پس از تعریف این تابع، می توانید بلافاصله آن را فراخوانی کنید تا دانلود مدل در بارگذاری صفحه آغاز شود.
اگر همین الان پیشنمایش زنده خود را مشاهده کنید، پس از چند لحظه میبینید که متن وضعیت از "در انتظار بارگیری TF.js" به "MobileNet v3 با موفقیت بارگیری شد!" همانطور که در زیر نشان داده شده است. قبل از ادامه، مطمئن شوید که این کار می کند.
همچنین می توانید خروجی کنسول را بررسی کنید تا اندازه چاپ شده ویژگی های خروجی که این مدل تولید می کند را ببینید. پس از اجرای صفرها از طریق مدل MobileNet، شکلی از [1, 1024]
مشاهده خواهید کرد. اولین مورد فقط به اندازه یک دسته است و میتوانید ببینید که در واقع 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.sequential شروع می کنید که لایه های مدل را به آن اضافه می کنید.
سپس یک لایه متراکم به عنوان لایه ورودی به این مدل اضافه کنید. این دارای شکل ورودی 1024
است زیرا خروجی های ویژگی های MobileNet v3 به این اندازه هستند. این را در مرحله قبل پس از عبور از مدل کشف کردید. این لایه دارای 128 نورون است که از تابع فعال سازی ReLU استفاده می کنند.
اگر با توابع فعالسازی و لایههای مدل تازه کار هستید، دورهای را که در ابتدای این کارگاه توضیح داده شده است را در نظر بگیرید تا بفهمید این ویژگیها در پشت صحنه چه میکنند.
لایه بعدی که اضافه می شود لایه خروجی است. تعداد نورونها باید برابر با تعداد کلاسهایی باشد که میخواهید پیشبینی کنید. برای انجام این کار، میتوانید از CLASS_NAMES.length
برای پیدا کردن تعداد کلاسهایی که میخواهید طبقهبندی کنید، استفاده کنید، که برابر با تعداد دکمههای جمعآوری دادههای موجود در رابط کاربری است. از آنجایی که این یک مشکل طبقه بندی است، شما از فعال سازی softmax
در این لایه خروجی استفاده می کنید، که باید هنگام ایجاد مدلی برای حل مسائل طبقه بندی به جای رگرسیون استفاده شود.
اکنون یک model.summary()
چاپ کنید تا نمای کلی مدل جدید تعریف شده را در کنسول چاپ کنید.
در نهایت مدل را کامپایل کنید تا برای آموزش آماده شود. در اینجا بهینهساز روی adam
تنظیم میشود، و اگر CLASS_NAMES.length
برابر با 2
باشد، ضرر یا binaryCrossentropy
خواهد بود، یا اگر 3 کلاس یا بیشتر برای طبقهبندی وجود داشته باشد، از categoricalCrossentropy
استفاده میکند. معیارهای دقت نیز درخواست میشود تا بتوان بعداً برای اهداف اشکالزدایی در گزارشها نظارت کرد.
در کنسول شما باید چیزی شبیه به این را ببینید:
توجه داشته باشید که این دارای بیش از 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()
ایجاد کنید تا با بررسی وجود ویژگی های کلیدی API مرورگر، بررسی کنید که آیا مرورگر از getUserMedia()
پشتیبانی می کند یا خیر.
در enableCam()
enableCam از تابع hasGetUserMedia()
که در بالا تعریف کردید استفاده کنید تا بررسی کنید که آیا پشتیبانی می شود یا خیر. اگر اینطور نیست، یک هشدار در کنسول چاپ کنید.
اگر از آن پشتیبانی میکند، محدودیتهایی را برای تماس getUserMedia()
خود تعریف کنید، مانند اینکه شما فقط پخش ویدیو را میخواهید، و ترجیح میدهید width
ویدیو 640
پیکسل و height
480
پیکسل باشد. چرا؟ خوب، دریافت ویدیویی بزرگتر از این فایده ای ندارد زیرا برای وارد شدن به مدل MobileNet باید اندازه آن به 224 در 224 پیکسل تغییر یابد. همچنین می توانید با درخواست رزولوشن کمتر، برخی منابع محاسباتی را ذخیره کنید. اکثر دوربین ها از وضوحی با این اندازه پشتیبانی می کنند.
سپس، navigator.mediaDevices.getUserMedia()
را با constraints
که در بالا توضیح داده شد، فراخوانی کنید و سپس منتظر بمانید تا stream
برگردانده شود. پس از بازگشت stream
، می توانید عنصر VIDEO
خود را با تنظیم آن به عنوان مقدار srcObject
، برای پخش stream
دریافت کنید.
همچنین باید یک eventListener روی عنصر VIDEO
اضافه کنید تا بدانید چه زمانی stream
بارگیری شده و با موفقیت در حال پخش است.
پس از بارگیری استیم، میتوانید videoPlaying
روی true تنظیم کنید و ENABLE_CAM_BUTTON
را حذف کنید تا با تنظیم کلاس آن بر روی " removed
" از کلیک مجدد روی آن جلوگیری کنید.
حالا کد خود را اجرا کنید، روی دکمه فعال کردن دوربین کلیک کنید و اجازه دسترسی به وب کم را بدهید. اگر اولین بار است که این کار را انجام میدهید، باید ببینید که به عنصر ویدیو در صفحه همانطور که نشان داده شده است:
خوب، اکنون زمان اضافه کردن یک تابع برای مقابله با کلیک دکمه dataCollector
است.
12. کنترل کننده رویداد دکمه جمع آوری داده ها
اکنون زمان آن رسیده است که تابع خالی فعلی خود را به نام gatherDataForClass().
این همان چیزی است که شما به عنوان عملکرد کنترل کننده رویداد خود برای دکمه های dataCollector
در شروع Codelab اختصاص داده اید.
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();
}
ابتدا با فراخوانی this.getAttribute()
با نام ویژگی، در این مورد data-1hot
data-1hot
به عنوان پارامتر، ویژگی 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
ذخیره می شود.
اکنون می توانید با استفاده از tf.browser.fromPixels()
یک قاب از VIDEO
وب کم بگیرید. تانسور حاصل حاوی داده های تصویر در متغیری به نام videoFrameAsTensor
ذخیره می شود.
در مرحله بعد ، متغیر videoFrameAsTensor
را تغییر دهید تا از شکل صحیح برای ورودی مدل Mobilenet استفاده شود. از یک تماس tf.image.resizeBilinear()
با تانسور که می خواهید به عنوان اولین پارامتر تغییر شکل دهید استفاده کنید ، و سپس شکلی که ارتفاع و عرض جدید را مطابق ثابت هایی که قبلاً ایجاد کرده اید تعریف می کند. سرانجام ، با عبور از پارامتر سوم ، گوشه های تراز را تنظیم کنید تا از هرگونه مشکل تراز در هنگام تغییر اندازه جلوگیری کنید. نتیجه این تغییر اندازه در متغیری به نام resizedTensorFrame
ذخیره می شود.
توجه داشته باشید که این تغییر اندازه بدوی تصویر را دراز می کند ، زیرا تصویر وب کم شما 640 در 480 پیکسل است و مدل به یک تصویر مربع از 224 در 224 پیکسل نیاز دارد.
برای اهداف این نسخه ی نمایشی باید خوب کار کند. با این حال ، پس از اتمام این CodeLab ، ممکن است بخواهید به جای آن حتی برای نتایج بهتر برای هر سیستم تولیدی که بعداً ایجاد می کنید ، یک مربع را از این تصویر بردارید.
بعد ، داده های تصویر را عادی کنید. داده های تصویر همیشه هنگام استفاده از tf.browser.frompixels()
در محدوده 0 تا 255 است ، بنابراین می توانید به سادگی اندازه گیری ResizedTensorFrame را 255 تقسیم کنید تا اطمینان حاصل شود که تمام مقادیر بین 0 تا 1 هستند ، این همان چیزی است که مدل Mobilenet به عنوان ورودی انتظار دارد.
سرانجام ، در بخش tf.tidy()
کد ، این تانسور نرمال شده را از طریق مدل بارگذاری شده با فراخوانی mobilenet.predict()
فشار دهید ، که نسخه گسترده ای از normalizedTensorFrame
را با استفاده از expandDims()
منتقل می کنید تا یک دسته باشد از 1 ، به عنوان مدل انتظار می رود دسته ای از ورودی ها برای پردازش باشد.
پس از بازگشت نتیجه ، می توانید بلافاصله تماس بگیرید squeeze()
در نتیجه برگشتی ، آن را به پایین تانسور 1D بکشید ، که سپس برمی گردید و به متغیر imageFeatures
اختصاص می دهید که نتیجه را از tf.tidy()
ضبط می کند.
اکنون که از مدل Mobilenet imageFeatures
را دارید ، می توانید با فشار دادن آنها به آرایه trainingDataInputs
که قبلاً تعریف کرده اید ، آنها را ضبط کنید.
همچنین می توانید با فشار دادن gatherDataState
فعلی به آرایه trainingDataOutputs
، این ورودی را ثبت کنید.
توجه داشته باشید که متغیر gatherDataState
می تواند بر روی شناسه عددی کلاس فعلی که شما در حال ضبط داده ها در هنگام کلیک بر روی دکمه در عملکرد قبلاً تعریف شده gatherDataForClass()
تنظیم شده است.
در این مرحله همچنین می توانید تعداد مثالهایی را که برای یک کلاس خاص دارید افزایش دهید. برای انجام این کار ، ابتدا بررسی کنید که آیا شاخص موجود در آرایه examplesCount
قبل از آن آغاز شده است یا خیر. اگر تعریف نشده است ، آن را روی 0 تنظیم کنید تا پیشخوان را برای شناسه عددی یک کلاس خاص تنظیم کنید ، و سپس می توانید examplesCount
برای gatherDataState
فعلی افزایش دهید.
اکنون متن عنصر STATUS
را در صفحه وب به روز کنید تا تعداد فعلی هر کلاس را به عنوان ضبط نشان دهید. برای انجام این کار ، از طریق آرایه CLASS_NAMES
حلقه کنید و نام قابل خواندن انسان را با تعداد داده ها در همان شاخص در examplesCount
چاپ کنید.
در آخر ، با window.requestAnimationFrame()
با dataGatherLoop
به عنوان یک پارامتر عبور کنید تا دوباره به صورت بازگشتی این عملکرد را فراخوانی کنید. این کار نمونه ای از فریم های فیلم را تا زمانی که mouseup
دکمه دکمه تشخیص داده شود ، ادامه می یابد و gatherDataState
روی STOP_DATA_GATHER,
در این مرحله حلقه جمع آوری داده به پایان می رسد.
اگر اکنون کد خود را اجرا کرده اید ، باید بر روی دکمه Enable Camera کلیک کنید ، در انتظار وب کم برای بارگیری باشید و سپس هر یک از دکمه های جمع آوری داده ها را کلیک کرده و نگه دارید تا نمونه هایی برای هر کلاس از داده ها جمع شود. در اینجا می بینید که من به ترتیب داده ها را برای تلفن همراه و دستم جمع می کنم.
شما باید متن وضعیت را به روز کنید زیرا تمام تنسورها را در حافظه ذخیره می کند ، همانطور که در ضبط صفحه در بالا نشان داده شده است.
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
ذخیره می شود.
با استفاده از این متغیر outputsAsTensor
به همراه حداکثر تعداد کلاس ها برای رمزگذاری ، از عملکرد tf.oneHot()
استفاده کنید ، که فقط CLASS_NAMES.length
است. یکی از خروجی های رمزگذاری شده شما اکنون در یک تانسور جدید به نام oneHotOutputs
ذخیره می شود.
توجه داشته باشید که در حال حاضر trainingDataInputs
مجموعه ای از تنسورهای ضبط شده است. به منظور استفاده از این موارد برای آموزش ، شما نیاز به تبدیل آرایه تانسور برای تبدیل شدن به یک تانسور 2D معمولی دارید.
برای انجام این کار یک عملکرد عالی در کتابخانه tensorflow.js به نام tf.stack()
وجود دارد.
که مجموعه ای از تانسور را می گیرد و آنها را با هم جمع می کند تا یک تانسور بعدی بالاتری به عنوان یک خروجی تولید کند. در این حالت یک تانسور 2D بازگردانده می شود ، این یک دسته از ورودی های 1 بعدی است که هر 1024 طول دارند که دارای ویژگی های ثبت شده است ، این همان چیزی است که شما برای آموزش نیاز دارید.
در مرحله بعد ، await model.fit()
برای آموزش سر مدل سفارشی باشید. در اینجا شما متغیر inputsAsTensor
خود را به همراه oneHotOutputs
منتقل می کنید تا داده های آموزش را برای استفاده به عنوان مثال ورودی ها و خروجی های هدف به ترتیب نشان دهید. در شیء پیکربندی برای پارامتر 3 ، 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()
مدل آموزش دیده پیدا شده است ، پیش بینی کنید. سپس می توانید تانسور حاصل را فشار دهید تا دوباره 1 بعدی شود و آن را به متغیری به نام prediction
اختصاص دهید.
با این prediction
می توانید شاخصی را که دارای بالاترین مقدار با استفاده از argMax()
، پیدا کنید و سپس این تانسور حاصل را با استفاده از arraySync()
به یک آرایه تبدیل کنید تا در داده های زیرین در جاوا اسکریپت دریافت کنید تا موقعیت بالاترین عنصر با ارزش را کشف کنید. این مقدار در متغیر بنام 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
را حذف کنید ، که این یک روش مفید برای پاک کردن همه مطالب از یک آرایه است.
اکنون تمام trainingDataInputs
ثبت شده فعلی را طی کنید و اطمینان حاصل کنید که dispose()
، زیرا تنشور توسط جمع کننده زباله JavaScript تمیز نمی شود.
پس از اتمام این کار ، اکنون می توانید با خیال راحت طول آرایه را روی 0 در هر دو trainingDataInputs
و trainingDataOutputs
آرایه ها تنظیم کنید تا این موارد را نیز پاک کنید.
سرانجام متن STATUS
را روی چیزی معقول تنظیم کنید و تنش های باقی مانده در حافظه را به عنوان یک بررسی عقل چاپ کنید.
توجه داشته باشید که چند صد تن از تانسور هنوز هم در حافظه وجود خواهد داشت زیرا هم مدل Mobilenet و هم Perceptron چند لایه ای که شما تعریف کرده اید از آن دور نشده است. اگر تصمیم دارید بعد از این تنظیم مجدد دوباره آموزش دهید ، باید آنها را با داده های جدید آموزش استفاده کنید.
16. بیایید آن را امتحان کنیم
وقت آن است که نسخه بسیار شخصی خود را از دستگاه قابل آموزش تست کنید!
به پیش نمایش زنده بروید ، وب کم را فعال کنید ، حداقل 30 نمونه را برای کلاس 1 برای برخی از شیء در اتاق خود جمع کنید و سپس برای کلاس 2 برای یک شیء متفاوت ، روی قطار کلیک کنید و برای دیدن پیشرفت ، روی قطار کلیک کنید و ورود به سیستم کنسول را بررسی کنید. باید خیلی سریع تمرین کند:
پس از آموزش ، اشیاء را به دوربین نشان دهید تا پیش بینی های زنده ای را که در قسمت متن وضعیت در صفحه وب در نزدیکی بالا چاپ می شود ، دریافت کنید. اگر مشکل دارید ، کد کار کامل من را بررسی کنید تا ببینید آیا کپی کردن از هر چیزی را از دست داده اید یا خیر.
17. تبریک می گویم
تبریک می گویم! شما به تازگی اولین مثال یادگیری انتقال خود را با استفاده از tensorflow.js در مرورگر به پایان رسانده اید.
آن را امتحان کنید ، آن را بر روی اشیاء مختلف آزمایش کنید ، ممکن است متوجه شوید که تشخیص برخی از موارد سخت تر از سایرین است ، به خصوص اگر شبیه چیز دیگری باشند. ممکن است لازم باشد کلاس ها یا داده های آموزشی بیشتری اضافه کنید تا بتوانید آنها را از هم جدا کنید.
خلاصه
در این CodeLab شما یاد گرفتید:
- یادگیری انتقال چیست و مزایای آن در آموزش یک مدل کامل است.
- چگونه می توان مدل هایی را برای استفاده مجدد از Hub TensorFlow دریافت کرد.
- نحوه تنظیم یک برنامه وب مناسب برای یادگیری انتقال.
- نحوه بارگیری و استفاده از یک مدل پایه برای تولید ویژگی های تصویر.
- نحوه آموزش یک پیش بینی جدید که می تواند اشیاء سفارشی را از تصاویر وب کم تشخیص دهد.
- نحوه استفاده از مدل های حاصل برای طبقه بندی داده ها در زمان واقعی.
بعدش چی؟
اکنون که یک پایگاه کاری برای شروع کار دارید ، چه ایده های خلاقانه ای را می توانید برای گسترش این دستگاه دیگ بخار مدل یادگیری برای یک مورد استفاده از دنیای واقعی که ممکن است روی آن کار کنید ، ارائه دهید؟ شاید شما بتوانید صنعتی را که در حال حاضر در آن کار می کنید برای کمک به مردمی در مدل های قطار شرکت خود برای طبقه بندی مواردی که در کارهای روزانه آنها مهم است ، متحول کنید؟ امکانات بی پایان هستند.
برای ادامه کار ، این دوره کامل را به صورت رایگان در نظر بگیرید ، که به شما نشان می دهد که چگونه می توانید 2 مدل مورد نظر خود را در این CodeLab در 1 مدل واحد برای بهره وری ترکیب کنید.
همچنین اگر بیشتر در مورد تئوری پشت برنامه اصلی آموزش ماشین قابل کنجکاو هستید ، این آموزش را بررسی کنید.
آنچه را که با ما درست می کنید به اشتراک بگذارید
شما به راحتی می توانید آنچه را که امروز برای سایر موارد استفاده خلاقانه نیز ساخته اید ، گسترش دهید و ما شما را ترغیب می کنیم که در خارج از صندوق فکر کنید و هک را ادامه دهید.
به یاد داشته باشید که ما را در رسانه های اجتماعی با استفاده از هشتگ #madewithtfjs برچسب گذاری کنید تا فرصتی برای پروژه شما در وبلاگ TensorFlow یا حتی رویدادهای آینده ارائه شود. ما دوست داریم ببینیم چه چیزی می سازید.
وب سایت ها برای بررسی
- وب سایت رسمی tensorflow.js
- tensorflow.js مدل های از پیش ساخته
- tensorflow.js api
- tensorflow.js Show & Tell - الهام بگیرید و ببینید دیگران چه ساخته اند.
1. قبل از شروع
استفاده از مدل Tensorflow.js طی چند سال گذشته به صورت نمایی رشد کرده است و بسیاری از توسعه دهندگان JavaScript اکنون به دنبال استفاده از مدلهای پیشرفته و پیشرفته هستند و آنها را برای کار با داده های سفارشی که منحصر به صنعت آنهاست ، کار می کنند. عمل گرفتن یک مدل موجود (که اغلب به آن یک مدل پایه گفته می شود) و استفاده از آن در یک دامنه مشابه اما متفاوت به عنوان یادگیری انتقال شناخته می شود.
یادگیری انتقال مزایای بسیاری از شروع از یک مدل کاملاً خالی دارد. شما می توانید از دانش قبلاً آموخته شده از یک مدل قبلی آموزش دیده استفاده مجدد کنید ، و به نمونه های کمتری از مورد جدید مورد نظر برای طبقه بندی نیاز دارید. همچنین ، آموزش اغلب به دلیل اینکه فقط به جای کل شبکه به جای کل شبکه ، به طور قابل توجهی سریعتر است ، به طور قابل توجهی سریعتر است. به همین دلیل ، یادگیری انتقال برای محیط مرورگر وب بسیار مناسب است که در آن منابع ممکن است بر اساس دستگاه اجرای متفاوت باشد ، اما همچنین برای دستیابی به داده های آسان به سنسورها نیز دسترسی مستقیم دارد.
این CodeLab به شما نشان می دهد که چگونه می توانید یک برنامه وب را از یک بوم خالی بسازید و وب سایت محبوب " دستگاه قابل آموزش " Google را بازآفرینی کنید. وب سایت به شما امکان می دهد یک برنامه وب کاربردی ایجاد کنید که هر کاربر بتواند از آن برای تشخیص یک شیء سفارشی فقط با چند تصویر مثال از وب کم خود استفاده کند. این وب سایت عمداً حداقل نگه داشته می شود تا بتوانید روی جنبه های یادگیری ماشین این CodeLab تمرکز کنید. با این وجود ، مانند وب سایت اصلی دستگاه آموزش قابل آموزش ، دامنه زیادی برای استفاده از تجربه توسعه دهنده وب موجود برای بهبود UX وجود دارد.
پیش نیازها
این CodeLab برای توسعه دهندگان وب نوشته شده است که تا حدودی با Tensorflow.js مدل های از پیش ساخته و استفاده اساسی API آشنا هستند و می خواهند با Transfer Learning Intensorflow.js شروع کنند.
- آشنایی اساسی با tensorflow.js ، HTML5 ، CSS و JavaScript برای این آزمایشگاه فرض شده است.
اگر تازه وارد Tensorflow.js هستید ، ابتدا این دوره صفر رایگان را به عنوان قهرمان در نظر بگیرید ، که هیچ پیش زمینه ای با یادگیری ماشین یا tensorflow.js فرض نمی کند و هر آنچه را که باید در مراحل کوچکتر بدانید به شما می آموزد.
چیزی که یاد خواهید گرفت
- tensorflow.js چیست و چرا باید از آن در برنامه وب بعدی خود استفاده کنید.
- نحوه ساخت یک صفحه وب ساده HTML /CSS /JS که تجربه کاربری دستگاه قابل آموزش را تکرار می کند.
- نحوه استفاده از tensorflow.js برای بارگذاری یک مدل پایه از پیش آموزش ، به طور خاص Mobilenet ، برای تولید ویژگی های تصویری که می تواند در یادگیری انتقال استفاده شود.
- نحوه جمع آوری داده ها از وب کم کاربر برای چندین کلاس از داده هایی که می خواهید تشخیص دهید.
- نحوه ایجاد و تعریف یک Perceptron چند لایه که ویژگی های تصویر را به خود اختصاص داده و یاد می گیرد که اشیاء جدید را با استفاده از آنها طبقه بندی کند.
بیایید هک کنیم ...
آنچه شما نیاز دارید
- یک حساب Glitch.com ترجیح داده می شود که در آن پیگیری کنید ، یا می توانید از یک محیط سرویس وب استفاده کنید که وی در حال ویرایش و اجرای خود هستید.
2. tensorflow.js چیست؟
tensorflow.js یک کتابخانه یادگیری ماشین منبع باز است که می تواند در هر جایی که جاوا اسکریپت می تواند اجرا کند. این مبتنی بر کتابخانه اصلی TensorFlow است که در پایتون نوشته شده است و هدف آن ایجاد مجدد این تجربه توسعه دهنده و مجموعه ای از API ها برای اکوسیستم JavaScript است.
کجا میشه ازش استفاده کرد؟
با توجه به قابلیت حمل JavaScript ، اکنون می توانید به 1 زبان بنویسید و یادگیری ماشین را در تمام سیستم عامل های زیر با سهولت انجام دهید:
- طرف مشتری در مرورگر وب با استفاده از وانیل JavaScript
- طرف سرور و حتی دستگاه های IoT مانند Raspberry Pi با استفاده از Node.js
- برنامه های دسک تاپ با استفاده از الکترون
- برنامه های موبایل بومی با استفاده از React Native
tensorflow.js همچنین از چندین پشتیبان در هر یک از این محیط ها پشتیبانی می کند (محیط های واقعی مبتنی بر سخت افزار که می تواند در داخل آن مانند CPU یا WebGL انجام شود. به عنوان مثال یک "پس زمینه" در این زمینه به معنای محیط جانبی سرور نیست - پس زمینه برای اجرای به عنوان مثال می تواند مشتری در WebGL باشد) برای اطمینان از سازگاری و همچنین نگه داشتن سریع کارها. در حال حاضر tensorflow.js پشتیبانی می کند:
- اجرای WebGL در کارت گرافیک دستگاه (GPU) - این سریعترین راه برای اجرای مدلهای بزرگتر (بیش از 3 مگابایت در اندازه) با شتاب GPU است.
- اجرای مونتاژ وب (WASM) در CPU - برای بهبود عملکرد CPU در بین دستگاه ها از جمله تلفن های همراه قدیمی تر به عنوان مثال. این مناسب تر برای مدل های کوچکتر (اندازه کمتر از 3 مگابایت) است که در واقع می تواند به دلیل وجود سربار بارگذاری محتوای در یک پردازنده گرافیکی ، سریعتر روی CPU با WASM نسبت به WebGL اجرا شود.
- اجرای CPU - بازپرداخت نباید هیچ یک از محیط های دیگر در دسترس باشد. این کمترین سرعت این سه است اما همیشه برای شما وجود دارد.
توجه: اگر می دانید چه دستگاهی را اجرا می کنید ، می توانید یکی از این باکتری ها را مجبور کنید ، یا به سادگی می توانید به TensorFlow.js اجازه دهید اگر این موضوع را مشخص نکردید ، تصمیم بگیرید.
قدرت فوق العاده طرف مشتری
اجرای tensorflow.js در مرورگر وب روی دستگاه مشتری می تواند به مزایای مختلفی منجر شود که ارزش در نظر گرفتن آن را دارند.
حریم خصوصی
شما می توانید بدون ارسال اطلاعات به سرور وب شخص ثالث ، داده ها را در دستگاه مشتری آموزش داده و طبقه بندی کنید. ممکن است مواقعی وجود داشته باشد که ممکن است این امر الزامی برای رعایت قوانین محلی مانند GDPR باشد ، یا هنگام پردازش هرگونه داده ای که کاربر ممکن است بخواهد دستگاه خود را نگه دارد و به شخص ثالث ارسال نشود.
سرعت
از آنجا که شما نیازی به ارسال داده به یک سرور از راه دور ندارید ، استنباط (عمل طبقه بندی داده ها) می تواند سریعتر باشد. حتی بهتر ، شما دسترسی مستقیم به سنسورهای دستگاه مانند دوربین ، میکروفون ، GPS ، شتاب سنج و موارد دیگر باید کاربر را به شما اعطا کند.
رسیدن و مقیاس
با یک کلیک هر کسی در جهان می تواند روی پیوندی که برای آنها ارسال می کنید کلیک کنید ، صفحه وب را در مرورگر آنها باز کنید و از آنچه ساخته اید استفاده کنید. نیازی به راه اندازی سمت سرور پیچیده لینوکس با درایورهای CUDA و خیلی بیشتر برای استفاده از سیستم یادگیری ماشین نیست.
هزینه
هیچ سرور به معنای تنها چیزی است که شما باید برای آن بپردازید CDN برای میزبانی پرونده های HTML ، CSS ، JS و مدل است. هزینه CDN بسیار ارزان تر از نگه داشتن سرور (به طور بالقوه با کارت گرافیک وصل شده) است که 24/7 در حال اجرا است.
ویژگی های سمت سرور
استفاده از اجرای node.js tensorflow.js ویژگی های زیر را امکان پذیر می کند.
پشتیبانی کامل CUDA
در سمت سرور ، برای شتاب کارت گرافیک ، باید درایورهای NVIDIA CUDA را نصب کنید تا TensorFlow بتواند با کارت گرافیک کار کند (برخلاف مرورگر که از WebGL استفاده می کند - بدون نصب مورد نیاز است). اما با پشتیبانی کامل CUDA می توانید از توانایی های سطح پایین کارت گرافیک به طور کامل استفاده کنید و منجر به آموزش سریعتر و زمان استنباط می شود. عملکرد با اجرای Python TensorFlow است زیرا هر دو با یک باکتری C ++ یکسان هستند.
سایز مدل
برای برش مدل های تحقیق ، ممکن است با مدلهای بسیار بزرگ کار کنید ، شاید اندازه گیگابایت. این مدل ها به دلیل محدودیت استفاده از حافظه در هر برگه مرورگر ، در حال حاضر نمی توانند در مرورگر وب اجرا شوند. برای اجرای این مدل های بزرگتر می توانید از Node.js در سرور خود با مشخصات سخت افزاری مورد نیاز برای اجرای چنین مدلی استفاده کنید.
IOT
Node.js در رایانه های تک تخته محبوب مانند Raspberry Pi پشتیبانی می شود ، که به نوبه خود به این معنی است که می توانید مدل های tensorflow.js را در چنین دستگاه هایی نیز اجرا کنید.
سرعت
Node.js در JavaScript نوشته شده است به این معنی که فقط از تدوین زمان سود می برد. این بدان معنی است که شما اغلب ممکن است هنگام استفاده از Node.js دستاوردهای عملکرد را مشاهده کنید زیرا در زمان اجرا بهینه سازی می شود ، به خصوص برای هر پردازشی که ممکن است انجام دهید. نمونه ای عالی از این مورد در این مطالعه موردی مشاهده می شود که نشان می دهد چگونه بغل کردن چهره از Node.js برای به دست آوردن عملکرد 2 برابر برای مدل پردازش زبان طبیعی خود استفاده می کند.
اکنون اصول اولیه tensorflow.js را می فهمید ، جایی که می تواند اجرا شود و برخی از مزایا ، بیایید با آن کارهای مفیدی را شروع کنیم!
3. انتقال یادگیری
یادگیری انتقال دقیقاً چیست؟
یادگیری انتقال شامل دانش است که قبلاً آموخته شده است تا به یادگیری یک چیز متفاوت اما مشابه کمک کند.
ما انسانها این کار را همیشه انجام می دهیم. شما یک عمر از تجربیات موجود در مغز خود دارید که می توانید برای کمک به تشخیص چیزهای جدیدی که قبلاً ندیده اید استفاده کنید. به عنوان مثال این درخت بید را بگیرید:
بسته به جایی که در جهان هستید ، فرصتی وجود دارد که ممکن است قبلاً این نوع درخت را ندیده باشید.
با این حال اگر از شما بخواهم که به من بگویید که آیا درختان بید در تصویر جدید زیر وجود دارد ، احتمالاً می توانید آنها را خیلی سریع مشاهده کنید ، حتی اگر آنها با زاویه ای متفاوت باشند و کمی متفاوت با اصلی که من به شما نشان دادم.
شما در حال حاضر یک دسته از نورون ها در مغز خود دارید که می دانید چگونه اشیاء شبیه درخت را شناسایی کنید ، و سایر نورونها که در یافتن خطوط مستقیم طولانی هستند. شما می توانید از این دانش استفاده کنید تا به سرعت یک درخت بید را طبقه بندی کنید ، که یک شیء مانند درخت است که دارای شاخه های عمودی طولانی و طولانی است.
به همین ترتیب ، اگر یک مدل یادگیری ماشین دارید که قبلاً در یک دامنه آموزش دیده است ، مانند شناخت تصاویر ، می توانید از آن برای انجام یک کار متفاوت اما مرتبط استفاده کنید.
شما می توانید همین کار را با یک مدل پیشرفته مانند Mobilenet انجام دهید ، که یک مدل تحقیقاتی بسیار محبوب است که می تواند تشخیص تصویر را در 1000 نوع شی مختلف انجام دهد. از سگها تا اتومبیل ، آن را در یک مجموعه داده عظیمی که به عنوان Imagenet معروف است که میلیون ها تصویر دارای برچسب دارد آموزش داده شد.
در این انیمیشن ، می توانید تعداد زیادی از لایه های موجود در این مدل Mobilenet V1 را مشاهده کنید:
در طول آموزش خود ، این مدل یاد گرفت که چگونه می توان ویژگی های مشترکی را که برای همه آن 1000 شیء مهم است ، استخراج کرد و بسیاری از ویژگی های سطح پایین که برای شناسایی چنین اشیاء استفاده می کند می تواند برای تشخیص اشیاء جدیدی که قبلاً نیز ندیده است مفید باشد. از این گذشته ، همه چیز در نهایت فقط ترکیبی از خطوط ، بافت ها و شکل ها است.
بیایید نگاهی به یک معماری سنتی شبکه عصبی Convolutional (CNN) (مشابه Mobilenet) بیندازیم و ببینیم که چگونه یادگیری انتقال می تواند از این شبکه آموزش دیده برای یادگیری چیز جدید استفاده کند. تصویر زیر معماری مدل معمولی یک CNN را نشان می دهد که در این حالت برای تشخیص رقم های دست نویس از 0 تا 9 آموزش داده شده است:
اگر می توانید لایه های سطح پایین تر از پیش آموزش از یک مدل آموزش دیده موجود مانند این را که در سمت چپ نشان داده شده است جدا کنید ، از لایه های طبقه بندی نزدیک انتهای مدل نشان داده شده در سمت راست (که گاهی به عنوان رئیس طبقه بندی مدل گفته می شود) ، شما می توانید از لایه های سطح پایین برای تولید ویژگی های خروجی برای هر تصویر معین بر اساس داده های اصلی که در آن آموزش داده شده است استفاده کنید. در اینجا همان شبکه با سر طبقه بندی حذف شده است:
با فرض اینکه چیز جدیدی که می خواهید تشخیص دهید نیز می تواند از چنین ویژگی های خروجی ای که مدل قبلی آموخته است استفاده کند ، سپس یک فرصت خوب وجود دارد که می توان آنها را برای یک هدف جدید استفاده مجدد کرد.
در نمودار بالا ، این مدل فرضی روی رقم ها آموزش داده شد ، بنابراین شاید آنچه در مورد ارقام آموخته شده است نیز می تواند در حروف مانند A ، B و C اعمال شود.
بنابراین اکنون می توانید یک سر طبقه بندی جدید را اضافه کنید که سعی در پیش بینی A ، B یا C به جای آن دارد ، همانطور که نشان داده شده است:
در اینجا لایه های سطح پایین منجمد شده و آموزش دیده نیستند ، فقط سر طبقه بندی جدید خود را به روز می کند تا از ویژگی های ارائه شده از مدل خرد شده از قبل آموزش دیده در سمت چپ یاد بگیرد.
عمل انجام این کار به عنوان یادگیری انتقال شناخته می شود و همان کاری است که ماشین قابل آموزش در پشت صحنه انجام می دهد.
همچنین می توانید ببینید که فقط با آموزش Perceptron چند لایه در انتهای شبکه ، بسیار سریعتر از آنچه که مجبور بودید کل شبکه را از ابتدا آموزش دهید آموزش می دهد.
اما چگونه می توانید دست خود را در زیر قسمت های یک مدل بدست آورید؟ برای پیدا کردن به بخش بعدی بروید.
4. توپی Tensorflow - مدل های پایه
یک مدل پایه مناسب برای استفاده پیدا کنید
برای مدلهای تحقیقاتی پیشرفته تر و محبوب تر مانند Mobilenet ، می توانید به Tensorflow Hub بروید ، و سپس مدل های مناسب برای tensorflow.js را که از معماری Mobilenet V3 استفاده می کنند ، فیلتر کنید تا نتایج مانند نتایج نشان داده شده در اینجا را پیدا کنید:
توجه داشته باشید که برخی از این نتایج از نوع "طبقه بندی تصویر" (به تفصیل در سمت چپ بالای هر نتیجه کارت مدل) و برخی دیگر از نوع "بردار ویژگی تصویر" هستند.
این نتایج وکتور ویژگی های تصویر در اصل نسخه های از پیش انتخاب شده MobilEnet است که می توانید به جای طبقه بندی نهایی ، از بردارهای ویژگی تصویر استفاده کنید.
مدلهایی از این دست اغلب "مدل های پایه" نامیده می شوند که می توانید با اضافه کردن یک سر طبقه بندی جدید و آموزش آن با داده های خود ، برای انجام یادگیری انتقال به همان روشی که در بخش قبلی نشان داده شده است استفاده کنید.
نکته بعدی که باید بررسی شود ، برای یک مدل پایه مورد علاقه است که فرمت tensorflow.js مدل به عنوان منتشر شده است. اگر صفحه را برای یکی از این مدل های Vector Vector Mobilenet V3 باز کنید ، می توانید از مستندات JS مشاهده کنید که به صورت یک مدل نمودار بر اساس قطعه کد مثال در اسناد و مدارک که از tf.loadGraphModel()
استفاده می کند ، مشاهده می کنید.
همچنین لازم به ذکر است که اگر به جای قالب نمودار ، مدلی را در قالب لایه ها پیدا کنید ، می توانید کدام لایه ها را برای یخ زدن انتخاب کنید و کدام یک را برای آموزش از بین ببرید. این می تواند هنگام ایجاد یک مدل برای یک کار جدید ، که اغلب به عنوان "مدل انتقال" گفته می شود ، بسیار قدرتمند باشد. در حال حاضر ، با این حال ، شما از نوع مدل پیش فرض نمودار برای این آموزش استفاده خواهید کرد ، که بیشتر مدل های TF Hub به عنوان مستقر شده اند. برای کسب اطلاعات بیشتر در مورد کار با مدل های Layers ، دوره Zero to Hero Tensorflow.js را ببینید.
مزایای یادگیری انتقال
مزایای استفاده از یادگیری انتقال به جای آموزش کل معماری مدل از ابتدا چیست؟
اول ، زمان آموزش یک مزیت مهم برای استفاده از یک رویکرد یادگیری انتقال است زیرا در حال حاضر یک مدل پایه آموزش دیده برای ساختن آن دارید.
ثانیا ، می توانید با نشان دادن نمونه های بسیار کمتری از چیز جدیدی که می خواهید طبقه بندی کنید به دلیل آموزش هایی که قبلاً انجام شده است ، دور شوید.
این بسیار عالی است اگر وقت و منابع محدودی برای جمع آوری داده های نمونه ای از چیزی که می خواهید طبقه بندی کنید ، دارید و باید قبل از جمع آوری داده های آموزش بیشتر به سرعت نمونه اولیه تهیه کنید تا آن را قوی تر کنید.
با توجه به نیاز به داده های کمتر و سرعت آموزش یک شبکه کوچکتر ، یادگیری انتقال از منابع کمتری برخوردار است. این امر آن را برای محیط مرورگر بسیار مناسب می کند و فقط ده ها ثانیه از یک دستگاه مدرن به جای ساعت ، روز یا هفته برای آموزش کامل مدل می گیرد.
باشه! اکنون شما می دانید که ذات یادگیری انتقال چیست ، وقت آن است که نسخه بسیار شخصی خود را از دستگاه قابل آموزش ایجاد کنید. بیایید شروع کنیم!
5. روی کد تنظیم شوید
آنچه شما نیاز دارید
- یک مرورگر وب مدرن.
- دانش اساسی HTML ، CSS ، JavaScript و Chrome Devtools (مشاهده خروجی کنسول).
بیایید کد نویسی کنیم
قالب های BoilerPlate برای شروع برای Glitch.com یا CodePen.io ایجاد شده اند. فقط با یک کلیک می توانید هر دو الگوی را به عنوان حالت پایه خود برای این آزمایشگاه کد کلون کنید.
در Glitch ، روی دکمه " Remix this" کلیک کنید تا آن را چنگ بزنید و مجموعه جدیدی از پرونده ها را که می توانید ویرایش کنید تهیه کنید.
از طرف دیگر ، در Codepen ، " چنگال" را در پایین سمت راست پایین صفحه کلیک کنید.
این اسکلت بسیار ساده پرونده های زیر را در اختیار شما قرار می دهد:
- صفحه 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>
جایگزین: از وب سایت دلخواه خود استفاده کنید یا به صورت محلی کار کنید
اگر می خواهید کد را بارگیری کرده و به صورت محلی یا در یک ویرایشگر آنلاین متفاوت کار کنید ، به سادگی 3 پرونده نامگذاری شده در بالا را در همان فهرست ایجاد کنید و کد را از دیگ بخار Glitch ما در هر یک از آنها کپی و چسباند.
6. App HTML BoilerPlate
از کجا شروع کنم؟
همه نمونه های اولیه به برخی از داربست های اساسی 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 & 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 برای کلاس اول شروع می شود. این شاخص عددی است که شما برای نشان دادن داده های یک کلاس خاص استفاده می کنید. این شاخص برای رمزگذاری کلاسهای خروجی به درستی با یک نمایش عددی به جای یک رشته استفاده می شود ، زیرا مدل های ML فقط می توانند با اعداد کار کنند.
There is also a data-name attribute that contains the human readable name you want to use for this class, which lets you provide a more meaningful name to the user instead of a numerical index value from the 1 hot encoding.
Finally, you have a train and reset button to kick off the training process once data has been collected, or to reset the app respectively.
- You also added 2
<script>
imports. One for TensorFlow.js, and the other for script.js that you will define shortly.
7. Add style
Element defaults
Add styles for the HTML elements you just added to ensure they render correctly. Here are some styles that are added to position and size elements correctly. هیچ چیز خیلی خاصی نیست. You could certainly add to this later to make an even better UX, like you saw in the teachable machine video.
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%;
}
عالیه این تمام چیزی است که شما نیاز دارید. If you preview the output right now, it should look something like this:
8. JavaScript: Key constants and listeners
Define key constants
First, add some key constants you'll use throughout the app. Start by replacing the contents of script.js
with these constants:
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 = [];
Let's break down what these are for:
-
STATUS
simply holds a reference to the paragraph tag you will write status updates to. -
VIDEO
holds a reference to the HTML video element that will render the webcam feed. -
ENABLE_CAM_BUTTON
,RESET_BUTTON
, andTRAIN_BUTTON
grab DOM references to all the key buttons from the HTML page. -
MOBILE_NET_INPUT_WIDTH
andMOBILE_NET_INPUT_HEIGHT
define the expected input width and height of the MobileNet model respectively. By storing this in a constant near the top of the file like this, if you decide to use a different version later, it makes it easier to update the values once instead of having to replace it in many different places. -
STOP_DATA_GATHER
is set to - 1. This stores a state value so you know when the user has stopped clicking a button to gather data from the webcam feed. By giving this number a more meaningful name, it makes the code more readable later. -
CLASS_NAMES
acts as a lookup and holds the human readable names for the possible class predictions. This array will be populated later.
OK, now that you have references to key elements, it is time to associate some event listeners to them.
Add key event listeners
Start by adding click event handlers to key buttons as shown:
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
- calls the enableCam function when clicked.
TRAIN_BUTTON
- calls trainAndPredict when clicked.
RESET_BUTTON
- calls reset when clicked.
Finally in this section you can find all buttons that have a class of 'dataCollector' using document.querySelectorAll()
. This returns an array of elements found from the document that match:
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!
}
توضیح کد:
You then iterate through the found buttons and associate 2 event listeners to each. One for 'mousedown', and one for 'mouseup'. This lets you keep recording samples as long as the button is pressed, which is useful for data collection.
Both events call a gatherDataForClass
function that you will define later.
At this point, you can also push the found human readable class names from the HTML button attribute data-name to the CLASS_NAMES
array.
Next, add some variables to store key things that will be used later.
script.js
let mobilenet = undefined;
let gatherDataState = STOP_DATA_GATHER;
let videoPlaying = false;
let trainingDataInputs = [];
let trainingDataOutputs = [];
let examplesCount = [];
let predict = false;
Let's walk through those.
First, you have a variable mobilenet
to store the loaded mobilenet model. Initially set this to undefined.
Next, you have a variable called gatherDataState
. If a 'dataCollector' button is pressed, this changes to be the 1 hot ID of that button instead, as defined in the HTML, so you know what class of data you are collecting at that moment. Initially, this is set to STOP_DATA_GATHER
so that the data gather loop you write later will not gather any data when no buttons are being pressed.
videoPlaying
keeps track of whether the webcam stream is successfully loaded and playing and is available to use. Initially, this is set to false
as the webcam is not on until you press the ENABLE_CAM_BUTTON.
Next, define 2 arrays, trainingDataInputs
and trainingDataOutputs
. These store the gathered training data values, as you click the 'dataCollector' buttons for the input features generated by MobileNet base model, and the output class sampled respectively.
One final array, examplesCount,
is then defined to keep track of how many examples are contained for each class once you start adding them.
Finally, you have a variable called predict
that controls your prediction loop. This is set to false
initially. No predictions can take place until this is set to true
later.
Now that all the key variables have been defined, let's go and load the pre-chopped up MobileNet v3 base model that provides image feature vectors instead of classifications.
9. Load the MobileNet base model
First, define a new function called loadMobileNetFeatureModel
as shown below. This must be an async function as the act of loading a model is asynchronous:
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();
In this code you define the URL
where the model to load is located from the TFHub documentation.
You can then load the model using await tf.loadGraphModel()
, remembering to set the special property fromTFHub
to true
as you are loading a model from this Google website. This is a special case only for using models hosted on TF Hub where this extra property has to be set.
Once loading is complete you can set the STATUS
element's innerText
with a message so you can visually see it has loaded correctly and you are ready to start gathering data.
The only thing left to do now is to warm up the model. With larger models like this, the first time you use the model, it can take a moment to set everything up. Therefore it helps to pass zeros through the model to avoid any waiting in the future where timing may be more critical.
You can use tf.zeros()
wrapped in a tf.tidy()
to ensure tensors are disposed of correctly, with a batch size of 1, and the correct height and width that you defined in your constants at the start. Finally, you also specify the color channels, which in this case is 3 as the model expects RGB images.
Next, log the resulting shape of the tensor returned using answer.shape()
to help you understand the size of the image features this model produces.
After defining this function, you can then call it immediately to initiate the model download on the page load.
If you view your live preview right now, after a few moments you will see the status text change from "Awaiting TF.js load" to become "MobileNet v3 loaded successfully!" همانطور که در زیر نشان داده شده است. Ensure this works before continuing.
You can also check the console output to see the printed size of the output features that this model produces. After running zeros through the MobileNet model, you'll see a shape of [1, 1024]
printed. The first item is just the batch size of 1, and you can see it actually returns 1024 features that can then be used to help you classify new objects.
10. Define the new model head
Now it's time to define your model head, which is essentially a very minimal multi-layer perceptron.
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']
});
Let's walk through this code. You start by defining a tf.sequential model to which you will add model layers to.
Next, add a dense layer as the input layer to this model. This has an input shape of 1024
as the outputs from the MobileNet v3 features are of this size. You discovered this in the previous step after passing ones through the model. This layer has 128 neurons that use the ReLU activation function.
If you are new to activation functions and model layers, consider taking the course detailed at the start of this workshop to understand what these properties do behind the scenes.
The next layer to add is the output layer. The number of neurons should equal the number of classes you are trying to predict. To do that you can use CLASS_NAMES.length
to find how many classes you are planning to classify, which is equal to the number of data gather buttons found in the user interface. As this is a classification problem, you use the softmax
activation on this output layer, which must be used when trying to create a model to solve classification problems instead of regression.
Now print a model.summary()
to print the overview of the newly defined model to the console.
Finally, compile the model so it is ready to be trained. Here the optimizer is set to adam
, and the loss will either be binaryCrossentropy
if CLASS_NAMES.length
is equal to 2
, or it will use categoricalCrossentropy
if there are 3 or more classes to classify. Accuracy metrics are also requested so they can be monitored in the logs later for debugging purposes.
In the console you should see something like this:
Note that this has over 130 thousand trainable parameters. But as this is a simple dense layer of regular neurons it will train pretty fast.
As an activity to do once the project is complete, you could try changing the number of neurons in the first layer to see how low you can make it while still getting decent performance. Often with machine learning there is some level of trial and error to find optimal parameter values to give you the best trade off between resource usage and speed.
11. Enable the webcam
It's now time to flesh out the enableCam()
function you defined earlier. Add a new function named hasGetUserMedia()
as shown below and then replace the contents of the previously defined enableCam()
function with the corresponding code below.
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');
}
}
First, create a function named hasGetUserMedia()
to check if the browser supports getUserMedia()
by checking for the existence of key browser APIs properties.
In the enableCam()
function use the hasGetUserMedia()
function you just defined above to check if it is supported. If it isn't, print a warning to the console.
If it supports it, define some constraints for your getUserMedia()
call, such as you want the video stream only, and that you prefer the width
of the video to be 640
pixels in size, and the height
to be 480
pixels. چرا؟ Well, there is not much point getting a video larger than this as it would need to be resized to 224 by 224 pixels to be fed into the MobileNet model. You may as well save some computing resources by requesting a smaller resolution. Most cameras support a resolution of this size.
Next, call navigator.mediaDevices.getUserMedia()
with the constraints
detailed above, and then wait for the stream
to be returned. Once the stream
is returned you can get your VIDEO
element to play the stream
by setting it as its srcObject
value.
You should also add an eventListener on the VIDEO
element to know when the stream
has loaded and is playing successfully.
Once the steam loads, you can set videoPlaying
to true and remove the ENABLE_CAM_BUTTON
to prevent it from being clicked again by setting its class to " removed
".
Now run your code, click the enable camera button, and allow access to the webcam. If it is your first time doing this, you should see yourself rendered to the video element on the page as shown:
Ok, now it is time to add a function to deal with the dataCollector
button clicks.
12. Data collection button event handler
Now it's time to fill out your currently empty function called gatherDataForClass().
This is what you assigned as your event handler function for dataCollector
buttons at the start of the codelab.
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();
}
First, check the data-1hot
attribute on the currently clicked button by calling this.getAttribute()
with the attribute's name, in this case data-1hot
as the parameter. As this is a string, you can then use parseInt()
to cast it to an integer and assign this result to a variable named classNumber.
Next, set the gatherDataState
variable accordingly. If the current gatherDataState
is equal to STOP_DATA_GATHER
(which you set to be -1), then that means you are not currently gathering any data and it was a mousedown
event that fired. Set the gatherDataState
to be the classNumber
you just found.
Otherwise, it means that you are currently gathering data and the event that fired was a mouseup
event, and you now want to stop gathering data for that class. Just set it back to the STOP_DATA_GATHER
state to end the data gathering loop you will define shortly.
Finally, kick off the call to dataGatherLoop(),
which actually performs the recording of class data.
13. Data collection
Now, define the dataGatherLoop()
function. This function is responsible for sampling images from the webcam video, passing them through the MobileNet model, and capturing the outputs of that model (the 1024 feature vectors).
It then stores them along with the gatherDataState
ID of the button that is currently being pressed so you know what class this data represents.
Let's walk through it:
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);
}
}
You are only going to continue this function's execution if videoPlaying
is true, meaning that the webcam is active, and gatherDataState
is not equal to STOP_DATA_GATHER
and a button for class data gathering is currently being pressed.
Next, wrap your code in a tf.tidy()
to dispose of any created tensors in the code that follows. The result of this tf.tidy()
code execution is stored in a variable called imageFeatures
.
You can now grab a frame of the webcam VIDEO
using tf.browser.fromPixels()
. The resulting tensor containing the image data is stored in a variable called videoFrameAsTensor
.
Next, resize the videoFrameAsTensor
variable to be of the correct shape for the MobileNet model's input. Use a tf.image.resizeBilinear()
call with the tensor you want to reshape as the first parameter, and then a shape that defines the new height and width as defined by the constants you already created earlier. Finally, set align corners to true by passing the third parameter to avoid any alignment issues when resizing. The result of this resize is stored in a variable called resizedTensorFrame
.
Note that this primitive resize stretches the image, as your webcam image is 640 by 480 pixels in size, and the model needs a square image of 224 by 224 pixels.
For the purposes of this demo this should work fine. However, once you complete this codelab, you may want to try and crop a square from this image instead for even better results for any production system you may create later.
Next, normalize the image data. Image data is always in the range of 0 to 255 when using tf.browser.frompixels()
, so you can simply divide resizedTensorFrame by 255 to ensure all values are between 0 and 1 instead, which is what the MobileNet model expects as inputs.
Finally, in the tf.tidy()
section of the code, push this normalized tensor through the loaded model by calling mobilenet.predict()
, to which you pass the expanded version of the normalizedTensorFrame
using expandDims()
so that it is a batch of 1, as the model expects a batch of inputs for processing.
Once the result comes back, you can then immediately call squeeze()
on that returned result to squash it back down to a 1D tensor, which you then return and assign to the imageFeatures
variable that captures the result from tf.tidy()
.
Now that you have the imageFeatures
from the MobileNet model, you can record those by pushing them onto the trainingDataInputs
array that you defined previously.
You can also record what this input represents by pushing the current gatherDataState
to the trainingDataOutputs
array too.
Note that the gatherDataState
variable would have been set to the current class's numerical ID you are recording data for when the button was clicked in the previously defined gatherDataForClass()
function.
At this point you can also increment the number of examples you have for a given class. To do this, first check if the index within the examplesCount
array has been initialized before or not. If it is undefined, set it to 0 to initialize the counter for a given class's numerical ID, and then you can increment the examplesCount
for the current gatherDataState
.
Now update the STATUS
element's text on the web page to show the current counts for each class as they're captured. To do this, loop through the CLASS_NAMES
array, and print the human readable name combined with the data count at the same index in examplesCount
.
Finally, call window.requestAnimationFrame()
with dataGatherLoop
passed as a parameter, to recursively call this function again. This will continue to sample frames from the video until the button's mouseup
is detected, and gatherDataState
is set to STOP_DATA_GATHER,
at which point the data gather loop will end.
If you run your code now, you should be able to click the enable camera button, await the webcam to load, and then click and hold each of the data gather buttons to gather examples for each class of data. Here you see me gather data for my mobile phone and my hand respectively.
You should see the status text updated as it stores all the tensors in memory as shown in the screen capture above.
14. Train and predict
The next step is to implement code for your currently empty trainAndPredict()
function, which is where the transfer learning takes place. Let's take a look at the code:
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);
}
First, ensure you stop any current predictions from taking place by setting predict
to false
.
Next, shuffle your input and output arrays using tf.util.shuffleCombo()
to ensure the order does not cause issues in training.
Convert your output array, trainingDataOutputs,
to be a tensor1d of type int32 so it is ready to be used in a one hot encoding . This is stored in a variable named outputsAsTensor
.
Use the tf.oneHot()
function with this outputsAsTensor
variable along with the max number of classes to encode, which is just the CLASS_NAMES.length
. Your one hot encoded outputs are now stored in a new tensor called oneHotOutputs
.
Note that currently trainingDataInputs
is an array of recorded tensors. In order to use these for training you will need to convert the array of tensors to become a regular 2D tensor.
To do that there is a great function within the TensorFlow.js library called tf.stack()
,
which takes an array of tensors and stacks them together to produce a higher dimensional tensor as an output. In this case a tensor 2D is returned, that's a batch of 1 dimensional inputs that are each 1024 in length containing the features recorded, which is what you need for training.
Next, await model.fit()
to train the custom model head. Here you pass your inputsAsTensor
variable along with the oneHotOutputs
to represent the training data to use for example inputs and target outputs respectively. In the configuration object for the 3rd parameter, set shuffle
to true
, use batchSize
of 5
, with epochs
set to 10
, and then specify a callback
for onEpochEnd
to the logProgress
function that you will define shortly.
Finally, you can dispose of the created tensors as the model is now trained. You can then set predict
back to true
to allow predictions to take place again, and then call the predictLoop()
function to start predicting live webcam images.
You can also define the logProcess()
function to log the state of training, which is used in model.fit()
above and that prints results to console after each round of training.
شما تقریباً آنجا هستید! Time to add the predictLoop()
function to make predictions.
Core prediction loop
Here you implement the main prediction loop that samples frames from a webcam and continuously predicts what is in each frame with real time results in the browser.
Let's check the code:
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);
}
}
First, check that predict
is true, so that predictions are only made after a model is trained and is available to use.
Next, you can get the image features for the current image just like you did in the dataGatherLoop()
function. Essentially, you grab a frame from the webcam using tf.browser.from pixels()
, normalise it, resize it to be 224 by 224 pixels in size, and then pass that data through the MobileNet model to get the resulting image features.
Now, however, you can use your newly trained model head to actually perform a prediction by passing the resulting imageFeatures
just found through the trained model's predict()
function. You can then squeeze the resulting tensor to make it 1 dimensional again and assign it to a variable called prediction
.
With this prediction
you can find the index that has the highest value using argMax()
, and then convert this resulting tensor to an array using arraySync()
to get at the underlying data in JavaScript to discover the position of the highest valued element. This value is stored in the variable called highestIndex
.
You can also get the actual prediction confidence scores in the same way by calling arraySync()
on the prediction
tensor directly.
You now have everything you need to update the STATUS
text with the prediction
data. To get the human readable string for the class you can just look up the highestIndex
in the CLASS_NAMES
array, and then grab the confidence value from the predictionArray
. To make it more readable as a percentage, just multiply by 100 and math.floor()
the result.
Finally, you can use window.requestAnimationFrame()
to call predictionLoop()
all over again once ready, to get real time classification on your video stream. This continues until predict
is set to false
if you choose to train a new model with new data.
Which brings you to the final piece of the puzzle. Implementing the reset button.
15. Implement the reset button
Almost complete! The final piece of the puzzle is to implement a reset button to start over. The code for your currently empty reset()
function is below. Go ahead and update it as follows:
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);
}
First, stop any running prediction loops by setting predict
to false
. Next, delete all contents in the examplesCount
array by setting its length to 0, which is a handy way to clear all contents from an array.
Now go through all the current recorded trainingDataInputs
and ensure you dispose()
of each tensor contained within it to free up memory again, as Tensors are not cleaned up by the JavaScript garbage collector.
Once that is done you can now safely set the array length to 0 on both the trainingDataInputs
and trainingDataOutputs
arrays to clear those too.
Finally set the STATUS
text to something sensible, and print out the tensors left in memory as a sanity check.
Note that there will be a few hundred tensors still in memory as both the MobileNet model and the multi-layer perceptron you defined are not disposed of. You will need to reuse them with new training data if you decide to train again after this reset.
16. Let's try it out
It's time to test out your very own version of Teachable Machine!
Head to the live preview, enable the webcam, gather at least 30 samples for class 1 for some object in your room, and then do the same for class 2 for a different object, click train, and check the console log to see progress. It should train pretty fast:
Once trained, show the objects to the camera to get live predictions that will be printed to the status text area on the web page near the top. If you are having trouble, check my completed working code to see if you missed copying over anything.
17. Congratulations
تبریک می گویم! You have just completed your very first transfer learning example using TensorFlow.js live in the browser.
Try it out, test it on a variety of objects, you may notice some things are harder to recognize than others, especially if they are similar to something else. You may need to add more classes or training data to be able to tell them apart.
خلاصه
In this codelab you learned:
- What transfer learning is, and its advantages over training a full model.
- How to get models for re-use from TensorFlow Hub.
- How to set up a web app suitable for transfer learning.
- How to load and use a base model to generate image features.
- How to train a new prediction head that can recognize custom objects from webcam imagery.
- How to use the resulting models to classify data in real time.
بعدش چی؟
Now that you have a working base to start from, what creative ideas can you come up with to extend this machine learning model boilerplate for a real world use case you may be working on? Maybe you could revolutionize the industry that you currently work in to help folk at your company train models to classify things that are important in their day-to-day work? امکانات بی پایان هستند.
To go further, consider taking this full course for free , which shows you how to combine the 2 models you currently have in this codelab into 1 single model for efficiency.
Also if you are curious more around the theory behind the original teachable machine application check out this tutorial .
Share what you make with us
You can easily extend what you made today for other creative use cases too and we encourage you to think outside the box and keep hacking.
Remember to tag us on social media using the #MadeWithTFJS hashtag for a chance for your project to be featured on our TensorFlow blog or even future events . We would love to see what you make.
Websites to check out
- TensorFlow.js official website
- TensorFlow.js pre-made models
- TensorFlow.js API
- TensorFlow.js Show & Tell — get inspired and see what others have made.