TensorFlow.js: создайте свою собственную «обучаемую машину»; использование трансферного обучения с TensorFlow.js

1. Прежде чем начать

За последние несколько лет использование модели TensorFlow.js выросло в геометрической прогрессии, и многие разработчики JavaScript теперь стремятся взять существующие современные модели и переобучить их для работы с пользовательскими данными, уникальными для их отрасли. Процесс взятия существующей модели (часто называемой базовой моделью) и ее использования в аналогичной, но другой области известен как трансферное обучение.

Трансферное обучение имеет много преимуществ по сравнению с началом с совершенно пустой модели. Вы можете повторно использовать знания, уже полученные из ранее обученной модели, и вам потребуется меньше примеров нового элемента, который вы хотите классифицировать. Кроме того, обучение часто происходит значительно быстрее, поскольку приходится переобучать только несколько последних слоев архитектуры модели, а не всю сеть. По этой причине трансферное обучение очень хорошо подходит для среды веб-браузера, где ресурсы могут различаться в зависимости от устройства выполнения, но также имеется прямой доступ к датчикам для упрощения сбора данных.

В этой лаборатории кода показано, как создать веб-приложение с чистого листа, воссоздавая популярный веб-сайт Google « Обучаемая машина ». Веб-сайт позволяет вам создать функциональное веб-приложение, которое любой пользователь может использовать для распознавания пользовательского объекта с помощью всего лишь нескольких примеров изображений со своей веб-камеры. Веб-сайт намеренно сделан минимальным, чтобы вы могли сосредоточиться на аспектах машинного обучения в этой лаборатории кода. Однако, как и в случае с исходным веб-сайтом Teachable Machine, здесь есть много возможностей применить свой существующий опыт веб-разработчика для улучшения UX.

Предварительные условия

Эта лаборатория написана для веб-разработчиков, которые в некоторой степени знакомы с готовыми моделями TensorFlow.js и базовым использованием API, а также хотят начать работу с трансферным обучением в TensorFlow.js.

  • Для этой лабораторной работы предполагается базовое знакомство с TensorFlow.js, HTML5, CSS и JavaScript.

Если вы новичок в Tensorflow.js, рассмотрите возможность сначала пройти этот бесплатный курс «с нуля до героя» , который не предполагает никакого опыта работы с машинным обучением или TensorFlow.js и научит вас всему, что вам нужно знать, небольшими шагами.

Что вы узнаете

  • Что такое TensorFlow.js и почему вам следует использовать его в своем следующем веб-приложении.
  • Как создать упрощенную веб-страницу HTML/CSS/JS, повторяющую взаимодействие с пользователем Teachable Machine.
  • Как использовать TensorFlow.js для загрузки предварительно обученной базовой модели, в частности MobileNet, для создания функций изображения, которые можно использовать при трансферном обучении.
  • Как собрать данные с веб-камеры пользователя для нескольких классов данных, которые вы хотите распознать.
  • Как создать и определить многослойный перцептрон, который принимает особенности изображения и учится с их помощью классифицировать новые объекты.

Давайте займемся взломом...

Что вам понадобится

  • Для отслеживания предпочтительнее использовать учетную запись Glitch.com, или вы можете использовать среду веб-сервиса, которую вам удобно редактировать и запускать самостоятельно.

2. Что такое TensorFlow.js?

54e81d02971f53e8.png

TensorFlow.js — это библиотека машинного обучения с открытым исходным кодом , которая может работать везде, где доступен JavaScript. Он основан на оригинальной библиотеке TensorFlow, написанной на Python , и призван воссоздать этот опыт разработки и набор API для экосистемы JavaScript.

Где его можно использовать?

Благодаря переносимости JavaScript теперь вы можете писать на одном языке и с легкостью выполнять машинное обучение на всех следующих платформах:

  • Клиентская часть в веб-браузере с использованием ванильного JavaScript.
  • Серверная часть и даже устройства IoT, такие как Raspberry Pi, использующие Node.js.
  • Настольные приложения, использующие Electron
  • Нативные мобильные приложения с использованием React Native

TensorFlow.js также поддерживает несколько бэкэндов в каждой из этих сред (фактические аппаратные среды, в которых он может выполняться, например, ЦП или WebGL. «Бэкэнд» в этом контексте не означает среду на стороне сервера — бэкэнд для выполнения). может быть, например, на стороне клиента в WebGL), чтобы обеспечить совместимость, а также обеспечить быструю работу. В настоящее время TensorFlow.js поддерживает:

  • Выполнение WebGL на видеокарте устройства (GPU) — это самый быстрый способ запуска больших моделей (размером более 3 МБ) с ускорением GPU.
  • Выполнение веб-сборки (WASM) на ЦП — для повышения производительности ЦП на всех устройствах, включая, например, мобильные телефоны старшего поколения. Это лучше подходит для моделей меньшего размера (размером менее 3 МБ), которые на самом деле могут работать на процессоре быстрее с WASM, чем с WebGL, из-за затрат на загрузку контента в графический процессор.
  • Выполнение ЦП — резервный вариант, если ни одна из других сред недоступна. Это самый медленный из трех, но он всегда рядом.

Примечание. Вы можете выбрать принудительное использование одного из этих бэкэндов, если знаете, на каком устройстве вы будете выполняться, или вы можете просто позволить TensorFlow.js решить за вас, если вы не укажете это.

Суперспособности клиентской стороны

Запуск TensorFlow.js в веб-браузере на клиентском компьютере может дать несколько преимуществ, которые стоит учитывать.

Конфиденциальность

Вы можете обучать и классифицировать данные на клиентском компьютере, даже не отправляя данные на сторонний веб-сервер. Могут быть случаи, когда это может быть требованием соблюдения местных законов, таких как, например, GDPR, или при обработке любых данных, которые пользователь может захотеть сохранить на своем компьютере и не отправлять третьей стороне.

Скорость

Поскольку вам не нужно отправлять данные на удаленный сервер, вывод (классификация данных) может выполняться быстрее. Более того, у вас есть прямой доступ к датчикам устройства, таким как камера, микрофон, GPS, акселерометр и т. д., если пользователь предоставит вам доступ.

Охват и масштабирование

Одним щелчком мыши любой человек в мире может щелкнуть ссылку, которую вы ему отправляете, открыть веб-страницу в своем браузере и использовать то, что вы создали. Нет необходимости в сложной настройке Linux на стороне сервера с драйверами CUDA и многим другим, просто используйте систему машинного обучения.

Расходы

Отсутствие серверов означает, что единственное, за что вам нужно платить, — это CDN для размещения ваших файлов HTML, CSS, JS и моделей. Стоимость CDN намного дешевле, чем круглосуточная работа сервера (возможно, с подключенной видеокартой).

Возможности серверной части

Использование реализации TensorFlow.js на Node.js обеспечивает следующие функции.

Полная поддержка CUDA

На стороне сервера для ускорения видеокарты необходимо установить драйверы NVIDIA CUDA, чтобы TensorFlow мог работать с видеокартой (в отличие от браузера, который использует WebGL — установка не требуется). Однако при полной поддержке CUDA вы можете полностью использовать возможности видеокарты более низкого уровня, что приводит к сокращению времени обучения и вывода. Производительность находится на одном уровне с реализацией Python TensorFlow, поскольку они оба используют один и тот же бэкэнд C++.

Размер модели

Что касается передовых моделей, полученных в ходе исследований, вы можете работать с очень большими моделями, возможно, размером в гигабайты. Эти модели в настоящее время невозможно запустить в веб-браузере из-за ограничений использования памяти для каждой вкладки браузера. Для запуска этих более крупных моделей вы можете использовать Node.js на своем собственном сервере с аппаратными характеристиками, необходимыми для эффективного запуска такой модели.

Интернет вещей

Node.js поддерживается на популярных одноплатных компьютерах, таких как Raspberry Pi , что, в свою очередь, означает, что вы можете выполнять модели TensorFlow.js и на таких устройствах.

Скорость

Node.js написан на JavaScript, а это означает, что он получает преимущества от своевременной компиляции. Это означает, что вы часто можете увидеть прирост производительности при использовании Node.js, поскольку он будет оптимизирован во время выполнения, особенно для любой предварительной обработки, которую вы можете выполнять. Отличный пример этого можно увидеть в этом тематическом исследовании , которое показывает, как Hugging Face использовал Node.js для двукратного повышения производительности своей модели обработки естественного языка.

Теперь вы понимаете основы TensorFlow.js, где он может работать, а также некоторые его преимущества, давайте начнем делать с ним полезные вещи!

3. Передача обучения

Что такое трансферное обучение?

Трансферное обучение предполагает использование уже полученных знаний для изучения другой, но похожей вещи.

Мы, люди, делаем это постоянно. В вашем мозгу хранится целый жизненный опыт, который вы можете использовать, чтобы узнавать новые вещи, которых вы никогда раньше не видели. Возьмем, к примеру, эту иву:

e28070392cd4afb9.png

В зависимости от того, где вы находитесь, есть вероятность, что вы раньше не видели этот тип дерева.

Тем не менее, если я попрошу вас сказать мне, есть ли ивы на новом изображении ниже, вы, вероятно, сможете заметить их довольно быстро, даже несмотря на то, что они находятся под другим углом и немного отличаются от исходного, который я вам показывал.

d9073a0d5df27222.png

В вашем мозгу уже есть группа нейронов, которые умеют распознавать древовидные объекты, а также другие нейроны, которые хорошо умеют находить длинные прямые линии. Вы можете повторно использовать эти знания, чтобы быстро классифицировать иву, которая представляет собой древовидный объект с множеством длинных прямых вертикальных ветвей.

Аналогично, если у вас есть модель машинного обучения, которая уже обучена в определенной области, например распознавание изображений, вы можете повторно использовать ее для выполнения другой, но связанной задачи.

Вы можете сделать то же самое с помощью продвинутой модели, такой как MobileNet, которая является очень популярной исследовательской моделью и может выполнять распознавание изображений на 1000 различных типах объектов. От собак до автомобилей — оно обучалось на огромном наборе данных, известном как ImageNet , который содержит миллионы помеченных изображений.

На этой анимации вы можете увидеть огромное количество слоев в этой модели MobileNet V1:

7d4e1e35c1a89715.gif

В ходе обучения эта модель научилась извлекать общие признаки, которые важны для всех этих 1000 объектов, и многие из функций более низкого уровня, которые она использует для идентификации таких объектов, могут быть полезны и для обнаружения новых объектов, которые она никогда раньше не видела. В конце концов, все в конечном итоге представляет собой просто комбинацию линий, фактур и форм.

Давайте взглянем на традиционную архитектуру сверточной нейронной сети (CNN) (похожую на MobileNet) и посмотрим, как трансферное обучение может использовать эту обученную сеть для изучения чего-то нового. На изображении ниже показана типичная архитектура модели CNN, которая в данном случае была обучена распознавать рукописные цифры от 0 до 9:

baf4e3d434576106.png

Если бы вы могли отделить предварительно обученные слои нижнего уровня существующей обученной модели, как показано слева, от слоев классификации в конце модели, показанной справа (иногда называемой главой классификации модели), вы можете использовать слои нижнего уровня для создания выходных функций для любого изображения на основе исходных данных, на которых оно было обучено. Вот та же сеть с удаленной классификационной головкой:

369a8a9041c6917d.png

Если предположить, что новая вещь, которую вы пытаетесь распознать, также может использовать такие выходные функции, изученные предыдущей моделью, тогда есть большая вероятность, что их можно будет повторно использовать для новой цели.

На диаграмме выше эта гипотетическая модель была обучена на цифрах, поэтому, возможно, то, что мы узнали о цифрах, можно применить и к таким буквам, как a, b и c.

Итак, теперь вы можете добавить новую классификационную главу, которая вместо этого пытается предсказать a, b или c, как показано:

db97e5e60ae73bbd.png

Здесь слои нижнего уровня заморожены и не обучаются, только новая глава классификации будет обновляться, чтобы учиться на функциях, предоставленных предварительно обученной измельченной моделью слева.

Этот процесс известен как трансферное обучение и именно это делает Teachable Machine за кулисами.

Вы также можете видеть, что, если обучать многослойный перцептрон только в самом конце сети, он обучается намного быстрее, чем если бы вам пришлось обучать всю сеть с нуля.

Но как получить доступ к частям модели? Чтобы узнать, перейдите в следующий раздел.

4. TensorFlow Hub — базовые модели

Найдите подходящую базовую модель для использования

Для более продвинутых и популярных исследовательских моделей, таких как MobileNet, вы можете перейти в хаб TensorFlow , а затем отфильтровать модели, подходящие для TensorFlow.js, которые используют архитектуру MobileNet v3, чтобы найти результаты, подобные показанным здесь:

c5dc1420c6238c14.png

Обратите внимание, что некоторые из этих результатов относятся к типу «классификация изображений» (подробно указано в левом верхнем углу каждого результата карточки модели), а другие относятся к типу «вектор признаков изображения».

Эти результаты векторов признаков изображения, по сути, представляют собой предварительно измельченные версии MobileNet, которые вы можете использовать для получения векторов признаков изображения вместо окончательной классификации.

Подобные модели часто называют «базовыми моделями», которые затем можно использовать для выполнения трансферного обучения таким же образом, как показано в предыдущем разделе, добавляя новую классификационную голову и обучая ее собственным данным.

Следующее, что нужно проверить для конкретной интересующей базовой модели, — это формат TensorFlow.js, в котором выпущена модель. Если вы откроете страницу одной из этих моделей MobileNet v3 с вектором функций, вы увидите в документации JS, что она имеет форму графовой модели, основанной на фрагменте кода примера в документации, который использует tf.loadGraphModel() .

f97d903d2e46924b.png

Также следует отметить, что если вы найдете модель в формате слоев вместо формата графика, вы можете выбрать, какие слои заморозить, а какие разморозить для обучения. Это может оказаться очень полезным при создании модели для новой задачи, которую часто называют «моделью переноса». Однако на данный момент для этого руководства вы будете использовать тип графовой модели по умолчанию, в качестве которого развертывается большинство моделей TF Hub. Чтобы узнать больше о работе с моделями слоев, ознакомьтесь с курсом TensorFlow.js с нуля до героя .

Преимущества трансферного обучения

Каковы преимущества использования трансферного обучения вместо обучения всей архитектуры модели с нуля?

Во-первых, время обучения является ключевым преимуществом использования подхода трансферного обучения, поскольку у вас уже есть обученная базовая модель, на которую можно опираться.

Во-вторых, благодаря уже проведенному обучению вам может сойти с рук демонстрация гораздо меньшего количества примеров того нового, что вы пытаетесь классифицировать.

Это действительно здорово, если у вас ограничено время и ресурсы для сбора примеров данных о том, что вы хотите классифицировать, и вам нужно быстро создать прототип, прежде чем собирать дополнительные обучающие данные, чтобы сделать его более надежным.

Учитывая потребность в меньшем количестве данных и скорость обучения меньшей сети, трансферное обучение требует меньше ресурсов. Это делает его очень подходящим для среды браузера: на современной машине для полного обучения модели требуются всего десятки секунд, а не часы, дни или недели.

Хорошо! Теперь, когда вы знаете суть трансферного обучения, пришло время создать свою собственную версию обучаемой машины. Давайте начнем!

5. Настройтесь на кодирование

Что вам понадобится

  • Современный веб-браузер.
  • Базовые знания HTML, CSS, JavaScript и Chrome DevTools (просмотр вывода консоли).

Давайте займемся кодированием

Шаблонные шаблоны для начала были созданы для 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. HTML-шаблон приложения

С чего мне начать?

Для всех прототипов требуются базовые HTML-шаблоны, на которых вы можете визуализировать свои результаты. Настройте это сейчас. Вы собираетесь добавить:

  • Заголовок страницы.
  • Немного описательного текста.
  • Статусный абзац.
  • Видео для трансляции с веб-камеры, когда оно будет готово.
  • Несколько кнопок для запуска камеры, сбора данных или сброса настроек.
  • Импорт файлов TensorFlow.js и JS, которые вы напишете позже.

Откройте index.html и вставьте в существующий код следующее, чтобы настроить вышеуказанные функции:

index.html

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

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

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

Разбейте это

Давайте разберем часть приведенного выше HTML-кода, чтобы выделить некоторые ключевые моменты, которые вы добавили.

  • Вы добавили тег <h1> для заголовка страницы вместе с тегом <p> с идентификатором «статус», куда вы будете печатать информацию, поскольку для просмотра результатов вы используете разные части системы.
  • Вы добавили элемент <video> с идентификатором «веб-камера», для которого позже вы будете отображать поток веб-камеры.
  • Вы добавили 5 элементов <button> . Первый, с идентификатором «enableCam», включает камеру. Следующие две кнопки имеют класс dataCollector, который позволяет собирать примеры изображений для объектов, которые вы хотите распознать. Код, который вы напишете позже, будет разработан таким образом, что вы сможете добавить любое количество этих кнопок, и они будут автоматически работать по назначению.

Обратите внимание, что эти кнопки также имеют специальный определяемый пользователем атрибут data-1hot с целочисленным значением, начинающимся с 0 для первого класса. Это числовой индекс, который вы будете использовать для представления данных определенного класса. Индекс будет использоваться для правильного кодирования выходных классов с помощью числового представления вместо строки, поскольку модели ML могут работать только с числами.

Существует также атрибут data-name, который содержит удобочитаемое имя, которое вы хотите использовать для этого класса, что позволяет предоставить пользователю более значимое имя вместо числового значения индекса из горячей кодировки 1.

Наконец, у вас есть кнопка обучения и сброса, позволяющая начать процесс обучения после сбора данных или соответственно сбросить приложение.

  • Вы также добавили 2 импорта <script> . Один для TensorFlow.js, а другой для script.js, который вы вскоре определите.

7. Добавьте стиль

Значения элемента по умолчанию

Добавьте стили для только что добавленных HTML-элементов, чтобы обеспечить их правильное отображение. Вот несколько стилей, которые правильно добавляются для правильного расположения и размера элементов. Ничего особенного. Вы, конечно, могли бы добавить к этому позже, чтобы сделать UX еще лучше, как вы видели в обучающем видео о машине.

стиль.css

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

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


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

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

.removed {
  display: none;
}

#status {
  font-size:150%;
}

Большой! Это все, что вам нужно. Если вы просмотрите результат прямо сейчас, он должен выглядеть примерно так:

81909685d7566dcb.png

8. JavaScript: ключевые константы и слушатели

Определить ключевые константы

Сначала добавьте несколько ключевых констант, которые вы будете использовать в приложении. Начните с замены содержимого script.js этими константами:

скрипт.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 выполняет функцию поиска и хранит удобочитаемые имена возможных предсказаний классов. Этот массив будет заполнен позже.

Хорошо, теперь, когда у вас есть ссылки на ключевые элементы, пришло время связать с ними некоторые прослушиватели событий.

Добавьте прослушиватели ключевых событий

Начните с добавления обработчиков событий щелчка к кнопкам клавиш, как показано:

скрипт.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() . Это возвращает массив элементов, найденных в документе, которые соответствуют:

скрипт.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!
}

Объяснение кода:

Затем вы перебираете найденные кнопки и связываете с каждой по два прослушивателя событий. Один для «mousedown», другой для «mouseup». Это позволяет вам продолжать записывать семплы, пока нажата кнопка, что полезно для сбора данных.

Оба события вызывают функцию gatherDataForClass , которую вы определите позже.

На этом этапе вы также можете перенести найденные удобочитаемые имена классов из имени данных атрибута кнопки HTML в массив CLASS_NAMES .

Затем добавьте несколько переменных для хранения ключевых данных, которые будут использоваться позже.

скрипт.js

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

Давайте пройдемся по ним.

Во-первых, у вас есть переменная mobilenet для хранения загруженной модели мобильной сети. Изначально установите для этого параметра значение undefined.

Далее у вас есть переменная с именем gatherDataState . Если нажата кнопка «dataCollector», вместо этого она изменится на 1 горячий идентификатор этой кнопки, как определено в HTML, поэтому вы знаете, какой класс данных вы собираете в данный момент. Изначально для этого параметра установлено значение STOP_DATA_GATHER , чтобы цикл сбора данных, который вы напишете позже, не собирал никаких данных, когда никакие кнопки не нажимаются.

videoPlaying отслеживает, успешно ли загружен и воспроизводится поток веб-камеры и доступен ли он для использования. Изначально для этого параметра установлено значение false поскольку веб-камера не включится, пока вы не нажмете ENABLE_CAM_BUTTON.

Затем определите два массива: trainingDataInputs и trainingDataOutputs . Они сохраняют собранные значения обучающих данных, когда вы нажимаете кнопки «dataCollector» для входных функций, сгенерированных базовой моделью MobileNet, и выходного класса, выбранного соответственно.

Затем определяется последний массив, examplesCount, для отслеживания количества примеров, содержащихся в каждом классе, после того, как вы начнете их добавлять.

Наконец, у вас есть переменная predict , которая управляет циклом прогнозирования. Изначально для этого параметра установлено значение false . Никакие прогнозы не могут иметь место, пока это не станет true позже.

Теперь, когда все ключевые переменные определены, давайте загрузим предварительно измельченную базовую модель MobileNet v3, которая предоставляет векторы признаков изображения вместо классификаций.

9. Загрузите базовую модель MobileNet.

Сначала определите новую функцию под названием loadMobileNetFeatureModel , как показано ниже. Это должна быть асинхронная функция, поскольку загрузка модели является асинхронной:

скрипт.js

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

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

В этом коде вы определяете URL , по которому находится загружаемая модель, из документации TFHub.

Затем вы можете загрузить модель с помощью await tf.loadGraphModel() , не забывая присвоить специальному свойству fromTFHub значение true при загрузке модели с этого веб-сайта Google. Это особый случай только при использовании моделей, размещенных в TF Hub, где необходимо установить это дополнительное свойство.

После завершения загрузки вы можете установить innerText элемента STATUS с сообщением, чтобы вы могли визуально видеть, что он загрузился правильно, и вы готовы начать сбор данных.

Теперь осталось только прогреть модель. В случае с такими более крупными моделями при первом использовании модели может потребоваться некоторое время, чтобы все настроить. Поэтому полезно передавать в модель нули, чтобы избежать ожидания в будущем, когда время может оказаться более критичным.

Вы можете использовать tf.zeros() завернутый в tf.tidy() чтобы гарантировать правильное удаление тензоров с размером пакета 1 и правильной высотой и шириной, которые вы определили в своих константах в начале. Наконец, вы также указываете цветовые каналы, которых в данном случае 3, поскольку модель ожидает изображения RGB.

Затем запишите результирующую форму тензора, возвращенную с помощью answer.shape() чтобы понять размер элементов изображения, которые создает эта модель.

После определения этой функции вы можете немедленно вызвать ее, чтобы инициировать загрузку модели при загрузке страницы.

Если вы просмотрите предварительный просмотр в реальном времени прямо сейчас, через несколько секунд вы увидите, что текст статуса изменится с «Ожидание загрузки TF.js» на «MobileNet v3 загружен успешно!» как показано ниже. Прежде чем продолжить, убедитесь, что это работает.

a28b734e190afff.png

Вы также можете проверить вывод консоли, чтобы увидеть размер печати выходных функций, которые создает эта модель. После ввода нулей в модель MobileNet вы увидите напечатанную фигуру [1, 1024] . Первый элемент имеет размер пакета 1, и вы можете видеть, что он фактически возвращает 1024 признака, которые затем можно использовать для классификации новых объектов.

10. Определите голову новой модели.

Теперь пришло время определить вашу модель головы, которая, по сути, представляет собой очень минимальный многослойный перцептрон.

скрипт.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 , и потеря будет либо binaryCrossentropy , если CLASS_NAMES.length равна 2 , либо будет использоваться categoricalCrossentropy , если нужно классифицировать 3 или более классов. Также запрашиваются показатели точности, чтобы их можно было позже отслеживать в журналах в целях отладки.

В консоли вы должны увидеть что-то вроде этого:

22eaf32286fea4bb.png

Обратите внимание, что здесь имеется более 130 тысяч обучаемых параметров. Но поскольку это простой плотный слой обычных нейронов, он будет обучаться довольно быстро.

В качестве действия, которое следует выполнить после завершения проекта, вы можете попробовать изменить количество нейронов в первом слое, чтобы увидеть, насколько низким вы можете его сделать, сохраняя при этом приличную производительность. Часто при машинном обучении приходится методом проб и ошибок найти оптимальные значения параметров, обеспечивающие наилучший компромисс между использованием ресурсов и скоростью.

11. Включите веб-камеру

Теперь пришло время конкретизировать функцию enableCam() которую вы определили ранее. Добавьте новую функцию с именем hasGetUserMedia() , как показано ниже, а затем замените содержимое ранее определенной функции enableCam() соответствующим кодом ниже.

скрипт.js

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

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

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

Сначала создайте функцию с именем hasGetUserMedia() , чтобы проверить, поддерживает ли браузер getUserMedia() , проверив наличие ключевых свойств API браузера.

В функции enableCam() используйте функцию hasGetUserMedia() которую вы только что определили выше, чтобы проверить, поддерживается ли она. Если это не так, выведите предупреждение на консоль.

Если он поддерживает это, определите некоторые ограничения для вашего вызова getUserMedia() , например, вам нужен только видеопоток и вы предпочитаете, чтобы width видео составляла 640 пикселей, а height480 пикселей. Почему? Что ж, нет особого смысла получать видео большего размера, поскольку его размер необходимо будет изменить до 224 на 224 пикселя, чтобы его можно было передать в модель MobileNet. Вы также можете сэкономить некоторые вычислительные ресурсы, запросив меньшее разрешение. Большинство камер поддерживают разрешение такого размера.

Затем вызовите navigator.mediaDevices.getUserMedia() с constraints подробно описанными выше, а затем дождитесь возврата stream . Как только stream будет возвращен, вы можете заставить свой элемент VIDEO воспроизводить stream , установив его в качестве значения srcObject .

Вам также следует добавить eventListener к элементу VIDEO , чтобы знать, когда stream загрузился и успешно воспроизводится.

Как только Steam загрузится, вы можете установить для videoPlaying значение true и удалить ENABLE_CAM_BUTTON чтобы предотвратить повторное нажатие на него, установив для его класса значение « removed ».

Теперь запустите свой код, нажмите кнопку включения камеры и разрешите доступ к веб-камере. Если вы делаете это впервые, вы должны увидеть себя в элементе видео на странице, как показано ниже:

b378eb1affa9b883.png

Хорошо, теперь пришло время добавить функцию для обработки нажатий кнопок dataCollector .

12. Обработчик событий кнопки сбора данных

Теперь пришло время заполнить пустую на данный момент функцию gatherDataForClass(). Это то, что вы назначили в качестве функции обработчика событий для кнопок dataCollector в начале кодовой лаборатории.

скрипт.js

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

Сначала проверьте атрибут data-1hot на нажатой в данный момент кнопке, вызвав this.getAttribute() с именем атрибута, в данном случае data-1hot в качестве параметра. Поскольку это строка, вы можете затем использовать parseInt() чтобы привести ее к целому числу и присвоить этот результат переменной с именем classNumber.

Затем соответствующим образом установите переменную gatherDataState . Если текущий gatherDataState равен STOP_DATA_GATHER (которому вы установили значение -1), это означает, что вы в настоящее время не собираете никаких данных и произошло событие mousedown . Установите для gatherDataState classNumber , который вы только что нашли.

В противном случае это означает, что в настоящее время вы собираете данные, а событие, которое выпущено, было событием mouseup , и теперь вы хотите прекратить собирать данные для этого класса. Просто установите его обратно в состояние STOP_DATA_GATHER , чтобы закончить цикл сбора данных, который вы определите в ближайшее время.

Наконец, начните вызов dataGatherLoop(), который фактически выполняет запись данных класса.

13. Сбор данных

Теперь определите функцию dataGatherLoop() . Эта функция отвечает за выборку изображений из видео веб -камеры, передача их через модель Mobilenet и захват выходов этой модели (1024 векторов функций).

Затем он хранит их вместе с идентификатором gatherDataState кнопки, которая в настоящее время нажимается, поэтому вы знаете, какой класс представляет эти данные.

Давайте пройдем через это:

script.js

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

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

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

Вы собираетесь продолжить выполнение этой функции только в том случае, если videoPlaying верно, то есть веб -камера активна, а gatherDataState не равна STOP_DATA_GATHER , и в настоящее время нажимается кнопка для сбора данных класса.

Затем оберните свой код в tf.tidy() чтобы утилизировать любые созданные тензоры в следующем коде. Результат этого выполнения кода tf.tidy() сохраняется в переменной, называемой imageFeatures .

Теперь вы можете взять кадр VIDEO веб -камеры с помощью tf.browser.fromPixels() . Полученный тензор, содержащий данные изображения, хранится в переменной, называемой videoFrameAsTensor .

Затем, измените размер переменной videoFrameAsTensor , чтобы иметь правильную форму для ввода модели Mobilenet. Используйте вызов tf.image.resizeBilinear() с тензором, который вы хотите изменить в качестве первого параметра, а затем форму, которая определяет новую высоту и ширину, как определено константы, которые вы уже создали ранее. Наконец, установите выравнивание углов в True, передавая третий параметр, чтобы избежать каких -либо проблем выравнивания при изменении размера. Результат этого изменения размера хранится в переменной, называемой resizedTensorFrame .

Обратите внимание, что это примитивное изменение размера растягивает изображение, так как размер вашего изображения веб -камеры составляет 640 на 480 пикселей, а модели нуждается в квадратном изображении 224 на 224 пикселей.

Для целей этой демонстрации это должно работать нормально. Однако, как только вы заполните этот коделаб, вы можете попытаться выключить квадрат с этого изображения вместо этого для еще лучших результатов для любой производственной системы, которую вы можете создать позже.

Затем нормализуйте данные изображения. Данные изображения всегда находятся в диапазоне от 0 до 255 при использовании tf.browser.frompixels() , поэтому вы можете просто разделить resizedtensorframe на 255, чтобы убедиться, что все значения находятся от 0 до 1, а это то, что модель Mobilenet ожидает в качестве входных данных.

Наконец, в разделе tf.tidy() кода протолкните этот нормализованный тензор через загруженную модель, вызывая mobilenet.predict() , к которой вы передаете расширенную версию normalizedTensorFrame используя expandDims() , так что она является партией. 1, поскольку модель ожидает партии входов для обработки.

После того, как результат вернется, вы можете немедленно вызвать squeeze() на этом возвращенном результате, чтобы раздавить его обратно к 1D -тензору, который вы затем возвращаете и присваиваете переменной imageFeatures , которая захватывает результат от tf.tidy() .

Теперь, когда у вас есть imageFeatures из модели Mobilenet, вы можете записать их, натолкнув их на массив trainingDataInputs , который вы определили ранее.

Вы также можете записать, что этот вход представляет, подтолкнув текущий gatherDataState в массив trainingDataOutputs .

Обратите внимание, что переменная gatherDataState была бы установлена ​​на численном идентификаторе текущего класса, в котором вы записываете данные, когда кнопка нажимала в ранее определенной функции gatherDataForClass() .

На этом этапе вы также можете увеличить количество примеров, которые у вас есть для данного класса. Чтобы сделать это, сначала проверьте, был ли индекс в массиве examplesCount был инициализирован до или нет. Если он не определен, установите его на 0 для инициализации счетчика для численного идентификатора данного класса, а затем вы сможете увеличить examplesCount для текущего gatherDataState .

Теперь обновите текст элемента STATUS на веб -странице, чтобы показать текущий счет для каждого класса, как они запечатлены. Для этого проберите массив CLASS_NAMES и распечатайте человеческое читаемое имя в сочетании с количеством данных в одном и том же индексе в examplesCount .

Наконец, вызов window.requestAnimationFrame() с dataGatherLoop пройденным в качестве параметра, чтобы снова вызвать эту функцию. Это будет продолжать выставлять кадры из видео до тех пор, пока mouseup кнопки не будут обнаружены, и gatherDataState установлен в STOP_DATA_GATHER, и в этот момент цикл сбора данных закончится.

Если вы запускаете свой код сейчас, вы сможете нажать кнопку «Включить камеру», ожидать загрузки веб -камеры, а затем нажимать и удерживать каждую из кнопок сбора данных, чтобы собрать примеры для каждого класса данных. Здесь вы видите, как я собираю данные для моего мобильного телефона и моей руки соответственно.

541051644A45131F.GIF

Вы должны увидеть обновленный текст состояния, поскольку он хранит все тензоры в памяти, как показано в захвате экрана выше.

14. Тренируйте и предсказывайте

Следующим шагом является внедрение кода для вашей в настоящее время функции пустого trainAndPredict() , где происходит обучение передачи. Давайте посмотрим на код:

script.js

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

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

Во -первых, убедитесь, что вы остановите любые текущие прогнозы, которые происходят, установив predict для false .

Затем перетасовывайте свои входные и выходные массивы с помощью tf.util.shuffleCombo() чтобы убедиться, что заказ не вызывает проблем при обучении.

Преобразуйте свой выходной массив, trainingDataOutputs, чтобы стать Tensor1D типа Int32, чтобы он был готов использовать в одном горячем кодировании . Это хранится в переменной с именем outputsAsTensor .

Используйте функцию tf.oneHot() с этой переменной outputsAsTensor , а также максимальное количество классов для кодирования, что является просто CLASS_NAMES.length . Ваши единственные горячие выходы теперь хранятся в новом тензоре под названием oneHotOutputs .

Обратите внимание, что в настоящее время trainingDataInputs - это множество записанных тензоров. Чтобы использовать их для обучения, вам нужно будет преобразовать множество тензоров, чтобы стать обычным 2D -тензором.

Для этого есть отличная функция в библиотеке Tensorflow.js под названием tf.stack() ,

который берет массив тензоров и складывает их вместе, чтобы создать тензор более высокого размера в качестве вывода. В этом случае возвращается тензор 2D, это партия из 1 размерных входов, каждый из которых имеет длину по 1024, содержащие записанные функции, которые вам нужны для обучения.

Далее, await model.fit() чтобы обучить индивидуальную модель. Здесь вы передаете свою переменную inputsAsTensor вместе с oneHotOutputs , чтобы представлять учебные данные для использования, например, входных и целевых выходов соответственно. В объекте Configuration для 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() , чтобы получить базовые данные в JavaScript, чтобы обнаружить позицию наивысшего ценного элемента. Это значение хранится в переменной, называемой highestIndex .

Вы также можете получить фактические оценки достоверности прогнозирования таким же образом, вызывая arraySync() на тензоре prediction напрямую.

Теперь у вас есть все, что вам нужно, чтобы обновить текст STATUS с помощью данных prediction . Чтобы получить человеческую читаемую строку для класса, вы можете просто найти highestIndex в массиве CLASS_NAMES , а затем получить значение доверия из predictionArray . Чтобы сделать его более читаемым в процентах, просто умножьте на 100 и math.floor() результат.

Наконец, вы можете использовать window.requestAnimationFrame() для вызова predictionLoop() снова и снова, как только готовая, чтобы получить классификацию в реальном времени в вашем видеопотоке. Это продолжается до тех пор, пока predict не будет установлен на false если вы решите обучить новую модель с новыми данными.

Что подводит вас к последнему кусочке головоломки. Реализация кнопки сброса.

15. Реализуйте кнопку сброса

Почти завершено! Последняя часть головоломки - реализовать кнопку сброса для начала. Код для вашей функции в настоящее время пустого reset() ниже. Идите вперед и обновите это следующим образом:

script.js

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

Во -первых, остановите любые управляемые циклы прогнозирования, установив predict для false . Затем удалите все содержимое в массиве examplesCount , установив его длину до 0, что является удобным способом очистить все содержимое из массива.

Теперь просмотрите все текущие записанные trainingDataInputs и убедитесь, что вы dispose() каждого тензора, содержащегося в нем, чтобы снова освободить память, поскольку тензоры не очищаются коллекционером мусора JavaScript.

Как только это будет сделано, теперь вы можете безопасно установить длину массива на 0 как на массивах trainingDataInputs , так и trainingDataOutputs , чтобы очистить их.

Наконец, установите текст STATUS на что -то разумное и распечатайте тензоры, оставленные в памяти в качестве проверки здравомыслия.

Обратите внимание, что в памяти все еще будет несколько сотен тензоров, поскольку модель Mobilenet и многослойный персептрон, который вы определили, не утилизируются. Вам нужно будет повторно использовать их с новыми учебными данными, если вы решите тренироваться снова после этого сброса.

16. Попробуем это

Пришло время проверить свою собственную версию обучаемой машины!

Отправляйтесь в Live Preview, включите веб -камеру, соберите не менее 30 образцов для класса 1 для некоторого объекта в вашей комнате, а затем сделайте то же самое для класса 2 для другого объекта, нажмите на поезд и проверьте журнал консоли, чтобы увидеть прогресс. Это должно тренироваться довольно быстро:

BF1AC3CC5B15740.GIF

После обучения покажите объекты в камеру, чтобы получить живые прогнозы, которые будут напечатаны в текстовую область состояния на веб -странице рядом с верхом. Если у вас возникли проблемы, проверьте мой завершенный рабочий код , чтобы увидеть, пропустили ли вы копирование над чем -либо.

17. Поздравления

Поздравляем! Вы только что закончили свой самый первый пример обучения переноса, используя Tensorflow.js Live в браузере.

Попробуйте, проверьте его на различных объектах, вы можете заметить, что некоторые вещи труднее распознать, чем другие, особенно если они похожи на что -то другое. Возможно, вам придется добавить больше классов или учебных данных, чтобы иметь возможность отделить их отдельно.

Резюме

В этом коделабе вы узнали:

  1. Что такое переносное обучение, и его преимущества по сравнению с обучением полной модели.
  2. Как получить модели для повторного использования из Tensorflow Hub.
  3. Как настроить веб -приложение, подходящее для перевода.
  4. Как загрузить и использовать базовую модель для создания функций изображения.
  5. Как обучить новую главу прогнозирования, которая может распознавать пользовательские объекты из изображений веб -камеры.
  6. Как использовать полученные модели для классификации данных в режиме реального времени.

Что дальше?

Теперь, когда у вас есть рабочая база для начала, с какими креативными идеями вы можете придумать, чтобы расширить эту модель машинного обучения Cowerplate для реального варианта использования, над которым вы можете работать? Может быть, вы могли бы революционизировать отрасль, в которой вы в настоящее время работаете, чтобы помочь людям в моделях вашей компании, чтобы классифицировать вещи, которые важны в их повседневной работе? Возможности безграничны.

Чтобы пойти дальше, рассмотрите возможность взять этот полный курс бесплатно , что показывает вам, как объединить 2 модели, которые у вас есть в этой коделабе в 1 единую модель для эффективности.

Также, если вам больше интересно вокруг теории, стоящей за оригинальным приложением обучаемой машины, ознакомьтесь с этим учебником .

Поделитесь тем, что делаете с нами

Вы можете легко расширить то, что вы сделали сегодня для других вариантов творческого использования, и мы рекомендуем вам мыслить нестандартно и продолжать взламывать.

Не забудьте отметить нас в социальных сетях, используя хэштег #madewithtfjs , чтобы получить шанс, чтобы ваш проект был показан в нашем блоге Tensorflow или даже будущих событиях . Мы хотели бы увидеть, что вы делаете.

Веб -сайты для проверки

,

1. Прежде чем начать

За последние несколько лет использование модели Tensorflow.js выросло в геометрической прогрессии, и многие разработчики JavaScript теперь стремятся принять существующие современные модели и переучить их для работы с пользовательскими данными, которые являются уникальными для их отрасли. Акт принятия существующей модели (часто называемой базовой моделью) и использования ее в аналогичной, но другой области, известен как обучение передачи.

Transfer Learning имеет много преимуществ по сравнению с началом совершенно пустой модели. Вы можете повторно использовать знания, уже изученные из предварительной обученной модели, и вам требуется меньше примеров нового элемента, который вы хотите классифицировать. Кроме того, обучение часто значительно быстрее из -за того, что только для переобучения последних нескольких слоев модельной архитектуры вместо всей сети. По этой причине, передача обучения очень хорошо подходит для среды веб -браузера, где ресурсы могут варьироваться в зависимости от устройства выполнения, но также имеет прямой доступ к датчикам для удобного сбора данных.

Этот CodeLab показывает вам, как создать веб -приложение с чистого холста, воссоздавая популярный веб -сайт Google « Учебная машина ». Веб -сайт позволяет создавать функциональное веб -приложение, которое любой пользователь может использовать для распознавания пользовательского объекта с несколькими примерами изображений из своей веб -камеры. Веб -сайт намеренно сохраняется минимальным, чтобы вы могли сосредоточиться на аспектах машинного обучения этого коделаба. Как и в случае с оригинальным веб -сайтом «Учебные машины», есть много возможностей для применения существующего опыта веб -разработчика для улучшения 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, для создания функций изображения, которые можно использовать при обучении передачи.
  • Как собирать данные из веб -камеры пользователя для нескольких классов данных, которые вы хотите распознать.
  • Как создать и определить многослойный персептрон, который принимает функции изображения и учится классифицировать новые объекты, используя их.

Давай взломаем ...

Что вам понадобится

  • Аккаунт Glitch.com предпочтительнее следовать, или вы можете использовать веб -среду обслуживания, которую вам удобно редактировать и запускать сами.

2. Что такое tensorflow.js?

54E81D02971F53E8.PNG

Tensorflow.js - это библиотека машинного обучения с открытым исходным кодом , которая может работать в любом месте JavaScript Can. Он основан на оригинальной библиотеке Tensorflow, написанной на Python , и направлена ​​на то, чтобы воссоздать этот опыт разработчика и набор API для экосистемы JavaScript.

Где его можно использовать?

Учитывая переносимость JavaScript, теперь вы можете писать на 1 языке и легко выполнить машинное обучение на всех следующих платформах:

  • Клиентская сторона в веб -браузере с использованием ванильного JavaScript
  • Серверная сторона и даже устройства IoT, такие как Raspberry Pi с помощью node.js
  • Настольные приложения с использованием электронов
  • Нативные мобильные приложения с использованием react Native

Tensorflow.js также поддерживает несколько бэкэндов в каждой из этих средств (фактические среды на основе аппаратного обеспечения, которые он может выполнять внутри, например, CPU или Webgl. «Бэкэнд» в этом контексте не означает среду на стороне сервера - бэкэнд для выполнения. Например, может быть клиентской стороной в Webgl), чтобы обеспечить совместимость, а также сохранить работу быстро. В настоящее время tensorflow.js поддерживает:

  • Выполнение WebGL на графической карте устройства (GPU) - это самый быстрый способ выполнить более крупные модели (размеры более 3 МБ) с ускорением графического процессора.
  • Выполнение веб -сборки (WASM) на процессоре - для повышения производительности процессора между устройствами, включая мобильные телефоны старшего поколения, например. Это лучше подходит для более мелких моделей (размером менее 3 МБ), которые фактически могут быстрее выполняться на процессоре с WASM, чем с WebGL из -за накладных расходов загрузки контента в графический процессор.
  • Выполнение процессора - запасная сторона, нельзя ли ни одна из других сред. Это самый медленный из трех, но всегда рядом с вами.

Примечание. Вы можете заставить одну из этих бэкэндов, если вы знаете, на каком устройстве вы будете выполнять, или вы можете просто позволить Tensorflow.js решать для вас, если вы не указаете это.

Клиентские сверхдержавы

Запуск tensorflow.js в веб -браузере на клиентской машине может привести к нескольким преимуществам, которые стоит рассмотреть.

Конфиденциальность

Вы можете обучать и классифицировать данные на клиентской машине, не отправляя данные на стороннем веб -сервере. Могут быть времена, когда это может быть требование, чтобы соблюдать локальные законы, такие как GDPR, или при обработке любых данных, которые пользователь может захотеть сохранить на своей машине, а не отправлено сторонней стороне.

Скорость

Поскольку вам не нужно отправлять данные на удаленный сервер, вывод (акт классификации данных) может быть быстрее. Более того, у вас есть прямой доступ к датчикам устройства, таким как камера, микрофон, GPS, акселерометр и многое другое, если пользователь предоставит вам доступ.

Достичь и масштаб

Одним щелчком мышлением, который любой в мире может щелкнуть ссылку, которую вы им отправляете, откройте веб -страницу в своем браузере и использовать то, что вы сделали. Нет необходимости в сложной настройке Linux на стороне сервера с драйверами CUDA и гораздо больше для использования системы машинного обучения.

Расходы

Никакие серверы означает единственное, что вам нужно заплатить, это CDN для размещения ваших файлов HTML, CSS, JS и модели. Стоимость CDN намного дешевле, чем сохранить сервер (потенциально с прикрепленной к видеокарте), работающего 24/7.

Функции на стороне сервера

Использование реализации Node.js tensorflow.js позволяет следующие функции.

Полная поддержка CUDA

На стороне сервера для ускорения видеокарты вы должны установить драйверы NVIDIA CUDA, чтобы позволить TensorFlow работать с видеокартой (в отличие от браузера, который использует WebGL - не требуется установка). Однако при полной поддержке CUDA вы можете полностью использовать способности более низкого уровня графической карты, что приводит к более быстрому обучению и времени вывода. Производительность находится в паритете с реализацией Python Tensorflow, поскольку они оба имеют один и тот же бэкэнд C ++.

Размер модели

Для передовых моделей из исследований вы можете работать с очень большими моделями, возможно, гигабайтами по размеру. Эти модели в настоящее время не могут работать в веб -браузере из -за ограничений использования памяти на вкладку браузера. Чтобы запустить эти более крупные модели, вы можете использовать Node.js на своем собственном сервере со спецификациями аппаратного обеспечения, необходимых для эффективного запуска такой модели.

Интернет вещей

Node.js поддерживается на популярных отдельных компьютерах, таких как Raspberry Pi , что, в свою очередь, означает, что вы также можете выполнить модели Tensorflow.js на таких устройствах.

Скорость

Node.js написан в JavaScript, что означает, что он выигрывает от срока времени. Это означает, что вы часто можете видеть повышение производительности при использовании node.js, поскольку он будет оптимизирован во время выполнения, особенно для любой предварительной обработки, которую вы можете делать. Отличный пример этого можно увидеть в этом тематическом исследовании , который показывает, как обнимаю Face Face Node.js, чтобы получить 2 -кратный импульс производительности для своей модели обработки естественного языка.

Теперь вы понимаете основы Tensorflow.js, где он может работать, и некоторые преимущества, давайте начнем делать с ним полезные вещи!

3. Передача обучения

Что именно такое переносное обучение?

Передача обучения включает в себя получение знаний, которые уже были научены, чтобы помочь выучить другую, но похожую вещь.

Мы, люди, делаем это все время. У вас есть опыт жизни, содержащийся в вашем мозгу, который вы можете использовать, чтобы помочь распознать новые вещи, которые вы никогда не видели раньше. Возьмите это дерево ивы, например:

E28070392CD4AFB9.png

В зависимости от того, где вы находитесь в мире, есть шанс, что вы, возможно, не видели этот тип дерева раньше.

Тем не менее, если я попрошу вас рассказать мне, есть ли какие -нибудь ивы на новом изображении ниже, вы, вероятно, сможете заметить их довольно быстро, даже если они находятся под другим углом и немного отличаются от оригинальной, которую я вам показал.

D9073A0D5DF27222.png

У вас уже есть куча нейронов в вашем мозге, которые знают, как идентифицировать, похожие на деревья объекты, и другие нейроны, которые хороши в поиске длинных прямых линий. Вы можете повторно использовать эти знания, чтобы быстро классифицировать дерево ивы, которое представляет собой дерево, который имеет много длинных прямых вертикальных ветвей.

Точно так же, если у вас есть модель машинного обучения, которая уже обучена на домене, например, распознавание изображений, вы можете повторно использовать ее для выполнения другой, но связанной задачи.

Вы можете сделать то же самое с продвинутой моделью, такой как Mobilenet, которая является очень популярной исследовательской моделью, которая может выполнять распознавание изображений на 1000 различных типов объектов. От собак до автомобилей он был обучен огромному набору данных, известному как ImageNet , в котором миллионы маркированных изображений.

В этой анимации вы можете увидеть огромное количество слоев, которые он имеет в этой модели Mobilenet V1:

7d4e1e35c1a89715.gif

Во время обучения эта модель узнала, как извлекать общие функции, которые имеют значение для всех этих 1000 объектов, и многие из функций более низкого уровня, которые она использует для идентификации таких объектов, могут быть полезны для обнаружения новых объектов, которых он никогда не видел раньше. В конце концов, в конечном итоге все является просто комбинацией строк, текстур и форм.

Давайте посмотрим на традиционную архитектуру нейронной сети (CNN) (аналогично Mobilenet) и посмотрим, как обучение передачи может использовать эту обученную сеть, чтобы узнать что -то новое. На изображении ниже показана типичная архитектура модели CNN, которая в этом случае было обучено распознавать рукописные цифры от 0 до 9:

BAF4E3D434576106.PNG

Если бы вы могли отделить предварительно обученные слои нижнего уровня существующей обученной модели, подобной этой, показанной слева, от классификационных слоев, ближе к концу модели, показанной справа (иногда называемый классификацией модели), Вы можете использовать более низкие уровни для создания выходных функций для любого данного изображения на основе исходных данных, на которых он был обучен. Вот та же самая сеть с удаленной головой классификации:

369A8A9041C6917D.PNG

Предполагая, что новая вещь, которую вы пытаетесь распознать, также может использовать такие выходные функции, которые изучила предыдущая модель, то есть большая вероятность, что они могут быть использованы повторно для новой цели.

На приведенной выше диаграмме эта гипотетическая модель была обучена цифрам, поэтому, возможно, то, что было изучено о цифрах, также может быть применено к такими буквами, как A, B и C.

Так что теперь вы можете добавить новую классификационную головку, которая пытается предсказать A, B или C вместо этого, как показано:

DB97E5E60AE73BBD.PNG

Здесь слои нижнего уровня заморожены и не обучены, только новая классификационная головка обновит себя, чтобы учиться на функциях, предоставленных из предварительно обученной модели нарезанной слева.

Акт об этом известен как обучение передачи, и это то, что обучающая машина делает за кулисами.

Вы также можете увидеть, что, только необходимо обучать многослойного персептрона в самом конце сети, он тренируется гораздо быстрее, чем если бы вам приходилось обучать всю сеть с нуля.

Но как вы можете получить в свои руки подразделения модели? Перейдите к следующему разделу, чтобы узнать.

4. Губ Tensorflow - базовые модели

Найдите подходящую базовую модель для использования

Для более продвинутых и популярных исследовательских моделей, таких как Mobilenet, вы можете перейти в Tensorflow Hub , а затем фильтровать для моделей, подходящих для Tensorflow.js, которые используют архитектуру Mobilenet V3, чтобы найти результаты, подобные показанным здесь:

C5DC1420C6238C14.PNG

Обратите внимание, что некоторые из этих результатов имеют тип «Классификация изображений» (подробно описано в левом верхнем результате каждой модельной карты), а другие имеют тип «вектор функций изображения».

Эти результаты вектора функций изображения по сути представляют собой предварительную версии Mobilenet, которые вы можете использовать для получения векторов функций изображения вместо окончательной классификации.

Подобные модели часто называют «базовыми моделями», которые вы можете затем использовать для выполнения передачи обучения таким же образом, как показано в предыдущем разделе, добавив новую классификационную головку и обучая его с вашими собственными данными.

Следующая вещь, которую нужно проверить, - это данная базовая модель, представляющая интересующей, какой формат tensorflow.js Модель выпускается как. Если вы откроете страницу для одной из этих моделей функций Vector Mobilenet V3, вы можете увидеть из документации JS, что она находится в форме графической модели на основе примера фрагмента кода в документации, в которой используется tf.loadGraphModel() .

F97D903D2E46924B.PNG

Следует также отметить, что если вы найдете модель в формате слоев вместо формата графика, вы можете выбрать, какие слои заморозить, а какие - разведывать для тренировки. Это может быть очень мощным при создании модели для новой задачи, которую часто называют «моделью передачи». На данный момент, однако, вы используете тип модели графика по умолчанию для этого урока, который большинство моделей TF Hub используются AS. Чтобы узнать больше о работе с моделями слоев, ознакомьтесь с курсом Zero To Hero Tensorflow.js .

Преимущества обучения передачи

Каковы преимущества использования трансферного обучения вместо обучения всей архитектуры модели с нуля?

Во -первых, время обучения является ключевым преимуществом для использования подхода к обучению трансферу, так как у вас уже есть обученная базовая модель, на которой можно основываться.

Во -вторых, вы можете сойти с рук, показывая гораздо меньше примеров новой вещи, которую вы пытаетесь классифицировать из -за обучения, которое уже состоялось.

Это действительно здорово, если у вас есть ограниченное время и ресурсы для сбора примеров данных о том, что вы хотите классифицировать, и вам нужно быстро сделать прототип, прежде чем собирать больше данных обучения, чтобы сделать его более надежным.

Учитывая необходимость в меньшем количестве данных и скорости обучения меньшей сети, передача обучения менее интенсивным. Это делает его очень подходящим для среды браузера, занимая всего десятки секунд на современной машине, а не часами, дням или неделям для полной модели.

Хорошо! Теперь вы знаете суть того, что такое Transfer Learning, пришло время создать свою собственную версию обучаемой машины. Давайте начнем!

5. Получите настройку в код

Что вам понадобится

  • Современный веб -браузер.
  • Основные знания HTML, CSS, JavaScript и Chrome Devtools (просмотр вывода консоли).

Получим кодирование

Шаблоны шаблонов для начала были созданы для Glitch.com или Codepen.io . Вы можете просто клонировать любой шаблон в качестве базового состояния для этой лаборатории кода, всего за один щелчок.

На Glitch нажмите кнопку « Remix This», чтобы разбросить ее и сделать новый набор файлов, которые вы можете редактировать.

В качестве альтернативы, на Codepen, нажмите « Вилка» в правом нижнем нижнем углу экрана.

Этот очень простой скелет предоставляет вам следующие файлы:

  • HTML -страница (index.html)
  • Stylesheet (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>

Альтернатива: используйте предпочитаемый Webeditor или работайте на локальном уровне

Если вы хотите загрузить код и работать локально или в другом онлайн -редакторе, просто создайте 3 файла, указанные выше в одном и том же каталоге, скопируйте и вставьте код из нашего сбоя в каждую из них.

6. Приложение HTML Cowerplate

С чего мне начать?

Все прототипы требуют некоторых базовых каркасов HTML, на которых вы можете сделать свои выводы. Установите это сейчас. Вы собираетесь добавить:

  • Заголовок для страницы.
  • Некоторый описательный текст.
  • Статус абзац.
  • Видео для хранения подачи веб -камеры однажды готово.
  • Несколько кнопок для запуска камеры, сбора данных или сброса опыта.
  • Импорт для файлов tensorflow.js и js вы будете кодировать позже.

Откройте index.html и вставьте существующий код со следующим образом для настройки вышеуказанных функций:

index.html

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

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

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

Разбейте это

Давайте разберем некоторые из приведенных выше HTML -кода, чтобы выделить некоторые ключевые вещи, которые вы добавили.

  • Вы добавили тег <h1> для заголовка страницы вместе с тегом <p> с идентификатором статуса », где вы будете печатать информацию, поскольку вы используете различные части системы для просмотра выходов.
  • Вы добавили элемент <video> с идентификатором «Веб -камеры», в который вы будете отображать поток веб -камеры позже.
  • Вы добавили 5 <button> . Первый, с идентификатором «EnableCam», позволяет камеру. В следующих двух кнопках есть класс Datacollector, который позволяет собирать примеры изображений для объектов, которые вы хотите распознать. Код, который вы пишете позже, будет спроектирован таким образом, чтобы вы могли добавить любое количество этих кнопок, и они будут работать автоматически.

Обратите внимание, что эти кнопки также имеют специальный пользовательский атрибут, называемый Data-1hot, с целочисленным значением, начинающимся из 0 для первого класса. Это числовой индекс, который вы используете для представления данных определенного класса. The index will be used to encode the output classes correctly with a numerical representation instead of a string, as ML models can only work with numbers.

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.

стиль.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:

81909685d7566dcb.png

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 , and TRAIN_BUTTON grab DOM references to all the key buttons from the HTML page.
  • MOBILE_NET_INPUT_WIDTH and MOBILE_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.

a28b734e190afff.png

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:

22eaf32286fea4bb.png

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:

b378eb1affa9b883.png

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.

541051644a45131f.gif

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. Давайте посмотрим на код:

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.

You're almost there! 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:

bf1ac3cc5b15740.gif

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. Поздравления

Поздравляем! 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:

  1. What transfer learning is, and its advantages over training a full model.
  2. How to get models for re-use from TensorFlow Hub.
  3. How to set up a web app suitable for transfer learning.
  4. How to load and use a base model to generate image features.
  5. How to train a new prediction head that can recognize custom objects from webcam imagery.
  6. 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