TensorFlow.js: Crea tu propia "Teachable Machine" usando aprendizaje por transferencia con TensorFlow.js

1. Antes de comenzar

El uso de modelos de TensorFlow.js creció exponencialmente durante los últimos años, y muchos desarrolladores de JavaScript ahora buscan tomar modelos innovadores y volver a entrenarlos para que funcionen con datos personalizados que son exclusivos de sus sectores. El acto de tomar un modelo existente (a menudo denominado modelo base) y usarlo en un dominio similar, pero diferente, se conoce como aprendizaje por transferencia.

El aprendizaje por transferencia tiene muchas ventajas en comparación con un modelo completamente en blanco. Puedes reutilizar el conocimiento que ya aprendiste de un modelo previamente entrenado y necesitas menos ejemplos del elemento nuevo que deseas clasificar. Además, el entrenamiento suele ser mucho más rápido porque solo debe volver a entrenar las últimas capas de la arquitectura del modelo en lugar de toda la red. Por eso, el aprendizaje por transferencia es muy adecuado para el entorno del navegador web donde los recursos pueden variar según el dispositivo de ejecución, pero también tiene acceso directo a los sensores para facilitar la adquisición de datos.

En este codelab, se muestra cómo compilar una app web a partir de un lienzo en blanco y recrear el popular sitio web "Teachable Machine" de Google. El sitio web te permite crear una aplicación web funcional que cualquier usuario puede utilizar para reconocer un objeto personalizado con solo algunas imágenes de ejemplo de su cámara web. El sitio web se mantiene a un nivel mínimo para que puedas concentrarte en los aspectos de aprendizaje automático de este codelab. Sin embargo, al igual que en el sitio web original de Teachable Machine, hay mucho alcance para aplicar su experiencia actual de desarrollador web a fin de mejorar la UX.

Requisitos previos

Este codelab está escrito para desarrolladores web que están familiarizados con los modelos prediseñados de TensorFlow.js y el uso básico de API, y que desean comenzar a usar el aprendizaje por transferencia en TensorFlow.js.

  • Para este lab, se asume que tiene conocimientos básicos de TensorFlow.js, HTML5, CSS y JavaScript.

Si es la primera vez que usas Tensorflow.js, primero considera realizar este curso gratuito de cero a impacto, que no supone conocimientos previos sobre el aprendizaje automático ni TensorFlow.js, y te enseña todo lo que necesitas saber en pasos más pequeños.

Qué aprenderá

  • Qué es TensorFlow.js y por qué deberías usarlo en tu próxima aplicación web.
  • Cómo compilar una página web HTML/CSS /JS simplificada que replique la experiencia del usuario de Teachable Machine
  • Cómo usar TensorFlow.js para cargar un modelo base previamente entrenado, específicamente MobileNet, a fin de generar atributos de imagen que se puedan usar en el aprendizaje por transferencia
  • Cómo recopilar datos de la cámara web del usuario para varias clases de datos que deseas reconocer
  • Cómo crear y definir un perceptrón de varias capas que toma las funciones de imagen y aprende a clasificar objetos nuevos con ellas.

¡Comencemos!

Requisitos

  • Se prefiere una cuenta de Glitch.com, o bien un entorno de publicación web que te resulte cómodo para editar y administrar.

2. ¿Qué es TensorFlow.js?

54e81d02971f53e8.png

TensorFlow.js es una biblioteca de aprendizaje automático de código abierto que puede ejecutarse en cualquier lugar donde JavaScript pueda. Se basa en la biblioteca original de TensorFlow escrita en Python y tiene como objetivo volver a crear esta experiencia para desarrolladores y este conjunto de API para el ecosistema de JavaScript.

¿Dónde se puede usar?

Dada la portabilidad de JavaScript, ahora puedes escribir en 1 lenguaje y realizar aprendizaje automático en todas las siguientes plataformas con facilidad:

  • Del cliente en el navegador web con JavaScript convencional
  • En el lado del servidor o incluso en dispositivos de IoT, como Raspberry Pi, mediante Node.js
  • Apps de escritorio con Electron
  • Apps nativas para dispositivos móviles que usan React Native

TensorFlow.js también admite múltiples backends dentro de cada uno de estos entornos (los entornos reales basados en hardware que puede ejecutar, por ejemplo, la CPU o WebGL). Un "backend" en este contexto no significa un entorno del servidor; por ejemplo, el backend para la ejecución podría ser el cliente en WebGL para garantizar la compatibilidad y para que todo funcione rápidamente. Actualmente, TensorFlow.js es compatible con lo siguiente:

  • Ejecución de WebGL en la tarjeta gráfica del dispositivo (GPU): esta es la forma más rápida de ejecutar modelos más grandes (de más de 3 MB) con aceleración de GPU.
  • Ejecución de Web Assembly (WASM) en CPU: Para mejorar el rendimiento de CPU en todos los dispositivos, incluidos los teléfonos de generación anterior, por ejemplo. Esto es más adecuado para modelos más pequeños (de menos de 3 MB) que, en realidad, pueden ejecutarse más rápido en la CPU con WASM que con WebGL debido a la sobrecarga de subir contenido a un procesador de gráficos.
  • Ejecución de CPU: El resguardo no debe estar disponible en ninguno de los otros entornos. Este es el más lento de los tres, pero siempre está ahí para ti.

Nota: Puedes forzar uno de estos backends si sabes en qué dispositivo se ejecutará o simplemente puedes permitir que TensorFlow.js lo decida por ti. no lo especifiques.

Superpoderes del cliente

Ejecutar TensorFlow.js en el navegador web de la máquina cliente puede generar varios beneficios que vale la pena considerar.

Privacidad

Puedes entrenar y clasificar datos en la máquina cliente sin necesidad de enviar datos a un servidor web de terceros. En ocasiones, es posible que este sea un requisito para cumplir con leyes locales, como el GDPR, por ejemplo, cuando se procesen datos que el usuario desee conservar en sus equipos y no se envíen a terceros.

Velocidad

Dado que no tiene que enviar datos a un servidor remoto, la inferencia (el acto de clasificar los datos) puede ser más rápida. Aún mejor, tienes acceso directo a los sensores del dispositivo, como la cámara, el micrófono, el GPS, el acelerómetro y más, si el usuario te otorga acceso.

Alcance y escala

Con un solo clic, cualquier persona en el mundo podrá hacer clic en un vínculo que le envíes, abrir la página web en su navegador y utilizar lo que hayas creado. No se necesita una configuración compleja del servidor en el servidor de Linux con controladores CUDA y mucho más solo para usar el sistema de aprendizaje automático.

Costo

Que no haya servidores significa que lo único que debes pagar es una CDN para alojar tus archivos HTML, CSS, JS y de modelo. El costo de una CDN es mucho más económico que mantener un servidor (posiblemente con una tarjeta gráfica adjunta) en ejecución las 24 horas, todos los días.

Características del servidor

Cuando aprovechas la implementación de Node.js en Node.js, puedes habilitar las siguientes funciones.

Compatibilidad completa con CUDA

En el servidor, para la aceleración de tarjetas gráficas, debes instalar los controladores de NVIDIA CUDA para permitir que TensorFlow funcione con la tarjeta gráfica (a diferencia en el navegador que usa WebGL; no se requiere instalación). Sin embargo, con la compatibilidad total con CUDA, puede aprovechar al máximo las capacidades de nivel inferior de la tarjeta gráfica, lo que acelera los entrenamientos e inferencias. El rendimiento se encuentra al mismo nivel que la implementación de Python para TensorFlow, ya que ambos comparten el mismo backend de C++.

Tamaño del modelo

Para modelos de vanguardia a partir de la investigación, es posible que trabajes con modelos muy grandes, tal vez gigabytes de tamaño. Actualmente, no se pueden ejecutar estos modelos en el navegador web debido a las limitaciones de uso de memoria por pestaña del navegador. Para ejecutar estos modelos más grandes, puedes usar Node.js en tu propio servidor con las especificaciones de hardware que necesitas para ejecutar ese modelo de manera eficiente.

IOT

Node.js es compatible con computadoras de placa única populares, como Raspberry Pi, lo que, a su vez, te permite ejecutar modelos de TensorFlow.js en esos dispositivos.

Velocidad

Node.js está escrito en JavaScript, lo que significa que se beneficia de una compilación oportuna. Esto significa que, a menudo, es posible que observe mejoras en el rendimiento cuando utilice Node.js, ya que se optimizará en el tiempo de ejecución, en especial para el procesamiento previo que realice. Se puede observar un excelente ejemplo de esto en este caso de éxito, que muestra cómo Hugging Face usó Node.js a fin de obtener un rendimiento 2 veces mejor para su modelo de procesamiento de lenguaje natural.

Ahora que comprendes los conceptos básicos de TensorFlow.js, dónde se puede ejecutar y algunos de sus beneficios, podemos comenzar a usarlo de forma útil.

3. Aprendizaje por transferencia

¿Qué es exactamente el aprendizaje por transferencia?

El aprendizaje por transferencia implica aprender conocimientos que ya adquirieron para algo diferente, pero similar.

Los humanos lo hacemos todo el tiempo. Tienes una vida de experiencias en tu cerebro que puedes usar para reconocer cosas nuevas que no viste antes. Tomemos como ejemplo este sauce:

e28070392cd4afb9.png

Según el lugar del mundo donde te encuentres, es posible que no hayas visto este tipo de árbol antes.

Sin embargo, si te pedimos que me digas si hay alguno de los álamos en la nueva imagen, lo más probable es que puedas encontrarlos bastante rápido, a pesar de que estén en un ángulo diferente y ligeramente diferentes al original.

d9073a0d5df27222.png

Ya tiene un grupo de neuronas en el cerebro que saben identificar objetos similares a árboles y otras neuronas que son buenas para encontrar líneas rectas largas. Puedes volver a usar ese conocimiento para clasificar rápidamente un álamos, que es un objeto similar a un árbol con muchas ramas verticales largas.

De manera similar, si tiene un modelo de aprendizaje automático que ya está entrenado en un dominio, como reconocer imágenes, puede volver a utilizarlo para realizar una tarea diferente, pero relacionada.

Puede hacer lo mismo con un modelo avanzado, como MobileNet, que es un modelo de investigación muy popular que puede realizar el reconocimiento de imágenes en 1,000 tipos de objetos diferentes. Desde perros hasta automóviles, se entrenó en un enorme conjunto de datos conocido como ImageNet, que tiene millones de imágenes etiquetadas.

En esta animación, puedes ver la gran cantidad de capas que tiene en este modelo de MobileNet V1:

7d4e1e35c1a89715.gif

Durante su entrenamiento, este modelo aprendió a extraer características comunes que resultan importantes para esos 1,000 objetos, y muchas de las funciones de nivel inferior que usa para identificar esos objetos también pueden ser útiles para detectar objetos nuevos que nunca antes vio. Después de todo, todo es solo una combinación de líneas, texturas y formas.

Veamos una arquitectura tradicional de red neuronal convolucional (CNN) (similar a MobileNet) y veamos cómo el aprendizaje por transferencia puede aprovechar esta red entrenada para aprender algo nuevo. En la siguiente imagen, se muestra la arquitectura de modelo típica de una CNN que, en este caso, se entrenó para reconocer dígitos escritos a mano de 0 a 9:

baf4e3d434576106.png

Si pudieras separar las capas de nivel inferior previamente entrenadas de un modelo existente entrenado, como se muestra a la izquierda, desde las capas de clasificación cerca del final del modelo que se muestra a la derecha (a veces denominado cabeza de clasificación del modelo). puede usar las capas de nivel inferior a fin de producir atributos de salida para cualquier imagen basada en los datos originales con los que se entrenó. A continuación, se muestra la misma red sin el encabezado de clasificación:

369a8a9041c6917d.png

Si suponemos que lo nuevo que intentas reconocer también puede usar esas funciones de salida que el modelo anterior aprendió, es muy probable que se puedan reutilizar con un nuevo propósito.

En el diagrama anterior, este modelo hipotético se entrenó con dígitos, por lo que tal vez lo que aprendimos sobre los dígitos también se pueda aplicar a letras como a, b y c.

Ahora, podría agregar un nuevo encabezado de clasificación que intente predecir a, b o c, como se muestra a continuación:

db97e5e60ae73bbd.png

Aquí, las capas de nivel inferior se congelan y no están entrenadas; solo el nuevo encabezado de clasificación se actualizará para aprender de los atributos proporcionados del modelo cortado previamente entrenado.

El acto de hacer esto se conoce como aprendizaje por transferencia y es lo que Teachable Machine hace en segundo plano.

También se puede ver que si solo se tiene que entrenar el perceptrón de varias capas al final de la red, se entrena mucho más rápido que si tuviera que entrenar toda la red desde cero.

Pero ¿cómo puede aprovechar las partes de un modelo? Ve a la siguiente sección para descubrirlo.

4. TensorFlow Hub: modelos base

Buscar un modelo base adecuado para usar

Si buscas modelos de investigación más avanzados y avanzados, como MobileNet, ve a TensorFlow Hub y, luego, filtra por modelos adecuados para TensorFlow.js que usen la arquitectura de MobileNet v3 a fin de encontrar resultados. como los que se muestran aquí:

c5dc1420c6238c14.png

Ten en cuenta que algunos de estos resultados son del tipo "clasificación de imágenes" (detallados en la parte superior izquierda de cada resultado de tarjeta de modelo), y otros son del tipo "vector de atributos de imagen".

Estos resultados de vectores de funciones de imagen son, básicamente, las versiones de MobileNet previamente cortadas que puedes usar para obtener los vectores de atributos de imagen en lugar de la clasificación final.

Los modelos como este suelen llamarse "modelos base", que luego puedes usar para realizar aprendizaje por transferencia de la misma manera que se muestra en la sección anterior. Para ello, agrega un encabezado de clasificación nuevo y entrenelo con tus propios datos.

Lo siguiente que se debe comprobar es para un modelo de interés determinado de formato de TensorFlow.js. Si abres la página de uno de estos modelos de vectores de funciones para MobileNet v3, podrás ver en la documentación de JS que tiene la forma de un modelo de gráfico basado en el fragmento de código de ejemplo en la documentación que usa tf.loadGraphModel().

f97d903d2e46924b.png

También se debe tener en cuenta que, si encuentras un modelo en formato de capas en lugar de hacerlo en formato de gráfico, puedes elegir las capas que deseas inmovilizar y las que deseas desbloquear para el entrenamiento. Esto puede ser muy útil cuando se crea un modelo para una nueva tarea, que suele denominarse "modelo de transferencia". Sin embargo, por ahora, usarás el tipo de modelo de gráfico predeterminado para este instructivo, en el que se implementan la mayoría de los modelos de TF Hub. Para obtener más información sobre cómo trabajar con modelos de capas, consulta el curso cero a hero TensorFlow.js.

Ventajas del aprendizaje por transferencia

¿Cuáles son las ventajas de usar el aprendizaje por transferencia en lugar de entrenar toda la arquitectura del modelo desde cero?

En primer lugar, el tiempo de entrenamiento es una ventaja clave para usar un enfoque de aprendizaje por transferencia, dado que ya tienes un modelo base entrenado sobre el cual desarrollar.

Segundo, puede dejar de mostrar muchos menos ejemplos de lo nuevo que está tratando de clasificar debido a la capacitación que ya tuvo lugar.

Esto es muy bueno si tienes tiempo y recursos limitados para recopilar datos de ejemplo de lo que deseas clasificar y necesitas crear un prototipo rápidamente antes de reunir más datos de entrenamiento para que sea más sólido.

Dada la necesidad de menos datos y la velocidad de entrenamiento de una red más pequeña, el aprendizaje por transferencia requiere menos recursos. Esto lo hace muy adecuado para el entorno del navegador, ya que tarda solo segundos en una máquina moderna en lugar de horas, días o semanas para el entrenamiento completo del modelo.

¡Muy bien! Ahora que conoces la esencia de lo que es el aprendizaje por transferencia, es momento de crear tu propia versión de Teachable Machine. Comencemos.

5. Configura el código

Requisitos

Comencemos a programar

Se crearon plantillas de plantilla para comenzar desde Glitch.com o Codepen.io. Simplemente puedes clonar cualquiera de las plantillas como estado base para este codelab con un solo clic.

En Glitch, haz clic en el botón "remix this" para bifurcarlo y crear un nuevo conjunto de archivos que puedes editar.

En CodePen también puedes hacer clic en bifurcar en la esquina inferior derecha de la pantalla.

Este esqueleto muy simple te proporciona los siguientes archivos:

  • Página HTML (index.html)
  • Hoja de estilo (style.css)
  • Archivo para escribir nuestro código JavaScript (script.js)

Para tu comodidad, se agregó una importación en el archivo HTML para la biblioteca de TensorFlow.js. Se ve del siguiente modo:

index.html

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

Alternativa: Use su editor web preferido o trabaje localmente

Si deseas descargar el código y trabajar de forma local o en otro editor en línea, simplemente crea los 3 archivos nombrados anteriormente en el mismo directorio y copia y pega el código de nuestro código estándar de Glitch en cada uno de ellos.

6. Código estándar HTML de la aplicación

¿Por dónde debo comenzar?

Todos los prototipos requieren una estructura básica de HTML para que puedas renderizar tus hallazgos. Configurar ahora. Agregarás:

  • Corresponde al título de la página.
  • Texto descriptivo.
  • Un párrafo de estado.
  • Un video que mantiene el feed de la cámara web una vez listo
  • Varios botones para iniciar la cámara, recopilar datos o restablecer la experiencia.
  • Importaciones para archivos de TensorFlow.js y JS que codificarás más adelante.

Abre index.html y pega el código existente con lo siguiente para configurar las funciones anteriores:

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>

Desglose

Desglosemos algunos de los códigos HTML anteriores para destacar algunos de los elementos clave que agregó.

  • Agregaste una etiqueta <h1> para el título de la página junto con una etiqueta <p> con el ID "estado", que es la página en la que imprimirás la información a medida que uses diferentes partes del sistema para ver los resultados.
  • Agregaste un elemento <video> con el ID de "cámara web", al cual renderizarás más tarde tu transmisión con cámara web.
  • Agregaste 5 elementos de <button>. La primera, con el ID de "enableCam", habilita la cámara. Los dos botones siguientes tienen una clase de "dataCollector", que permite recopilar imágenes de ejemplo para los objetos que deseas reconocer. El código que escribas más adelante se diseñará para que puedas agregar la cantidad de botones que desees para que funcionen automáticamente.

Ten en cuenta que estos botones también tienen un atributo especial definido por el usuario llamado data-1hot, con un valor entero que comienza en 0 para la primera clase. Este es el índice numérico que utilizará para representar los datos de una clase determinada. El índice se usará para codificar las clases de salida de manera correcta con una representación numérica, en lugar de una string, ya que los modelos de AA solo pueden funcionar con números.

También hay un atributo de nombre de datos que contiene el nombre en lenguaje natural que quieres usar para esta clase, que te permite proporcionarle al usuario un nombre más significativo en lugar de un valor de índice numérico de la codificación de 1 caliente.

Por último, tienes un botón de entrenamiento y restablecimiento para iniciar el proceso de entrenamiento una vez que se hayan recopilado datos, o para restablecer la app respectivamente.

  • También agregaste 2 importaciones de <script>. Uno para TensorFlow.js y otro para script.js que definirás a la brevedad.

7. Agr. estilo

Valores predeterminados del elemento

Agrega estilos para los elementos HTML que acabas de agregar a fin de asegurarte de que se rendericen correctamente. Estos son algunos estilos que se agregan a los elementos de posición y tamaño correctamente. Nada demasiado especial. Puedes agregar esto más adelante para mejorar la UX, como viste en el video instructivo de Teachable.

estilo.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%;
}

¡Genial! ¡Eso es todo lo que necesitas! Si obtienes una vista previa del resultado en este momento, debería verse de la siguiente manera:

81909685d7566dcb.png

8. JavaScript: Objetos de escucha y constantes clave

Cómo definir constantes de clave

Primero, agrega algunas constantes clave que usarás en toda la app. Para comenzar, reemplaza el contenido de script.js con estas constantes:

secuencia de comandos.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 = [];

Desglosemos para qué sirven:

  • STATUS simplemente hace referencia a la etiqueta de párrafo en la que escribirás las actualizaciones de estado.
  • VIDEO contiene una referencia al elemento de video HTML que renderizará el feed de la cámara web.
  • ENABLE_CAM_BUTTON, RESET_BUTTON y TRAIN_BUTTON toman referencias del DOM a todos los botones clave de la página HTML.
  • MOBILE_NET_INPUT_WIDTH y MOBILE_NET_INPUT_HEIGHT definen el ancho y el alto de entrada esperados del modelo de MobileNet, respectivamente. Si almacenas esto en una constante cerca de la parte superior del archivo de esta manera, si decides usar una versión diferente más adelante, será más fácil actualizar los valores una vez en lugar de tener que reemplazarlo en muchos lugares diferentes.
  • Se estableció STOP_DATA_GATHER en -1. Esto almacena un valor de estado para que sepas cuándo el usuario dejó de hacer clic en un botón para recopilar datos del feed de la cámara web. Al asignarle un nombre más significativo a este número, el código será más legible más adelante.
  • CLASS_NAMES actúa como una búsqueda y contiene los nombres legibles para las posibles predicciones de clase. Este arreglo se propagará más adelante.

Ahora que tienes referencias a elementos clave, es momento de asociarles algunos objetos de escucha de eventos.

Cómo agregar objetos de escucha de eventos clave

Para comenzar, agrega controladores de eventos de clic a los botones clave, como se muestra a continuación:

secuencia de comandos.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: Llama a la función enableCam cuando se hace clic en él.

TRAIN_BUTTON: llama a trainAndPrediction cuando se hace clic en él.

RESET_BUTTON: Las llamadas se restablecen cuando se hace clic en ellos.

Por último, en esta sección, encontrarás document.querySelectorAll(). Esto muestra un arreglo de elementos encontrados en el documento que coinciden:

secuencia de comandos.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!
}

Explicación del código:

Luego, iterarás a través de los botones encontrados y asociarás 2 objetos de escucha de eventos a cada uno. Uno para "mousedown" y otro para "mouseup". Esto te permite seguir registrando muestras mientras se presiona el botón, lo que es útil para la recopilación de datos.

Ambos eventos llaman a una función gatherDataForClass que definirás más adelante.

En este punto, también puedes enviar los nombres de clase encontrados legibles desde el atributo de nombre de datos del botón HTML al array CLASS_NAMES.

A continuación, agrega algunas variables para almacenar elementos clave que se usarán más adelante.

secuencia de comandos.js

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

Veamos estas opciones.

Primero, tienes una variable mobilenet para almacenar el modelo de mobilenet cargado. Inicialmente, configúrela como no definida.

A continuación, tienes una variable llamada gatherDataState. Si se presiona el botón "dataCollector", este cambia a 1 ID activo de ese botón, como se define en el HTML, para que pueda determinar qué clase de datos recopila en ese momento. Inicialmente, se configura como STOP_DATA_GATHER para que el bucle de recopilación de datos que escribas más adelante no recopilará ningún dato cuando no se presione ningún botón.

videoPlaying realiza un seguimiento de si la transmisión de la cámara web se cargó y está reproduciendo con éxito y si está disponible para usarla. Inicialmente, se configura como false, ya que la cámara web no está encendida hasta que presionas ENABLE_CAM_BUTTON..

A continuación, define 2 arreglos, trainingDataInputs y trainingDataOutputs. Estos almacenan los valores de datos de entrenamiento recopilados, ya que haces clic en los botones “dataCollector” para los atributos de entrada generados por el modelo base de MobileNet y la clase de salida de muestra, respectivamente.

Luego, se define un array final, examplesCount,, para realizar un seguimiento de la cantidad de ejemplos que contiene cada clase una vez que comienzas a agregarlos.

Por último, tienes una variable llamada predict que controla el bucle de predicción. Esta se configura inicialmente como false. No se pueden realizar predicciones hasta que se establezca en true más adelante.

Ahora que se definieron todas las variables clave, vamos a cargar el modelo base MobileNet v3 cortado previamente que proporciona vectores de atributos de imagen en lugar de clasificaciones.

9. Carga el modelo base de MobileNet

Primero, define una nueva función llamada loadMobileNetFeatureModel, como se muestra a continuación. Debe ser una función asíncrona, ya que el acto de cargar un modelo es asíncrono:

secuencia de comandos.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();

En este código, definirás el objeto URL en el que se encuentra el modelo que se cargará desde la documentación de TFHub.

Luego, puedes cargar el modelo usando await tf.loadGraphModel() y recordar establecer la propiedad especial fromTFHub en true mientras cargas un modelo desde este sitio web de Google. Este es un caso especial solo para usar modelos alojados en TF Hub en el que se debe establecer esta propiedad adicional.

Una vez que se completa la carga, puedes configurar el elemento innerText del elemento STATUS con un mensaje para que visualices que se cargó de manera correcta y estés listo para comenzar a recopilar datos.

Lo único que queda por hacer ahora es preparar el modelo. Con modelos más grandes como este, la primera vez que usas el modelo, la configuración puede demorar un momento. Por lo tanto, resulta útil pasar ceros a través del modelo para evitar cualquier espera en el futuro en la que la temporización pueda ser más crítica.

Puedes usar tf.zeros() unido a un tf.tidy() para asegurarte de que los tensores se desechen correctamente, con un tamaño de lote de 1 y la altura y el ancho correctos que definiste en tus constantes al principio. Por último, también se especifican los canales de color, que en este caso es 3, ya que el modelo espera imágenes RGB.

A continuación, registra la forma resultante del tensor que se muestra con answer.shape() para ayudarte a comprender el tamaño de los atributos de imagen que produce este modelo.

Después de definir esta función, podrá llamarla de inmediato para iniciar la descarga del modelo en la carga de la página.

Si miras la vista previa en vivo ahora, después de unos momentos, verás que el texto del estado cambia de "Esperando TF.js a cargar" y se convierte en "Se cargó correctamente MobileNet v3". como se muestra a continuación. Asegúrese de que esto funcione antes de continuar.

a28b734e190afff.png

También puedes consultar el resultado de la consola para ver el tamaño impreso de los atributos de salida que produce este modelo. Después de ejecutar ceros a través del modelo MobileNet, verás una forma de [1, 1024] impresa. El primer elemento es solo el tamaño de lote de 1 y puedes ver que realmente muestra 1, 024 atributos que luego se pueden usar para ayudarte a clasificar objetos nuevos.

10. Define el nuevo encabezado del modelo

Ahora es el momento de definir tu cabeza de modelo, que es básicamente un perceptrón multicapa muy mínimo.

secuencia de comandos.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']
});

Veamos este código paso a paso. Comenzarás por definir un modelo de tf.Sequence al que agregarás capas de modelos.

A continuación, agrega una capa densa como capa de entrada a este modelo. Esto tiene una forma de entrada de 1024, ya que los resultados de las funciones de MobileNet v3 son de este tamaño. Descubriste esto en el paso anterior después de pasarlos por el modelo. Esta capa tiene 128 neuronas que usan la función de activación ReLU.

Si no tienes experiencia con las funciones de activación y las capas de modelos, considera realizar el curso que se detalla al comienzo de este taller para comprender lo que hacen estas propiedades en segundo plano.

La siguiente capa que debes agregar es la capa de salida. La cantidad de neuronas debe ser igual a la cantidad de clases que intentas predecir. Para hacerlo, puedes usar CLASS_NAMES.length a fin de averiguar cuántas clases planeas clasificar, que equivale a la cantidad de botones de recopilación de datos que se encuentran en la interfaz de usuario. Dado que este es un problema de clasificación, usa la activación softmax de esta capa de salida, que debe usarse cuando intentas crear un modelo para resolver problemas de clasificación en lugar de regresión.

Ahora, imprime un model.summary() para imprimir la descripción general del modelo recién definido en la consola.

Por último, compila el modelo para que esté listo para el entrenamiento. Aquí, el optimizador se configura en adam, y la pérdida será binaryCrossentropy si CLASS_NAMES.length es igual a 2, o usará categoricalCrossentropy si hay 3 o más clases para Clasificar También se solicitan métricas de precisión para poder supervisarlas en los registros más adelante con fines de depuración.

En la consola, deberías ver algo como lo siguiente:

22eaf32286fea4bb.png

Ten en cuenta que tiene más de 130,000 parámetros de entrenamiento. Sin embargo, dado que se trata de una capa densa y simple de neuronas normales, se entrenará bastante rápido.

Como actividad una vez que se completa el proyecto, puedes intentar cambiar la cantidad de neuronas en la primera capa para ver qué tan bajo puede ser y, al mismo tiempo, obtener un rendimiento decente. A menudo, con el aprendizaje automático, se encuentra algún nivel de prueba y error para encontrar valores de parámetros óptimos que le brinden la mejor relación entre el uso de recursos y la velocidad.

11. Habilitar la cámara web

Ahora es momento de perfeccionar la función enableCam() que definiste antes. Agrega una función nueva llamada hasGetUserMedia() como se muestra a continuación y, luego, reemplaza el contenido de la función enableCam() definida anteriormente con el código correspondiente a continuación.

secuencia de comandos.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');
  }
}

Primero, crea una función llamada hasGetUserMedia() para verificar si el navegador es compatible con getUserMedia(). Para ello, verifica la existencia de propiedades clave de API del navegador.

En la función enableCam(), usa la función hasGetUserMedia() que acabas de definir para verificar si es compatible. Si no es así, imprime una advertencia en la consola.

Si la admite, define algunas restricciones para tugetUserMedia() llamada, como si solo quieres la transmisión de video por Internet y que prefiereswidth del video a640 píxeles de tamaño y losheight ser480 píxeles. ¿Por qué? Bueno, no tiene mucho sentido obtener un video más grande, ya que tendría que cambiar el tamaño a 224 por 224 píxeles para incorporarlo al modelo MobileNet. Será posible que solicites una resolución más pequeña para ahorrar algunos recursos de procesamiento. La mayoría de las cámaras admiten una resolución de este tamaño.

A continuación, llama a navigator.mediaDevices.getUserMedia() con el constraints descrito anteriormente y, luego, espera a que se muestre el stream. Una vez que se muestra el stream, puedes hacer que tu elemento VIDEO reproduzca el stream. Para ello, debes establecerlo como su valor srcObject.

También debes agregar un eventListener en el elemento VIDEO para saber cuándo se cargó stream y se está reproduciendo correctamente.

Una vez que se cargue la transmisión, puedes establecer videoPlaying como verdadero y quitar el ENABLE_CAM_BUTTON para evitar que se vuelva a hacer clic estableciendo su clase en "removed".

Ahora, ejecuta tu código, haz clic en el botón de habilitar cámara y permite el acceso a la cámara web. Si es la primera vez que lo haces, deberías ver que se renderiza en el elemento de video en la página como se muestra a continuación:

b378eb1affa9b883.png

Ahora es el momento de agregar una función para manejar los clics en el botón dataCollector.

12. Controlador de eventos del botón de recopilación de datos

Ahora es el momento de completar tu función vacía actualmente llamada gatherDataForClass(). Esto es lo que asignaste como tu función de controlador de eventos para los botones dataCollector al comienzo del codelab.

secuencia de comandos.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();
}

Primero, verifica el atributo data-1hot en el botón en el que se hace clic. Para ello, llama a this.getAttribute() con el nombre del atributo, en este caso, data-1hot como parámetro. Como esta es una string, puedes usar parseInt() para convertirla en un número entero y asignar este resultado a una variable llamada classNumber..

A continuación, configura la variable gatherDataState según corresponda. Si el gatherDataState actual es igual a STOP_DATA_GATHER (que configuraste como -1), significa que actualmente no estás recopilando datos y se activó un evento mousedown. Configura gatherDataState para que sea el classNumber que acabas de encontrar.

De lo contrario, significa que actualmente estás recopilando datos y el evento que se activó fue un evento mouseup, y ahora deseas dejar de recopilar datos para esa clase. Solo vuelve a configurarlo en el estado STOP_DATA_GATHER para finalizar el bucle de recopilación de datos que definirás en breve.

Por último, inicia la llamada a dataGatherLoop(),, que realiza la grabación de los datos de la clase.

13. Recopilación de datos

Ahora, define la función dataGatherLoop(). Esta función es responsable de tomar muestras de los videos de la cámara web, pasarlos por el modelo de MobileNet y capturar los resultados de ese modelo (los vectores de atributos de 1, 024).

Luego, los almacena junto con el ID de gatherDataState del botón que se está presionando para que sepas qué clase representan estos datos.

Veamos cómo hacerlo paso a paso.

secuencia de comandos.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);
  }
}

Solo continuarás con la ejecución de esta función si videoPlaying es verdadero, lo que significa que la cámara web está activa, y que gatherDataState no es igual a STOP_DATA_GATHER y se está presionando un botón para la recopilación de datos de la clase.

Luego, une el código en un tf.tidy() para desechar los tensores creados en el siguiente código. El resultado de la ejecución de este código tf.tidy() se almacena en una variable llamada imageFeatures.

Ahora puedes tomar un marco de la cámara web VIDEO con tf.browser.fromPixels(). El tensor resultante que contiene los datos de la imagen se almacena en una variable llamada videoFrameAsTensor.

A continuación, cambia el tamaño de la variable videoFrameAsTensor a fin de que sea del tipo correcto para la entrada del modelo MobileNet. Usa una llamada tf.image.resizeBilinear() con el tensor que quieras cambiar de forma como el primer parámetro y, luego, una forma que defina la nueva altura y el ancho, según lo definido por las constantes que ya creaste antes. Por último, para alinear las esquinas a verdadero, pasa el tercer parámetro a fin de evitar problemas de alineación al cambiar el tamaño. El resultado de este cambio de tamaño se almacena en una variable llamada resizedTensorFrame.

Ten en cuenta que este primitivo cambio de tamaño extiende la imagen, ya que la imagen de tu cámara web es de 640 por 480 píxeles, y el modelo necesita una imagen cuadrada de 224 por 224 píxeles.

A los fines de esta demostración, esto debería funcionar bien. Sin embargo, una vez que completes este codelab, te recomendamos que intentes recortar un cuadrado de esta imagen para obtener mejores resultados para cualquier sistema de producción que puedas crear más adelante.

A continuación, normaliza los datos de imagen. Los datos de imagen siempre están en el rango de 0 a 255 cuando se usa tf.browser.frompixels(), de modo que puedes simplemente dividir el tamaño de TensorFrame por 255 para asegurarte de que todos los valores estén entre 0 y 1, que es lo que el modelo de MobileNet espera como entrada.

Por último, en la sección tf.tidy() del código, envía este tensor normalizado a través del modelo cargado llamando a mobilenet.predict(), a la que pasas la versión expandida del normalizedTensorFrame con expandDims() para que quede un lote de 1, ya que el modelo espera un lote de entradas para el procesamiento

Una vez que se muestre el resultado, podrás llamar de inmediatosqueeze() en ese resultado que se muestra para comprimirlo en un tensor 1D, que luego devuelves y asignas alimageFeatures variable que captura el resultado detf.tidy() ,

Ahora que tienes el imageFeatures del modelo MobileNet, puedes registrarlos. Para ello, envíalos al array trainingDataInputs que definiste antes.

También puedes registrar lo que representa esta entrada si envías también el gatherDataState actual al array trainingDataOutputs.

Ten en cuenta que la variable gatherDataState se hubiera establecido con el ID numérico de la clase actual para el que registras los datos cuando se hizo clic en el botón en la función gatherDataForClass() definida anteriormente.

En este punto, también puedes aumentar la cantidad de ejemplos que tienes para una clase determinada. Para ello, primero verifica si el índice dentro del array examplesCount se inicializó antes o no. Si no está definido, configúralo en 0 para inicializar el contador del ID numérico de una clase determinada y, luego, puedes aumentar el valor de examplesCount para el objeto gatherDataState actual.

Ahora, actualiza el texto del elemento STATUS en la página web para mostrar los recuentos actuales de cada clase a medida que se capturan. Para ello, repite el arreglo CLASS_NAMES e imprime el nombre legible combinado con el recuento de datos en el mismo índice en examplesCount.

Por último, llama a window.requestAnimationFrame() con dataGatherLoop pasado como parámetro para volver a llamar a esta función de forma recursiva. Se seguirán realizando muestras de los videos hasta que se detecte la mouseup del botón y gatherDataState se establezca en STOP_DATA_GATHER,, momento en el que finalizará el bucle de recopilación de datos.

Si ejecutas el código ahora, deberías poder hacer clic en el botón para habilitar la cámara, esperar a que se cargue la cámara web y, a continuación, hacer clic en cada uno de los botones para recopilar datos a fin de recopilar ejemplos para cada clase de datos. Aquí veo que recolecto datos para mi teléfono celular y mi mano, respectivamente.

541051644a45131f.gif

Deberías ver el texto del estado actualizado a medida que almacena todos los tensores en la memoria, como se muestra en la captura de pantalla anterior.

14. Entrenar y predecir

El siguiente paso es implementar el código para la función trainAndPredict() vacía actualmente, que es donde se realiza el aprendizaje por transferencia. Veamos el código:

secuencia de comandos.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);
}

Primero, asegúrate de detener cualquier predicción actual. Para ello, configura predict en false.

A continuación, mezcla tus arreglos de entrada y salida con tf.util.shuffleCombo() para asegurarte de que el orden no cause problemas en el entrenamiento.

Convierte tu arreglo de salida, trainingDataOutputs,, para que sea un tensor de tipo int32 a fin de que esté listo para usarse en una codificación única. Se almacena en una variable llamada outputsAsTensor.

Usa la función tf.oneHot() con esta variable outputsAsTensor junto con la cantidad máxima de clases para codificar, que es solo CLASS_NAMES.length. Tus salidas codificadas en caliente ahora se almacenan en un tensor nuevo llamado oneHotOutputs.

Ten en cuenta que, por el momento, trainingDataInputs es un arreglo de tensores grabados. A fin de usarlos para el entrenamiento, deberás convertir el arreglo de tensores en uno normal en 2D.

Para ello, hay una función excelente en la biblioteca de TensorFlow.js, llamada tf.stack(),

que toma un arreglo de tensores y los apila para generar un tensor de mayor dimensión como resultado. En este caso, se muestra un tensor 2D, que es un lote de entradas de 1 dimensión que cada 1024 de longitud contiene las características registradas, que es lo que necesitas para el entrenamiento.

A continuación, await model.fit() para entrenar el encabezado del modelo personalizado. Aquí pasas tu variable inputsAsTensor junto con la oneHotOutputs para representar los datos de entrenamiento que se usarán, por ejemplo, entradas y salidas de destino, respectivamente. En el objeto de configuración del tercer parámetro, establezca lo siguiente:shuffle atrue , utilizarbatchSize de5 , conepochs Establecer en10 y, luego, especifique unacallback paraonEpochEnd a lalogProgress que definirás a la brevedad.

Por último, puedes desechar los tensores creados a medida que se entrena el modelo. Luego, puedes volver a configurar predict como true para permitir que se realicen las predicciones nuevamente y, luego, llamar a la función predictLoop() para comenzar a predecir imágenes de cámara web en vivo.

También puedes definir la función logProcess() para registrar el estado del entrenamiento, que se usa en el model.fit() anterior y que imprime los resultados en la consola después de cada ronda de entrenamiento.

Ya casi Es hora de agregar la función predictLoop() para hacer predicciones.

Bucle de predicción principal

Aquí, implementarás el bucle de predicción principal que muestrea los fotogramas de una cámara web y predice de forma continua lo que hay en cada fotograma con resultados en tiempo real en el navegador.

Veamos el código:

secuencia de comandos.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);
  }
}

Primero, comprueba que predict sea verdadero, de modo que las predicciones solo se realicen después de que se entrene un modelo y esté disponible para usar.

A continuación, puedes obtener las características de imagen para la imagen actual como lo hiciste en la función dataGatherLoop(). En esencia, toma un marco de la cámara web con tf.browser.from pixels(), normalízalo, cambia su tamaño para que sea de 224 por 224 píxeles y, luego, pasa esos datos por medio del modelo MobileNet a fin de obtener las funciones de imagen resultantes.

Sin embargo, ahora puedes usar tu cabeza de modelo recién entrenado para realizar una predicción pasando el imageFeatures resultante que se encuentra a través de la función predict() del modelo entrenado. Luego, puedes apretar el tensor resultante para que vuelva a ser 1 dimensional y asignarlo a una variable llamada prediction.

Con prediction, puedes encontrar el índice que tiene el valor más alto con argMax() y, luego, convertir este tensor resultante en un array con arraySync() a fin de obtener los datos subyacentes en JavaScript y descubrir la posición de la de mayor valor. Este valor se almacena en la variable llamada highestIndex.

También puedes obtener las puntuaciones de confianza de predicción reales de la misma manera si llamas directamente a arraySync() en el tensor prediction.

Ahora tienes todo lo que necesitas para actualizar el texto STATUS con los datos de prediction. A fin de obtener una string legible para la clase, puedes buscar el highestIndex en el array CLASS_NAMES y tomar el valor de confianza de predictionArray. Para que sea más fácil de leer como un porcentaje, solo multiplíquelo por 100 y math.floor() el resultado.

Por último, puedes usar window.requestAnimationFrame() para volver a llamar a predictionLoop() una vez lista, a fin de obtener clasificación en tiempo real en tu transmisión de video por Internet. Esto continuará hasta que predict se establezca en false si eliges entrenar un modelo nuevo con datos nuevos.

Esto te llevará a la pieza final del rompecabezas. Se está implementando el botón de restablecimiento.

15. Implementa el botón de restablecimiento

Falta poco La pieza final del rompecabezas es la implementación de un botón de restablecimiento para volver a empezar. El código de la función reset() actualmente vacía se encuentra a continuación. Actualízala de la siguiente manera:

secuencia de comandos.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);
}

Primero, detén cualquier bucle de predicción en ejecución. Para ello, establece predict en false. A continuación, borra todo el contenido del array examplesCount configurando su longitud en 0, lo que es una forma práctica de borrar todo el contenido de un array.

Ahora, revisa todos los trainingDataInputs grabados actualmente y asegúrate de que dispose() de cada tensor incluido en este elemento vuelva a liberar memoria, ya que el recolector de elementos no utilizados de JavaScript no limpia los tensores.

Una vez hecho esto, ahora puedes establecer de forma segura la longitud del arreglo en 0 y los arreglos trainingDataInputs y trainingDataOutputs para borrarlos también.

Por último, configura el texto STATUS en un valor razonable e imprime los tensores que quedan en la memoria como una comprobación de estado.

Ten en cuenta que habrá varios cientos de tensores todavía en la memoria, ya que el modelo MobileNet y el perceptrón de varias capas que definiste no están desechados. Deberás volver a usarlos con nuevos datos de entrenamiento si decides entrenar nuevamente después de este restablecimiento.

16. Vamos a probarlo

Es momento de probar tu propia versión de Teachable Machine.

Dirígete a la vista previa en vivo, habilita la cámara web, reúne al menos 30 muestras para la clase 1 de algún objeto de la sala y, luego, haz lo mismo para la clase 2 en el caso de un objeto diferente, haz clic en Entrenar y consulta el registro de la consola para ver el progreso. Se debería entrenar bastante rápido:

bf1ac3cc5b15740.gif

Una vez entrenado, muéstrale los objetos a la cámara para obtener predicciones en vivo que se imprimirán en el área de texto de estado en la página web cerca de la parte superior. Si tienes problemas, consulta el código de trabajo que completé para ver si te perdiste alguna copia.

17. Felicitaciones

¡Felicitaciones! Completaste tu primer ejemplo de aprendizaje por transferencia con TensorFlow.js en vivo en el navegador.

Pruébalo, pruébalo con varios objetos y es posible que notes que algunos elementos son más difíciles de reconocer que otros, sobre todo si son similares a otros elementos. Es posible que debas agregar más datos de entrenamiento o clases para poder distinguirlos.

Resumen

En este codelab, aprendiste lo siguiente:

  1. Qué es el aprendizaje por transferencia y sus ventajas sobre entrenar un modelo completo
  2. Cómo obtener modelos para reutilizarlos en TensorFlow Hub
  3. Cómo configurar una app web adecuada para el aprendizaje por transferencia
  4. Cómo cargar y usar un modelo base para generar atributos de imagen
  5. Cómo entrenar un nuevo encabezado de predicción que pueda reconocer objetos personalizados de imágenes de cámara web
  6. Cómo usar los modelos resultantes para clasificar datos en tiempo real.

¿Qué sigue?

Ahora que tienes una base de trabajo desde la cual comenzar, ¿qué ideas creativas se te ocurren para extender este código estándar de modelo de aprendizaje automático a un caso de uso real en el que podrías estar trabajando? ¿Quizás podría revolucionar la industria en la que trabaja actualmente para ayudar al personal de los modelos de sus empresas a clasificar los elementos que son importantes en sus tareas diarias? Las posibilidades son infinitas.

Para ir más allá, considera realizar el curso completo de forma gratuita; en él se muestra cómo combinar los 2 modelos que tienes actualmente en este codelab en 1 modelo único para que sea más eficiente.

Además, consulta este instructivo si te interesa saber más sobre la teoría detrás de la aplicación original de Teachable Machine.

Comparte tus creaciones con nosotros

También puedes ampliar fácilmente lo que creaste hoy para otros casos de uso creativo. Te recomendamos pensar de manera creativa y seguir hackeando.

Recuerda etiquetarnos en redes sociales con el hashtag #MadeWithTFJS para tener la oportunidad de que se incluya tu proyecto en el blog de TensorFlow o, incluso, en eventos futuros. Nos encantaría ver tus creaciones.

Sitios web que puedes revisar