1. Antes de comenzar
El uso de modelos de TensorFlow.js creció de forma exponencial en los últimos años, y muchos desarrolladores de JavaScript ahora buscan tomar modelos existentes de vanguardia y volver a entrenarlos para que funcionen con datos personalizados que son únicos para su industria. 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 comenzar con un modelo completamente en blanco. Puedes reutilizar el conocimiento que ya se aprendió de un modelo entrenado previamente y necesitas menos ejemplos del elemento nuevo que deseas clasificar. Además, el entrenamiento suele ser mucho más rápido, ya que solo se deben volver a entrenar las últimas capas de la arquitectura del modelo en lugar de toda la red. Por este motivo, el aprendizaje por transferencia es muy adecuado para el entorno del navegador web, en el que 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 desde cero, recreando el popular sitio web "Teachable Machine" de Google. El sitio web te permite crear una app web funcional que cualquier usuario puede usar para reconocer un objeto personalizado con solo algunas imágenes de ejemplo de su cámara web. El sitio web se mantiene deliberadamente minimalista para que puedas enfocarte en los aspectos de aprendizaje automático de este codelab. Sin embargo, al igual que con el sitio web original de Teachable Machine, hay mucho margen para aplicar tu experiencia existente como desarrollador web y mejorar la UX.
Requisitos previos
Este codelab está escrito para desarrolladores web que tienen cierta familiaridad con los modelos prediseñados de TensorFlow.js y el uso básico de la API, y que desean comenzar a usar el aprendizaje por transferencia en TensorFlow.js.
- En este lab, se supone que tienes conocimientos básicos de TensorFlow.js, HTML5, CSS y JavaScript.
Si no conoces TensorFlow.js, te recomendamos que primero realices este curso gratuito para principiantes, que no requiere conocimientos previos de aprendizaje automático ni de TensorFlow.js, y te enseña todo lo que necesitas saber en pasos más pequeños.
Qué aprenderás
- Qué es TensorFlow.js y por qué deberías usarlo en tu próxima app web
- Cómo compilar una página web simplificada en HTML/CSS /JS que replique la experiencia del usuario de Teachable Machine
- Cómo usar TensorFlow.js para cargar un modelo base previamente entrenado, específicamente MobileNet, para generar características de imágenes que se pueden usar en el aprendizaje por transferencia
- Cómo recopilar datos de la cámara web de un usuario para varias clases de datos que deseas reconocer
- Cómo crear y definir un perceptrón multicapa que tome los atributos de la imagen y aprenda a clasificar objetos nuevos con ellos
Comencemos a hackear…
Requisitos
- Se recomienda tener una cuenta de Glitch.com para seguir los pasos, o bien puedes usar un entorno de servicio web con el que te sientas cómodo para editar y ejecutar por tu cuenta.
2. ¿Qué es TensorFlow.js?

TensorFlow.js es una biblioteca de aprendizaje automático de código abierto que se puede ejecutar en cualquier lugar donde se pueda ejecutar JavaScript. Se basa en la biblioteca original de TensorFlow escrita en Python y tiene como objetivo recrear esta experiencia del desarrollador y este conjunto de APIs para el ecosistema de JavaScript.
¿Dónde se puede usar?
Gracias a la portabilidad de JavaScript, ahora puedes escribir en 1 idioma y realizar aprendizaje automático en todas las siguientes plataformas con facilidad:
- Lado del cliente en el navegador web con JavaScript estándar
- Dispositivos del servidor y hasta de IoT, como Raspberry Pi, con Node.js
- Apps para computadoras que usan Electron
- Apps nativas para dispositivos móviles con React Native
TensorFlow.js también admite varios backends dentro de cada uno de estos entornos (los entornos basados en hardware real en los que se puede ejecutar, como la CPU o WebGL, por ejemplo). En este contexto, un "backend" no significa un entorno del servidor (por ejemplo, el backend para la ejecución podría ser del cliente en WebGL) para garantizar la compatibilidad y mantener todo en funcionamiento rápido. Actualmente, TensorFlow.js admite 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 WebAssembly (WASM) en la CPU: Para mejorar el rendimiento de la CPU en todos los dispositivos, incluidos los teléfonos celulares de generaciones anteriores, por ejemplo. Esto es más adecuado para modelos más pequeños (menos de 3 MB de tamaño) que 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: Es la alternativa en caso de que no esté disponible ninguno de los otros entornos. Es el más lento de los tres, pero siempre está disponible.
Nota: Puedes forzar uno de estos backends si sabes en qué dispositivo se ejecutará el código o, simplemente, dejar que TensorFlow.js decida por ti si no especificas esta opción.
Superpoderes del cliente
Ejecutar TensorFlow.js en el navegador web de la máquina del cliente puede generar varios beneficios que vale la pena tener en cuenta.
Privacidad
Puedes entrenar y clasificar datos en la máquina del cliente sin enviar datos a un servidor web de terceros. En ocasiones, esto puede ser un requisito para cumplir con las leyes locales, como el RGPD, por ejemplo, o cuando se procesan datos que el usuario puede querer conservar en su máquina y no enviar a un tercero.
Velocidad
Como no tienes 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 muchos más, si el usuario te otorga acceso.
Alcance y escala
Con un solo clic, cualquier persona del mundo puede hacer clic en un vínculo que le envíes, abrir la página web en su navegador y usar lo que creaste. No es necesario realizar una configuración compleja de Linux del servidor con controladores CUDA y mucho más solo para usar el sistema de aprendizaje automático.
Costo
Sin servidores, 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 menor que el de mantener un servidor (posiblemente con una tarjeta gráfica conectada) en funcionamiento las 24 horas, todos los días.
Funciones del servidor
Aprovechar la implementación de TensorFlow.js en Node.js permite las siguientes funciones.
Compatibilidad total con CUDA
En el servidor, para la aceleración de la tarjeta gráfica, debes instalar los controladores de NVIDIA CUDA para habilitar TensorFlow para que funcione con la tarjeta gráfica (a diferencia del navegador, que usa WebGL, no se necesita instalación). Sin embargo, con la compatibilidad total con CUDA, puedes aprovechar al máximo las capacidades de nivel inferior de la tarjeta gráfica, lo que genera tiempos de entrenamiento y de inferencia más rápidos. El rendimiento es similar al de la implementación de TensorFlow en Python, ya que ambos comparten el mismo backend de C++.
Tamaño del modelo
En el caso de los modelos de vanguardia de la investigación, es posible que trabajes con modelos muy grandes, tal vez de gigabytes de tamaño. Actualmente, estos modelos no se pueden ejecutar 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 un modelo de este tipo de manera eficiente.
IOT
Node.js es compatible con computadoras de placa única populares, como la Raspberry Pi, lo que significa que también puedes ejecutar modelos de TensorFlow.js en esos dispositivos.
Velocidad
Node.js está escrito en JavaScript, lo que significa que se beneficia de la compilación Just-in-Time. Esto significa que, a menudo, puedes observar mejoras en el rendimiento cuando usas Node.js, ya que se optimizará en el tiempo de ejecución, en especial para cualquier preprocesamiento que realices. Un gran ejemplo de esto se puede ver en este caso de estudio, que muestra cómo Hugging Face usó Node.js para duplicar el rendimiento de 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, comencemos a hacer cosas útiles con él.
3. Aprendizaje por transferencia
¿Qué es exactamente el aprendizaje por transferencia?
El aprendizaje por transferencia implica tomar el conocimiento que ya se adquirió para ayudar a aprender algo diferente, pero similar.
Los humanos hacemos esto todo el tiempo. En tu cerebro, tienes una vida de experiencias que puedes usar para reconocer cosas nuevas que nunca habías visto. Tomemos este sauce como ejemplo:

Según el lugar del mundo en el que te encuentres, es posible que no hayas visto este tipo de árbol antes.
Sin embargo, si te pido que me digas si hay sauces en la nueva imagen que se muestra a continuación, probablemente puedas identificarlos con bastante rapidez, aunque estén en un ángulo diferente y sean ligeramente distintos del que te mostré originalmente.

Ya tienes un montón de neuronas en el cerebro que saben cómo identificar objetos con forma de árbol y otras neuronas que son buenas para encontrar líneas rectas largas. Puedes reutilizar ese conocimiento para clasificar rápidamente un sauce, que es un objeto similar a un árbol con muchas ramas verticales largas y rectas.
Del mismo modo, si tienes un modelo de aprendizaje automático que ya se entrenó en un dominio, como el reconocimiento de imágenes, puedes volver a usarlo para realizar una tarea diferente, pero relacionada.
Puedes 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ó con un enorme conjunto de datos conocido como ImageNet, que contiene millones de imágenes etiquetadas.
En esta animación, puedes ver la gran cantidad de capas que tiene este modelo de MobileNet V1:

Durante su entrenamiento, este modelo aprendió a extraer atributos comunes que son importantes para todos esos 1,000 objetos, y muchos de los atributos de nivel inferior que usa para identificar esos objetos también pueden ser útiles para detectar objetos nuevos que nunca ha visto. Después de todo, todo es, en última instancia, una combinación de líneas, texturas y formas.
Veamos una arquitectura de red neuronal convolucional (CNN) tradicional (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 típica de un modelo de CNN que, en este caso, se entrenó para reconocer dígitos escritos a mano del 0 al 9:

Si pudieras separar las capas de nivel inferior previamente entrenadas de un modelo entrenado existente, como se muestra a la izquierda, de las capas de clasificación cerca del final del modelo, como se muestra a la derecha (a veces, se hace referencia a ellas como el encabezado de clasificación del modelo), podrías usar las capas de nivel inferior para generar características de salida para cualquier imagen determinada en función de los datos originales con los que se entrenó. Esta es la misma red con el encabezado de clasificación quitado:

Si suponemos que lo nuevo que intentas reconocer también puede usar esas características de salida que aprendió el modelo a priori, es muy probable que se puedan reutilizar para un propósito nuevo.
En el diagrama anterior, este modelo hipotético se entrenó con dígitos, por lo que tal vez lo que se aprendió sobre los dígitos también se pueda aplicar a letras como a, b y c.
Por lo tanto, ahora podrías agregar un nuevo encabezado de clasificación que intente predecir a, b o c, como se muestra a continuación:

Aquí, las capas de nivel inferior están congeladas y no se entrenan. Solo el nuevo encabezado de clasificación se actualizará para aprender de los atributos proporcionados por el modelo previamente entrenado y segmentado de la izquierda.
El acto de hacer esto se conoce como aprendizaje por transferencia y es lo que hace Teachable Machine detrás de escena.
También puedes ver que, al tener que entrenar el perceptrón multicapa solo al final de la red, el entrenamiento es mucho más rápido que si tuvieras que entrenar toda la red desde cero.
Pero ¿cómo puedes obtener subpartes de un modelo? Ve a la siguiente sección para averiguarlo.
4. TensorFlow Hub: Modelos básicos
Cómo encontrar un modelo base adecuado para usar
Para modelos de investigación más avanzados y populares, como MobileNet, puedes ir a TensorFlow Hub y, luego, filtrar los modelos adecuados para TensorFlow.js que usan la arquitectura MobileNet v3 para encontrar resultados como los que se muestran aquí:

Ten en cuenta que algunos de estos resultados son de tipo "clasificación de imágenes" (detallados en la parte superior izquierda de cada resultado de la tarjeta de modelo) y otros son de tipo "vector de características de la imagen".
Estos resultados de vectores de características de imágenes son, básicamente, las versiones previamente cortadas de MobileNet que puedes usar para obtener los vectores de características de imágenes en lugar de la clasificación final.
Los modelos como este suelen llamarse "modelos base", que luego puedes usar para realizar la transferencia de aprendizaje de la misma manera que se muestra en la sección anterior agregando un nuevo encabezado de clasificación y entrenándolo con tus propios datos.
Lo siguiente que debes verificar es en qué formato de TensorFlow.js se lanza un modelo base determinado de interés. Si abres la página de uno de estos modelos de vectores de características de MobileNet v3, puedes ver en la documentación de JS que se encuentra en forma de un modelo de grafo basado en el fragmento de código de ejemplo de la documentación que usa tf.loadGraphModel().

También se debe tener en cuenta que, si encuentras un modelo en formato de capas en lugar de formato de gráfico, puedes elegir qué capas congelar y cuáles descongelar para el entrenamiento. Esto puede ser muy útil cuando se crea un modelo para una tarea nueva, que a menudo se conoce como "modelo de transferencia". Sin embargo, por ahora, usarás el tipo de modelo de grafo predeterminado para este instructivo, que es el tipo 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 Layers, consulta el curso de cero a héroe de 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 de usar un enfoque de aprendizaje por transferencia, ya que tienes un modelo base entrenado sobre el cual puedes crear.
En segundo lugar, puedes mostrar muchos menos ejemplos de lo nuevo que intentas clasificar debido al entrenamiento que ya se llevó a cabo.
Esto es muy útil si tienes poco tiempo y recursos para recopilar datos de ejemplo de lo que deseas clasificar, y necesitas crear un prototipo rápidamente antes de recopilar más datos de entrenamiento para que sea más sólido.
Dado que se necesitan 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 decenas de 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 del aprendizaje por transferencia, es hora de crear tu propia versión de Teachable Machine. ¡Comencemos!
5. Cómo prepararse para programar
Requisitos
- Un navegador web moderno
- Conocimientos básicos de HTML, CSS, JavaScript y las Herramientas para desarrolladores de Chrome (visualización del resultado de la consola)
Vamos a codificar
Se crearon plantillas de código estándar para comenzar en Glitch.com o Codepen.io. 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 crear una bifurcación y un nuevo conjunto de archivos que puedes editar.
También puedes hacer clic en"fork" en la parte inferior derecha de la pantalla en Codepen.
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 de JavaScript (script.js)
Para tu comodidad, se agregó una importación en el archivo HTML para la biblioteca de TensorFlow.js. El aspecto resultante será el siguiente:
index.html
<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>
Alternativa: Usa tu editor web preferido o trabaja de forma local
Si quieres descargar el código y trabajar de forma local o en otro editor en línea, simplemente crea los 3 archivos mencionados anteriormente en el mismo directorio y copia y pega el código de nuestra plantilla de Glitch en cada uno de ellos.
6. Plantilla HTML de la app
¿Por dónde empiezo?
Todos los prototipos requieren una estructura HTML básica en la que puedas renderizar tus hallazgos. Configúralo ahora. Agregarás lo siguiente:
- Es un título para la página.
- Texto descriptivo
- Es un párrafo de estado.
- Un video para mantener el feed de la cámara web una vez que esté listo.
- Varios botones para iniciar la cámara, recopilar datos o restablecer la experiencia
- Importaciones para TensorFlow.js y archivos JS que codificarás más adelante.
Abre index.html y pega el siguiente código sobre el existente 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 & 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>
Desglosarlo
Analicemos parte del código HTML anterior para destacar algunos elementos clave que agregaste.
- Agregaste una etiqueta
<h1>para el título de la página junto con una etiqueta<p>con un ID de "status", que es donde imprimirás la información, ya que usas diferentes partes del sistema para ver los resultados. - Agregaste un elemento
<video>con el ID "webcam", en el que renderizarás tu transmisión de cámara web más adelante. - Agregaste 5 elementos
<button>. El primero, con el ID "enableCam", habilita la cámara. Los siguientes dos botones tienen la clase "dataCollector", que te permite recopilar imágenes de ejemplo de los objetos que deseas reconocer. El código que escribirás más adelante se diseñará de manera que puedas agregar cualquier cantidad de estos botones y funcionarán automáticamente según lo previsto.
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 usarás para representar los datos de una clase determinada. El índice se usará para codificar las clases de salida correctamente con una representación numérica en lugar de una cadena, ya que los modelos de AA solo pueden funcionar con números.
También hay un atributo data-name que contiene el nombre legible que deseas usar para esta clase, lo que te permite proporcionar un nombre más significativo al usuario en lugar de un valor de índice numérico de la codificación one-hot.
Por último, tienes un botón de entrenamiento y restablecimiento para iniciar el proceso de entrenamiento una vez que se hayan recopilado los datos o para restablecer la app, respectivamente.
- También agregaste 2 importaciones de
<script>. Uno para TensorFlow.js y el otro para script.js, que definirás en breve.
7. Agr. estilo
Valores predeterminados de los elementos
Agrega estilos para los elementos HTML que acabas de agregar y asegúrate de que se rendericen correctamente. Estos son algunos estilos que se agregan para posicionar y dimensionar los elementos correctamente. Nada demasiado especial. Sin duda, podrías agregar más elementos más adelante para crear una UX aún mejor, como viste en el video de Teachable Machine.
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%;
}
¡Genial! ¡Eso es todo lo que necesitas! Si obtienes una vista previa del resultado ahora mismo, debería verse de la siguiente manera:

8. JavaScript: Constantes y objetos de escucha de teclas
Define constantes clave
Primero, agrega algunas constantes clave que usarás en toda la app. Para comenzar, reemplaza el contenido de script.js con estas constantes:
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 = [];
Analicemos para qué sirven:
STATUSsimplemente contiene una referencia a la etiqueta de párrafo en la que escribirás las actualizaciones de estado.VIDEOcontiene una referencia al elemento de video HTML que renderizará el feed de la cámara web.ENABLE_CAM_BUTTON,RESET_BUTTONyTRAIN_BUTTONtoman referencias del DOM a todos los botones clave de la página HTML.MOBILE_NET_INPUT_WIDTHyMOBILE_NET_INPUT_HEIGHTdefinen el ancho y la altura de entrada esperados del modelo MobileNet, respectivamente. Si almacenas esto en una constante cerca de la parte superior del archivo, como se muestra aquí, si decides usar una versión diferente más adelante, será más fácil actualizar los valores una sola vez en lugar de tener que reemplazarlos en muchos lugares diferentes.STOP_DATA_GATHERse establece 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. Si le asignas un nombre más significativo a este número, el código será más legible más adelante.CLASS_NAMESactúa como una búsqueda y contiene los nombres legibles para las posibles predicciones de clase. Este array se completará más adelante.
Bien, ahora que tienes referencias a elementos clave, es hora de asociarles algunos objetos de escucha de eventos.
Agrega objetos de escucha de eventos de teclado
Comienza por agregar controladores de eventos de clic a los botones clave, como se muestra a continuación:
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: Llama a la función enableCam cuando se hace clic.
TRAIN_BUTTON: Llama a trainAndPredict cuando se hace clic.
RESET_BUTTON: Las llamadas se restablecen cuando se hace clic.
Por último, en esta sección, puedes encontrar todos los botones que tienen una clase de "dataCollector" con document.querySelectorAll(). Esto devuelve un array de elementos encontrados en el documento que coinciden con lo siguiente:
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!
}
Explicación del código:
Luego, iteras por los botones encontrados y asocias 2 objetos de escucha de eventos a cada uno. Uno para "mousedown" y otro para "mouseup". Esto te permite seguir grabando muestras mientras mantienes presionado el botón, lo que resulta ú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 legibles encontrados desde el atributo data-name 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.
script.js
let mobilenet = undefined;
let gatherDataState = STOP_DATA_GATHER;
let videoPlaying = false;
let trainingDataInputs = [];
let trainingDataOutputs = [];
let examplesCount = [];
let predict = false;
Veamos cada uno de ellos.
Primero, tienes una variable mobilenet para almacenar el modelo de MobileNet cargado. Inicialmente, se establece como indefinido.
A continuación, tienes una variable llamada gatherDataState. Si se presiona un botón "dataCollector", este cambia al ID de 1 hot de ese botón, según se define en el código HTML, para que sepas qué clase de datos estás recopilando en ese momento. Inicialmente, se establece en STOP_DATA_GATHER para que el bucle de recopilación de datos que escribas más adelante no recopile 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 reprodujo correctamente, y si está disponible para su uso. Inicialmente, se establece en false, ya que la cámara web no está encendida hasta que presionas ENABLE_CAM_BUTTON..
A continuación, define 2 arrays, trainingDataInputs y trainingDataOutputs. Estos almacenan los valores de los datos de entrenamiento recopilados a medida que haces clic en los botones "dataCollector" para los atributos de entrada generados por el modelo base de MobileNet y la clase de salida muestreada, respectivamente.
Luego, se define un array final, examplesCount,, para hacer un seguimiento de la cantidad de ejemplos que se incluyen para cada clase una vez que comiences a agregarlos.
Por último, tienes una variable llamada predict que controla tu bucle de predicción. Inicialmente, se establece en false. No se podrán realizar predicciones hasta que se establezca en true más adelante.
Ahora que se definieron todas las variables clave, carguemos el modelo base MobileNet v3 previamente cortado 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 la acción de cargar un modelo es asíncrona:
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();
En este código, defines el URL en el que se encuentra el modelo que se cargará desde la documentación de TFHub.
Luego, puedes cargar el modelo con await tf.loadGraphModel(). Recuerda establecer la propiedad especial fromTFHub en true, ya que estás cargando 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 complete la carga, puedes configurar el innerText del elemento STATUS con un mensaje para que puedas ver visualmente que se cargó correctamente y que ya puedes comenzar a recopilar datos.
Lo único que queda por hacer ahora es calentar el modelo. Con modelos más grandes como este, la primera vez que lo uses, puede tardar un momento en configurarse todo. Por lo tanto, es útil pasar ceros por el modelo para evitar cualquier espera en el futuro, en la que la sincronización puede ser más crítica.
Puedes usar tf.zeros() envuelto en un tf.tidy() para asegurarte de que los tensores se descarten 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 especificas los canales de color, que en este caso son 3, ya que el modelo espera imágenes RGB.
A continuación, registra la forma resultante del tensor que se devolvió con answer.shape() para ayudarte a comprender el tamaño de las características de la imagen que produce este modelo.
Después de definir esta función, puedes llamarla de inmediato para iniciar la descarga del modelo cuando se cargue la página.
Si ves la vista previa en vivo ahora mismo, después de unos momentos, verás que el texto de estado cambia de "Awaiting TF.js load" a "MobileNet v3 loaded successfully!", como se muestra a continuación. Asegúrate de que funcione antes de continuar.

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 de MobileNet, verás una forma de [1, 1024] impresa. El primer elemento es solo el tamaño del lote de 1, y puedes ver que, en realidad, devuelve 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 el encabezado del modelo, que es esencialmente un perceptrón multicapa muy minimalista.
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']
});
Analicemos este código. Comenzarás definiendo un modelo tf.sequential al que agregarás capas del modelo.
A continuación, agrega una capa densa como capa de entrada a este modelo. Tiene una forma de entrada de 1024, ya que las salidas de las características de MobileNet V3 son de este tamaño. Descubriste esto en el paso anterior después de pasar unos a través del modelo. Esta capa tiene 128 neuronas que usan la función de activación ReLU.
Si no conoces las funciones de activación y las capas del modelo, considera realizar el curso que se detalla al comienzo de este taller para comprender qué hacen estas propiedades en segundo plano.
La siguiente capa que se debe agregar es la capa de salida. La cantidad de neuronas debe ser igual a la cantidad de clases que intentas predecir. Para ello, puedes usar CLASS_NAMES.length para averiguar cuántas clases planeas clasificar, lo que equivale a la cantidad de botones de recopilación de datos que se encuentran en la interfaz de usuario. Como se trata de un problema de clasificación, usas la activación softmax en esta capa de salida, que se debe usar cuando intentas crear un modelo para resolver problemas de clasificación en lugar de regresión.
Ahora, imprime un model.summary() para imprimir el resumen del modelo recién definido en la consola.
Por último, compila el modelo para que esté listo para el entrenamiento. Aquí, el optimizador se establece en adam, y la pérdida será binaryCrossentropy si CLASS_NAMES.length es igual a 2, o bien usará categoricalCrossentropy si hay 3 o más clases para clasificar. También se solicitan métricas de precisión para que se puedan supervisar en los registros más adelante con fines de depuración.
En la consola, deberías ver algo como lo siguiente:

Ten en cuenta que tiene más de 130,000 parámetros entrenables. Sin embargo, como se trata de una capa densa simple de neuronas regulares, se entrenará con bastante rapidez.
Como actividad para realizar una vez que se complete el proyecto, puedes intentar cambiar la cantidad de neuronas en la primera capa para ver qué tan baja puedes hacerla y seguir obteniendo un rendimiento decente. A menudo, con el aprendizaje automático, se requiere cierto nivel de prueba y error para encontrar los valores óptimos de los parámetros que te brindan el mejor equilibrio entre el uso de recursos y la velocidad.
11. Cómo habilitar la cámara web
Ahora es el momento de completar la función enableCam() que definiste antes. Agrega una nueva función llamada hasGetUserMedia() como se muestra a continuación y, luego, reemplaza el contenido de la función enableCam() definida anteriormente por el código correspondiente que se muestra a continuación.
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');
}
}
Primero, crea una función llamada hasGetUserMedia() para verificar si el navegador admite getUserMedia(). Para ello, comprueba la existencia de propiedades clave de las APIs 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 es compatible, define algunas restricciones para tu llamada a getUserMedia(), como que solo quieres la transmisión de video y que prefieres que el width del video tenga un tamaño de 640 píxeles y que el height sea de 480 píxeles. ¿Por qué? Bueno, no tiene mucho sentido obtener un video más grande que este, ya que debería cambiar su tamaño a 224 por 224 píxeles para ingresarlo en el modelo de MobileNet. También puedes ahorrar recursos de procesamiento solicitando una resolución más pequeña. 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 detallado anteriormente y, luego, espera a que se devuelva el stream. Una vez que se devuelve el stream, puedes obtener tu elemento VIDEO para reproducir el stream configurándolo como su valor srcObject.
También debes agregar un eventListener en el elemento VIDEO para saber cuándo se cargó y se reproduce correctamente el stream.
Una vez que se cargue el vapor, puedes establecer videoPlaying como verdadero y quitar el ENABLE_CAM_BUTTON para evitar que se vuelva a hacer clic en él. Para ello, establece su clase como "removed".
Ahora ejecuta tu código, haz clic en el botón para habilitar la cámara y permite el acceso a la cámara web. Si es la primera vez que lo haces, deberías verte renderizado en el elemento de video de la página, como se muestra a continuación:

Bien, ahora es momento de agregar una función para controlar 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 la función gatherDataForClass(). que está vacía. Esta es la función que asignaste como controlador de eventos para los botones dataCollector al comienzo del codelab.
script.js
/**
* Handle Data Gather for button mouseup/mousedown.
**/
function gatherDataForClass() {
let classNumber = parseInt(this.getAttribute('data-1hot'));
gatherDataState = (gatherDataState === STOP_DATA_GATHER) ? classNumber : STOP_DATA_GATHER;
dataGatherLoop();
}
Primero, verifica el atributo data-1hot en el botón en el que se hizo clic actualmente llamando a this.getAttribute() con el nombre del atributo, en este caso data-1hot como parámetro. Como se trata de una cadena, 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 estableciste en -1), significa que no estás recopilando datos en este momento y que se activó un evento mousedown. Establece gatherDataState como el classNumber que acabas de encontrar.
De lo contrario, significa que actualmente estás recopilando datos y que el evento que se activó fue un evento mouseup, y ahora quieres dejar de recopilar datos para esa clase. Solo debes volver a establecerlo 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 es la 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 imágenes del video de la cámara web, pasarlas por el modelo MobileNet y capturar los resultados de ese modelo (los 1, 024 vectores de atributos).
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.
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);
}
}
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 gatherDataState no es igual a STOP_DATA_GATHER y se está presionando un botón para recopilar datos de la clase.
A continuación, incluye tu código en un tf.tidy() para descartar los tensores creados en el código que sigue. El resultado de esta ejecución de código tf.tidy() se almacena en una variable llamada imageFeatures.
Ahora puedes capturar un fotograma 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 para que tenga la forma correcta para la entrada del modelo de MobileNet. Usa una llamada a tf.image.resizeBilinear() con el tensor que deseas cambiar de forma como el primer parámetro y, luego, una forma que defina la nueva altura y el nuevo ancho según las constantes que ya creaste. Por último, establece alignCorners en true pasando el tercer parámetro para 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 cambio de tamaño primitivo estira la imagen, ya que la imagen de la cámara web tiene un tamaño de 640 por 480 píxeles, y el modelo necesita una imagen cuadrada de 224 por 224 píxeles.
Para los fines de esta demostración, esto debería funcionar bien. Sin embargo, una vez que completes este codelab, es posible que desees intentar recortar un cuadrado de esta imagen para obtener mejores resultados en cualquier sistema de producción que crees más adelante.
A continuación, normaliza los datos de la imagen. Los datos de la imagen siempre están en el rango de 0 a 255 cuando se usa tf.browser.frompixels(), por lo que puedes dividir resizedTensorFrame por 255 para asegurarte de que todos los valores estén entre 0 y 1, que es lo que espera el modelo de MobileNet como entradas.
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(), al que le pasas la versión expandida de normalizedTensorFrame con expandDims() para que sea un lote de 1, ya que el modelo espera un lote de entradas para el procesamiento.
Una vez que se devuelve el resultado, puedes llamar de inmediato a squeeze() en ese resultado devuelto para reducirlo a un tensor 1D, que luego devuelves y asignas a la variable imageFeatures que captura el resultado de tf.tidy().
Ahora que tienes el imageFeatures del modelo de MobileNet, puedes registrarlo agregándolo al array trainingDataInputs que definiste anteriormente.
También puedes registrar lo que representa esta entrada enviando el gatherDataState actual al array trainingDataOutputs.
Ten en cuenta que la variable gatherDataState se habría establecido en el ID numérico de la clase actual para la que estás registrando 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 incrementar el examplesCount del 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, itera el array CLASS_NAMES y, luego, imprime el nombre legible para los usuarios 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. Esto continuará tomando muestras de fotogramas del video hasta que se detecte el mouseup del botón y se establezca gatherDataState 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, luego, hacer clic y mantener presionado cada uno de los botones de recopilación de datos para recopilar ejemplos de cada clase de datos. Aquí me ves recopilando datos para mi teléfono celular y mi mano, respectivamente.

Deberías ver el texto de 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 código para tu función trainAndPredict() actualmente vacía, que es donde se lleva a cabo el aprendizaje por transferencia. Veamos el código:
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);
}
Primero, asegúrate de detener las predicciones actuales configurando predict en false.
A continuación, mezcla los arrays de entrada y salida con tf.util.shuffleCombo() para asegurarte de que el orden no cause problemas en el entrenamiento.
Convierte tu array de salida, trainingDataOutputs,, en un tensor1d de tipo int32 para que esté listo para usarse en una codificación one-hot. Esto 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 la CLASS_NAMES.length. Tus resultados codificados con un solo valor activo ahora se almacenan en un tensor nuevo llamado oneHotOutputs.
Ten en cuenta que, actualmente, trainingDataInputs es un array de tensores registrados. Para usarlos en el entrenamiento, deberás convertir el array de tensores en un tensor 2D normal.
Para ello, existe una excelente función dentro de la biblioteca de TensorFlow.js llamada tf.stack().
que toma un array de tensores y los apila para producir un tensor de mayor dimensión como salida. En este caso, se devuelve un tensor 2D, que es un lote de entradas unidimensionales de 1,024 de longitud cada una que contiene los atributos registrados, 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 oneHotOutputs para representar los datos de entrenamiento que se usarán para las entradas de ejemplo y los resultados objetivo, respectivamente. En el objeto de configuración del 3ᵉʳ parámetro, establece shuffle en true, usa batchSize de 5, con epochs establecido en 10 y, luego, especifica un callback para onEpochEnd en la función logProgress que definirás en breve.
Por último, puedes desechar los tensores creados, ya que el modelo ahora está entrenado. Luego, puedes volver a establecer predict en true para permitir que se realicen predicciones nuevamente y, luego, llamar a la función predictLoop() para comenzar a predecir imágenes de la cámara web en vivo.
También puedes definir la función logProcess() para registrar el estado del entrenamiento, que se usa en model.fit() más arriba 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 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:
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);
}
}
Primero, verifica que predict sea verdadero, de modo que las predicciones solo se realicen después de que se entrene un modelo y esté disponible para su uso.
A continuación, puedes obtener las características de la imagen actual de la misma manera que lo hiciste en la función dataGatherLoop(). Básicamente, tomas un fotograma de la cámara web con tf.browser.from pixels(), lo normalizas, lo redimensionas a 224 x 224 píxeles y, luego, pasas esos datos por el modelo MobileNet para obtener las características de la imagen resultante.
Sin embargo, ahora puedes usar el encabezado del modelo recién entrenado para realizar una predicción pasando el imageFeatures resultante que acabas de encontrar a través de la función predict() del modelo entrenado. Luego, puedes comprimir el tensor resultante para que vuelva a ser unidimensional y asignarlo a una variable llamada prediction.
Con este 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() para acceder a los datos subyacentes en JavaScript y descubrir la posición del elemento con el valor más alto. Este valor se almacena en la variable llamada highestIndex.
También puedes obtener las puntuaciones de confianza de la predicción reales de la misma manera llamando a arraySync() en el tensor prediction directamente.
Ahora tienes todo lo que necesitas para actualizar el texto de STATUS con los datos de prediction. Para obtener la cadena legible por humanos de la clase, solo tienes que buscar el highestIndex en el array CLASS_NAMES y, luego, tomar el valor de confianza de predictionArray. Para que sea más fácil de leer como porcentaje, solo tienes que multiplicar por 100 y math.floor() el resultado.
Por último, puedes usar window.requestAnimationFrame() para llamar a predictionLoop() de nuevo cuando esté todo listo y obtener la clasificación en tiempo real de tu transmisión de video. Esto continúa hasta que predict se establece en false si eliges entrenar un modelo nuevo con datos nuevos.
Esto te lleva a la pieza final del rompecabezas. Implementar el botón de restablecimiento
15. Implementa el botón de restablecimiento
¡Ya casi terminas! La última pieza del rompecabezas es implementar un botón de restablecimiento para comenzar de nuevo. A continuación, se muestra el código de tu función reset() actualmente vacía. Actualízalo de la siguiente manera:
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);
}
Primero, detén todos los bucles de predicción en ejecución configurando predict en false. A continuación, borra todo el contenido del array examplesCount estableciendo 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 actuales y asegúrate de dispose() cada tensor que contenga para liberar memoria nuevamente, ya que el recolector de basura de JavaScript no limpia los tensores.
Una vez que lo hagas, podrás establecer la longitud del array en 0 de forma segura en los arrays trainingDataInputs y trainingDataOutputs para borrarlos también.
Por último, establece el texto de STATUS en algo razonable y, luego, imprime los tensores que quedan en la memoria como una verificación de cordura.
Ten en cuenta que aún habrá unos cientos de tensores en la memoria, ya que no se desecharán el modelo de MobileNet ni el perceptrón multicapa que definiste. Deberás volver a utilizarlos con datos de entrenamiento nuevos si decides volver a entrenar el modelo después de este restablecimiento.
16. Vamos a probarlo.
Es hora de probar tu propia versión de Teachable Machine.
Ve a la vista previa en vivo, habilita la cámara web, recopila al menos 30 muestras para la clase 1 de algún objeto de tu habitación y, luego, haz lo mismo para la clase 2 de un objeto diferente, haz clic en Train y revisa el registro de la consola para ver el progreso. El entrenamiento debería ser bastante rápido:

Una vez que se haya entrenado, muestra los objetos a la cámara para obtener predicciones en vivo que se imprimirán en el área de texto de estado de la página web cerca de la parte superior. Si tienes problemas, consulta mi código de trabajo completo para ver si olvidaste copiar algo.
17. Felicitaciones
¡Felicitaciones! Acabas de completar tu primer ejemplo de aprendizaje por transferencia con TensorFlow.js en vivo en el navegador.
Pruébalo con una variedad de objetos. Es posible que notes que algunas cosas son más difíciles de reconocer que otras, en especial si se parecen a algo más. Es posible que debas agregar más clases o datos de entrenamiento para poder distinguirlas.
Resumen
En este codelab, aprendiste lo siguiente:
- Qué es el aprendizaje por transferencia y sus ventajas en comparación con el entrenamiento de un modelo completo
- Cómo obtener modelos para volver a usarlos desde TensorFlow Hub
- Cómo configurar una app web adecuada para el aprendizaje por transferencia
- Cómo cargar y usar un modelo base para generar características de imágenes
- Cómo entrenar un nuevo encabezado de predicción que pueda reconocer objetos personalizados a partir de imágenes de la cámara web
- 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 para un caso de uso del mundo real en el que podrías estar trabajando? Quizás podrías revolucionar la industria en la que trabajas actualmente para ayudar a las personas de tu empresa a entrenar modelos que clasifiquen elementos importantes en su trabajo diario. Las posibilidades son infinitas.
Para ir más allá, considera tomar este curso completo de forma gratuita, que te muestra cómo combinar los 2 modelos que tienes actualmente en este codelab en un solo modelo para mayor eficiencia.
Además, si te interesa conocer más sobre la teoría detrás de la aplicación original de Teachable Machine, consulta este instructivo.
Comparte tus creaciones con nosotros
También puedes extender fácilmente lo que creaste hoy para otros casos de uso creativos, y te recomendamos que pienses de manera innovadora y sigas hackeando.
Recuerda etiquetarnos en las redes sociales con el hashtag #MadeWithTFJS para tener la oportunidad de que se incluya tu proyecto en nuestro blog de TensorFlow o, incluso, en futuros eventos. Nos encantaría ver tus creaciones.
Sitios web que puedes revisar
- Sitio web oficial de TensorFlow.js
- Modelos prediseñados de TensorFlow.js
- API de TensorFlow.js
- TensorFlow.js Show & Tell: Inspírate y mira lo que crearon otros.