TensorFlow.js: Eigene Teachable Machine erstellen Lerntransfers mit TensorFlow.js,

1. Hinweis

Die Nutzung von TensorFlow.js-Modellen hat in den letzten Jahren exponentiell zugenommen. Viele JavaScript-Entwickler versuchen, vorhandene hochmoderne Modelle umzutrainieren, damit sie mit benutzerdefinierten Daten arbeiten können, die nur für ihre Branche gelten. Der Vorgang, ein bestehendes Modell (oft als Basismodell bezeichnet) in einem ähnlichen, aber anderen Bereich zu verwenden, wird als Lerntransfer bezeichnet.

Lerntransfers bieten viele Vorteile gegenüber einem völlig leeren Modell. Sie können Kenntnisse wiederverwenden, die Sie aus einem zuvor trainierten Modell gelernt haben, und benötigen weniger Beispiele für das neue Element, das Sie klassifizieren möchten. Außerdem ist das Training oft deutlich schneller, da nur die letzten paar Ebenen der Modellarchitektur neu trainiert werden müssen und nicht das gesamte Netzwerk. Aus diesem Grund eignet sich Lerntransfer sehr gut für Webbrowser, in denen die Ressourcen je nach Ausführungsgerät variieren können, aber auch direkten Zugriff auf die Sensoren für eine einfache Datenerfassung haben.

In diesem Codelab erfahren Sie, wie Sie eine Webanwendung aus einem leeren Canvas erstellen und dabei die beliebten „ Teachable Machine Website. Auf der Website können Sie eine funktionsfähige Webanwendung erstellen, mit der jeder Benutzer ein benutzerdefiniertes Objekt anhand von ein paar Beispielbildern von seiner Webcam erkennen kann. Die Website wurde bewusst minimal gehalten, damit Sie sich in diesem Codelab auf die Aspekte des maschinellen Lernens konzentrieren können. Wie bei der ursprünglichen Teachable Machine-Website gibt es jedoch viele Möglichkeiten, mit Ihrer vorhandenen Webentwicklererfahrung die UX zu verbessern.

Voraussetzungen

Dieses Codelab ist für Webentwickler geschrieben, die mit vorgefertigten TensorFlow.js-Modellen und grundlegender API-Nutzung vertraut sind und mit Lerntransfers in TensorFlow.js beginnen möchten.

  • Für dieses Lab wird vorausgesetzt, dass Sie mit TensorFlow.js, HTML5, CSS und JavaScript vertraut sind.

Wenn Sie mit Tensorflow.js noch nicht vertraut sind, können Sie zuerst diesen kostenlosen Zero-to-Hero-Kurs absolvieren. Dabei werden keine Vorkenntnisse zu maschinellem Lernen oder TensorFlow.js vorausgesetzt und Sie erfahren in kleineren Schritten alles, was Sie wissen müssen.

Aufgaben in diesem Lab

  • Was TensorFlow.js ist und warum Sie es in Ihrer nächsten Webanwendung verwenden sollten
  • Hier erfahren Sie, wie Sie eine vereinfachte HTML/CSS /JS-Webseite erstellen, die die Teachable Machine-Nutzererfahrung nachbildet.
  • Hier erfahren Sie, wie Sie mit TensorFlow.js ein vortrainiertes Basismodell laden, insbesondere MobileNet, um Bildfunktionen für den Lerntransfer zu generieren.
  • So sammeln Sie Daten von der Webcam eines Nutzers für mehrere Datenklassen, die Sie erkennen möchten.
  • Hier erfahren Sie, wie Sie ein mehrschichtiges Perceptron erstellen und definieren, das anhand der Bildmerkmale neue Objekte anhand dieser klassifiziert.

Lass uns hacken...

Voraussetzungen

  • Am besten nutzen Sie ein Glitch.com-Konto oder eine Web-Serving-Umgebung, in der Sie sich mit der Bearbeitung und Ausführung selbst auskennen.

2. Was ist TensorFlow.js?

54e81d02971f53e8.png

TensorFlow.js ist eine Open-Source-Bibliothek für maschinelles Lernen, die überall JavaScript ausgeführt werden kann. Es basiert auf der ursprünglichen TensorFlow-Bibliothek, die in Python geschrieben wurde, und zielt darauf ab, diese Entwicklerumgebung und eine Reihe von APIs für die JavaScript-Umgebung nachzubilden.

Wo kann sie verwendet werden?

Aufgrund der Übertragbarkeit von JavaScript können Sie jetzt problemlos in nur einer Sprache schreiben und maschinelles Lernen ganz einfach auf allen folgenden Plattformen ausführen:

  • Clientseite im Webbrowser mit einfachem JavaScript
  • Serverseitige und sogar IoT-Geräte wie Raspberry Pi mit Node.js
  • Desktop-Apps mit Electron
  • Native mobile Apps, in denen React Native verwendet wird

TensorFlow.js unterstützt außerdem mehrere Back-Ends in jeder dieser Umgebungen (die tatsächlichen hardwarebasierten Umgebungen, in denen es ausgeführt werden kann, z. B. die CPU oder WebGL). Ein „Back-End“ bedeutet in diesem Zusammenhang keine serverseitige Umgebung. Das Back-End für die Ausführung könnte beispielsweise clientseitig in WebGL ausgeführt werden, um die Kompatibilität sicherzustellen und gleichzeitig eine schnelle Ausführung zu gewährleisten. Derzeit unterstützt TensorFlow.js:

  • WebGL-Ausführung auf der Grafikkarte (GPU) des Geräts: Dies ist die schnellste Möglichkeit, größere Modelle (über 3 MB) mit GPU-Beschleunigung auszuführen.
  • Web Assembly-Ausführung (WASM) auf der CPU – zur Verbesserung der CPU-Leistung auf verschiedenen Geräten, z. B. auf Smartphones älterer Generationen. Diese Methode eignet sich besser für kleinere Modelle (kleiner als 3 MB), die auf der CPU mit WASM schneller ausgeführt werden können als mit WebGL, da Inhalte auf einen Grafikprozessor hochgeladen werden müssen.
  • CPU-Ausführung: Das Fallback sollte keine der anderen Umgebungen verfügbar sein. Das ist zwar die langsamste Methode, ist aber immer für dich da.

Hinweis:Sie können eines dieser Back-Ends erzwingen, wenn Sie wissen, auf welchem Gerät die Ausführung erfolgen soll, oder einfach TensorFlow.js die Entscheidung überlassen, wenn Sie dies nicht angeben.

Clientseitige Funktionen

Die Ausführung von TensorFlow.js im Webbrowser auf dem Clientcomputer kann mehrere Vorteile bieten, die in Betracht gezogen werden sollten.

Datenschutz

Sie können Daten auf dem Clientcomputer trainieren und klassifizieren, ohne Daten an einen Drittanbieter-Webserver senden zu müssen. In bestimmten Fällen kann dies erforderlich sein, um lokale Gesetze wie die DSGVO einzuhalten oder wenn Daten verarbeitet werden, die der Nutzer auf seinem Computer behalten und nicht an Dritte senden möchte.

Geschwindigkeit

Da keine Daten an einen Remote-Server gesendet werden müssen, kann die Inferenz (die Klassifizierung der Daten) schneller erfolgen. Noch besser: Sie haben direkten Zugriff auf die Sensoren des Geräts, wie Kamera, Mikrofon, GPS, Beschleunigungsmesser und mehr, wenn der Nutzer Ihnen Zugriff gewährt.

Reichweite und Skalierung

Mit nur einem Klick kann jeder auf der Welt auf einen Link klicken, den Sie ihm senden, die Webseite in einem Browser öffnen und Ihre Arbeit nutzen. Eine komplexe serverseitige Linux-Einrichtung mit CUDA-Treibern entfällt und vieles mehr, nur um das ML-System zu verwenden.

Kosten

Da es keine Server gibt, müssen Sie nur für ein CDN bezahlen, das Ihre HTML-, CSS-, JS- und Modelldateien hostet. Die Kosten für ein CDN sind wesentlich günstiger als den Betrieb eines Servers (ggf. mit angeschlossener Grafikkarte) rund um die Uhr.

Serverseitige Funktionen

Durch die Nutzung der Node.js-Implementierung von TensorFlow.js werden die folgenden Funktionen aktiviert.

Vollständiger CUDA-Support

Auf der Serverseite müssen Sie für die Grafikkartenbeschleunigung die NVIDIA CUDA-Treiber installieren, damit TensorFlow mit der Grafikkarte funktioniert. Im Gegensatz zum Browser, der WebGL verwendet, ist keine Installation erforderlich. Mit vollständiger CUDA-Unterstützung können Sie jedoch die niedrigeren Fähigkeiten der Grafikkarte voll nutzen, was zu kürzeren Trainings- und Inferenzzeiten führt. Die Leistung entspricht der Python-Implementierung von TensorFlow, da beide dasselbe C++-Backend nutzen.

Modellgröße

Bei hochmodernen Modellen aus der Forschung arbeiten Sie möglicherweise mit sehr großen Modellen, die vielleicht Gigabyte groß sind. Diese Modelle können derzeit aufgrund der Einschränkungen der Arbeitsspeichernutzung pro Browsertab nicht im Webbrowser ausgeführt werden. Wenn Sie diese größeren Modelle ausführen möchten, können Sie Node.js auf Ihrem eigenen Server mit den Hardwarespezifikationen verwenden, die für eine effiziente Ausführung eines solchen Modells erforderlich sind.

IOT

Node.js wird auf gängigen Einplatinencomputern wie Raspberry Pi unterstützt, was wiederum bedeutet, dass Sie TensorFlow.js-Modelle auch auf diesen Geräten ausführen können.

Geschwindigkeit

Node.js ist in JavaScript geschrieben und profitiert daher von einer Just-in-Time-Kompilierung. Das bedeutet, dass Sie bei der Verwendung von Node.js häufig eine Leistungssteigerung erzielen, da das Programm während der Laufzeit optimiert wird, insbesondere für die Vorverarbeitung. Ein gutes Beispiel hierfür ist diese Fallstudie, die zeigt, wie Hugging Face mit Node.js die Leistung seines Natural Language Processing-Modells verdoppeln konnte.

Sie kennen jetzt die Grundlagen von TensorFlow.js, wissen, wo es ausgeführt werden kann und welche Vorteile es bietet. Lassen Sie uns nun anfangen, nützliche Dinge damit zu beginnen.

3. Lerntransfer

Was genau ist ein Lerntransfer?

Beim Lerntransfer werden bereits erlernte Kenntnisse verwendet, um eine andere, aber ähnliche Sache zu erlernen.

Wir Menschen tun das ständig. In deinem Gehirn steckt voller Erfahrungen, die du nutzen kannst, um neue Dinge zu erkennen, die du noch nie gesehen hast. Ein Beispiel für diesen Weidenbaum:

e28070392cd4afb9.png

Je nachdem, wo Sie sich auf der Welt befinden, kann es sein, dass Sie diese Art von Baum noch nicht gesehen haben.

Wenn ich dich aber bitte, mir zu sagen, ob auf dem neuen Bild unten Weiden zu sehen sind, kannst du sie wahrscheinlich ziemlich schnell erkennen, obwohl sie aus einem anderen Winkel sind und sich leicht von dem ursprünglichen Bild unterscheiden, das ich dir gezeigt habe.

d9073a0d5df27222.png

Sie haben bereits eine Reihe von Neuronen in Ihrem Gehirn, die baumähnliche Objekte erkennen, und andere Neuronen, die gut darin sind, lange gerade Linien zu finden. Dieses Wissen können Sie wiederverwenden, um Weiden schnell zu klassifizieren. Ein baumähnliches Objekt mit vielen langen geraden vertikalen Zweigen.

Wenn Sie ein Modell für maschinelles Lernen haben, das bereits auf einer Domäne trainiert wurde (z. B. das Erkennen von Bildern), können Sie es wiederverwenden, um eine andere, aber verwandte Aufgabe auszuführen.

Dasselbe erreichen Sie mit einem erweiterten Modell wie MobileNet, einem sehr beliebten Forschungsmodell, das die Bilderkennung für 1.000 verschiedene Objekttypen durchführen kann. Von Hunden bis hin zu Autos wurde es mit einem riesigen Dataset namens ImageNet trainiert, das Millionen von beschrifteten Bildern enthält.

In dieser Animation sehen Sie die große Anzahl von Ebenen in diesem MobileNet V1-Modell:

7d4e1e35c1a89715.gif

Während des Trainings hat das Modell gelernt, allgemeine Merkmale zu extrahieren, die für alle diese 1.000 Objekte wichtig sind. Viele der untergeordneten Merkmale, die zur Identifizierung solcher Objekte verwendet werden, können auch nützlich sein, um neue Objekte zu erkennen, die es zuvor noch nicht gesehen hat. Schließlich ist alles letztendlich nur eine Kombination aus Linien, Texturen und Formen.

Werfen wir einen Blick auf eine traditionelle Convolutional Neural Network (CNN)-Architektur (ähnlich MobileNet) und sehen wir uns an, wie Lerntransfers dieses trainierte Netzwerk nutzen können, um etwas Neues zu lernen. Die folgende Abbildung zeigt die typische Modellarchitektur eines CNN, das in diesem Fall dafür trainiert wurde, handschriftliche Ziffern von 0 bis 9 zu erkennen:

baf4e3d434576106.png

Wenn Sie die vortrainierten Ebenen eines vorhandenen trainierten Modells wie links gezeigt von den Klassifizierungsebenen am Ende des Modells auf der rechten Seite trennen könnten (auch als Klassifizierungskopf des Modells bezeichnet), könnten Sie die Ebenen niedrigerer Ebene verwenden, um Ausgabemerkmale für jedes Bild basierend auf den ursprünglichen Daten zu erstellen, mit denen es trainiert wurde. Im Folgenden sehen Sie dasselbe Netzwerk ohne Klassifizierungskopf:

369a8a9041c6917d.png

Wenn wir davon ausgehen, dass das neue Ding, das Sie erkennen möchten, auch die Ausgabemerkmale des vorherigen Modells nutzen kann, stehen die Chancen gut, dass sie für einen neuen Zweck wiederverwendet werden können.

Im obigen Diagramm wurde dieses hypothetische Modell mit Ziffern trainiert. Daher kann das, was über Ziffern gelernt wurde, auch auf Buchstaben wie a, b und c angewendet werden.

Sie könnten jetzt also einen neuen Klassifizierungskopf hinzufügen, der stattdessen versucht, a, b oder c vorherzusagen, wie hier gezeigt:

db97e5e60ae73bbd.png

Hier sind die Ebenen auf der unteren Ebene eingefroren und werden nicht trainiert. Nur der neue Klassifizierungskopf aktualisiert sich selbst, um von den Funktionen des vortrainierten, abgeschnittenen Modells auf der linken Seite zu lernen.

Dieser Vorgang wird als Lerntransfer bezeichnet und ist das, was Teachable Machine hinter den Kulissen macht.

Sie können auch sehen, dass es viel schneller trainiert wird, wenn das mehrschichtige Perceptron nur am Ende des Netzwerks trainiert wird, als wenn Sie das gesamte Netzwerk von Grund auf neu trainieren müssten.

Aber wie bekommen Sie die Teilteile eines Modells in die Hand? Im nächsten Abschnitt erfahren Sie es.

4. TensorFlow Hub – Basismodelle

Geeignetes Basismodell finden

Erweiterte und beliebtere Forschungsmodelle wie MobileNet finden Sie im TensorFlow Hub. Hier können Sie nach Modellen filtern, die für TensorFlow.js geeignet sind und die MobileNet v3-Architektur verwenden, um Ergebnisse wie die hier gezeigten zu erhalten:

c5dc1420c6238c14.png

Einige dieser Ergebnisse sind vom Typ "image classification" (Bildklassifizierung). (werden oben links in jedem Modellkartenergebnis aufgeführt) und andere sind vom Typ „image feature vector“.

Diese Bildmerkmalsvektorergebnisse sind im Wesentlichen die vorgeschnittenen Versionen von MobileNet, die Sie anstelle der endgültigen Klassifizierung verwenden können, um die Bildmerkmalsvektoren zu erhalten.

Solche Modelle werden oft als „Basismodelle“ bezeichnet. den Sie dann wie im vorherigen Abschnitt gezeigt für einen Lerntransfer nutzen können. Fügen Sie dazu einen neuen Klassifizierungskopf hinzu und trainieren Sie ihn mit Ihren eigenen Daten.

Als Nächstes müssen Sie prüfen, ob ein bestimmtes Basismodell von Interesse für das TensorFlow.js-Format ist, in dem das Modell veröffentlicht wird. Wenn Sie die Seite für eines dieser MobileNet v3-Featurevektor-Modelle öffnen, sehen Sie in der JS-Dokumentation, dass es in Form eines Graphenmodells vorliegt, das auf dem Beispielcode-Snippet in der Dokumentation basiert, in dem tf.loadGraphModel() verwendet wird.

f97d903d2e46924b.png

Wenn Sie ein Modell im Ebenenformat statt im Grafikformat finden, können Sie auswählen, welche Ebenen für das Training fixiert werden sollen. Dies kann sehr nützlich sein, wenn ein Modell für eine neue Aufgabe erstellt wird. Dies wird oft als „Übertragungsmodell“ bezeichnet. Vorerst verwenden Sie jedoch den standardmäßigen Grafikmodelltyp für diese Anleitung, als den die meisten TF Hub-Modelle bereitgestellt werden. Weitere Informationen zur Arbeit mit Layers-Modellen finden Sie im Zero-to-Hero-TensorFlow.js-Kurs.

Vorteile von Lerntransfers

Was sind die Vorteile von Lerntransfers, anstatt die gesamte Modellarchitektur von Grund auf neu zu trainieren?

Zunächst einmal ist die Trainingszeit ein entscheidender Vorteil eines Lerntransferansatzes, da Sie bereits ein trainiertes Basismodell haben, auf dem Sie aufbauen können.

Zweitens können Sie aufgrund des bereits durchgeführten Trainings deutlich weniger Beispiele für das neue Element zeigen, das Sie klassifizieren möchten.

Dies ist sehr hilfreich, wenn Sie wenig Zeit und Ressourcen haben, um Beispieldaten des zu klassifizierenden Produkts zu sammeln, und einen Prototyp schnell erstellen müssen, bevor Sie weitere Trainingsdaten sammeln müssen, um ihn robuster zu machen.

Da weniger Daten benötigt werden und ein kleineres Netzwerk schnell trainiert werden kann, ist der Lerntransfer weniger ressourcenintensiv. Das macht es sehr gut für Browser-Umgebungen geeignet, da das vollständige Modelltraining auf einem modernen Computer nur wenige Sekunden dauert, anstatt Stunden, Tage oder Wochen.

Das wars! Jetzt wissen Sie, was Lerntransfers ausmacht. Jetzt ist es an der Zeit, Ihre eigene Version von Teachable Machine zu entwickeln. Los gehts!

5. Code einrichten

Voraussetzungen

  • Ein moderner Webbrowser.
  • Grundkenntnisse in HTML, CSS, JavaScript und Chrome DevTools (Betrachten der Konsolenausgabe).

Erste Schritte mit dem Programmieren

Standardvorlagen wurden für Glitch.com oder Codepen.io erstellt. Sie können beide Vorlagen mit nur einem Klick als Basisstatus für dieses Code-Lab klonen.

Klicke in Glitch auf die Schaltfläche „remix this“, um ihn zu verzweigen und einen neuen Satz Dateien zu erstellen, die du bearbeiten kannst.

Alternativ können Sie auf Codepen auf fork" rechts unten auf dem Bildschirm ab.

Dieses sehr einfache Gerüst stellt folgende Dateien bereit:

  • HTML-Seite (index.html)
  • Stylesheet (style.css)
  • Datei zum Schreiben des JavaScript-Codes (script.js)

Der Einfachheit halber gibt es in der HTML-Datei einen zusätzlichen Import für die TensorFlow.js-Bibliothek. Sie sieht so aus:

index.html

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

Alternative: Ihren bevorzugten Webeditor verwenden oder lokal bearbeiten

Wenn du den Code herunterladen und lokal oder in einem anderen Online-Editor arbeiten möchtest, erstelle einfach die drei oben genannten Dateien im selben Verzeichnis, kopiere den Code aus unserem Glitch-Boilerplate und füge ihn dann in die einzelnen Dateien ein.

6. App-HTML-Boilerplate

Wo fange ich an?

Alle Prototypen erfordern ein grundlegendes HTML-Gerüst, auf dem Sie Ihre Ergebnisse rendern können. Richte das jetzt ein. Sie werden Folgendes hinzufügen:

  • Ein Titel für die Seite.
  • Beschreibender Text
  • Ein Statusabsatz.
  • Ein Video, in dem der Webcam-Feed gespeichert wird, sobald er fertig ist.
  • Mehrere Tasten zum Starten der Kamera, zum Erfassen von Daten oder zum Zurücksetzen des Geräts
  • Importiert für TensorFlow.js- und JS-Dateien, die Sie später programmieren.

Öffnen Sie index.html und fügen Sie den vorhandenen Code folgendermaßen ein, um die oben genannten Funktionen einzurichten:

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>

Hier gehts los

Lassen Sie uns einige der obigen HTML-Codes aufschlüsseln, um einige der wichtigsten Elemente hervorzuheben, die Sie hinzugefügt haben.

  • Du hast ein <h1>-Tag für den Seitentitel sowie das <p>-Tag mit der ID „status“ hinzugefügt. Hier geben Sie Informationen aus, da Sie verschiedene Teile des Systems zum Anzeigen der Ausgaben verwenden.
  • Du hast ein <video>-Element mit der ID „Webcam“ hinzugefügt in dem du deinen Webcam-Stream später renderst.
  • Du hast 5 <button>-Elemente hinzugefügt. Die erste, mit der ID „enableCam“, aktiviert die Kamera. Die nächsten beiden Schaltflächen haben die Klasse "dataCollector", wo Sie Beispielbilder für die Objekte zusammenstellen können, die Sie erkennen möchten. Der Code, den Sie später schreiben, ist so konzipiert, dass Sie eine beliebige Anzahl dieser Schaltflächen hinzufügen können, und sie funktionieren automatisch wie beabsichtigt.

Beachten Sie, dass diese Schaltflächen auch ein spezielles benutzerdefiniertes Attribut mit dem Namen data-1hot haben, dessen Ganzzahlwert für die erste Klasse bei 0 beginnt. Dies ist der numerische Index, mit dem Sie die Daten einer bestimmten Klasse darstellen. Der Index wird verwendet, um die Ausgabeklassen korrekt mit einer numerischen Darstellung anstelle eines Strings zu codieren, da ML-Modelle nur mit Zahlen arbeiten können.

Es gibt auch ein data-name-Attribut, das den visuell lesbaren Namen enthält, den Sie für diese Klasse verwenden möchten. So können Sie dem Nutzer einen aussagekräftigeren Namen anstelle eines numerischen Indexwerts aus der Hot-Codierung 1 angeben.

Schließlich gibt es eine Schaltfläche zum Trainieren und Zurücksetzen, um den Trainingsprozess zu starten, sobald Daten erfasst wurden, bzw. um die App zurückzusetzen.

  • Sie haben außerdem 2 Importe vom Typ „<script>“ hinzugefügt. eine für TensorFlow.js und die andere für script.js, die Sie gleich definieren.

7. Stil hinzufügen

Standardeinstellungen für Elemente

Fügen Sie Stile für die HTML-Elemente hinzu, die Sie gerade hinzugefügt haben, um sicherzustellen, dass sie korrekt gerendert werden. Hier sind einige Stile, die zur korrekten Positionierung und Größenanpassung von Elementen hinzugefügt werden. Nichts Besonderes. Wie Sie im Teachable Machine-Video gesehen haben, können Sie dies später auf jeden Fall erweitern, um die User Experience noch weiter zu verbessern.

style.css

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

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


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

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

.removed {
  display: none;
}

#status {
  font-size:150%;
}

Sehr gut! Das ist alles, was du brauchst. Wenn Sie jetzt eine Vorschau der Ausgabe anzeigen, sollte sie in etwa so aussehen:

81909685d7566dcb.png

8. JavaScript: Schlüsselkonstanten und Listener

Schlüsselkonstanten definieren

Fügen Sie zunächst einige wichtige Konstanten hinzu, die Sie in der gesamten App verwenden werden. Ersetzen Sie zuerst den Inhalt von script.js durch diese Konstanten:

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 = [];

Sehen wir uns an, wofür sie gedacht sind:

  • STATUS enthält lediglich einen Verweis auf das Absatz-Tag, in das Statusaktualisierungen geschrieben werden.
  • VIDEO enthält eine Referenz auf das HTML-Videoelement, das den Webcam-Feed rendert.
  • ENABLE_CAM_BUTTON, RESET_BUTTON und TRAIN_BUTTON erfassen DOM-Verweise auf alle wichtigen Schaltflächen der HTML-Seite.
  • MOBILE_NET_INPUT_WIDTH und MOBILE_NET_INPUT_HEIGHT definieren die erwartete Eingabebreite bzw. -höhe des MobileNet-Modells. Wenn Sie dies in einer Konstante am Anfang der Datei speichern, wenn Sie sich später für eine andere Version entscheiden, ist es einfacher, die Werte einmal zu aktualisieren, anstatt sie an vielen verschiedenen Stellen ersetzen zu müssen.
  • STOP_DATA_GATHER ist auf -1 eingestellt. Dadurch wird ein Statuswert gespeichert, damit Sie wissen, wenn der Nutzer nicht mehr auf eine Schaltfläche klickt, um Daten aus dem Webcam-Feed zu erfassen. Durch die Eingabe eines aussagekräftigen Namens wird der Code später besser lesbar.
  • CLASS_NAMES dient als Lookup und enthält die visuell lesbaren Namen für die möglichen Klassenvorhersagen. Dieses Array wird später gefüllt.

Jetzt, da Sie über Verweise auf Schlüsselelemente verfügen, ist es an der Zeit, ihnen einige Ereignis-Listener zuzuordnen.

Listener für Schlüsselereignisse hinzufügen

Fügen Sie den wichtigsten Schaltflächen zuerst Klick-Event-Handler hinzu, wie hier gezeigt:

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: ruft beim Anklicken die Funktion „enableCam“ auf.

TRAIN_BUTTON: ruft „trainAndPredict“ auf, wenn darauf geklickt wird.

RESET_BUTTON: Anrufe werden zurückgesetzt, wenn sie angeklickt werden.

Schließlich finden Sie in diesem Abschnitt alle Schaltflächen mit der Klasse „dataCollector“. mit document.querySelectorAll(). Dies gibt ein Array von Elementen zurück, die im Dokument gefunden wurden und übereinstimmen:

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!
}

Erläuterung des Codes:

Anschließend durchlaufen Sie die gefundenen Schaltflächen und weisen jeder zwei Ereignis-Listener zu. eine für mouseup und eine für mouseup. Auf diese Weise können Sie die Samples so lange aufzeichnen, wie Sie auf die Schaltfläche klicken. Dies ist nützlich für die Datenerfassung.

Beide Ereignisse rufen eine gatherDataForClass-Funktion auf, die Sie später definieren.

An dieser Stelle können Sie auch die gefundenen visuell lesbaren Klassennamen aus dem HTML-Schaltflächenattribut „data-name“ in das Array CLASS_NAMES übertragen.

Als Nächstes fügen Sie einige Variablen hinzu, um wichtige Dinge zu speichern, die später verwendet werden.

script.js

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

Sehen wir uns diese genauer an.

Zuerst haben Sie die Variable mobilenet, um das geladene Mobilenet-Modell zu speichern. Legen Sie dieses Feld anfangs auf "Nicht definiert" fest.

Als Nächstes haben Sie eine Variable mit dem Namen gatherDataState. Wenn ein „dataCollector“ wenn auf die Schaltfläche geklickt wird, ändert sich diese zu der 1-heißen ID dieser Schaltfläche, wie im HTML-Code definiert, sodass Sie wissen, welche Datenklasse Sie zu diesem Zeitpunkt sammeln. Anfangs ist diese Einstellung auf STOP_DATA_GATHER gesetzt, damit die Datenerfassungsschleife, die Sie später schreiben, keine Daten erfasst, wenn keine Schaltflächen gedrückt werden.

videoPlaying verfolgt, ob der Webcam-Stream erfolgreich geladen und abgespielt wurde und ob er verwendet werden kann. Anfangs ist diese Einstellung auf false eingestellt, da die Webcam erst eingeschaltet ist, wenn Sie auf ENABLE_CAM_BUTTON. drücken

Definieren Sie als Nächstes zwei Arrays, trainingDataInputs und trainingDataOutputs. Auf diesen werden die gesammelten Trainingsdatenwerte gespeichert, sobald Sie auf „dataCollector“ klicken. Schaltflächen für die vom MobileNet-Basismodell generierten Eingabemerkmale bzw. für die Stichprobenklasse der Ausgabeklasse.

Ein letztes Array, examplesCount,, wird dann definiert, um zu verfolgen, wie viele Beispiele für jede Klasse enthalten sind, sobald Sie mit dem Hinzufügen beginnen.

Schließlich haben Sie eine Variable namens predict, die Ihre Vorhersageschleife steuert. Dieser Wert ist anfänglich auf false festgelegt. Es können keine Vorhersagen getroffen werden, bis dieser Wert später auf true festgelegt wird.

Nachdem Sie nun alle Schlüsselvariablen definiert haben, laden wir das vorabgeschnittene MobileNet v3-Basismodell, das Bildmerkmalsvektoren anstelle von Klassifizierungen bereitstellt.

9. MobileNet-Basismodell laden

Definieren Sie zuerst eine neue Funktion mit dem Namen loadMobileNetFeatureModel, wie unten gezeigt. Dies muss eine asynchrone Funktion sein, da das Laden eines Modells asynchron ist:

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 diesem Code definieren Sie den URL, in dem sich das zu ladende Modell befindet, aus der TFHub-Dokumentation.

Anschließend kannst du das Modell mit await tf.loadGraphModel() laden. Denke daran, die spezielle Eigenschaft fromTFHub auf true zu setzen, wenn du ein Modell von dieser Google-Website lädst. Dies ist nur bei der Verwendung von Modellen, die auf TF Hub gehostet werden, ein Sonderfall, bei dem diese zusätzliche Eigenschaft festgelegt werden muss.

Sobald der Ladevorgang abgeschlossen ist, können Sie den innerText des STATUS-Elements mit einer Nachricht festlegen, damit Sie visuell sehen können, dass das Element korrekt geladen wurde und Sie mit der Datenerfassung beginnen können.

Jetzt müssen Sie nur noch das Modell aufwärmen. Bei größeren Modellen wie diesem kann es einen Moment dauern, bis alles eingerichtet ist, wenn Sie das Modell zum ersten Mal verwenden. Daher ist es hilfreich, Nullen durch das Modell zu übergeben, um zukünftige Wartezeiten zu vermeiden, bei denen das Timing wichtiger sein könnte.

Sie können tf.zeros() in einer tf.tidy() verwenden, um dafür zu sorgen, dass Tensoren korrekt mit einer Batchgröße von 1 und der korrekten Höhe und Breite, die Sie zu Beginn in Ihren Konstanten definiert haben, platziert werden. Abschließend geben Sie noch die Farbkanäle an. In diesem Fall ist das 3, da das Modell RGB-Bilder erwartet.

Protokollieren Sie als Nächstes die resultierende Form des Tensors, der mit answer.shape() zurückgegeben wurde, um die Größe der Bildmerkmale dieses Modells besser zu verstehen.

Nachdem Sie diese Funktion definiert haben, können Sie sie sofort aufrufen, um den Modelldownload beim Laden der Seite zu starten.

Wenn Sie die Livevorschau jetzt aufrufen, ändert sich nach kurzer Zeit der Statustext von „Warten auf TF.js-Ladevorgang“. um zu "MobileNet v3 wurde erfolgreich geladen" zu sehen. wie unten dargestellt. Prüfen Sie, ob das funktioniert, bevor Sie fortfahren.

a28b734e190afff.png

Sie können auch die Konsolenausgabe überprüfen, um die gedruckte Größe der Ausgabefeatures zu sehen, die dieses Modell erzeugt. Nachdem Sie über das MobileNet-Modell Nullen ausgeführt haben, wird die Form [1, 1024] gedruckt. Das erste Element ist nur die Batchgröße 1. Wie Sie sehen, gibt es tatsächlich 1024 Merkmale zurück, die dann zur Klassifizierung neuer Objekte verwendet werden können.

10. Neuen Modellkopf definieren

Als Nächstes definieren Sie den Modellkopf, der im Wesentlichen ein sehr minimales mehrschichtiges Perceptron ist.

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']  
});

Sehen wir uns diesen Code einmal genauer an. Sie beginnen mit der Definition eines tf.semantic-Modells, dem Sie Modellebenen hinzufügen.

Als Nächstes fügen Sie diesem Modell eine dichte Ebene als Eingabeebene hinzu. Die Eingabeform lautet 1024, da die Ausgaben der MobileNet v3-Funktionen diese Größe haben. Sie haben dies im vorherigen Schritt festgestellt, nachdem Sie diejenigen übergeben haben, die das Modell durchlaufen haben. Diese Schicht hat 128 Neuronen, die die ReLU-Aktivierungsfunktion verwenden.

Wenn Sie mit Aktivierungsfunktionen und Modellebenen noch nicht vertraut sind, sehen Sie sich den Kurs zu Beginn dieses Workshops an, um zu verstehen, was diese Eigenschaften im Hintergrund bewirken.

Die nächste Ebene, die hinzugefügt werden soll, ist die Ausgabeebene. Die Anzahl der Neuronen sollte der Anzahl der Klassen entsprechen, die Sie vorhersagen möchten. Dazu können Sie CLASS_NAMES.length verwenden, um herauszufinden, wie viele Klassen Sie klassifizieren möchten. Das entspricht der Anzahl der Schaltflächen zum Erfassen von Daten, die auf der Benutzeroberfläche zu finden sind. Da dies ein Klassifizierungsproblem ist, verwenden Sie die Aktivierung softmax auf dieser Ausgabeebene. Sie muss verwendet werden, wenn Sie versuchen, ein Modell zur Lösung von Klassifizierungsproblemen anstelle einer Regression zu erstellen.

Geben Sie jetzt model.summary() aus, um die Übersicht des neu definierten Modells in der Konsole auszugeben.

Kompilieren Sie schließlich das Modell, damit es trainiert werden kann. Hier ist das Optimierungstool auf adam gesetzt und der Verlust ist entweder binaryCrossentropy, wenn CLASS_NAMES.length gleich 2 ist, oder es wird categoricalCrossentropy verwendet, wenn mindestens drei Klassen zu klassifizieren sind. Genauigkeitsmesswerte werden ebenfalls angefordert, damit sie später zu Fehlerbehebungszwecken in den Logs überwacht werden können.

In der Konsole sollte in etwa Folgendes angezeigt werden:

22eaf32286fea4bb.png

Beachten Sie, dass mehr als 130.000 trainierbare Parameter vorhanden sind. Da es sich jedoch um eine einfache, dichte Schicht regelmäßiger Neuronen handelt, wird sie ziemlich schnell trainiert.

Als Aktivität, die Sie nach Abschluss des Projekts durchführen sollten, könnten Sie versuchen, die Anzahl der Neuronen in der ersten Schicht zu ändern, um zu sehen, wie niedrig Sie die Anzahl erreichen können, während Sie dennoch eine angemessene Leistung erzielen. Beim maschinellen Lernen wird oft ein gewisses Maß an Versuch und Irrtum durchgeführt, um optimale Parameterwerte zu finden, damit Sie den besten Kompromiss zwischen Ressourcennutzung und Geschwindigkeit finden.

11. Webcam aktivieren

Jetzt ist es an der Zeit, die zuvor definierte Funktion enableCam() zu definieren. Fügen Sie eine neue Funktion mit dem Namen hasGetUserMedia() wie unten gezeigt hinzu und ersetzen Sie dann den Inhalt der zuvor definierten enableCam()-Funktion durch den entsprechenden Code unten.

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');
  }
}

Erstellen Sie zuerst eine Funktion mit dem Namen hasGetUserMedia(), um zu prüfen, ob der Browser getUserMedia() unterstützt. Dazu prüfen Sie, ob wichtige Browser-API-Eigenschaften vorhanden sind.

Verwenden Sie in der enableCam()-Funktion die oben definierte hasGetUserMedia()-Funktion, um zu prüfen, ob sie unterstützt wird. Ist dies nicht der Fall, geben Sie eine Warnung in der Konsole aus.

Wenn dies unterstützt wird, definiere einige Einschränkungen für deinen getUserMedia()-Aufruf, z. B. dass nur der Videostream und die width des Videos 640 Pixel und die height 480 Pixel groß sein soll. Warum? Nun, es bringt nicht viel Sinn, ein größeres Video zu erhalten, da es auf 224 x 224 Pixel verkleinert werden müsste, um es in das MobileNet-Modell zu übernehmen. Sie können auch Rechenressourcen einsparen, indem Sie eine kleinere Auflösung anfordern. Die meisten Kameras unterstützen eine Auflösung dieser Größe.

Rufen Sie als Nächstes navigator.mediaDevices.getUserMedia() mit der oben beschriebenen constraints auf und warten Sie, bis die stream zurückgegeben wird. Sobald stream zurückgegeben wurde, kannst du dein VIDEO-Element dazu bringen, stream wiederzugeben. Lege es dazu als srcObject-Wert fest.

Sie sollten auch einen eventListener zum VIDEO-Element hinzufügen, um zu erfahren, wann stream geladen wurde und erfolgreich wiedergegeben wird.

Sobald der Stream geladen wurde, können Sie videoPlaying auf "true" setzen und ENABLE_CAM_BUTTON entfernen, um durch Festlegen der Klasse auf "removed" zu verhindern, dass erneut auf den Stream geklickt wird.

Führen Sie nun Ihren Code aus, klicken Sie auf die Schaltfläche zum Aktivieren der Kamera und gewähren Sie den Zugriff auf die Webcam. Wenn Sie dies zum ersten Mal tun, sollten Sie im Videoelement auf der Seite wie dargestellt sehen:

b378eb1affa9b883.png

Fügen Sie jetzt eine Funktion hinzu, um die Klicks auf die Schaltfläche dataCollector zu verarbeiten.

12. Event-Handler für die Schaltfläche zur Datenerfassung

Jetzt ist es an der Zeit, die aktuell leere Funktion namens gatherDataForClass(). auszufüllen. Diese hast du zu Beginn des Codelabs als Event-Handler-Funktion für dataCollector-Schaltflächen zugewiesen.

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();
}

Prüfen Sie zuerst das data-1hot-Attribut der aktuell angeklickten Schaltfläche. Rufen Sie dazu this.getAttribute() mit dem Namen des Attributs auf, in diesem Fall data-1hot als Parameter. Da dies ein String ist, können Sie ihn mit parseInt() in eine Ganzzahl umwandeln und dieses Ergebnis einer Variablen namens classNumber. zuweisen.

Legen Sie als Nächstes die Variable gatherDataState entsprechend fest. Wenn der aktuelle gatherDataState gleich STOP_DATA_GATHER ist (dieser ist auf -1 gesetzt), bedeutet dies, dass derzeit keine Daten erfasst werden und ein mousedown-Ereignis ausgelöst wurde. Lege für gatherDataState die classNumber fest, die du gerade gefunden hast.

Andernfalls werden derzeit Daten erfasst und das ausgelöste Ereignis war ein mouseup-Ereignis. Sie möchten nun die Datenerfassung für diese Klasse beenden. Setzen Sie ihn einfach auf den Status STOP_DATA_GATHER zurück, um die von Ihnen definierte Datenerfassungsschleife zu beenden.

Starten Sie zum Schluss den Aufruf von dataGatherLoop(),, mit dem die Kursdaten tatsächlich aufgezeichnet werden.

13. Datenerfassung

Definieren Sie nun die Funktion dataGatherLoop(). Diese Funktion ist verantwortlich für das Sampling von Bildern aus dem Webcam-Video, die Übergabe durch das MobileNet-Modell und die Erfassung der Ausgaben dieses Modells (die 1024 Featurevektoren).

Sie werden dann zusammen mit der gatherDataState-ID der Schaltfläche gespeichert, die gerade gedrückt wird, damit Sie wissen, welche Klasse diese Daten darstellen.

Gehen wir dies einmal durch:

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);
  }
}

Die Ausführung dieser Funktion wird nur fortgesetzt, wenn videoPlaying den Wert „true“ hat, d. h., die Webcam ist aktiv, gatherDataState ist nicht gleich STOP_DATA_GATHER und eine Schaltfläche zum Erfassen von Kursdaten wird gedrückt.

Verpacken Sie als Nächstes Ihren Code in einem tf.tidy(), um alle erstellten Tensoren im folgenden Code zu entfernen. Das Ergebnis dieser tf.tidy()-Codeausführung wird in einer Variablen namens imageFeatures gespeichert.

Du kannst jetzt mit tf.browser.fromPixels() einen Frame der Webcam VIDEO aufnehmen. Der resultierende Tensor mit den Bilddaten wird in einer Variablen namens videoFrameAsTensor gespeichert.

Passen Sie als Nächstes die Größe der Variablen videoFrameAsTensor so an, dass sie die richtige Form für die Eingabe des MobileNet-Modells hat. Verwenden Sie einen tf.image.resizeBilinear()-Aufruf mit dem Tensor, den Sie umformen möchten, als ersten Parameter und dann mit einer Form, die die neue Höhe und Breite gemäß den zuvor erstellten Konstanten definiert. Legen Sie schließlich für die Ausrichtung der Ecken „true“ fest, indem Sie den dritten Parameter übergeben. So vermeiden Sie Ausrichtungsprobleme bei der Größenanpassung. Das Ergebnis dieser Größenanpassung wird in einer Variablen namens resizedTensorFrame gespeichert.

Beachten Sie, dass diese primitive Größenanpassung das Bild streckt, da Ihr Webcam-Bild 640 x 480 Pixel groß ist und das Modell ein quadratisches Bild mit 224 x 224 Pixeln benötigt.

Für diese Demo sollte dies funktionieren. Sobald Sie dieses Codelab abgeschlossen haben, können Sie versuchen, ein Quadrat aus diesem Bild zuzuschneiden, um noch bessere Ergebnisse für jedes Produktionssystem zu erzielen, das Sie später erstellen.

Als Nächstes normalisieren Sie die Bilddaten. Die Bilddaten liegen immer im Bereich von 0 bis 255, wenn tf.browser.frompixels() verwendet wird. Sie können also einfach sizedTensorFrame durch 255 teilen, um sicherzustellen, dass alle Werte zwischen 0 und 1 liegen. Dies ist die Anforderung, die das MobileNet-Modell als Eingaben erwartet.

Übertragen Sie diesen normalisierten Tensor schließlich im Abschnitt tf.tidy() des Codes durch das geladene Modell. Rufen Sie dazu mobilenet.predict() auf, an den Sie die erweiterte Version von normalizedTensorFrame mit expandDims() übergeben, sodass es sich um einen Batch von 1 handelt, da das Modell einen Batch von Eingaben zur Verarbeitung erwartet.

Sobald das Ergebnis zurückgegeben wird, können Sie sofort squeeze() für dieses zurückgegebene Ergebnis aufrufen, um es wieder in einen 1D-Tensor zu quetschen, den Sie dann zurückgeben und der imageFeatures-Variable zuweisen, die das Ergebnis aus tf.tidy() erfasst.

Da Sie nun über die imageFeatures aus dem MobileNet-Modell verfügen, können Sie diese aufzeichnen, indem Sie sie in das trainingDataInputs-Array übertragen, das Sie zuvor definiert haben.

Sie können auch aufzeichnen, was diese Eingabe darstellt, indem Sie den aktuellen gatherDataState auch in das trainingDataOutputs-Array übertragen.

Beachte, dass die Variable gatherDataState auf die numerische ID der aktuellen Klasse gesetzt wurde, für die du Daten aufzeichnest, wenn in der zuvor definierten gatherDataForClass()-Funktion auf die Schaltfläche geklickt wurde.

An dieser Stelle können Sie auch die Anzahl der Beispiele für eine bestimmte Klasse erhöhen. Prüfen Sie dazu zuerst, ob der Index im Array examplesCount zuvor initialisiert wurde oder nicht. Wenn er nicht definiert ist, setzen Sie ihn auf 0, um den Zähler für die numerische ID einer bestimmten Klasse zu initialisieren. Anschließend können Sie den examplesCount für den aktuellen gatherDataState erhöhen.

Aktualisieren Sie nun den Text des STATUS-Elements auf der Webseite, damit die aktuelle Anzahl für jede Klasse angezeigt wird. Gehen Sie dazu das Array CLASS_NAMES durch und geben Sie den visuell lesbaren Namen zusammen mit der Anzahl der Daten am selben Index in examplesCount aus.

Rufen Sie schließlich window.requestAnimationFrame() mit dataGatherLoop als Parameter auf, um diese Funktion noch einmal rekursiv aufzurufen. Dabei werden so lange Frames aus dem Video erhoben, bis mouseup der Schaltfläche erkannt wird und gatherDataState auf STOP_DATA_GATHER, gesetzt ist. Danach endet die Schleife der Datenerfassung.

Wenn Sie Ihren Code jetzt ausführen, sollten Sie in der Lage sein, auf die Schaltfläche zum Aktivieren der Kamera zu klicken, auf das Laden der Webcam zu warten und dann auf die einzelnen Schaltflächen zum Erfassen von Daten zu klicken und sie gedrückt zu halten, um Beispiele für jede Datenklasse zu sammeln. Hier sehe ich, dass ich Daten für mein Mobiltelefon bzw. meine Hand erfasse.

541051644a45131f.gif

Der Statustext sollte aktualisiert werden, da alle Tensoren im Arbeitsspeicher gespeichert werden, wie in der Abbildung oben gezeigt.

14. Trainieren und vorhersagen

Im nächsten Schritt implementieren Sie Code für die derzeit leere trainAndPredict()-Funktion, in der der Lerntransfer stattfindet. Sehen wir uns den Code einmal an:

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);
}

Achten Sie zuerst darauf, dass keine aktuellen Vorhersagen durchgeführt werden. Setzen Sie dazu predict auf false.

Zufallsmix der Eingabe- und Ausgabearrays mit tf.util.shuffleCombo(), damit die Reihenfolge beim Training keine Probleme verursacht

Wandeln Sie das Ausgabearray trainingDataOutputs, in einen Tensor1d vom Typ int32 um, damit es in einer One-Hot-Codierung verwendet werden kann. Sie wird in einer Variablen namens outputsAsTensor gespeichert.

Verwenden Sie die tf.oneHot()-Funktion mit dieser outputsAsTensor-Variablen zusammen mit der maximalen Anzahl von zu codierenden Klassen (nur CLASS_NAMES.length). Ihre One-Hot-codierten Ausgaben werden jetzt in einem neuen Tensor namens oneHotOutputs gespeichert.

Beachten Sie, dass trainingDataInputs derzeit ein Array von aufgezeichneten Tensoren ist. Um diese für das Training zu verwenden, müssen Sie das Array der Tensoren in einen regulären 2D-Tensor umwandeln.

Dazu gibt es in der TensorFlow.js-Bibliothek eine tolle Funktion namens tf.stack().

bei dem ein Array von Tensoren gestapelt wird, um einen höherdimensionalen Tensor als Ausgabe zu erzeugen. In diesem Fall wird ein 2D-Tensor zurückgegeben, also ein Batch von 1-dimensionalen Eingaben, die jeweils eine Länge von 1.024 haben und die aufgezeichneten Merkmale enthalten. Das ist das, was Sie für das Training benötigen.

Als Nächstes await model.fit(), um den benutzerdefinierten Modellkopf zu trainieren. Hier übergeben Sie die Variable inputsAsTensor zusammen mit dem oneHotOutputs, um die Trainingsdaten darzustellen, die für Beispieleingaben bzw. Zielausgaben verwendet werden sollen. Setzen Sie im Konfigurationsobjekt für den dritten Parameter shuffle auf true, verwenden Sie batchSize von 5, setzen Sie epochs auf 10 und geben Sie dann einen callback für onEpochEnd für die Funktion logProgress an, die Sie in Kürze definieren werden.

Schließlich können Sie die erstellten Tensoren entsorgen, wenn das Modell jetzt trainiert wird. Sie können dann predict auf true setzen, damit Vorhersagen wieder stattfinden können, und dann die Funktion predictLoop() aufrufen, um Live-Webcam-Bilder vorherzusagen.

Sie können auch die Funktion logProcess() definieren, um den Trainingsstatus zu protokollieren, der in model.fit() oben verwendet wird und Ergebnisse nach jeder Trainingsrunde an die Konsole ausgibt.

Du hast es fast geschafft. Jetzt wird die Funktion predictLoop() hinzugefügt, um Vorhersagen zu treffen.

Kern-Vorhersageschleife

Hier implementieren Sie die Hauptvorhersageschleife, die Frames von einer Webcam abtastet und kontinuierlich prognostiziert, was sich in jedem Frame befindet. Dies geschieht mithilfe von Echtzeitergebnissen im Browser.

Überprüfen wir den 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);
  }
}

Prüfen Sie zuerst, ob predict „true“ ist, damit Vorhersagen erst getroffen werden, nachdem ein Modell trainiert wurde und einsatzbereit ist.

Als Nächstes können Sie die Bildmerkmale für das aktuelle Bild abrufen, so wie Sie es mit der Funktion dataGatherLoop() getan haben. Im Wesentlichen nehmen Sie mit tf.browser.from pixels() einen Frame von der Webcam, normalisieren ihn, passen seine Größe auf 224 x 224 Pixel an und übergeben diese Daten dann über das MobileNet-Modell, um die resultierenden Bildmerkmale zu erhalten.

Jetzt können Sie jedoch den neu trainierten Modellkopf verwenden, um eine Vorhersage zu erstellen. Dazu übergeben Sie das Ergebnis imageFeatures, das Sie gerade über die predict()-Funktion des trainierten Modells gefunden haben. Anschließend können Sie den resultierenden Tensor zusammendrücken, um ihn wieder 1 Dimension zu machen, und ihn einer Variablen namens prediction zuweisen.

Mit diesem prediction können Sie den Index mit dem höchsten Wert mithilfe von argMax() ermitteln und diesen resultierenden Tensor dann mit arraySync() in ein Array konvertieren, um die zugrunde liegenden Daten in JavaScript abzurufen und die Position des Elements mit dem höchsten Wert zu ermitteln. Dieser Wert wird in der Variablen highestIndex gespeichert.

Sie können die tatsächlichen Vorhersage-Konfidenzwerte auch auf die gleiche Weise abrufen, indem Sie arraySync() direkt für den Tensor prediction aufrufen.

Sie haben jetzt alles, was Sie brauchen, um den STATUS-Text mit den prediction-Daten zu aktualisieren. Wenn Sie den menschenlesbaren String für die Klasse abrufen möchten, können Sie einfach den highestIndex im Array CLASS_NAMES suchen und dann den Konfidenzwert aus predictionArray abrufen. Um die Lesbarkeit in Prozent zu verbessern, multiplizieren Sie einfach mit 100 und math.floor().

Schließlich können Sie window.requestAnimationFrame() verwenden, um predictionLoop() erneut anzurufen, wenn Sie bereit sind, um in Echtzeit Klassifizierungen in Ihrem Videostream zu sehen. Dies wird fortgesetzt, bis predict auf false gesetzt wird, wenn Sie ein neues Modell mit neuen Daten trainieren.

Das bringt dich zum letzten Teil des Rätsels. Implementierung der Schaltfläche zum Zurücksetzen

15. Schaltfläche zum Zurücksetzen implementieren

Fast fertig! Der letzte Teil des Puzzles besteht darin, eine Schaltfläche zum Zurücksetzen zu implementieren, um von vorn zu beginnen. Den Code für Ihre derzeit leere reset()-Funktion finden Sie unten. Aktualisieren Sie sie wie folgt:

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);
}

Beenden Sie zuerst alle laufenden Vorhersageschleifen, indem Sie predict auf false setzen. Löschen Sie als Nächstes alle Inhalte im Array examplesCount, indem Sie die Länge auf 0 festlegen. Dies ist eine praktische Möglichkeit, um den gesamten Inhalt aus einem Array zu löschen.

Gehen Sie nun alle aktuell aufgezeichneten trainingDataInputs durch und achten Sie darauf, dass Sie dispose() jedes darin enthaltenen Tensors haben, um wieder Arbeitsspeicher freizugeben, da Tensoren nicht vom JavaScript Garbage Collector bereinigt werden.

Danach können Sie die Array-Länge im Array trainingDataInputs und trainingDataOutputs sicher auf 0 festlegen, um auch diese zu löschen.

Legen Sie schließlich den STATUS-Text auf einen sinnvollen Wert fest und geben Sie die im Arbeitsspeicher verbliebenen Tensoren als Plausibilitätsprüfung aus.

Beachten Sie, dass sich noch einige hundert Tensoren im Speicher befinden, da sowohl das MobileNet-Modell als auch das von Ihnen definierte mehrschichtige Perceptron nicht entsorgt sind. Wenn Sie nach dem Zurücksetzen noch einmal trainieren möchten, müssen Sie sie mit neuen Trainingsdaten wiederverwenden.

16. Probieren wir es aus

Es ist an der Zeit, deine eigene Version von Teachable Machine zu testen!

Rufe die Live-Vorschau auf, aktiviere die Webcam, sammle mindestens 30 Beispiele für Klasse 1 für ein Objekt in deinem Raum und mache dann dasselbe für Klasse 2 für ein anderes Objekt, klicke auf „Trainieren“ und überprüfe das Konsolenprotokoll, um den Fortschritt zu sehen. Es sollte ziemlich schnell trainiert werden:

bf1ac3cc5b15740.gif

Zeige nach dem Training die Objekte der Kamera, um Live-Vorhersagen zu erhalten, die dann im Statustextbereich auf der Webseite im oberen Bereich angezeigt werden. Wenn Probleme auftreten, prüfen Sie den fertigen funktionierenden Code, um zu sehen, ob Sie etwas kopiert haben.

17. Glückwunsch

Glückwunsch! Sie haben gerade Ihren allerersten Lerntransfer mit TensorFlow.js live im Browser abgeschlossen.

Probieren Sie es aus, testen Sie es an einer Vielzahl von Objekten. Möglicherweise stellen Sie fest, dass einige Dinge schwerer zu erkennen sind als andere, insbesondere wenn sie etwas anderem ähneln. Möglicherweise müssen Sie weitere Kurse oder Trainingsdaten hinzufügen, um sie voneinander unterscheiden zu können.

Zusammenfassung

In diesem Codelab haben Sie Folgendes gelernt:

  1. Was Lerntransfers sind und welche Vorteile sie gegenüber dem Training eines vollständigen Modells haben.
  2. Anleitung zum Abrufen von Modellen zur Wiederverwendung aus TensorFlow Hub.
  3. Hier erfahren Sie, wie Sie eine Webanwendung für Lerntransfers einrichten.
  4. Basismodell zum Generieren von Bildfunktionen laden und verwenden.
  5. Hier erfahren Sie, wie Sie einen neuen Vorhersagekopf trainieren, der benutzerdefinierte Objekte in Webcam-Bildern erkennen kann.
  6. Wie Sie die resultierenden Modelle verwenden, um Daten in Echtzeit zu klassifizieren.

Was liegt als Nächstes an?

Sie haben nun eine Arbeitsgrundlage. Welche kreativen Ideen können Sie entwickeln, um dieses Standardmodell für maschinelles Lernen für einen realen Anwendungsfall zu erweitern, an dem Sie möglicherweise arbeiten? Vielleicht könnten Sie die Branche, in der Sie derzeit arbeiten, revolutionieren, damit Mitarbeiter Ihres Unternehmens Modelle trainieren können, um Dinge zu klassifizieren, die für ihre tägliche Arbeit wichtig sind? Deiner Fantasie sind keine Grenzen gesetzt.

Wenn Sie mehr erfahren möchten, können Sie den kostenlosen vollständigen Kurs absolvieren. Dort erfahren Sie, wie Sie die beiden Modelle aus diesem Codelab zu einem einzigen Modell kombinieren, um die Effizienz zu erhöhen.

Wenn Sie mehr über die Theorie hinter der ursprünglichen lehrbaren Maschinenanwendung erfahren möchten, sehen Sie sich diese Anleitung an.

Deine Arbeit mit uns teilen

Ihr könnt das, was ihr heute gemacht habt, ganz einfach für andere kreative Anwendungsfälle erweitern. Wir empfehlen euch, unkonventionell zu denken und weiter Hacking zu betreiben.

Vergessen Sie nicht, uns in den sozialen Medien mit dem Hashtag #MadeWithTFJS zu taggen, damit Ihr Projekt in unserem TensorFlow-Blog oder sogar auf zukünftigen Veranstaltungen vorgestellt wird. Wir freuen uns auf Ihr Feedback.

Interessante Websites