Nothing Special   »   [go: up one dir, main page]

Javascript Libro Español

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 561

Traducido del inglés al español - www.onlinedoctranslator.

com

Redes neuronales en TensorFlow.js

Shanqing Cai
stanley bileschi
Eric D. Nielsen
François Chollet
Prefacio por

Nikhil Thorat y Daniel Smilkov

MANEJO
Trabajar con datos

Ingerir datos Limpiar datos Aumentar datos


Datos Secta. 6.1, 6.2, 6.3 Secta. 6.4 Secta. 6.5

Visualizar datos
Secta. 7.1

Construcción de modelos 1: elegir tipos de capas clave en función de sus datos

Tipo de datos de entrada Capa recomendada A Pi Referencia

Datos numéricos Denso Capítulos 2 y 3


(sin orden secuencial)

Imágenes o datos que se pueden representar como convolución y agrupación 2D Capítulos 4 y 5


imágenes (p. ej., audio, tablero de juego)

Datos secuenciales, incluido el texto • RNN (LSTM, GRU) • Secta. 9.1.2


• Incrustación • Secta. 9.2.3
• convolución 1D • Secta. 9.2.4
• Atencional • Secta. 9.3

Construcción de modelos 2: elección de funciones de activación, pérdida y métricas de última capa

tipo de tarea última capa


(¿Qué estás prediciendo?) activación Función de pérdida Métrica) Referencia

Regresión Lineal error medio cuadrado (Igual que pérdida) Capitulo 2


(predecir un número real) error absoluto medio Secta. 9.1

clasificación binaria Sigmoideo binariaCrosentropía Exactitud, precisión, Secta. 3.1, 3.2, 9.2
(tomar una decisión binaria) memoria, sensibilidad,
TPR, FPR, ROC, AUC

Clasificación multiclase softmax Cruzentropía categórica Precisión, confusión Secta. 3.3, 9.3
(decidir entre varias matriz
clases)
Una combinación de lo anterior (por (Múltiple) Función de pérdida personalizada (múltiple) Secta. 5.2
ejemplo, números más clases)

Tipos de tareas avanzadas y misceláneas Referencia

Transferencia de aprendizaje Capítulo 5


(aplicando un modelo previamente entrenado a nuevos datos)

aprendizaje generativo Capítulo 10


(generando nuevos ejemplos basados en datos de entrenamiento)

Aprendizaje reforzado Capítulo 11


(capacitar a un agente para interactuar con el entorno)

Continúa dentro de la contraportada


Aprendizaje profundo con JavaScript
Aprendizaje profundo
con JavaScript
norteREDES EURALES ENTENSORFBAJO.JS

SHANQING CAI
STANLEY BILESCHI
ERIC D. NIELSEN
CON FRANÇOIS CHOLLET

PRÓLOGO DE NIKHIL THORAT


DANIEL SMILKOV

MANEJO
SHELTERIISLA
Para obtener información en línea y solicitar este y otros libros de Manning, visite
www.manning.com. La editorial ofrece descuentos en este libro cuando se pide en cantidad. Para
obtener más información, póngase en contacto

Departamento de Ventas
Especiales Manning Publications
Co. 20 Baldwin Road
apartado de correos 761

Shelter Island, NY 11964 Correo


electrónico: orders@manning.com

© 2020 por Manning Publications Co. Todos los derechos reservados.

Ninguna parte de esta publicación puede reproducirse, almacenarse en un sistema de recuperación o


transmitirse de ninguna forma o por medios electrónicos, mecánicos, fotocopiados o de otro modo, sin el
permiso previo por escrito del editor.

Muchas de las designaciones utilizadas por los fabricantes y vendedores para distinguir sus productos se reclaman como
marcas comerciales. Donde esas designaciones aparecen en el libro, y Manning Publications estaba al tanto de un
reclamo de marca registrada, las designaciones se han impreso en mayúsculas iniciales o en mayúsculas.

Reconociendo la importancia de preservar lo que se ha escrito, es política de Manning que los libros que
publicamos se impriman en papel libre de ácido, y realizamos nuestros mejores esfuerzos con ese fin.
Reconociendo también nuestra responsabilidad de conservar los recursos de nuestro planeta, los libros de
Manning se imprimen en papel que es al menos un 15 por ciento reciclado y procesado sin el uso de cloro
elemental.

Manning Publications Co. Redactor de desarrollo: jenny fuerte


20 Baldwin Road Editor de desarrollo técnico: Marc Philippe Huget
apartado de correos 761 Editor de reseñas: Ivan Martinovic
Isla del refugio, NY 11964 redactor del proyecto: lori weidert
Editor de copia: Rebecca Deuel Gallegos
Corrector de pruebas: jason everett
Corrector técnico: Karsten Strøbæck
Tipógrafo: dottie marsico
Diseñador de la portada: Marija Tudor

ISBN 9781617296178
Impreso en los Estados Unidos de América
contenidos breves
PAGSARTE1 METROOTIVACIÓN Y CONCEPTOS BÁSICOS. ..................................1
1- Aprendizaje profundo y JavaScript 3

PAGSARTE2 ASUAVE INTRODUCCIÓN ATENSORFBAJO.JS. ............. 35


2 - Primeros pasos: regresión lineal simple en TensorFlow.js 37
3 - Adición de no linealidad: más allá de las sumas ponderadas 79
4 - Reconocimiento de imágenes y sonidos usando convnets 117
5 - Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas 152

PAGSARTE3 AAPRENDIZAJE PROFUNDO AVANZADO CONTENSORFBAJO.JS. ....... 199


6 - Trabajar con datos 201
7 - Visualización de datos y modelos 246
8 - Underfitting, overfitting y el flujo de trabajo universal del
aprendizaje automático 273
9 - Deep learning para secuencias y texto 292
10 - Aprendizaje profundo generativo 334
11 - Conceptos básicos del aprendizaje por refuerzo profundo 371

PAGSARTE4 SRESUMEN Y PALABRAS DE CIERRE. .................................... 415


12 - Probar, optimizar e implementar modelos 417
13 - Resumen, conclusiones y más allá 453

v
contenido
prefacio XIII
prefacio XV
expresiones de gratitud xvii
sobre este libro xix
Sobre los autores XXII
sobre la ilustración de la portada XXIII

PAGSARTE1 METROOTIVACIÓN Y CONCEPTOS BÁSICOS..................1

1 Aprendizaje profundo y JavaScript 3


1.1 Inteligencia artificial, aprendizaje automático, redes neuronales,
y aprendizaje profundo 6
Programación tradicional 6 -
Aprendizaje automático: en qué se diferencia de
de inteligencia artificial 7 Redes neuronales y profundas
-

aprendizaje 12 ¿Por qué aprendizaje profundo? ¿Porqué ahora?


- dieciséis

1.2 ¿Por qué combinar JavaScript y aprendizaje automático? 18


Aprendizaje profundo con Node.js 24 -
El ecosistema JavaScript 25
1.3 ¿Por qué TensorFlow.js? 27
Una breve historia de TensorFlow, Keras y TensorFlow.js 27 Por qué -

TensorFlow.js: una breve comparación con bibliotecas 31 Cómo -

similares ¿Usa TensorFlow.js en todo el mundo? 31Lo que este libro


y no -

te enseñará sobre TensorFlow.js 32

viii
viii CONTENIDO

PAGSARTE2 ASUAVE INTRODUCCIÓN A


TENSORFBAJO.JS.............................................35

2 Primeros pasos: regresión lineal simple en TensorFlow.js 37


2.1 Ejemplo 1: Predecir la duración de una descarga usando
TensorFlow.js 38
Resumen del proyecto: predicción de la duración 38
-
Una nota sobre las listas de códigos

e interacciones de la consola 39 Creando y formateando el


-

datos 40 -
Definición de un modelo simple 43 Ajuste del modelo -

a los datos de entrenamiento 46 -


Usando nuestro modelo entrenado para hacer

predicciones 48 Resumen de nuestro primer ejemplo 49


-

2.2 Inside Model.fit(): Descenso de gradiente de disección


del ejemplo 1 50
Las intuiciones detrás de la optimización del descenso de gradiente 50
Propagación hacia atrás: descenso de gradiente interno 56

2.3 Regresión lineal con múltiples funciones de entrada 59


El conjunto de datos de precios de vivienda de Boston 60 Obtener y ejecutar el
-

Proyecto de vivienda de Boston de datos de vivienda 61 Accediendo al Boston-


-

de GitHub 63 -
Definiendo con precisión el Boston-vivienda
problema 65 -
Una ligera desviación hacia la normalización de datos 66
Regresión lineal sobre los datos de vivienda de Boston 70
2.4 Cómo interpretar su modelo 74
Extracción
Extrayendo el significado de los pesos aprendidos 74 pesos
-
del interna
modelo 75 Advertencias sobre la interpretabilidad
- 77

3 Adición de no linealidad: más allá de las sumas ponderadas 79

3.1 No linealidad: qué es y para qué sirve 80


Construyendo la intuición para la no linealidad en redes neuronales 82
Hiperparámetros y optimización de hiperparámetros 89

3.2 No linealidad en la salida: Modelos para la clasificación 92


¿Qué es la clasificación binaria? 92 -
Midiendo la calidad de
clasificadores binarios: precisión, recuperación, exactitud y curvas ROC La curva 96
ROC: muestra las compensaciones en la clasificación binaria 99 Entropía cruzada
binaria: la función de pérdida para la clasificación binaria 103
3.3 Clasificación multiclase 106
Codificación one-hot de datos categóricos 107 softmax -

activación 109 Entropía cruzada categórica: la función de pérdida para


-

la clasificación multiclase 111 Matriz de confusión: análisis detallado


-

de la clasificación multiclase 113


CONTENIDO ix

4 Reconocimiento de imágenes y sonidos usando convnets


4.1 De vectores a tensores: representación de imágenes
117
118
El conjunto de datos MNIST 119
4.2 Tu primera convnet 120
capa conv2d 122 -
maxPooling2d capa 126 repitiendo
-

motivos de convolución y capas de 127 Aplanado y denso


-

agrupación 128 Entrenamiento de la


- 130 Usando una convnet para
-

convnet hacer predicciones 134

4.3 Más allá de los navegadores: entrenar modelos más rápido con
Node.js 137
Dependencias e importaciones para usar el modelo tfjs-nodeGuardando el-

137 de Node.js y cargarlo en el navegador 142

4.4 Reconocimiento de palabras habladas: aplicación de convnets en


datos de audio 144

Espectrogramas: representación de sonidos como imágenes 145

5 Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas 152

5.1 Introducción al aprendizaje por transferencia: reutilización de modelos


preentrenados 153

Transferir aprendizaje basado en formas de salida compatibles: congelar capas


155 Transferencia de aprendizaje en formas de salida incompatibles: creación de
-

un nuevo modelo utilizando salidas del modelo base 161 Cómo aprovechar al
máximo el aprendizaje de transferencia a través del ajuste fino: un ejemplo de
audio 174

5.2 Detección de objetos mediante transferencia de aprendizaje en


una convnet 185
Un problema simple de detección de objetos basado en escenas sintetizadas 186
Inmersión profunda en la detección simple de objetos 187

PAGSARTE3 AAPRENDIZAJE PROFUNDO AVANZADO CON


TENSORFBAJO.JS. ..........................................199

6 Trabajar con datos 201


6.1 Uso de tf.data para administrar datos 202
El objeto tf.data.Dataset 203 Acceder a los -
Crear un tf.data.Dataset 203 209 -

datos en su conjunto de datos conjuntos Manipulación de datos de tfjs


de datos 210

6.2 Modelos de entrenamiento con model.fitDataset 214


X CONTENIDO

6.3 Patrones comunes para acceder a datos 220


Trabajar con datos en formato CSV 220 tf.data.webcam()
Acceder a datos de video usando
-

225 Acceder a datos de audio usando


-

tf.data.microphone() 228

6.4 Es probable que sus datos sean defectuosos: Cómo lidiar con los problemas

en tus datos 230


Teoría de los datos 231 -
Detección y limpieza de problemas con
datos 235
6.5 Aumento de datos 242

7 Visualización de datos y modelos


7.1 Visualización de datos 247
246

247
Visualización de datos con tfjs-vis Visualización -
Un estudio de caso integrador:
de datos meteorológicos con tfjs-vis 255

7.2 Visualización de modelos después del entrenamiento 260


Visualizando las activaciones internas de un convnet 262
Visualizar a qué capas convolucionales son sensibles: activar al máximo
las imágenes 265 -
Interpretación visual de un convnet
resultado de clasificación 269

8 Underfitting, overfitting y el flujo de trabajo universal


de aprendizaje automático 273
8.1 Formulación del problema de predicción de la temperatura 274
8.2 Ajuste insuficiente, sobreajuste y contramedidas 278
Desmontaje 278 -
Reequipamiento 280 Reducción del sobreajuste
-

con regularización de peso y visualizarlo funcionando 282


8.3 El flujo de trabajo universal del aprendizaje automático 287

9 Aprendizaje profundo para secuencias y texto 292


9.1 Segundo intento de predicción meteorológica:
Introducción a los RNN 294
Por qué las capas densas no logran modelar el orden 294 -
Cómo RNN
secuencial modelar el orden secuencial 296

9.2 Creación de modelos de aprendizaje profundo para texto 305


Cómo se representa el texto en el aprendizaje automático: codificación one-hot y
multi-hot 306 Primer intento de análisis de sentimiento
-

problema 308 Una representación de texto más eficiente: Word


-

incrustaciones 310 Convenciones 1D 312


-
CONTENIDO xi

9.3 Tareas de secuencia a secuencia con mecanismo


de atención 321
Formulación de la arquitectura del decodificador de tarea 321 -
El codificador-
de secuencia a secuencia y el mecanismo de atención en el 324 -
Bucear profundo

modelo codificador-decodificador basado en la atención 327

10 Aprendizaje profundo generativo

10.1 Generación de texto con LSTM 335


334

335
Predictor del siguiente carácter: una forma sencilla de generar texto
El ejemplo de generación de texto LSTM 337 Temperatura:
-

Aleatoriedad ajustable en el texto generado 342

10.2 Codificadores automáticos variacionales: Encontrando un sistema eficiente y

representación vectorial estructurada de imágenes 345


Autocodificador clásico y VAE: Ideas básicas 345 Un detallado -

ejemplo de VAE: El ejemplo Fashion-MNIST 349


10.3 Generación de imágenes con GAN 356
La idea básica detrás de GAN 357 Los bloques de construcción de
-

ACGAN 360 Profundizar en el entrenamiento de ACGAN Ver el


- 363
entrenamiento de MNIST ACGAN y la generación 366

11 Conceptos básicos del aprendizaje por refuerzo profundo 371

11.1 La formulación de problemas de aprendizaje por


refuerzo 373
11.2 Redes de políticas y gradientes de políticas: el poste del carro
ejemplo 376
Cart-pole como red de problemas de aprendizaje por 376 -
Política
refuerzo 378 -
Capacitación de la red de políticas: El REINFORCE
algoritmo 381
11.3 Redes de valor y Q-learning: El juego de la serpiente
ejemplo 389
Serpiente como un proceso de problema de aprendizaje 389 -
decisión markoviana
por refuerzo y valores Q 392 -
Red Q profunda 396 Capacitación
-

la red Q profunda 399

PAGSARTE4 SRESUMEN Y PALABRAS DE CIERRE. ....................415

12 Probar, optimizar e implementar modelos 417


12.1 Prueba de modelos de TensorFlow.js 418
Prueba conen
Pruebas unitarias tradicionales 419 Consideraciones
-
valores dorados 422
torno a la formación continua 424
xi CONTENIDO

12.2 Optimización del modelo 425


Optimización del tamaño del modelo a través de la cuantificación del peso posterior

al entrenamiento 426 Optimización de la velocidad de inferencia mediante la


-

conversión de GraphModel 434

12.3 Implementación de modelos de TensorFlow.js en varias plataformas


y ambientes 439
Consideraciones adicionales al implementar en la web 439
Implementación en la nube que sirve 440 -
Implementación en un navegador 441 -

extensión, como Chrome Extension Implementación de TensorFlow.js


443
modelos en aplicaciones móviles basadas en JavaScript TensorFlow.js Desplegando
modelos en -

aplicaciones de escritorio multiplataforma basadas en JavaScript 445 Implementación de


-

modelos de TensorFlow.js en WeChat y otros sistemas de complementos de aplicaciones


móviles basados en JavaScript 447 Implementación de modelos de TensorFlow.js en
computadoras de placa única Resumen de implementaciones 450 448

13 Resumen, conclusiones y más allá


13.1 Conceptos clave en revisión 454
453

Diversos enfoques de la IA 454 -


Lo que hace que el aprendizaje profundo se mantenga

entre los subcampos del aprendizaje automático sobre 455 Cómo pensar
-

el aprendizaje profundo a un alto nivel 455 -


Habilitación de llave

tecnologías de aprendizaje profundo 456 -


Aplicaciones y
oportunidades desbloqueadas por el aprendizaje profundo en JavaScript 457
13.2 Descripción general rápida del flujo de trabajo y los algoritmos de
aprendizaje profundo en TensorFlow.js 458

458
El flujo de trabajo universal del aprendizaje profundo supervisado
Revisión de modelos y tipos de capas en TensorFlow.js: una referencia
rápida 460 Uso de modelos preentrenados de TensorFlow.js El espacio
- 465
de posibilidades 468 Limitaciones del aprendizaje profundo
- 470
13.3 Tendencias en el aprendizaje profundo 473

13.4 Consejos para una mayor exploración 474


Practique problemas de aprendizaje automático del mundo real en 474
Kaggle Lea sobre los últimos desarrollos en arXiv 475 -
Explorar el
TensorFlow.js Ecosistema 475

Apéndice A Instalación de tfjs-node-gpu y sus dependencias 477


apéndice B Un tutorial rápido de tensores y operaciones en el glosario de 482
TensorFlow.js507

índice 519
prefacio
Cuando iniciamos TensorFlow.js (TF.js), anteriormente llamado deeplearn.js, el aprendizaje automático
(ML) se realizaba principalmente en Python. Como desarrolladores de JavaScript y practicantes de ML en
el equipo de Google Brain, rápidamente nos dimos cuenta de que había una oportunidad de unir los dos
mundos. Hoy, TF.js ha empoderado a un nuevo conjunto de desarrolladores de la extensa comunidad de
JavaScript para construir e implementar modelos ML y habilitó nuevas clases de computación en el
dispositivo.
TF.js no existiría en su forma actual sin Shanqing, Stan y Eric. Sus contribuciones a TensorFlow
Python, incluido TensorFlow Debugger, una ejecución entusiasta y una infraestructura de
compilación y prueba, los posicionaron de manera única para unir los mundos de Python y
JavaScript. Al principio del desarrollo, su equipo se dio cuenta de la necesidad de una biblioteca
además de deeplearn.js que proporcionaría elementos básicos de alto nivel para desarrollar
modelos de aprendizaje automático. Shanqing, Stan y Eric, entre otros, crearon capas de TF.js, lo
que permitió la conversión de modelos de Keras a JavaScript, lo que aumentó drásticamente la
riqueza de modelos disponibles en el ecosistema de TF.js. Cuando TF.js Layers estuvo listo,
lanzamos TF.js al mundo.
Para investigar las motivaciones, los obstáculos y los deseos de los desarrolladores de software,
Carrie Cai y Philip Guo implementaron una encuesta en el sitio web TF.js. Este libro es una respuesta
directa al resumen del estudio: "Nuestro análisis encontró que los deseos de los desarrolladores por los
marcos de ML iban más allá de simplemente querer ayuda con las API: más fundamentalmente, deseaban
orientación sobre la comprensión y la aplicación de los fundamentos conceptuales del propio ML".1

Aprendizaje profundo con JavaScriptcontiene una combinación de teoría de aprendizaje profundo y ejemplos
del mundo real en JavaScript con TF.js. Es un gran recurso para los desarrolladores de JavaScript.

1
C. Cai y P. Guo, (2019) "Desarrolladores de software que aprenden aprendizaje automático: motivaciones, obstáculos y deseos"
Simposio IEEE sobre lenguajes visuales y computación centrada en el ser humano, 2019.

XIII
xiv PREFACIO

sin experiencia en ML o antecedentes matemáticos formales, así como profesionales de ML que deseen
ampliar su trabajo en el ecosistema de JavaScript. Este libro sigue la plantilla deAprendizaje profundo con
Python, uno de los textos de ML aplicado más populares, escrito por el creador de Keras, François Chollet.
Ampliando el trabajo de Chollet,Aprendizaje profundo con JavaScripthace un trabajo increíble basándose
en las cosas únicas que JavaScript tiene para ofrecer: interactividad, portabilidad y computación en el
dispositivo. Cubre los conceptos básicos de ML, pero no rehuye los temas de ML de última generación,
como la traducción de textos, los modelos generativos y el aprendizaje por refuerzo. Incluso brinda
consejos pragmáticos sobre la implementación de modelos ML en aplicaciones del mundo real escritos
por profesionales que tienen una amplia experiencia en la implementación de ML en el mundo real. Los
ejemplos de este libro están respaldados por demostraciones interactivas que demuestran las ventajas
únicas del ecosistema de JavaScript. Todo el código es de código abierto, por lo que puede interactuar con
él y bifurcarlo en línea.
Este libro debería servir como fuente autorizada para los lectores que desean aprender
ML y usar JavaScript como su idioma principal. Sentado a la vanguardia de ML y JavaScript,
esperamos que encuentre útiles los conceptos de este libro y que el viaje en Java-Script ML
sea fructífero y emocionante.
— norteIKHILTHORA YDanielSMILKOV,
inventores de deeplearn.js
y líderes técnicos de TensorFlow.js
prefacio
El evento más significativo en la historia reciente de la tecnología es quizás la explosión en el poder de las
redes neuronales desde 2012. Fue entonces cuando el crecimiento de los conjuntos de datos etiquetados,
los aumentos en el poder de cómputo y las innovaciones en los algoritmos se juntaron y alcanzaron una
masa crítica. Desde entonces, las redes neuronales profundas han hecho posibles tareas que antes eran
inalcanzables y han aumentado la precisión en otras tareas, llevándolas más allá de la investigación
académica y hacia aplicaciones prácticas en dominios como el reconocimiento de voz, el etiquetado de
imágenes, los modelos generativos y los sistemas de recomendación, solo por nombrar algunos. pocos.

Fue en este contexto que nuestro equipo de Google Brain comenzó a desarrollar TensorFlow.js.
Cuando comenzó el proyecto, muchos consideraron el "aprendizaje profundo en JavaScript" como una
novedad, tal vez un truco, divertido para ciertos casos de uso, pero que no debía perseguirse con
seriedad. Si bien Python ya tenía varios marcos potentes y bien establecidos para el aprendizaje profundo,
el panorama del aprendizaje automático de JavaScript seguía fragmentado e incompleto. Del puñado de
bibliotecas de JavaScript disponibles en ese momento, la mayoría solo admitía la implementación de
modelos entrenados previamente en otros idiomas (generalmente en Python). Para los pocos que
admitían la creación y el entrenamiento de modelos desde cero, el alcance de los tipos de modelos
admitidos era limitado. Teniendo en cuenta el estado popular de JavaScript y su ubicuidad que se extiende
a ambos lados del cliente y del servidor, esta era una situación extraña.
TensorFlow.js es la primera biblioteca de calidad industrial completa para crear redes neuronales en
JavaScript. La gama de capacidades que proporciona abarca múltiples dimensiones. En primer lugar,
admite una amplia gama de capas de redes neuronales, adecuadas para varios tipos de datos, desde
numéricos hasta texto, desde audio hasta imágenes. En segundo lugar, proporciona API para cargar
modelos previamente entrenados para inferencia, ajustar modelos previamente entrenados y construir y
entrenar modelos desde cero. En tercer lugar, proporciona una API similar a Keras de alto nivel para los
profesionales que optan por usar tipos de capas bien establecidos, y una API similar a TensorFlow de bajo
nivel para aquellos que desean implementar algoritmos más novedosos. Finalmente, se diseña

XV
xvi PREFACIO

para poder ejecutarse en una amplia selección de entornos y tipos de hardware, incluido el navegador
web, el lado del servidor (Node.js), móvil (p. ej., React Native y WeChat) y escritorio (electron). A la
capacidad multidimensional de TensorFlow.js se suma su estado como una parte integrada de primera
clase del ecosistema más grande de TensorFlow/Keras, específicamente su consistencia de API y
compatibilidad de formato de modelo bidireccional con las bibliotecas de Python.
El libro que tienes en tus manos te guiará en tu gran recorrido por este espacio
multidimensional de capacidades. Hemos elegido un camino que atraviesa principalmente la
primera dimensión (tareas de modelado), enriquecido por excursiones a lo largo de las
dimensiones restantes. Partimos de la tarea relativamente más sencilla de predecir números a
partir de números (regresión) hasta las más complejas como predecir clases a partir de imágenes y
secuencias, terminando nuestro viaje en los fascinantes temas del uso de redes neuronales para
generar nuevas imágenes y entrenar agentes para tomar decisiones. (aprendizaje reforzado).
Escribimos el libro no solo como una receta sobre cómo escribir código en TensorFlow.js,
sino como un curso de introducción a los fundamentos del aprendizaje automático en el
lenguaje nativo de JavaScript y desarrolladores web. El campo del aprendizaje profundo
evoluciona rápidamente. Creemos que es posible una comprensión firme del aprendizaje
automático sin un tratamiento matemático formal, y esta comprensión le permitirá
mantenerse actualizado en la evolución futura de las técnicas.
Con este libro, ha dado el primer paso para convertirse en miembro de la creciente
comunidad de profesionales del aprendizaje automático de JavaScript, que ya han creado
muchas aplicaciones impactantes en la intersección entre JavaScript y el aprendizaje
profundo. Es nuestra sincera esperanza que este libro encienda su propia creatividad e
ingenio en este espacio.
SHANQINGCAI, SBRONCEARSEBILESCHI,YmiRICnorteIELSEN
septiembre 2019
Cambridge, MA
expresiones de gratitud
Este libro debeAprendizaje profundo con Pythonpor François Chollet por su estructura
general. A pesar de que el código se reescribió en un idioma diferente y se agregó mucho
contenido nuevo para el ecosistema de JavaScript y para reflejar los nuevos desarrollos en el
campo, ni este libro ni toda la API de alto nivel de TensorFlow.js habrían sido una realidad. sin
el trabajo pionero en Keras dirigido por François.
Nuestro viaje para completar este libro y todo el código relacionado fue placentero y
satisfactorio gracias al increíble apoyo de nuestros colegas en el equipo TensorFlow.js de
Google. El trabajo seminal y fundamental de Daniel Smilkov y Nikhil Thorat sobre los núcleos
WebGL de bajo nivel y la retropropagación constituye una base sólida para la formación y la
creación de modelos. El trabajo de Nick Kreeger sobre el enlace de Node.js a la biblioteca C
de TensorFlow es la razón principal por la que podemos ejecutar redes neuronales en el
navegador y Node.js con el mismo código. La API de datos TensorFlow.js de David Soergel y
Kangyi Zhang hace posible el capítulo 6 del libro, mientras que el capítulo 7 fue posible
gracias al trabajo de visualización de Yannick Assogba. Las técnicas de optimización del
rendimiento descritas en el capítulo 11 no serían posibles sin el trabajo de Ping Yu en la
interfaz de nivel operativo con TensorFlow. La velocidad de nuestros ejemplos no sería tan
rápida como lo es hoy en día sin el trabajo de optimización del rendimiento centrado de Ann
Yuan. El liderazgo de Sarah Sirajuddin, Sandeep Gupta y Brijesh Krishnaswami es
fundamental para el éxito general a largo plazo del proyecto TensorFlow.js.
Nos habríamos desviado del camino sin el apoyo y el estímulo de
D. Sculley, quien revisó cuidadosamente todos los capítulos del libro. También estamos
inmensamente agradecidos por todo el aliento que recibimos de Fernanda Viegas, Martin
Wattenberg, Hal Abelson y muchos otros colegas nuestros en Google. Nuestra escritura y
contenido mejoraron mucho como resultado de la revisión detallada de François Chollet,

xvii
xviii EXPRESIONES DE GRATITUD

Nikhil Thorat, Daniel Smilkov, Jamie Smith, Brian K. Lee y Augustus Odena, así como por
un debate en profundidad con Suharsh Sivakumar.
Uno de los placeres únicos de trabajar en un proyecto como TensorFlow.js es la
oportunidad de trabajar e interactuar con la comunidad mundial de software de código
abierto. TensorFlow.js tuvo la suerte de contar con un grupo de colaboradores
talentosos y motivados, incluidosmanraj singh,Kai Sasaki,jose gartman,Sasha Illarionov,
lijadoras david, syt123450@ y muchos otros, cuyo trabajo incansable en la biblioteca
amplió su capacidad y mejoró su calidad. Manraj Singh también contribuyó con el
ejemplo de detección de phishing utilizado en el capítulo 3 del libro.
Agradecemos a nuestro equipo editorial de Manning Publications. El trabajo dedicado e
incansable de Brian Sawyer, Jennifer Stout, Rebecca Rinehart y Mehmed Pasic, entre muchos
otros, hizo posible que los autores nos concentráramos en escribir el contenido. Marc-Philip
Huget proporcionó una revisión técnica extensa e incisiva durante todo el proceso de
desarrollo. Un agradecimiento especial a nuestros revisores, Alain Lompo, Andreas
Refsgaard, Buu Nguyen, David DiMaria, Edin Kapic, Edwin Kwok, Eoghan O'Donnell, Evan
Wallace, George Thomas, Giuliano Bertoti, Jason Hales, Marcio Nicolau, Michael Wall, Paulo
Nuin , Pietro Maffi, Polina Keselman, Prabhuti Prakash, Ryan Burrows, Satej Sahu, Suresh
Rangarajulu, Ursin Stauss y Vaijanath Rao, cuyas sugerencias ayudaron a mejorar este libro.

Agradecemos a nuestros lectores de MEAP por detectar y señalar algunos errores


tipográficos y técnicos.
Finalmente, nada de esto sería posible sin la tremenda comprensión y sacrificio por
parte de nuestras familias. Shanqing Cai desea expresar su más profundo
agradecimiento a su esposa, Wei, así como a sus padres y suegros por su ayuda y apoyo
durante el proceso de redacción de este libro, que duró un año. Stan Bileschi desea
agradecer a su madre y su padre, así como a su madrastra y padrastro, por
proporcionar una base y dirección para construir una carrera exitosa en ciencia e
ingeniería. También le gustaría agradecer a su esposa, Constance, por su amor y apoyo.
Eric Nielsen quisiera decirles a sus amigos y familiares, gracias.
sobre este libro
Quién debería leer este libro
Este libro está escrito para programadores que tienen un conocimiento práctico de JavaScript, de
experiencia previa con el desarrollo de frontend web o el desarrollo de backend basado en Node.js,
y desean aventurarse en el mundo del aprendizaje profundo. Su objetivo es satisfacer las
necesidades de aprendizaje de los siguientes dos subgrupos de lectores:

-Programadores de JavaScript que aspiran a pasar de tener poca o ninguna experiencia con el

aprendizaje automático o sus antecedentes matemáticos, a un conocimiento decente de cómo


funciona el aprendizaje profundo y una comprensión práctica del flujo de trabajo del aprendizaje
profundo que es suficiente para resolver problemas comunes de ciencia de datos. como
clasificación y regresión
- Desarrolladores web o Node.js que tienen la tarea de implementar modelos previamente entrenados en su

aplicación web o backend stack como nuevas características

Para el primer grupo de lectores, este libro desarrolla los conceptos básicos del aprendizaje automático y
el aprendizaje profundo desde cero, utilizando ejemplos de código JavaScript que son divertidos y están
listos para manipular y piratear. Usamos diagramas, pseudocódigo y ejemplos concretos en lugar de
matemáticas formales para ayudarlo a formar una comprensión intuitiva, pero firme, de los fundamentos
de cómo funciona el aprendizaje profundo.
Para el segundo grupo de lectores, cubrimos los pasos clave para convertir modelos existentes (p. ej.,
de bibliotecas de entrenamiento de Python) en un formato web y/o compatible con Node adecuado para
la implementación en la interfaz o la pila de Node. Hacemos hincapié en aspectos prácticos como la
optimización del tamaño y el rendimiento del modelo, así como en consideraciones para varios entornos
de implementación que van desde un servidor hasta extensiones de navegador y aplicaciones móviles.

xix
XX SOBRE ESTE LIBRO

Este libro brinda una cobertura detallada de la API de TensorFlow.js para ingerir y
formatear datos, crear y cargar modelos y ejecutar inferencias, evaluaciones y
capacitación para todos los lectores.
Finalmente, las personas con mentalidad técnica que no codifican regularmente en JavaScript o
cualquier otro lenguaje también encontrarán útil este libro como texto introductorio para las redes
neuronales básicas y avanzadas.

Cómo está organizado este libro: una hoja de ruta


Este libro está organizado en cuatro partes. La primera parte, que consta únicamente del capítulo 1, le
presenta el panorama de la inteligencia artificial, el aprendizaje automático y el aprendizaje profundo, y
explica por qué tiene sentido practicar el aprendizaje profundo en JavaScript.
La segunda parte forma una introducción suave a los conceptos más fundamentales
y más frecuentes en el aprendizaje profundo. En particular:
-Los capítulos 2 y 3 son su suave rampa de acceso al aprendizaje automático. El Capítulo 2
resuelve un problema simple de predecir un solo número a partir de otro número ajustando
una línea recta (regresión lineal) y lo usa para ilustrar cómo funciona la retropropagación (el
motor del aprendizaje profundo). El Capítulo 3 se basa en el Capítulo 2 al presentar tareas
de clasificación, redes multicapa y no linealidad. A partir de este capítulo, comprenderá qué
es la no linealidad, cómo funciona y por qué otorga a las redes neuronales profundas su
poder expresivo. El capítulo 4 trata de los datos de imágenes y la arquitectura de red
- neuronal dedicada a resolver problemas de aprendizaje automático relacionados con
imágenes: redes convolucionales (convnets). También le mostraremos por qué la
convolución es un método genérico que tiene usos más allá de las imágenes mediante el
uso de entradas de audio como ejemplo.
- El capítulo 5 continúa centrándose en las conexiones y las entradas similares a imágenes, pero cambia al
tema del aprendizaje de transferencia: cómo entrenar nuevos modelos basados en los existentes, en
lugar de comenzar desde cero.

La Parte 3 del libro cubre sistemáticamente temas más avanzados en el aprendizaje profundo para los
usuarios que desean desarrollar una comprensión de las técnicas más avanzadas, con un enfoque en
áreas específicas desafiantes de los sistemas ML y las herramientas TensorFlow.js para trabajar con ellos:

- El Capítulo 6 analiza las técnicas para manejar datos en el contexto del aprendizaje
profundo.
- El Capítulo 7 muestra las técnicas para visualizar datos y los modelos que los procesan, un
paso importante e indispensable para cualquier flujo de trabajo de aprendizaje profundo. El
- Capítulo 8 se centra en los temas importantes de ajuste insuficiente y sobreajuste en el
aprendizaje profundo, y las técnicas para analizarlos y mitigarlos. A través de esta discusión,
condensamos lo que hemos aprendido en este libro hasta ahora en una receta denominada
"el flujo de trabajo universal del aprendizaje automático". Este capítulo lo prepara para las
arquitecturas de redes neuronales avanzadas y los problemas de los capítulos 9 a 11.
SOBRE ESTE LIBRO xxx

- El Capítulo 9 está dedicado a las redes neuronales profundas que procesan datos secuenciales y
entradas de texto.
- Los capítulos 10 y 11 cubren las áreas avanzadas de aprendizaje profundo de los modelos
generativos (incluidas las redes adversarias generativas) y el aprendizaje por refuerzo,
respectivamente.

En la cuarta y última parte del libro, cubrimos técnicas para probar, optimizar e
implementar modelos entrenados o convertidos con TensorFlow.js (capítulo 12) y
resumimos todo el libro recapitulando los conceptos y flujos de trabajo más importantes
(capítulo 13).
Cada capítulo termina con ejercicios para ayudarlo a medir su nivel de comprensión y perfeccionar
sus habilidades de aprendizaje profundo en TensorFlow.js de manera práctica.

sobre el código
Este libro contiene muchos ejemplos de código fuente tanto en listados numerados como en línea con el
texto normal. En ambos casos, el código fuente se formatea en unfuente de ancho fijo como estapara
separarlo del texto ordinario. A veces el código también esen negritapara resaltar el código que ha
cambiado con respecto a los pasos anteriores del capítulo, como cuando se agrega una nueva función a
una línea de código existente.
En muchos casos, se ha reformateado el código fuente original; hemos agregado saltos
de línea y rediseñado la sangría para acomodar el espacio de página disponible en el libro. En
casos raros, incluso esto no fue suficiente, y los listados incluyen marcadores de continuación
de línea (-). Además, los comentarios en el código fuente a menudo se eliminan de las listas
cuando el código se describe en el texto. Las anotaciones de código acompañan a muchos de
los listados, destacando conceptos importantes. El código de los ejemplos de este libro está
disponible para su descarga desde GitHub enhttps:/ /github.com/tensorflow/tfjs-examples.

foro de discusión liveBook


Compra deAprendizaje profundo con JavaScriptincluye acceso gratuito a un foro web privado
administrado por Manning Publications donde puede hacer comentarios sobre el libro, hacer
preguntas técnicas y recibir ayuda del autor y de otros usuarios. Para acceder al foro, vaya a
https://livebook.manning.com/#!/book/deep-learning-withjavascript/discussion. También
puede obtener más información sobre los foros de Manning y las reglas de conducta en
https://livebook.manning.com/#!/discusión.
El compromiso de Manning con nuestros lectores es proporcionar un lugar donde
pueda tener lugar un diálogo significativo entre lectores individuales y entre lectores y
el autor. No es un compromiso de participación específica por parte del autor, cuya
contribución al foro es voluntaria (y no remunerada). ¡Le sugerimos que intente hacerles
algunas preguntas desafiantes a los autores para que su interés no se desvíe! El foro y
los archivos de debates anteriores estarán accesibles desde el sitio web de la editorial
mientras el libro esté impreso.
Sobre los autores
SHANQINGCAI, STANLEYBILESCHI,YmiRICnorteIELSENson ingenieros de software en el
Equipo de cerebro de Google. Fueron los principales desarrolladores de la API de alto nivel de
Tensor-Flow.js, incluidos los ejemplos, la documentación y las herramientas relacionadas. Han
aplicado el aprendizaje profundo basado en TensorFlow.js a problemas del mundo real, como la
comunicación alternativa para personas con discapacidades. Cada uno tiene títulos avanzados del
MIT.

XXII
sobre la ilustración de la portada
La figura de la portada deAprendizaje profundo con JavaScriptse titula "Finne Katschin", o una
niña de la tribu Katschin. La ilustración procede de una colección de trajes de gala de varios
países de Jacques Grasset de Saint-Sauveur (1757-1810), tituladaTrajes de Différents Pays,
publicado en Francia en 1797. Cada ilustración está finamente dibujada y coloreada a mano.
La rica variedad de la colección de Grasset de Saint-Sauveur nos recuerda vívidamente cuán
diferentes culturalmente eran las ciudades y regiones del mundo hace apenas 200 años.
Aislados unos de otros, las personas hablaban diferentes dialectos e idiomas. En las calles o
en el campo, era fácil identificar dónde vivían y cuál era su oficio o posición en la vida solo por
su vestimenta.
La forma de vestir ha cambiado desde entonces y la diversidad por regiones, tan rica en la
época, se ha desvanecido. Ahora es difícil distinguir a los habitantes de los diferentes continentes, y
mucho menos a los diferentes pueblos, regiones o países. Quizás hemos cambiado la diversidad
cultural por una vida personal más variada, ciertamente por una vida tecnológica más variada y
acelerada.
En una época en la que es difícil distinguir un libro informático de otro, Manning
celebra la inventiva y la iniciativa del negocio informático con portadas de libros basadas
en la rica diversidad de la vida regional de hace dos siglos, revivida por Grasset de Saint-
Los cuadros de Sauveur.

XXIII
Parte 1

Motivación
y conceptos basicos

PAGS El arte 1 consta de un solo capítulo que lo orienta hacia los conceptos básicos que
formarán el telón de fondo del resto del libro. Estos incluyen inteligencia artificial,
aprendizaje automático y aprendizaje profundo y las relaciones entre ellos. El Capítulo
1 también aborda el valor y el potencial de practicar el aprendizaje profundo en
JavaScript.
Aprendizaje profundo

y JavaScript

Este capítulo cubre


- Qué es el aprendizaje profundo y cómo se relaciona con la
inteligencia artificial (IA) y el aprendizaje automático

- Qué hace que el aprendizaje profundo se destaque entre varias técnicas


de aprendizaje automático y los factores que llevaron a la actual
"revolución del aprendizaje profundo"

- Las razones para hacer un aprendizaje profundo en JavaScript usando


TensorFlow.js

- La organización general de este libro.

Todo el alboroto en torno a la inteligencia artificial (IA) está sucediendo por una buena razón: la
revolución del aprendizaje profundo, como a veces se le llama, de hecho ha sucedido.Revolución del
aprendizaje profundose refiere al rápido progreso logrado en la velocidad y las técnicas de las redes
neuronales profundas que comenzó alrededor de 2012 y aún continúa. Desde entonces, las redes
neuronales profundas se han aplicado a una gama cada vez más amplia de problemas, lo que permite
que las máquinas resuelvan problemas que antes no tenían solución en algunos casos y mejoran
drásticamente la precisión de la solución en otros (consulte la tabla 1.1 para ver ejemplos). Para los
expertos en IA, muchos de estos avances en las redes neuronales fueron asombrosos.

3
4 CPASADO1Aprendizaje profundo y JavaScript

Para los ingenieros que usan redes neuronales, las oportunidades que ha creado este progreso son
estimulantes.
JavaScript es un lenguaje tradicionalmente dedicado a crear la interfaz de usuario del navegador web y
la lógica comercial de back-end (con Node.js). Como alguien que expresa ideas y creatividad en JavaScript,
es posible que se sienta un poco excluido de la revolución del aprendizaje profundo, que parece ser
territorio exclusivo de lenguajes como Python, R y C++. Este libro tiene como objetivo unir el aprendizaje
profundo y JavaScript a través de la biblioteca de aprendizaje profundo de JavaScript llamada
TensorFlow.js. Hacemos esto para que los desarrolladores de JavaScript como usted puedan aprender a
escribir redes neuronales profundas sin aprender un nuevo idioma; más importante aún, creemos que el
aprendizaje profundo y JavaScript van de la mano.
La polinización cruzada creará oportunidades únicas, que no están disponibles en ningún otro lenguaje de
programación. Funciona en ambos sentidos para JavaScript y el aprendizaje profundo. Con JavaScript, las
aplicaciones de aprendizaje profundo pueden ejecutarse en más plataformas, llegar a un público más amplio y
volverse más visuales e interactivas. Con el aprendizaje profundo, los desarrolladores de JavaScript pueden hacer
que sus aplicaciones web sean más inteligentes. Describiremos cómo más adelante en este capítulo.

La Tabla 1.1 enumera algunos de los logros más emocionantes del aprendizaje profundo
que hemos visto hasta ahora en esta revolución del aprendizaje profundo. En este libro,
seleccionamos varias de estas aplicaciones y creamos ejemplos de cómo implementarlas en
Tensor-Flow.js, ya sea en todo su esplendor o en forma reducida. Estos ejemplos se tratarán
en profundidad en los próximos capítulos. Por lo tanto, no dejará de maravillarse con los
avances: puede aprender sobre ellos, comprenderlos e implementarlos en JavaScript.

Pero antes de sumergirse en estos emocionantes ejemplos prácticos de aprendizaje profundo,


debemos presentar el contexto esencial en torno a la IA, el aprendizaje profundo y las redes neuronales.

Tabla 1.1 Ejemplos de tareas en las que la precisión mejoró significativamente gracias a las técnicas de
aprendizaje profundo desde el comienzo de la revolución del aprendizaje profundo alrededor de 2012. Esta lista
no es exhaustiva. El ritmo de progreso, sin duda, continuará en los próximos meses y años.

Dónde usamos TensorFlow.js


Aprendizaje automático Representante para realizar una tarea similar
tarea tecnología de aprendizaje profundo en este libro

Categorizando el Redes neuronales convolucionales Convnets de entrenamiento para MNIST


contenido de imagenes profundas (convnets) como ResNetae inicioB (capítulo 4); Inferencia de MobileNet y
redujo la tasa de error en la tarea de transferencia de aprendizaje (capítulo 5)
clasificación de Image-Net de ~25 % en
2011 a menos del 5 % en 2017.C

un. Kaiming He et al., "Aprendizaje residual profundo para el reconocimiento de imágenes",proc. Conferencia IEEE Visión por computadora y
reconocimiento de patrones(CVPR), 2016, págs. 770–778,http://mng.bz/PO5P.
B. Christian Szegedy et al., "Profundizando con las circunvoluciones",proc. Conferencia IEEE Visión por computadora y reconocimiento
de patrones(CVPR), 2015, págs. 1–9,http://mng.bz/JzGv.
C. Resultados del desafío de reconocimiento visual a gran escala 2017 (ILSVRC2017),http://image-net.org/challenges/LSVRC/ 2017/
resultados.
5

Tabla 1.1 Ejemplos de tareas en las que la precisión mejoró significativamente gracias a las técnicas de
aprendizaje profundo desde el comienzo de la revolución del aprendizaje profundo alrededor de 2012. Esta lista
no es exhaustiva. El ritmo de progreso, sin duda, continuará en los próximos meses y años.(continuado)

Dónde usamos TensorFlow.js


Aprendizaje automático Representante para realizar una tarea similar
tarea tecnología de aprendizaje profundo en este libro

Localización de objetos y Variantes de conexiones profundasDerror de YOLO en TensorFlow.js


imágenes localización reducido de 0,33 en 2012 a 0,06 en (sección 5.2)
2017.

Traduciendo uno natural La traducción automática neuronal (GNMT) de Google Modelos de secuencia a secuencia
lengua ral a otra redujo el error de traducción en aproximadamente un basados en memoria a largo plazo
60 % en comparación con las mejores técnicas (LSTM) con mecanismos de atención
tradicionales de traducción automática.mi (capítulo 9)

Reconociendo grandes- Una arquitectura de codificador-descodificador de Reconocimiento de voz continuo de


vocabulario, continua- atención basada en LSTM logra una tasa de error de vocabulario pequeño LSTM basado
nuestro discurso palabra más baja que el mejor sistema de en la atención (capítulo 9)
reconocimiento de voz sin aprendizaje profundo.F

Generando realista- Las redes antagónicas generativas (GAN) Generación de imágenes utilizando
mirando imágenes ahora son capaces de generar imágenes de codificadores automáticos variacionales
aspecto realista basadas en datos de (VAEs) y GAN (capítulo 9)
entrenamiento (verhttps://github.com/
junyanz/ CycleGAN).

Generando musica Las redes neuronales recurrentes (RNN) y los VAE están Entrenamiento de LSTM para generar texto
ayudando a crear partituras musicales y sonidos de (capítulo 9)
instrumentos novedosos (ver https://
magenta.tensorflow.org/demostraciones).

Aprendiendo a jugar juegos El aprendizaje profundo combinado con el aprendizaje por Uso de RL para resolver el problema del
refuerzo (RL) permite que las máquinas aprendan a jugar control del poste del carro y un
juegos simples de Atari utilizando píxeles sin procesar como videojuego de serpientes (capítulo 11)
única entrada.gramoCombinando el aprendizaje profundo y la

búsqueda del árbol de Monte Carlo, Alpha-Zero alcanzó un

nivel sobrehumano de Go puramente a través del autojuego.

Diagnóstico de enfermedades Deep Convnets pudo lograr una especificidad y una Transfiera el aprendizaje utilizando un modelo de

uso de imágenes médicas sensibilidad comparables a las de los oftalmólogos imagen de MobileNet entrenado previamente

humanos capacitados en el diagnóstico de la (capítulo 5).

retinopatía diabética en función de las imágenes de


las retinas de los pacientes.I

D. Yunpeng Chen et al., "Redes de ruta dual",https://arxiv.org/pdf/1707.01629.pdf.


mi. Yonghui Wu et al., "Sistema de traducción automática neuronal de Google: cerrar la brecha entre la traducción humana y la
automática", presentado el 26 de septiembre de 2016,https://arxiv.org/abs/1609.08144.
F. Chung-Cheng Chiu et al., “Reconocimiento de voz de última generación con modelos de secuencia a secuencia”, presentado el 5 de
diciembre de 2017,https://arxiv.org/abs/1712.01769.
gramo. Volodymyr Mnih et al., "Jugar Atari con aprendizaje de refuerzo profundo", Taller de aprendizaje profundo de NIPS 2013,
https://arxiv.org/abs/1312.5602.
H. David Silver et al., "Mastering Chess and Shogi by Self-Play with a General Reinforcement Learning Algorithm",
presentado el 5 de diciembre de 2017,https://arxiv.org/abs/1712.01815.
I. Varun Gulshan et al., "Desarrollo y validación de un algoritmo de aprendizaje profundo para la detección de retinopatía
diabética en fotografías de fondo de retina", JAMA, vol. 316, núm. 22, 2016, págs. 2402–2410,http://mng.bz/wlDQ.
6 CPASADO1Aprendizaje profundo y JavaScript

1.1 Inteligencia artificial, aprendizaje automático, redes


neuronales y aprendizaje profundo
frases comoAI,aprendizaje automático,Redes neuronales, yaprendizaje profundosignifican cosas
relacionadas pero diferentes. Para orientarse en el deslumbrante mundo de la IA, debe
comprender a qué se refieren. Definamos estos términos y las relaciones entre ellos.

1.1.1 Inteligencia artificial


Como muestra el diagrama de Venn en la figura 1.1, la IA es un campo amplio. Una definición concisa del campo
sería la siguiente:el esfuerzo por automatizar tareas intelectuales normalmente realizadas por humanos. Como
tal, la IA abarca el aprendizaje automático, las redes neuronales y el aprendizaje profundo, pero también incluye
muchos enfoques distintos del aprendizaje automático. Los primeros programas de ajedrez, por ejemplo,
involucraban reglas codificadas de forma rígida creadas por programadores. Esos no calificaron como
aprendizaje automático porque las máquinas fueron programadas explícitamente para resolver los problemas en
lugar de permitirles descubrir estrategias para resolver los problemas aprendiendo de los datos. Durante mucho
tiempo, muchos expertos creyeron que

Inteligencia artificial

Aprendizaje automático

IA simbólica

Árboles de decisión

Redes neuronales

Métodos del núcleo


...
Poco profundo

neural
Aprendizaje profundo
...
redes

Figura 1.1 Relaciones entre IA, aprendizaje automático, redes neuronales y aprendizaje profundo. Como muestra este diagrama
de Venn, el aprendizaje automático es un subcampo de la IA. Algunas áreas de la IA utilizan enfoques diferentes del aprendizaje
automático, como la IA simbólica. Las redes neuronales son un subcampo del aprendizaje automático. Existen técnicas de
aprendizaje automático sin redes neuronales, como los árboles de decisión. El aprendizaje profundo es la ciencia y el arte de
crear y aplicar redes neuronales "profundas" (redes neuronales con múltiples "capas") frente a redes neuronales
"superficiales" (redes neuronales con menos capas).
Inteligencia artificial, aprendizaje automático, redes neuronales y aprendizaje profundo 7

La IA a nivel humano podría lograrse mediante la elaboración artesanal de un conjunto suficientemente grande
de reglas explícitas para manipular el conocimiento y tomar decisiones. Este enfoque se conoce comoIA simbólica
, y fue el paradigma dominante en la IA desde la década de 1950 hasta finales de la década de 1980.1

1.1.2 Aprendizaje automático: en qué se diferencia de la programación tradicional

El aprendizaje automático, como un subcampo de la IA distinto de la IA simbólica, surge de una


pregunta: ¿Podría una computadora ir más allá de lo que un programador sabe programar para
que realice y aprender por sí misma cómo realizar una tarea específica? Como puede ver, el
enfoque del aprendizaje automático es fundamentalmente diferente al de la IA simbólica. Mientras
que la IA simbólica se basa en reglas y conocimientos de codificación rígida, el aprendizaje
automático busca evitar esta codificación rígida. Entonces, si una máquina no recibe instrucciones
explícitas sobre cómo realizar una tarea, ¿cómo aprendería a hacerlo? La respuesta es aprendiendo
de ejemplos en los datos.
Esto abrió la puerta a un nuevo paradigma de programación (figura 1.2). Para dar un ejemplo del
paradigma del aprendizaje automático, supongamos que está trabajando en una aplicación web que
maneja las fotos cargadas por los usuarios. Una función que desea en la aplicación es la clasificación
automática de fotos en las que contienen rostros humanos y las que no. La aplicación realizará diferentes
acciones en las imágenes de rostros y en las imágenes sin rostros. Con este fin, desea crear un programa
para generar una respuesta binaria cara/no cara dada cualquier imagen de entrada (hecha de una matriz
de píxeles).

Normas
Clásico
respuestas
programación
Datos

Figura 1.2 Comparando la


Datos programación clásica
Máquina
aprendiendo
Normas paradigma y el paradigma de
respuestas aprendizaje automático

Los humanos podemos realizar esta tarea en una fracción de segundo: el cableado genético de nuestros
cerebros y la experiencia de vida nos dan la capacidad de hacerlo. Sin embargo, es difícil para cualquier
programador, sin importar cuán inteligente y experimentado sea, escribir un conjunto explícito de reglas
en un lenguaje de programación (la única forma práctica para que los humanos se comuniquen con una
computadora) sobre cómo decidir con precisión si una imagen contiene un cara humana. Puede pasar
días estudiando detenidamente el código que hace aritmética en los valores RGB (rojo-verde-azul) de los
píxeles para detectar contornos elípticos que parecen caras, ojos y bocas, así como idear reglas
heurísticas sobre las relaciones geométricas entre los contornos. . Pero pronto se dará cuenta de que tal
esfuerzo está cargado de elecciones arbitrarias de lógica y parámetros que son

1
Un tipo importante de IA simbólica essistemas expertos. Vea este artículo de Britannica para aprender sobre ellos:http://
mng.bz/7zmy.
8 CPASADO1Aprendizaje profundo y JavaScript

difícil de justificar. Más importante aún, ¡es difícil hacer que funcione bien!2Es probable que
cualquier heurística que se le ocurra se quede corta al enfrentarse a la miríada de variaciones que
los rostros pueden presentar en las imágenes de la vida real, como las diferencias en el tamaño, la
forma y los detalles del rostro; expresión facial; peinado; color de piel; orientación; la presencia o
ausencia de oscurecimiento parcial; lentes; condiciones de iluminación; objetos en el fondo; y así.
En el paradigma del aprendizaje automático, reconoce que elaborar un conjunto de reglas para tal
tarea es inútil. En su lugar, encuentra un conjunto de imágenes, algunas con caras en ellas y otras sin
ellas. Luego ingresa la respuesta deseada (es decir, correcta) cara o no cara para cada uno. Estas
respuestas se conocen comoetiquetas. Esta es una tarea mucho más manejable (de hecho, trivial). Puede
llevar algún tiempo etiquetar todas las imágenes si hay muchas, pero la tarea de etiquetado se puede
dividir entre varias personas y puede realizarse en paralelo. Una vez que haya etiquetado las imágenes,
aplique el aprendizaje automático y deje que las máquinas descubran el conjunto de reglas por sí mismas.
Si utiliza las técnicas correctas de aprendizaje automático, llegará a un conjunto de reglas entrenadas
capaces de realizar la tarea presencial/no presencial con una precisión > 99 %, mucho mejor que cualquier
cosa que pueda esperar lograr con reglas hechas a mano.
Del ejemplo anterior, podemos ver que el aprendizaje automático es el proceso de
automatización del descubrimiento de reglas para resolver problemas complejos. Esta
automatización es beneficiosa para problemas como la detección de rostros, en los que los
humanos conocen las reglas de manera intuitiva y pueden etiquetar fácilmente los datos. Para
otros problemas, las reglas no se conocen intuitivamente. Por ejemplo, considere el problema de
predecir si un usuario hará clic en un anuncio que se muestra en una página web, dado el
contenido de la página y del anuncio y otra información, como la hora y la ubicación. Ningún ser
humano tiene un buen sentido de cómo hacer predicciones precisas para tales problemas en
general. Incluso si lo hace, el patrón probablemente cambiará con el tiempo y con la aparición de
nuevos contenidos y nuevos anuncios. Pero los datos de entrenamiento etiquetados están
disponibles en el historial del servicio de anuncios: están disponibles en los registros de los
servidores de anuncios.
En la figura 1.3, observamos más de cerca los pasos involucrados en el aprendizaje automático.
Hay dos fases importantes. El primero es elfase de entrenamiento. Esta fase toma los datos y las
respuestas, denominados en conjunto como eldatos de entrenamiento. Cada par de datos de
entrada y la respuesta deseada se denominaejemplo. Con la ayuda de los ejemplos, el proceso de
entrenamiento produce los descubrimientos automáticos.normas. Aunque las reglas se descubren
automáticamente, no se descubren completamente desde cero. En otras palabras, los algoritmos
de aprendizaje automático no son creativos al crear reglas. En particular, un ingeniero humano
proporciona un modelo para las reglas al comienzo del entrenamiento. El plano está encapsulado
en unmodelo, que forma unespacio de hipótesispor las reglas que la máquina posiblemente pueda
aprender. Sin este espacio de hipótesis, hay un espacio infinito y sin restricciones de reglas posibles
para buscar, lo que no es propicio para encontrar buenas

2
De hecho, estos enfoques se han intentado antes y no funcionaron muy bien. Este documento de encuesta proporciona
buenos ejemplos de reglas artesanales para la detección de rostros antes de la llegada del aprendizaje profundo: Erik
Hjelmås y Boon Kee Low, "Face Detection: A Survey"Comprensión de imágenes y visión artificial, septiembre de 2001,
págs. 236–274,http://mng.bz/m4d2.
Inteligencia artificial, aprendizaje automático, redes neuronales y aprendizaje profundo 9

Fase de entrenamiento Fase de inferencia

Datos
Capacitación Normas
respuestas Inferencia Responder
Nuevos datos

arquitectura modelo

Figura 1.3 Una vista más detallada del paradigma de aprendizaje automático que la de la figura 1.2. El flujo de trabajo del
aprendizaje automático consta de dos fases: entrenamiento e inferencia. El entrenamiento es el proceso en el que la
máquina descubre automáticamente las reglas que convierten los datos en respuestas. Las reglas aprendidas, encapsuladas
en un “modelo” entrenado, son el fruto de la fase de entrenamiento y forman la base de la fase de inferencia. Inferencia
significa usar el modelo para obtener respuestas para nuevos datos.

reglas en un tiempo limitado. Describiremos con gran detalle los tipos de modelos
disponibles y cómo elegir los mejores en función del problema en cuestión. Por ahora, basta
con decir que en el contexto del aprendizaje profundo, los modelos varían en términos de
cuántas capas consta la red neuronal, qué tipos de capas son y cómo están conectadas entre
sí.
Con los datos de entrenamiento y la arquitectura del modelo, el proceso de entrenamiento produce
las reglas aprendidas, encapsuladas en un modelo entrenado. Este proceso toma el modelo y lo altera (o
lo ajusta) de manera que empuja la salida del modelo más y más cerca de la salida deseada. La fase de
entrenamiento puede durar desde milisegundos hasta días, según la cantidad de datos de
entrenamiento, la complejidad de la arquitectura del modelo y la velocidad del hardware. Este estilo de
aprendizaje automático, es decir, el uso de ejemplos etiquetados para reducir progresivamente el error
en los resultados de un modelo, se conoce comoaprendizaje supervisado.3La mayoría de los algoritmos
de aprendizaje profundo que cubrimos en este libro son aprendizaje supervisado. Una vez que tenemos el
modelo entrenado, estamos listos para aplicar las reglas aprendidas en nuevos datos, datos que el
proceso de entrenamiento nunca ha visto. Esta es la segunda fase, ofase de inferencia. La fase de
inferencia es menos intensiva desde el punto de vista computacional que la fase de entrenamiento
porque 1) la inferencia generalmente ocurre en una entrada (por ejemplo, una imagen) a la vez, mientras
que el entrenamiento implica pasar por todos los datos de entrenamiento; y 2) durante la inferencia, no
es necesario modificar el modelo.

LOBTENCIÓN DE REPRESENTACIONES DE DATOS


El aprendizaje automático se trata de aprender a partir de datos. Peroquéexactamente se aprende? La
respuesta: una forma de transformar los datos de manera efectiva o, en otras palabras, cambiar las
antiguas representaciones de los datos por una nueva que nos acerque a resolver el problema en
cuestión.

3
Otro estilo de aprendizaje automático esaprendizaje sin supervisión, en el que se utilizan datos sin etiquetar. Ejemplos de aprendizaje no
supervisado son el agrupamiento (descubrir distintos subconjuntos de ejemplos en un conjunto de datos) y la detección de anomalías
(determinar si un ejemplo dado es lo suficientemente diferente de los ejemplos en el conjunto de entrenamiento).
10 CPASADO1Aprendizaje profundo y JavaScript

Antes de continuar, ¿qué es una representación? En esencia, es una forma de ver los datos. Los
mismos datos se pueden ver de diferentes maneras, lo que lleva a diferentes representaciones. Por
ejemplo, una imagen en color puede tener una codificación RGB o HSV (valor de saturación de
tono). Aquí, las palabrascodificaciónyrepresentaciónsignifican esencialmente lo mismo y pueden
usarse indistintamente. Cuando se codifica en estos dos formatos diferentes, los valores numéricos
que representan los píxeles son completamente diferentes, aunque se trate de la misma imagen.
Diferentes representaciones son útiles para resolver diferentes problemas. Por ejemplo, para
encontrar todas las partes rojas de una imagen, la representación RGB es más útil; pero para
encontrar partes saturadas de color de la misma imagen, la representación HSV es más útil. De
esto se trata esencialmente el aprendizaje automático: encontrar una transformación adecuada
que convierta la representación anterior de los datos de entrada en una nueva, una que sea capaz
de resolver la tarea específica en cuestión, como detectar la ubicación de los automóviles en una
imagen. o decidir si una imagen contiene un gato y un perro.

Para dar un ejemplo visual, tenemos una colección de puntos blancos y varios puntos
negros en un plano (figura 1.4). Digamos que queremos desarrollar un algoritmo que pueda
tomar las coordenadas 2D (x, y) de un punto y predecir si ese punto es blanco o negro. En
este caso,

-Los datos de entrada son las coordenadas cartesianas bidimensionales (x e y) de un


punto.
- La salida es el color predicho del punto (ya sea blanco o negro).
Los datos muestran un patrón en el panel A de la figura 1.4. ¿Cómo decidiría la máquina el color de
un punto dadas las coordenadas x e y? ¡No puede simplemente comparar x con un número, porque
el rango de las coordenadas x de los puntos blancos se superpone con el rango de las coordenadas
x de los puntos negros! De manera similar, el algoritmo no puede basarse en la coordenada y. Por
lo tanto, podemos ver que la representación original de los puntos no es buena para la tarea de
clasificación en blanco y negro.
Lo que necesitamos es una nueva representación que separe los dos colores de
una manera más directa. Aquí, transformamos la representación xy cartesiana
original en una representación del sistema de coordenadas polares. En otras
palabras, representamos un punto por 1) su ángulo—el ángulo formado por el eje x
y la línea que conecta el origen con el punto (ver el ejemplo en el panel A de la
figura 1.4) y 2) su radio—su distancia del origen. Después de esta transformación,
llegamos a una nueva representación del mismo conjunto de datos, como muestra
el panel B de la figura 1.4. Esta representación es más adecuada para nuestra tarea,
ya que los valores de los ángulos de los puntos blancos y negros ahora no se
superponen en absoluto. Sin embargo,

Afortunadamente, podemos aplicar una segunda transformación para llegar allí. Esta transformación
se basa en la fórmula simple

(valor absoluto del ángulo) - 135 grados


Inteligencia artificial, aprendizaje automático, redes neuronales y aprendizaje profundo 11

A B
1

0.5
1

Radio
0
y
0.5
– 0.5

–1 0
–1 – 0.5 0 0.5 1 – 100 0 100
X Ángulo (grados)

– 100 – 50 0
abdominales (Ángulo) – 135

Figura 1.4 Un ejemplo de juguete de las transformaciones de representación de las que trata el aprendizaje automático.
Panel A: la representación original de un conjunto de datos que consta de puntos blancos y negros en un plano.
Paneles B y C: dos pasos de transformación sucesivos convierten la representación original en una más adecuada para
la tarea de clasificación de colores.

La representación resultante, como se muestra en el panel C, es unidimensional. En


comparación con la representación en el panel B, descarta la información irrelevante sobre la
distancia de los puntos al origen. Pero es una representación perfecta porque permite un
proceso de decisión completamente sencillo:

si el valor < 0, el punto se clasifica como blanco;


de lo contrario, el punto se clasifica como negro

En este ejemplo, definimos manualmente una transformación de dos pasos de la representación de datos. Pero
si, en cambio, intentáramos la búsqueda automática de diferentes transformaciones de coordenadas posibles
usando comentarios sobre el porcentaje de puntos clasificados correctamente, entonces estaríamos haciendo
aprendizaje automático. La cantidad de pasos de transformación involucrados en la resolución de problemas
reales de aprendizaje automático suele ser mucho mayor que dos, especialmente en el aprendizaje profundo,
donde puede llegar a cientos. Además, el tipo de transformaciones de representación que se ven en el
aprendizaje automático real pueden ser mucho más complejas en comparación con las que se ven en este
ejemplo simple. La investigación en curso en el aprendizaje profundo sigue descubriendo transformaciones más
sofisticadas y poderosas. Pero el ejemplo de la figura 1.4 capta la esencia de la búsqueda de mejores
representaciones. Esto se aplica a todos
12 CPASADO1Aprendizaje profundo y JavaScript

algoritmos de aprendizaje automático, incluidas redes neuronales, árboles de decisión,


métodos kernel, etc.

1.1.3 Redes neuronales y aprendizaje profundo


Las redes neuronales son un subcampo del aprendizaje automático, en el que la
transformación de la representación de datos se realiza mediante un sistema con una
arquitectura inspirada en la forma en que las neuronas están conectadas en los cerebros
humanos y animales. ¿Cómo se conectan las neuronas entre sí en el cerebro? Varía entre
especies y regiones del cerebro. Pero un tema frecuente de conexión neuronal es la
organización de capas. Muchas partes del cerebro de los mamíferos están organizadas en
capas. Los ejemplos incluyen la retina, la corteza cerebral y la corteza cerebelosa.
Al menos en un nivel superficial, este patrón es algo similar a la organización general deredes
neuronales artificiales(simplemente llamadoRedes neuronalesen el mundo de la informática,
donde el riesgo de confusión es mínimo), en el que los datos se procesan en múltiples etapas
separables, acertadamente denominadascapas. Estas capas generalmente se apilan una encima de
la otra, con conexiones solo entre las adyacentes. La Figura 1.5 muestra una red neuronal simple
(artificial) con cuatro capas. Los datos de entrada (una imagen, en este caso) se introducen en la
primera capa (en el lado izquierdo de la figura), luego fluyen secuencialmente de una capa a la
siguiente. Cada capa aplica una nueva transformación sobre la representación de los datos. A
medida que los datos fluyen a través de las capas, la representación se vuelve cada vez más
diferente del original y se acerca cada vez más al objetivo de la red neuronal, es decir, aplicar una
etiqueta correcta a la imagen de entrada. La última capa (en el lado derecho de la figura) emite la
salida final de la red neuronal, que es el resultado de la tarea de clasificación de imágenes.

Una capa de redes neuronales es similar a una función matemática en que es un mapeo de un
valor de entrada a un valor de salida. Sin embargo, las capas de redes neuronales se diferencian de
las funciones matemáticas puras en que generalmente soncon estado. En otras palabras, tienen
memoria interna. La memoria de una capa se captura en supesos. ¿Qué son los pesos? Son
simplemente un conjunto de valores numéricos que pertenecen a la capa y gobiernan los detalles
de cómo la capa transforma cada representación de entrada en una representación de salida. Por
ejemplo, el uso frecuentedensoLa capa transforma sus datos de entrada multiplicándolos con una
matriz y agregando un vector al resultado de la multiplicación de la matriz. La matriz y el vector son
los pesos de la capa densa. Cuando una red neuronal se entrena a través de la exposición a datos
de entrenamiento, los pesos se modifican sistemáticamente de una manera que minimiza un cierto
valor llamadofunción de pérdida, que cubriremos en detalle usando ejemplos concretos en los
capítulos 2 y 3.
Aunque las redes neuronales están inspiradas en el cerebro, debemos tener cuidado de no
humanizarlas demasiado. El propósito de las redes neuronales esnopara estudiar o imitar cómo
funciona el cerebro. Ese es el reino de la neurociencia, una disciplina académica separada. Las
redes neuronales permiten que las máquinas realicen tareas prácticas interesantes aprendiendo de
los datos. El hecho de que algunas redes neuronales se parezcan a algunas
Inteligencia artificial, aprendizaje automático, redes neuronales y aprendizaje profundo 13

Capa 1 Capa 2 Capa 3


representaciones representaciones representaciones

Capa 4
representaciones
(salida final)

0
Original
aporte
1
2
3
4
5
6
7
8
9
Capa 1 Capa 2 Capa 3 Capa 4

Figura 1.5 Diagrama esquemático de una red neuronal, organizada en capas. Esta red neuronal clasifica
imágenes de dígitos escritos a mano. Entre las capas, puede ver la representación intermedia de los datos
originales. Reproducido con autorización de François Chollet,Aprendizaje profundo con Python,
Publicaciones de Manning, 2017.

partes del cerebro biológico, tanto en estructura como en función,4es realmente notable. Pero si
esto es una coincidencia está más allá del alcance de este libro. En cualquier caso, no debe
exagerarse el parecido. Es importante destacar que no hay evidencia de que el cerebro aprenda a
través de ninguna forma de descenso de gradiente, la forma principal en que se entrenan las redes
neuronales (que se trata en el próximo capítulo). Muchas técnicas importantes en las redes
neuronales que ayudaron a marcar el comienzo de la revolución del aprendizaje profundo se
inventaron y adoptaron no porque estuvieran respaldadas por la neurociencia, sino porque
ayudaron a las redes neuronales a resolver tareas prácticas de aprendizaje mejor y más rápido.
Ahora que sabes qué son las redes neuronales, podemos decirte quéaprendizaje profundoes. El
aprendizaje profundo es el estudio y la aplicación deredes neuronales profundas, que son, sencillamente,
redes neuronales conmuchas capas(típicamente, de una docena a cientos de capas). Aquí, la palabra
profundose refiere a la idea de un gran número de capas sucesivas de representaciones. El número de
capas que forman un modelo de los datos se denomina capa del modelo.profundidad. Otros nombres
apropiados para el campo podrían haber sido "aprendizaje de representación en capas" o "aprendizaje de
representación jerárquica". El aprendizaje profundo moderno a menudo implica decenas o cientos de
capas sucesivas de representaciones, y todas se aprenden automáticamente a partir de la exposición a los
datos de entrenamiento. Mientras tanto, otros

4
Para ver un ejemplo convincente de similitud en las funciones, vea las entradas que activan al máximo varias capas de
una red neuronal convolucional (ver capítulo 4), que se parecen mucho a los campos receptivos neuronales de varias
partes del sistema visual humano.
14 CPASADO1Aprendizaje profundo y JavaScript

los enfoques del aprendizaje automático tienden a centrarse en aprender solo una o dos capas de
representaciones de los datos; por lo tanto, a veces se les llamaaprendizaje superficial.
Es un error pensar que lo "profundo" en el aprendizaje profundo se trata de cualquier tipo de
comprensión profunda de los datos, es decir, "profundo" en el sentido de comprender el significado
detrás de oraciones como "la libertad no es libre" o saborear las contradicciones y las autorreferencias. en
los dibujos de MC Escher. Ese tipo de "profundidad" sigue siendo un objetivo difícil de alcanzar para los
investigadores de IA.5En el futuro, el aprendizaje profundo puede acercarnos a este tipo de profundidad,
pero eso sin duda será más difícil de cuantificar y lograr que agregar capas a las redes neuronales.

ICAJA NFO1.1 No solo redes neuronales: otras técnicas populares de aprendizaje


automático
Pasamos directamente del círculo de "aprendizaje automático" del diagrama de Venn en la figura
1.1 al círculo de "red neuronal" en el interior. Sin embargo, vale la pena que visitemos brevemente
las técnicas de aprendizaje automático que no son redes neuronales, no solo porque hacerlo nos
brindará un mejor contexto histórico, sino también porque es posible que se encuentre con
algunas de las técnicas en el código existente.

losClasificador Naive Bayeses una de las primeras formas de aprendizaje automático. En pocas
palabras, el teorema de Bayes se trata de cómo estimar la probabilidad de un evento dado 1) la
creencia a priori de cuán probable es el evento y 2) los hechos observados (llamadoscaracteristicas)
relacionado con el evento. Este teorema se puede usar para clasificar los puntos de datos
observados en una de muchas categorías conocidas al elegir la categoría con la mayor
probabilidad (verosimilitud) dados los hechos observados. Naive Bayes se basa en la suposición de
que los hechos observados son mutuamente independientes (una suposición fuerte e ingenua, de
ahí el nombre).

Regresión logística(ologrego) es también una técnica de clasificación. Gracias a su naturaleza simple y


versátil, sigue siendo popular y, a menudo, lo primero que intentará un científico de datos para tener una
idea de la tarea de clasificación que tiene entre manos.

Métodos del núcleo, de las cuales las máquinas de vectores de soporte (SVM) son los ejemplos más
conocidos, abordan problemas de clasificación binaria (es decir, de dos clases) asignando los datos
originales a espacios de mayor dimensionalidad y encontrando una transformación que maximiza
una distancia (llamada unamargen) entre dos clases de ejemplos.

Árboles de decisiónson estructuras similares a diagramas de flujo que le permiten clasificar puntos de
datos de entrada o predecir valores de salida dadas las entradas. En cada paso del diagrama de flujo,
responde una simple pregunta de sí/no, como "¿Es la característica X mayor que cierto umbral?"
Dependiendo de si la respuesta es sí o no, avanza a una de las dos siguientes preguntas posibles, que es
solo otra pregunta de sí/no, y así sucesivamente. Una vez que llegue al final del diagrama de flujo,
obtendrá la respuesta final. Como tal, los árboles de decisión son fáciles de visualizar e interpretar para
los humanos.

5Douglas Hofstadter, “La superficialidad del traductor de Google”,El Atlántico, 30 de enero de 2018,http://mng.bz/5AE1.
Inteligencia artificial, aprendizaje automático, redes neuronales y aprendizaje profundo 15

Los bosques aleatorios y las máquinas impulsadas por gradientes aumentan la precisión de los árboles de
decisión al formar un conjunto de una gran cantidad de árboles de decisión individuales especializados.
Ensamblaje, también conocido comoaprendizaje conjunto, es la técnica de entrenar una colección (es
decir, un conjunto) de modelos individuales de aprendizaje automático y usar un agregado de sus
resultados durante la inferencia. Hoy en día, el aumento de gradiente puede ser uno de los mejores
algoritmos, si no el mejor, para tratar con datos no perceptibles (por ejemplo, detección de fraude con
tarjetas de crédito). Junto con el aprendizaje profundo, es una de las técnicas más utilizadas en las
competencias de ciencia de datos, como las de Kaggle.

TSE LEVANTO,OTOÑO,Y EL AUGE DE LAS REDES NEURONALES,Y LAS RAZONES DETRÁS DE ELLOS
Las ideas centrales de las redes neuronales se formaron ya en la década de 1950.
Las técnicas clave para entrenar redes neuronales, incluida la retropropagación, se
inventaron en la década de 1980. Sin embargo, durante un largo período de tiempo
entre la década de 1980 y la de 2010, la comunidad investigadora rechazó casi por
completo las redes neuronales, en parte debido a la popularidad de los métodos de
la competencia, como las SVM, y en parte debido a la falta de capacidad para
entrenar en profundidad ( redes neuronales de muchas capas). Pero alrededor de
2010, varias personas que aún trabajaban en redes neuronales comenzaron a
lograr avances importantes: los grupos de Geoffrey Hinton en la Universidad de
Toronto, Yoshua Bengio en la Universidad de Montreal y Yann LeCun en la
Universidad de Nueva York, así como investigadores en el Instituto Dalle Molle para
la Investigación de Inteligencia Artificial (IDSIA) en Suiza.

Desde 2012, profundoredes neuronales convolucionales(convnets) se han convertido en el


algoritmo de referencia para todas las tareas de visión artificial; más generalmente, trabajan
en todas las tareas perceptivas. Los ejemplos de tareas de percepción que no son de visión
por computadora incluyen el reconocimiento de voz. En las principales conferencias de visión
por computadora en 2015 y 2016, fue casi imposible encontrar presentaciones que no
involucraran convenciones de alguna forma. Al mismo tiempo, el aprendizaje profundo
también ha encontrado aplicaciones en muchos otros tipos de problemas, como el
procesamiento del lenguaje natural. Ha reemplazado por completo las SVM y los árboles de
decisión en una amplia gama de aplicaciones. Por ejemplo, durante varios años, la
Organización Europea para la Investigación Nuclear, CERN, utilizó métodos basados en
árboles de decisión para analizar datos de partículas del detector ATLAS en el Gran
Colisionador de Hadrones;
Entonces, ¿qué hace que el aprendizaje profundo se destaque de la gama de algoritmos de aprendizaje automático
disponibles? (Consulte el cuadro de información 1.1 para obtener una lista de algunas técnicas populares de aprendizaje
automático que no son redes neuronales profundas). La razón principal por la que el aprendizaje profundo despegó tan
rápido es que ofreció un mejor rendimiento en muchos problemas. Pero esa no es la única razón. El aprendizaje profundo
también facilita mucho la resolución de problemas porque automatiza
dieciséis CPASADO1Aprendizaje profundo y JavaScript

lo que solía ser el paso más crucial y difícil en un flujo de trabajo de aprendizaje automático:ingeniería de
características.
Las técnicas anteriores de aprendizaje automático (aprendizaje superficial) solo implicaban la transformación
de los datos de entrada en uno o dos espacios de representación sucesivos, generalmente a través de
transformaciones simples como proyecciones no lineales de alta dimensión (métodos kernel) o árboles de
decisión. Pero las representaciones refinadas requeridas por problemas complejos generalmente no se pueden
lograr con tales técnicas. Como tal, los ingenieros humanos tuvieron que hacer todo lo posible para que los datos
de entrada iniciales fueran más fáciles de procesar con estos métodos: tuvieron que diseñar manualmente
buenas capas de representaciones para sus datos. Se llamaingeniería de características. El aprendizaje profundo,
por otro lado, automatiza este paso: con el aprendizaje profundo, aprende todas las características en una sola
pasada en lugar de tener que diseñarlas usted mismo. Esto ha simplificado en gran medida los flujos de trabajo
de aprendizaje automático, a menudo reemplazando canalizaciones sofisticadas de varias etapas con un modelo
de aprendizaje profundo único, simple y de extremo a extremo. Mediante la automatización de la ingeniería de
funciones, el aprendizaje profundo hace que el aprendizaje automático sea menos laborioso y más sólido: dos
pájaros de un tiro.
Estas son las dos características esenciales de cómo el aprendizaje profundo aprende de los
datos: la forma incremental, capa por capa, en la que se desarrollan representaciones cada vez más
complejas; y el hecho de que estas representaciones incrementales intermedias se aprenden
conjuntamente, cada capa se actualiza para seguir tanto las necesidades de representación de la
capa superior como las necesidades de la capa inferior. Juntas, estas dos propiedades han hecho
que el aprendizaje profundo sea mucho más exitoso que los enfoques anteriores del aprendizaje
automático.

1.1.4 ¿Por qué aprendizaje profundo? ¿Porqué ahora?

Si las ideas básicas y las técnicas centrales para las redes neuronales ya existían en la década de
1980, ¿por qué la revolución del aprendizaje profundo comenzó a ocurrir solo después de 2012?
¿Qué cambió en las dos décadas intermedias? En general, tres fuerzas técnicas impulsan los
avances en el aprendizaje automático:

- Hardware
- Conjuntos de datos y puntos de

- referencia Avances algorítmicos

Vamos a visitar estos factores uno por uno.

HFERRETERÍA
El aprendizaje profundo es una ciencia de la ingeniería guiada por hallazgos experimentales más que por la
teoría. Los avances algorítmicos son posibles solo cuando se dispone del hardware adecuado para probar nuevas
ideas (o para ampliar ideas antiguas, como suele ser el caso). Los modelos típicos de aprendizaje profundo
utilizados en la visión por computadora o el reconocimiento de voz requieren órdenes de magnitud más de poder
computacional que lo que su computadora portátil puede ofrecer.
A lo largo de la década de 2000, empresas como NVIDIA y AMD invirtieron miles de millones de
dólares en el desarrollo de chips paralelos masivos (GPU) rápidos para potenciar los gráficos de
videojuegos cada vez más fotorrealistas: supercomputadoras baratas de un solo propósito.
Inteligencia artificial, aprendizaje automático, redes neuronales y aprendizaje profundo 17

diseñado para representar escenas 3D complejas en su pantalla en tiempo real. Esta inversión
benefició a la comunidad científica cuando, en 2007, NVIDIA lanzó CUDA (abreviatura de Compute
Unified Device Architecture), una interfaz de programación de propósito general para su línea de
GPU. Una pequeña cantidad de GPU comenzó a reemplazar grupos masivos de CPU en varias
aplicaciones altamente paralelizables, comenzando con el modelado físico. Las redes neuronales
profundas, que consisten principalmente en muchas multiplicaciones y sumas de matrices,
también son altamente paralelizables.
Alrededor de 2011, algunos investigadores comenzaron a escribir implementaciones CUDA de
redes neuronales; Dan Ciresan y Alex Krizhevsky estuvieron entre los primeros. Hoy en día, las GPU
de gama alta pueden ofrecer cientos de veces más potencia de cómputo paralelo al entrenar redes
neuronales profundas que lo que es capaz de hacer una CPU típica. Sin el poder computacional
puro de las GPU modernas, sería imposible entrenar muchas redes neuronales profundas de
última generación.

DATA Y BENCHMARKS
Si el hardware y los algoritmos son la máquina de vapor de la revolución del aprendizaje profundo,
entonces los datos son su carbón: la materia prima que impulsa nuestras máquinas inteligentes, sin la
cual nada sería posible. Cuando se trata de datos, además del progreso exponencial en el hardware de
almacenamiento en los últimos 20 años (siguiendo la ley de Moore), el cambio de juego ha sido el auge de
Internet, que ha hecho factible recopilar y distribuir conjuntos de datos muy grandes para aprendizaje
automático. Hoy en día, las grandes empresas trabajan con conjuntos de datos de imágenes, conjuntos
de datos de video y conjuntos de datos de lenguaje natural que no podrían haberse recopilado sin
Internet. Las etiquetas de imágenes generadas por los usuarios en Flickr, por ejemplo, han sido un tesoro
de datos para la visión artificial. Así son los videos de YouTube. Y Wikipedia es un conjunto de datos clave
para el procesamiento del lenguaje natural.
Si hay un conjunto de datos que ha sido un catalizador para el auge del aprendizaje profundo, es
Image-Net, que consta de 1,4 millones de imágenes que se han anotado a mano con 1000 categorías de
imágenes. Lo que hace que ImageNet sea especial no es solo su gran tamaño; también es la competencia
anual asociada con él. Como ImageNet y Kaggle han estado demostrando desde 2010, los concursos
públicos son una excelente manera de motivar a los investigadores e ingenieros a ir más allá. Tener
puntos de referencia comunes por los que los investigadores compiten ha ayudado en gran medida al
reciente aumento del aprendizaje profundo.

AAVANCES LGORITMICOS
Además del hardware y los datos, hasta finales de la década de 2000, nos faltaba una forma confiable de
entrenar redes neuronales muy profundas. Como resultado, las redes neuronales aún eran bastante
superficiales y usaban solo una o dos capas de representaciones; por lo tanto, no podían brillar contra
métodos superficiales más refinados, como SVM y bosques aleatorios. El tema clave fue el de la
propagación del gradiente a través de pilas profundas de capas. La señal de retroalimentación utilizada
para entrenar las redes neuronales se desvanecería a medida que aumentara el número de capas.
Esto cambió alrededor de 2009 a 2010 con la llegada de varias mejoras algorítmicas simples
pero importantes que permitieron una mejor propagación del gradiente:

- Mejores funciones de activación para capas de redes neuronales (como la unidad lineal
rectificada o relu)
18 CPASADO1Aprendizaje profundo y JavaScript

- Mejores esquemas de inicialización de peso (por ejemplo, inicialización de Glorot)


- Mejores esquemas de optimización (por ejemplo, optimizadores RMSProp y ADAM)

Solo cuando estas mejoras comenzaron a permitir modelos de entrenamiento con 10 o más capas, el
aprendizaje profundo comenzó a brillar. Finalmente, en 2014, 2015 y 2016, se descubrieron formas aún
más avanzadas para ayudar a la propagación de gradientes, como la normalización por lotes, las
conexiones residuales y las circunvoluciones separables en profundidad. Hoy podemos entrenar desde
cero modelos que tienen miles de capas de profundidad.

1.2 ¿Por qué combinar JavaScript y aprendizaje automático?


El aprendizaje automático, al igual que otras ramas de la IA y la ciencia de datos, generalmente se realiza
con lenguajes tradicionalmente centrados en el backend, como Python y R, que se ejecutan en servidores
o estaciones de trabajo fuera del navegador web.6Este statu quo no es sorprendente. El entrenamiento de
redes neuronales profundas a menudo requiere el tipo de computación acelerada por GPU y multinúcleo
que no está disponible directamente en una pestaña del navegador; la enorme cantidad de datos que a
veces se necesita para entrenar dichos modelos se ingiere de manera más conveniente en el backend: por
ejemplo, desde un sistema de archivos nativo de tamaño prácticamente ilimitado. Hasta hace poco,
muchos consideraban el “aprendizaje profundo en JavaScript” como una novedad. En esta sección,
presentaremos las razones por las que, para muchos tipos de aplicaciones, realizar aprendizaje profundo
en el entorno del navegador con JavaScript es una buena elección, y explicaremos cómo combinar el
poder del aprendizaje profundo y el navegador web crea oportunidades únicas, especialmente con la
ayuda de TensorFlow.js.
Primero, una vez que se entrena un modelo de aprendizaje automático, debe implementarse en algún
lugar para hacer predicciones sobre datos reales (como clasificar imágenes y texto, detectar eventos en
transmisiones de audio o video, etc.). Sin implementación, entrenar un modelo es solo una pérdida de
poder de cómputo. A menudo es deseable o imperativo que el "algún lugar" sea una interfaz web. Es
probable que los lectores de este libro aprecien la importancia general del navegador web. En
computadoras de escritorio y portátiles, el navegador web es el medio dominante a través del cual los
usuarios acceden a contenido y servicios en Internet. Así es como los usuarios de computadoras de
escritorio y portátiles pasan la mayor parte de su tiempo usando esos dispositivos, superando el segundo
lugar por un amplio margen. Es la forma en que los usuarios realizan gran parte de su trabajo diario, se
mantienen conectados y se entretienen. La amplia gama de aplicaciones que se ejecutan en el navegador
web ofrece excelentes oportunidades para aplicar el aprendizaje automático del lado del cliente. Para la
interfaz móvil, el navegador web va a la zaga de las aplicaciones móviles nativas en términos de
participación del usuario y tiempo dedicado. No obstante, los navegadores móviles son una fuerza a tener
en cuenta debido a su alcance más amplio, acceso instantáneo y ciclos de desarrollo más rápidos.7De
hecho, debido a su flexibilidad y facilidad de uso, muchas aplicaciones móviles, como Twitter y Facebook,
pasan a una vista web habilitada para JavaScript para ciertos tipos de contenido.

6
Srishti Deoras, "Los 10 principales lenguajes de programación para que los científicos de datos aprendan en 2018"Revista de análisis de la India, 25 de
enero de 2018,http://mng.bz/6wrD.
7
Rishabh Borde, "Paso de tiempo en Internet en aplicaciones móviles, 2017–19: es 8 veces más que la Web móvil", DazeInfo, 12 de abril de
2017,http://mng.bz/omDr.
¿Por qué combinar JavaScript y aprendizaje automático? 19

Debido a este amplio alcance, el navegador web es una opción lógica para implementar modelos de
aprendizaje profundo, siempre que los tipos de datos que esperan los modelos estén disponibles en el
navegador. Pero, ¿qué tipos de datos están disponibles en el navegador? La respuesta es ¡muchas!
Tomemos, por ejemplo, las aplicaciones más populares de aprendizaje profundo: clasificación y detección
de objetos en imágenes y videos, transcripción de voz, traducción de idiomas y análisis de contenido de
texto. Los navegadores web están equipados con posiblemente las tecnologías y API más completas para
presentar (y, con el permiso del usuario, para capturar) datos de texto, imagen, audio y video. Como
resultado, se pueden usar modelos potentes de aprendizaje automático directamente en el navegador,
por ejemplo, con TensorFlow.js y procesos de conversión sencillos. En los últimos capítulos de este libro,
Cubriremos muchos ejemplos concretos de implementación de modelos de aprendizaje profundo en el
navegador. Por ejemplo, una vez que haya capturado imágenes de una cámara web, puede usar
TensorFlow.js para ejecutar MobileNet para etiquetar objetos, ejecutar YOLO2 para colocar cuadros
delimitadores alrededor de objetos detectados, ejecutar Lipnet para leer los labios o ejecutar una red
CNN-LSTM para aplicar subtítulos a las imágenes.
Una vez que haya capturado el audio del micrófono con la API WebAudio del navegador, TensorFlow.js
puede ejecutar modelos para realizar el reconocimiento de palabras habladas en tiempo real. También
hay aplicaciones interesantes con datos textuales, como la asignación de puntajes de sentimiento al texto
del usuario, como reseñas de películas (capítulo 9). Más allá de estas modalidades de datos, el navegador
web moderno puede acceder a una variedad de sensores en dispositivos móviles. Por ejemplo, HTML5
proporciona acceso API a geolocalización (latitud y longitud), movimiento (orientación y aceleración del
dispositivo) y luz ambiental (verhttp://mobilehtml5.org). Combinados con el aprendizaje profundo y otras
modalidades de datos, los datos de dichos sensores abren las puertas a muchas aplicaciones nuevas y
emocionantes.
La aplicación de aprendizaje profundo basada en navegador viene con cinco beneficios adicionales: costo de
servidor reducido, latencia de inferencia reducida, privacidad de datos, aceleración de GPU instantánea y acceso
instantáneo:

- costo del servidorsuele ser una consideración importante al diseñar y escalar servicios web.
El cómputo requerido para ejecutar modelos de aprendizaje profundo de manera oportuna
suele ser significativo, lo que requiere el uso de la aceleración de GPU. Si los modelos no se
implementan en el lado del cliente, deben implementarse en máquinas respaldadas por
GPU, como máquinas virtuales con GPU CUDA de Google Cloud o Amazon Web Services.
Tales máquinas de GPU en la nube suelen ser costosas. Incluso las máquinas con GPU más
básicas actualmente cuestan entre $0.5 y $1 por hora (verhttps://www.ec2instances.infoy
https://cloud.google.com/gpu). Con el aumento del tráfico, el costo de ejecutar una flota de
máquinas GPU en la nube aumenta, sin mencionar el desafío de la escalabilidad y la
complejidad adicional de su pila de servidores. Todas estas preocupaciones se pueden
eliminar implementando el modelo en el cliente. La sobrecarga de la descarga del modelo
del lado del cliente (que a menudo es de varios megabytes o más) se puede aliviar con las
capacidades de almacenamiento local y de caché del navegador (capítulo 2).

- Latencia de inferencia reducida—Para ciertos tipos de aplicaciones, el requisito de latencia


es tan estricto que los modelos de aprendizaje profundo deben ejecutarse en el cliente
20 CPASADO1Aprendizaje profundo y JavaScript

lado. Cualquier aplicación que involucre datos de audio, imagen y video en


tiempo real se incluye en esta categoría. Considere lo que sucederá si los
marcos de imagen deben transferirse al servidor para la inferencia.
Supongamos que se capturan imágenes desde una cámara web con un
tamaño modesto de 400 × 400 píxeles con tres canales de color (RGB) y una
profundidad de 8 bits por canal de color a una velocidad de 10 cuadros por
segundo. Incluso con compresión JPEG, cada imagen tiene un tamaño de
unos 150 Kb. En una red móvil típica con un ancho de banda de carga de
aproximadamente 300 Kbps, puede tomar más de 500 milisegundos cargar
cada imagen, lo que genera una latencia que es notable y quizás inaceptable
para ciertas aplicaciones (por ejemplo, juegos). Este cálculo no tiene en
cuenta la fluctuación (y la posible pérdida de) conectividad de la red,
La inferencia del lado del cliente aborda estos posibles problemas de latencia y conectividad al
mantener los datos y el cálculo en el dispositivo. Es imposible ejecutar aplicaciones de aprendizaje
automático en tiempo real, como el etiquetado de objetos y la detección de poses en imágenes de
cámaras web, sin que el modelo se ejecute exclusivamente en el cliente. Incluso para aplicaciones
sin requisitos de latencia, la reducción de la latencia de inferencia del modelo puede conducir a
una mayor capacidad de respuesta y, por lo tanto, a una experiencia de usuario mejorada.

- Privacidad de datos—Otro beneficio de dejar los datos de entrenamiento e inferencia en el cliente es la


protección de la privacidad de los usuarios. El tema de la privacidad de los datos es cada vez más
importante en la actualidad. Para ciertos tipos de aplicaciones, la privacidad de los datos es un requisito
absoluto. Las aplicaciones relacionadas con la salud y los datos médicos son un ejemplo destacado.
Considere una "ayuda para el diagnóstico de enfermedades de la piel" que recopila imágenes de la piel
de un paciente desde su cámara web y utiliza el aprendizaje profundo para generar posibles diagnósticos
de la condición de la piel. Las regulaciones de privacidad de la información de salud en muchos países no
permitirán que las imágenes se transfieran a un servidor centralizado para su inferencia. Al ejecutar la
inferencia del modelo en el navegador, no es necesario que los datos abandonen el teléfono del usuario
ni se almacenen en ningún lugar, lo que garantiza la privacidad de los datos de salud del usuario.

Considere otra aplicación basada en navegador que utiliza el aprendizaje profundo para
brindar a los usuarios sugerencias sobre cómo mejorar el texto que escriben en la aplicación.
Algunos usuarios pueden usar esta aplicación para escribir contenido confidencial, como
documentos legales, y no se sentirán cómodos con la transferencia de datos a un servidor remoto
a través de Internet público. Ejecutar el modelo únicamente en JavaScript del navegador del lado
del cliente es una forma eficaz de abordar este problema.
- Aceleración WebGL instantánea—Además de la disponibilidad de datos, otro requisito previo para
ejecutar modelos de aprendizaje automático en el navegador web es suficiente poder de cómputo a
través de la aceleración de GPU. Como se mencionó anteriormente, muchos modelos de aprendizaje
profundo de última generación son tan computacionalmente intensivos que la aceleración a través del
cómputo paralelo en la GPU es imprescindible (a menos que esté dispuesto a dejar que los usuarios
esperen minutos por un solo resultado de inferencia, lo que rara vez sucede en
¿Por qué combinar JavaScript y aprendizaje automático? 21

aplicaciones reales). Afortunadamente, los navegadores web modernos vienen equipados


con la API WebGL que, aunque se diseñó originalmente para la representación acelerada de
gráficos 2D y 3D, se puede aprovechar ingeniosamente para el tipo de cómputo paralelo
requerido para acelerar las redes neuronales. Los autores de TensorFlow.js
cuidadosamente envolvieron la aceleración basada en WebGL de los componentes de
aprendizaje profundo en la biblioteca, por lo que la aceleración está disponible para usted a
través de una sola línea de importación de JavaScript.
Es posible que la aceleración de redes neuronales basada en WebGL no esté perfectamente a
la par con la aceleración de GPU nativa y personalizada, como CUDA y CuDNN de NVIDIA
(utilizadas por las bibliotecas de aprendizaje profundo de Python, como TensorFlow y PyTorch),
pero aún conduce a una aceleración de órdenes de magnitud de redes neuronales y permite la
inferencia en tiempo real, como lo que ofrece la extracción PoseNet de una pose del cuerpo
humano.
Si realizar inferencias en modelos previamente entrenados es costoso, realizar entrenamiento
o transferir aprendizaje en dichos modelos lo es aún más. El entrenamiento y el aprendizaje de
transferencia permiten aplicaciones emocionantes como la personalización de modelos de
aprendizaje profundo, la visualización frontal del aprendizaje profundo y el aprendizaje federado
(entrenar el mismo modelo en muchos dispositivos y luego agregar los resultados del
entrenamiento para obtener un buen modelo). La aceleración WebGL de TensorFlow.js permite
entrenar o ajustar redes neuronales con suficiente velocidad, únicamente dentro del navegador
web.
- Acceso instantáneo—En términos generales, las aplicaciones que se ejecutan en el
navegador tienen la ventaja natural de "instalación cero": todo lo que se necesita para
acceder a la aplicación es escribir una URL o hacer clic en un enlace. Esto evita los
pasos de instalación potencialmente tediosos y propensos a errores, junto con el
control de acceso posiblemente riesgoso al instalar un nuevo software. En el contexto
del aprendizaje profundo en el navegador, la aceleración de red neuronal basada en
WebGL que proporciona TensorFlow.js no requiere tipos especiales de tarjetas gráficas
ni la instalación de controladores para dichas tarjetas, lo que suele ser un proceso no
trivial. La mayoría de los dispositivos de escritorio, portátiles y móviles
razonablemente actualizados vienen con tarjetas gráficas disponibles para el
navegador y WebGL. Dichos dispositivos, siempre que tengan instalado un navegador
web compatible con TensorFlow.js (una barra baja), están automáticamente listos para
ejecutar redes neuronales aceleradas por WebGL.

ICAJA NFO1.2 Acelerar el cómputo usando GPU y WebGL


Se necesita una gran cantidad de operaciones matemáticas para entrenar modelos de aprendizaje
automático y usarlos para la inferencia. Por ejemplo, las capas de redes neuronales "densas"
ampliamente utilizadas implican multiplicar una matriz grande con un vector y agregar el resultado
a otro vector. Una operación típica de este tipo implica miles o millones de operaciones de punto
flotante. Un hecho importante acerca de tales operaciones es que a menudo sonparalelizable.
22 CPASADO1Aprendizaje profundo y JavaScript

(continuado)
Por ejemplo, la suma de dos vectores se puede dividir en muchas operaciones más pequeñas,
como la suma de dos números individuales. Estas operaciones más pequeñas no dependen unas
de otras. Por ejemplo, no necesita saber la suma de los dos elementos de los dos vectores en el
índice 0 para calcular la suma de los dos elementos en el índice 1. Como resultado, las operaciones
más pequeñas se pueden realizar al mismo tiempo, en lugar de uno a la vez, sin importar cuán
grandes sean los vectores. El cómputo en serie, como una implementación ingenua de la CPU de la
suma de vectores, se conoce como datos únicos de instrucción única (SISD). El cálculo paralelo en
la GPU se conoce como datos múltiples de instrucción única (SIMD). Por lo general, la CPU tarda
menos tiempo en calcular cada adición individual que una GPU. Pero el costo total de esta gran
cantidad de datos hace que el SIMD de la GPU supere al SISD de la CPU. Una red neuronal
profunda puede contener millones de parámetros. Para una entrada determinada, puede que se
necesiten miles de millones de operaciones matemáticas elemento por elemento para ejecutarse
(si no más). La computación masivamente paralela de la que son capaces las GPU realmente brilla a
esta escala.

Tarea: Añadir dos vectores, elemento por elemento:

X: – 1.2 3.4 – 0.5 ... 2.4

Y: 0.7 2.5 4.1 ... 1.1

Computación en una CPU

– 1.2 3.4 2.4


+ + ... +
– 0.5 5.9 3.5
0.7 2.5 1.1

hora
Computación en una GPU

– 1.2 – 1.2 – 0.5


+
3.4 + 3.4 5.9

... ... ... ...

+
2.4 2.4 3.5

hora

Cómo la aceleración WebGL aprovecha la capacidad de cómputo paralelo de una GPU para lograr una operación
vectorial más rápida que una CPU

Para ser precisos, las CPU modernas también son capaces de ciertos niveles de instrucciones SIMD. Sin
embargo, una GPU viene con una cantidad mucho mayor de unidades de procesamiento (del orden de
cientos o miles) y puede ejecutar instrucciones en muchos segmentos de los datos de entrada al mismo
tiempo. La suma de vectores es una tarea SIMD relativamente simple en la que cada
¿Por qué combinar JavaScript y aprendizaje automático? 23

El paso de cálculo analiza un solo índice, y los resultados en diferentes índices son
independientes entre sí. Otras tareas SIMD vistas en el aprendizaje automático son más
complejas. Por ejemplo, en la multiplicación de matrices, cada paso del cálculo utiliza datos
de múltiples índices y existen dependencias entre los índices. Pero la idea básica de la
aceleración a través de la paralelización sigue siendo la misma.

Es interesante notar que las GPU no fueron diseñadas originalmente para acelerar las redes neuronales.
Esto se puede ver en el nombre:unidad de procesamiento gráfico. El propósito principal de las GPU es
procesar gráficos 2D y 3D. En muchas aplicaciones gráficas, como los juegos en 3D, es fundamental que el
procesamiento se realice en el menor tiempo posible para que las imágenes en la pantalla se puedan
actualizar a una velocidad de fotogramas suficientemente alta para una experiencia de juego fluida. Esta
fue la motivación original cuando los creadores de la GPU explotaron la paralelización SIMD. Pero, como
sorpresa agradable, el tipo de GPU de cómputo paralelo que son capaces de hacer también se adapta a
las necesidades del aprendizaje automático.

La biblioteca WebGL que TensorFlow.js usa para la aceleración de GPU se diseñó originalmente para
tareas como la representación de texturas (patrones de superficie) en objetos 3D en el navegador web.
¡Pero las texturas son solo conjuntos de números! Por lo tanto, podemos pretender que los números son
pesos o activaciones de redes neuronales y reutilizar las operaciones de textura SIMD de WebGL para
ejecutar redes neuronales. Así es exactamente como TensorFlow.js acelera las redes neuronales en el
navegador.

Además de las ventajas que hemos descrito, las aplicaciones de aprendizaje automático basadas en web
disfrutan de los mismos beneficios que las aplicaciones web genéricas que no implican aprendizaje
automático:

- A diferencia del desarrollo de aplicaciones nativas, la aplicación de JavaScript que escribe con
Tensor-Flow.js funcionará en muchas familias de dispositivos, desde computadoras de escritorio
Mac, Windows y Linux hasta dispositivos Android e iOS.
- Con sus capacidades gráficas 2D y 3D optimizadas, el navegador web es el entorno más rico
y maduro para la visualización e interactividad de datos. En lugares donde a las personas les
gustaría presentar el comportamiento y el funcionamiento interno de las redes neuronales
a los humanos, es difícil pensar en un entorno que supere al navegador. Tome TensorFlow
Playground, por ejemplo (https://patio de recreo. tensorflow.org). Es una aplicación web
muy popular en la que puedes resolver de forma interactiva problemas de clasificación con
redes neuronales. Puede ajustar la estructura y los hiperparámetros de la red neuronal y
observar cómo cambian sus capas y salidas ocultas como resultado (consulte la figura 1.6).
Si no has jugado con él antes, te recomendamos que lo pruebes. Muchos han expresado la
opinión de que este es uno de los materiales educativos más instructivos y agradables que
han visto sobre el tema de las redes neuronales. TensorFlow Playground es, de hecho, un
antepasado importante de TensorFlow.js. Como descendiente de Playground, TensorFlow.js
cuenta con una gama mucho más amplia de capacidades de aprendizaje profundo y un
rendimiento mucho más optimizado. Además, está equipado con un componente dedicado
para la visualización de modelos de aprendizaje profundo (cubierto en el capítulo 7 en
24 CPASADO1Aprendizaje profundo y JavaScript

detalle). No importa si desea crear aplicaciones educativas básicas a lo largo de las líneas de
TensorFlow Playground o presentar su investigación de aprendizaje profundo de
vanguardia de una manera visualmente atractiva e intuitiva, TensorFlow.js lo ayudará a
recorrer un largo camino hacia sus objetivos (vea ejemplos como visualización de
incrustación de tSNE en tiempo real8).

Figura 1.6 Una captura de pantalla de TensorFlow Playground (https://playground.tensorflow.org), una popular interfaz de
usuario basada en navegador para enseñar cómo funcionan las redes neuronales de Daniel Smilkov y sus colegas en Google.
TensorFlow Playground también fue un precursor importante del proyecto posterior TensorFlow.js.

1.2.1 Aprendizaje profundo con Node.js


Por motivos de seguridad y rendimiento, el navegador web está diseñado para ser un entorno con
recursos limitados en términos de memoria limitada y cuota de almacenamiento. Esto significa que el
navegador no es un entorno ideal para entrenar grandes modelos de aprendizaje automático con
grandes cantidades de datos, a pesar de que es ideal para muchos tipos de inferencia, entrenamiento a
pequeña escala y tareas de aprendizaje por transferencia, que requieren menos recursos. Sin embargo,
Node.js altera la ecuación por completo. Node.js permite que JavaScript se ejecute fuera del navegador
web, lo que le otorga acceso a todos los recursos nativos, como la RAM y el sistema de archivos.
TensorFlow.js viene con una versión de Node.js, llamadanodo tfjs. Se vincula directamente con las
bibliotecas nativas de TensorFlow compiladas a partir del código C++ y CUDA, y por lo tanto permite a los
usuarios usar los mismos kernels de operación de GPU y CPU paralelizados que TensorFlow usa bajo el
capó (en Python). Como se puede demostrar empíricamente, la velocidad de entrenamiento del modelo
en tfjs-node está a la par con la velocidad de Keras en Python. Entonces, tfjs-node es

8Consulte Nicola Pezzotti, "Visualizaciones tSNE en tiempo real con TensorFlow.js"googblogs,http://mng.bz/nvDg.


Traducido del inglés al español - www.onlinedoctranslator.com

¿Por qué combinar JavaScript y aprendizaje automático? 25

un entorno apropiado para entrenar grandes modelos de aprendizaje automático con grandes cantidades
de datos. En este libro, verá ejemplos en los que usamos tfjs-node para entrenar el tipo de modelos
grandes que están más allá de la capacidad del navegador (por ejemplo, el reconocedor de palabras en el
capítulo 5 y el analizador de sentimiento de texto en el capítulo 9).
Pero, ¿cuáles son las posibles razones para elegir Node.js en lugar del entorno Python más establecido
para entrenar modelos de aprendizaje automático? Las respuestas son 1) rendimiento y 2) compatibilidad
con conjuntos de habilidades de desarrollador y pila existentes. En primer lugar, en términos de
rendimiento, los intérpretes de JavaScript de última generación, como el motor V8 que utiliza Node.js,
realizan una compilación justo a tiempo (JIT) del código JavaScript, lo que genera un rendimiento superior
al de Python. Como resultado, a menudo es más rápido entrenar modelos en tfjs-node que en Keras
(Python), siempre que el modelo sea lo suficientemente pequeño para que el rendimiento del intérprete
de lenguaje sea el factor determinante.
En segundo lugar, Node.js es un entorno muy popular para crear aplicaciones del lado del
servidor. Si su backend ya está escrito en Node.js y le gustaría agregar aprendizaje automático a su
pila, usar tfjs-node suele ser una mejor opción que usar Python. Al mantener el código en un solo
idioma, puede reutilizar directamente grandes porciones de su base de código, incluidos esos bits
para cargar y formatear los datos. Esto lo ayudará a configurar la tubería de entrenamiento de
modelos más rápido. Al no agregar un nuevo lenguaje a su pila, también mantiene bajos sus costos
de complejidad y mantenimiento, lo que posiblemente ahorre el tiempo y el costo de contratar a un
programador de Python.
Finalmente, el código de aprendizaje automático escrito en TensorFlow.js funcionará tanto en el
entorno del navegador como en Node.js, con la posible excepción del código relacionado con los datos
que se basa en las API solo del navegador o solo del nodo. La mayoría de los ejemplos de código que
encontrará en este libro funcionarán en ambos entornos. Nos hemos esforzado por separar la parte del
código independiente del entorno y centrada en el aprendizaje automático de la ingesta de datos
específica del entorno y el código de la interfaz de usuario. El beneficio adicional es que obtiene la
capacidad de realizar un aprendizaje profundo tanto en el lado del servidor como en el del cliente al
aprender solo una biblioteca.

1.2.2 El ecosistema JavaScript


Al evaluar la idoneidad de JavaScript para cierto tipo de aplicación, como el aprendizaje profundo,
no debemos ignorar el factor de que JavaScript es un lenguaje con un ecosistema
excepcionalmente sólido. Durante años, JavaScript ha sido constantemente clasificado como el
número uno entre unas pocas docenas de lenguajes de programación en términos de recuento de
repositorios y actividades de extracción en GitHub (verhttp://githut.info). En npm, el repositorio
público de facto de paquetes de JavaScript, hay más de 600 000 paquetes a partir de julio de 2018.
Esto cuadruplica con creces la cantidad de paquetes en PyPI, el repositorio público de facto de
paquetes de Python (www.modulecounts.com). A pesar de que Python y R tienen una comunidad
mejor establecida para el aprendizaje automático y la ciencia de datos, la comunidad de JavaScript
también está creando soporte para las canalizaciones de datos relacionadas con el aprendizaje
automático.
26 CPASADO1Aprendizaje profundo y JavaScript

¿Quiere ingerir datos de almacenamiento en la nube y bases de datos? Tanto Google Cloud
como Amazon Web Services proporcionan API de Node.js. Los sistemas de bases de datos más
populares en la actualidad, como MongoDB y RethinkDB, tienen soporte de primera clase para los
controladores de Node.js. ¿Quieres disputar datos en JavaScript? Recomendamos el libroGestión de
datos con Java-Scriptpor Ashley Davis (Manning Publications, 2018,www.manning.com/books/
datawrangling-with-javascript). ¿Quieres visualizar tus datos? Hay bibliotecas maduras y poderosas
como d3.js, vega.js y plotly.js que eclipsan a las bibliotecas de visualización de Python en muchos
aspectos. Una vez que tenga listos sus datos de entrada, TensorFlow.js, el tema principal de este
libro, lo tomará desde allí y lo ayudará a crear, entrenar y ejecutar sus modelos de aprendizaje
profundo, así como a guardarlos, cargarlos y visualizarlos.
Finalmente, el ecosistema de JavaScript todavía está en constante evolución de maneras
emocionantes. Su alcance se está ampliando desde sus bastiones tradicionales, a saber, el navegador web
y los entornos de back-end de Node.js, a nuevos territorios, como las aplicaciones de escritorio (por
ejemplo, Electron) y las aplicaciones móviles nativas (por ejemplo, React Native e Ionic). A menudo, es más
fácil escribir interfaces de usuario y aplicaciones para dichos marcos que usar innumerables herramientas
de creación de aplicaciones específicas de la plataforma. JavaScript es un lenguaje que tiene el potencial
de llevar el poder del aprendizaje profundo a todas las plataformas principales. Resumimos los
principales beneficios de combinar JavaScript y el aprendizaje profundo en la tabla 1.2.

Tabla 1.2 Un breve resumen de los beneficios de hacer aprendizaje profundo en JavaScript

Consideración Ejemplos

Razones relacionadas con el lado del • Inferencia reducida y latencia de entrenamiento debido a la localidad de los datos
cliente • Capacidad para ejecutar modelos cuando el cliente está fuera de línea

• Protección de la privacidad (los datos nunca salen del navegador)


• Costo de servidor reducido
• Pila de implementación simplificada

Razones relacionadas con el • Disponibilidad de múltiples modalidades de datos (API de sensor, audio y
navegador web video HTML5) para inferencia y capacitación
• La experiencia de usuario sin instalación
• El acceso sin instalación al cómputo paralelo a través de la API de WebGL en una
amplia gama de GPU
• Soporte multiplataforma
• Entorno ideal para la visualización y la interactividad
• El entorno inherentemente interconectado abre el acceso directo a varias
fuentes de datos y recursos de aprendizaje automático

Razones relacionadas con • JavaScript es el lenguaje de programación de código abierto más popular en
Java-Script muchos aspectos, por lo que hay una gran cantidad de talento y entusiasmo
por JavaScript.
• JavaScript tiene un ecosistema vibrante y amplias aplicaciones tanto en el lado del cliente como
en el del servidor.
• Node.js permite que las aplicaciones se ejecuten en el lado del servidor sin las
limitaciones de recursos del navegador.
• El motor V8 hace que el código JavaScript se ejecute rápidamente.
¿Por qué TensorFlow.js? 27

1.3 ¿Por qué TensorFlow.js?


Para realizar un aprendizaje profundo en JavaScript, debe seleccionar una biblioteca. TensorFlow.js es
nuestra elección para este libro. En esta sección, describiremos qué es TensorFlow.js y las razones por las
que lo seleccionamos.

1.3.1 Una breve historia de TensorFlow, Keras y TensorFlow.js


TensorFlow.js es una biblioteca que le permite realizar un aprendizaje profundo en JavaScript. Como
sugiere su nombre, TensorFlow.js está diseñado para ser coherente y compatible con Tensor-Flow, el
marco de trabajo de Python para el aprendizaje profundo. Para comprender TensorFlow.js, debemos
examinar brevemente la historia de TensorFlow.
TensorFlow se convirtió en código abierto en noviembre de 2015 por un equipo de ingenieros que
trabajan en aprendizaje profundo en Google. Los autores de este libro son miembros de este equipo.
Desde su debut en código abierto, TensorFlow ha ganado una inmensa popularidad. Ahora se utiliza para
una amplia gama de aplicaciones industriales y proyectos de investigación tanto en Google como en la
comunidad técnica más amplia. El nombre "TensorFlow" se acuñó para reflejar lo que sucede dentro de
un programa típico escrito con el marco: representaciones de datos llamadastensoresfluya a través de
capas y otros nodos de procesamiento de datos, lo que permite que la inferencia y el entrenamiento
ocurran en modelos de aprendizaje automático.
En primer lugar, ¿qué es un tensor? Es solo la forma que tiene un científico informático de decir
"matriz multidimensional" de manera concisa. En las redes neuronales y el aprendizaje profundo, cada
dato y cada resultado de cálculo se representa como un tensor. Por ejemplo, una imagen en escala de
grises se puede representar como una matriz de números en 2D: un tensor en 2D; una imagen en color
generalmente se representa como un tensor 3D, siendo la dimensión adicional los canales de color. Los
sonidos, videos, texto y cualquier otro tipo de datos se pueden representar como tensores. Cada tensor
tiene dos propiedades básicas: el tipo de datos (como float32 o int32) y la forma. La forma describe el
tamaño del tensor en todas sus dimensiones. Por ejemplo, un tensor 2D puede tener la forma [128, 256],y
un tensor 3D puede tener la forma [10, 20, 128].Una vez que los datos se convierten en un tensor de un
tipo y forma de datos determinados, se pueden introducir en cualquier tipo de capa que acepte ese tipo y
forma de datos, independientemente del significado original de los datos. Por lo tanto, el tensor es la
lingua franca de los modelos de aprendizaje profundo.
Peropor quétensores? En la sección anterior, aprendimos que la mayor parte de los
cálculos involucrados en la ejecución de una red neuronal profunda se realizan como
operaciones masivamente paralelizadas, comúnmente en GPU, que requieren realizar el
mismo cálculo en múltiples piezas de datos. Los tensores son contenedores que organizan
nuestros datos en estructuras que se pueden procesar de manera eficiente en paralelo.
Cuando sumamos el tensor A con forma [128, 128]al tensor B con forma [128, 128],es muy
claro que hay128 * 128adiciones independientes que deben llevarse a cabo.
¿Qué hay de la parte del "flujo"? Imagine un tensor como una especie de fluido que transporta
datos. En TensorFlow, fluye a través de ungrafico—una estructura de datos que consta de
operaciones matemáticas interconectadas (llamadasnodos). Como muestra la figura 1.7, el nodo
puede ser capas sucesivas en una red neuronal. Cada nodo toma tensores como entradas y
produce tensores como salidas. El “fluido tensor” se transforma en diferentes formas y
28 CPASADO1Aprendizaje profundo y JavaScript

Aporte:
tensor 1 tensor 2
Tensor 0 Predicción:
tensor 3

Capa 1 Capa 2 Capa 3

Figura 1.7 Los tensores “fluyen” a través de varias capas, un escenario común en TensorFlow y
TensorFlow.js.

diferentes valores a medida que "fluye" a través del gráfico de TensorFlow. Esto corresponde a la
transformación de representaciones: es decir, el quid de lo que hacen las redes neuronales, como hemos
descrito en apartados anteriores. Con TensorFlow, los ingenieros de aprendizaje automático pueden
escribir todo tipo de redes neuronales, desde las superficiales hasta las muy profundas, desde convnets
para visión artificial hasta redes neuronales recurrentes (RNN) para tareas de secuencia. La estructura de
datos del gráfico se puede serializar e implementar para ejecutar muchos tipos de dispositivos, desde
mainframes hasta teléfonos móviles.
En esencia, TensorFlow fue diseñado para ser muy general y flexible: las operaciones
pueden ser cualquier función matemática bien definida, no solo capas de redes neuronales.
Por ejemplo, pueden ser operaciones matemáticas de bajo nivel, como sumar y multiplicar
dos tensores, el tipo de operaciones que ocurrenen el interioruna capa de red neuronal. Esto
les da a los ingenieros e investigadores de aprendizaje profundo un gran poder para definir
operaciones arbitrarias y novedosas para el aprendizaje profundo. Sin embargo, para una
gran parte de los profesionales del aprendizaje profundo, manipular maquinaria de tan bajo
nivel es más problemático de lo que vale. Conduce a un código inflado y más propenso a
errores y ciclos de desarrollo más largos. La mayoría de los ingenieros de aprendizaje
profundo usan un puñado de tipos de capas fijas (por ejemplo, convolución, agrupación o
densa, como aprenderá en detalle en capítulos posteriores). Rara vez necesitan crear nuevos
tipos de capas. Aquí es donde la analogía de LEGO es apropiada. Con LEGO, solo hay una
pequeña cantidad de tipos de bloques. Los constructores de LEGO no necesitan pensar en lo
que se necesita para hacer un bloque de LEGO. Esto es diferente de un juguete como, por
ejemplo, Play-Doh, que es análogo a la API de bajo nivel de TensorFlow.
¿Por qué TensorFlow.js? 29

poder infinito. Es posible construir una casa de juguete con LEGO o Play-Doh, pero a menos que
tenga requisitos muy especiales para el tamaño, la forma, la textura o el material de la casa, es
mucho más fácil y rápido construirla con LEGO. Para la mayoría de nosotros, la casa LEGO que
construimos se mantendrá más estable y se verá mejor que la casa Play-Doh que haríamos.

En el mundo de TensorFlow, el equivalente de LEGO es la API de alto nivel llamada Keras.9


Keras proporciona un conjunto de los tipos de capas de redes neuronales más utilizados,
cada uno con parámetros configurables. También permite a los usuarios conectar las capas
para formar redes neuronales. Además, Keras también viene con API para

- Especificar cómo se entrenará la red neuronal (funciones de pérdida, métricas y


optimizadores)
-Alimentar datos para entrenar o evaluar la red neuronal o usar el modelo para
inferencia
- Supervisar el proceso de formación en curso (devoluciones de
- llamada) Guardar y cargar modelos
- Impresión o trazado de la arquitectura de modelos.

Con Keras, los usuarios pueden realizar el flujo de trabajo completo de aprendizaje profundo
con muy pocas líneas de código. Con la flexibilidad de la API de bajo nivel y la facilidad de uso
de la API de alto nivel, TensorFlow y Keras forman un ecosistema que lidera el campo de los
marcos de aprendizaje profundo en términos de adopción industrial y académica (ver el
tweet enhttp://mng.bz/vlDJ). Como parte de la revolución del aprendizaje profundo en curso,
no se debe subestimar su papel para hacer que el aprendizaje profundo sea accesible a un
público más amplio. Antes de los marcos como TensorFlow y Keras, solo aquellos con
habilidades de programación CUDA y una amplia experiencia en la escritura de redes
neuronales en C ++ podían realizar un aprendizaje profundo práctico. Con TensorFlow y
Keras, se necesita mucha menos habilidad y esfuerzo para crear redes neuronales profundas
aceleradas por GPU. Pero había un problema: no era posible ejecutar modelos TensorFlow o
Keras en JavaScript o directamente en el navegador web. Para servir modelos de aprendizaje
profundo entrenados en el navegador, tuvimos que hacerlo a través de solicitudes HTTP a un
servidor back-end. Aquí es donde TensorFlow.js entra en escena. TensorFlow.js fue un
esfuerzo iniciado por Nikhil Thorat y Daniel Smilkov,10en Google. Como hemos mencionado,
la demostración muy popular de TensorFlow Playground de una red neuronal profunda
plantó la semilla inicial del proyecto TensorFlow.js. En septiembre de 2017, se lanzó una
biblioteca llamada deeplearn.js que tiene una API de bajo nivel análoga a la API de bajo nivel
de TensorFlow. Deeplearn.js defendió la tecnología neuronal acelerada por WebGL

9
De hecho, desde la introducción de TensorFlow, han surgido varias API de alto nivel, algunas creadas por ingenieros de
Google y otras por la comunidad de código abierto. Entre los más populares se encuentran Keras, tf.Estimator,
tf.contrib.slim y TensorLayers. Para los lectores de este libro, la API de alto nivel más relevante para TensorFlow.js es, con
mucho, Keras, porque la API de alto nivel de TensorFlow.js está modelada a partir de Keras y porque TensorFlow.js
proporciona compatibilidad bidireccional en el guardado de modelos. y cargando con Keras.
10 Como nota histórica interesante, estos autores también desempeñaron un papel clave en la creación de TensorBoard, el popular
herramienta de visualización para modelos TensorFlow.
30 CPASADO1Aprendizaje profundo y JavaScript

operaciones de red, lo que hace posible ejecutar redes neuronales reales con latencias de
inferencia bajas en el navegador web.
Tras el éxito inicial de deeplearn.js, más miembros del equipo de Google Brain se unieron al
proyecto y pasó a llamarse TensorFlow.js. La API de JavaScript experimentó una importante
renovación, lo que mejoró la compatibilidad de la API con TensorFlow. Además, se creó una API de
alto nivel similar a Keras sobre el núcleo de bajo nivel, lo que facilita mucho a los usuarios definir,
entrenar y ejecutar modelos de aprendizaje profundo en la biblioteca de JavaScript. Hoy, lo que
dijimos anteriormente sobre el poder y la facilidad de uso de Keras también es cierto para
TensorFlow.js. Para mejorar aún más la interoperabilidad, se crearon convertidores para que
TensorFlow.js pueda importar modelos guardados de TensorFlow y Keras y exportar modelos a
ellos. Desde su debut en la Cumbre mundial de desarrolladores de TensorFlow y Google I/O en la
primavera de 2018 (verwww.youtube.com/watch?v=YB-kfeNIPCEy www.youtube.com/watch?
v=OmofOvMApTU), TensorFlow.js se ha convertido rápidamente en una biblioteca de aprendizaje
profundo de JavaScript muy popular, actualmente con la mayor cantidad de estrellas y
bifurcaciones entre bibliotecas similares en GitHub.
La figura 1.8 presenta una descripción general de la arquitectura de TensorFlow.js. El nivel más bajo es
responsable de la computación paralela para operaciones matemáticas rápidas. Aunque esta capa no es
visible para la mayoría de los usuarios, es fundamental que tenga un alto rendimiento para que el
entrenamiento y la inferencia del modelo en los niveles más altos de la API puedan ser lo más rápidos
posible. En el navegador, aprovecha WebGL para lograr la aceleración de GPU (consulte el cuadro de
información 1.2). En Node.js, el enlace directo a la paralelización de CPU multinúcleo y la aceleración de
GPU CUDA están disponibles. Estos son los mismos backends matemáticos utilizados por TensorFlow y
Keras en Python. Construido en la parte superior del nivel matemático más bajo es elAPI de operaciones,
que tiene una buena paridad con la API de bajo nivel de TensorFlow y admite la carga de modelos
guardados desde TensorFlow. En el nivel más alto está el Keras-likeAPI de capas. La API de capas es la
opción de API correcta para la mayoría de los programadores que usan TensorFlow.js y será el enfoque
principal de este libro. La API de capas también admite la importación/exportación de modelos
bidireccionales con Keras.

Keras
API de capas
modelo

TensorFlow
API central
Modelo guardado

Navegador Nodo.js

WebGL TF TF TF
UPC GPU TPU

Figura 1.8 La arquitectura de TensorFlow.js de un vistazo. También se muestra su relación


con Python TensorFlow y Keras.
¿Por qué TensorFlow.js? 31

1.3.2 Por qué TensorFlow.js: una breve comparación con bibliotecas similares

TensorFlow.js no es la única biblioteca de JavaScript para el aprendizaje profundo; tampoco fue el primero
en aparecer (por ejemplo, brain.js y ConvNetJS tienen una historia mucho más larga). Entonces, ¿por qué
TensorFlow.js se destaca entre bibliotecas similares? La primera razón es su amplitud: TensorFlow.js es la
única biblioteca disponible actualmente que admite todas las partes clave del flujo de trabajo de
aprendizaje profundo de producción:

- Admite inferencia y entrenamiento


- Admite navegadores web y Node.js
- Aprovecha la aceleración de GPU (WebGL en navegadores y núcleos CUDA en Node.js)
- Admite la definición de arquitecturas de modelos de redes neuronales en JavaScript Admite
- serialización y deserialización de modelos
- Admite conversiones hacia y desde marcos de aprendizaje profundo de Python
- Compatible en API con marcos de aprendizaje profundo de Python
- Equipado con soporte integrado para ingesta de datos y con una API para visualización

La segunda razón es el ecosistema. La mayoría de las bibliotecas de aprendizaje profundo de


JavaScript definen su propia API única, mientras que TensorFlow.js está estrechamente integrado
con TensorFlow y Keras. ¿Tiene un modelo entrenado de Python TensorFlow o Keras y desea usarlo
en el navegador? No hay problema. ¿Ha creado un modelo TensorFlow.js en el navegador y desea
llevarlo a Keras para acceder a aceleradores más rápidos como Google TPU? ¡Eso también funciona!
La estrecha integración con marcos que no son de JavaScript no solo aumenta la interoperabilidad,
sino que también facilita a los desarrolladores la migración entre los mundos de los lenguajes de
programación y las pilas de infraestructura. Por ejemplo, una vez que haya dominado
TensorFlow.js después de leer este libro, será fácil si desea comenzar a usar Keras en Python. El
viaje inverso es tan fácil: alguien con un buen conocimiento de Keras debería poder aprender
TensorFlow.js rápidamente (suponiendo suficientes conocimientos de Java-Script). Por último, pero
no menos importante, no se debe pasar por alto la popularidad de TensorFlow.js y la fuerza de su
comunidad. Los desarrolladores de TensorFlow.js están comprometidos con el mantenimiento y la
asistencia técnica a largo plazo de la biblioteca. Desde el recuento de estrellas y bifurcaciones de
GitHub hasta la cantidad de colaboradores externos, desde la animación de la discusión hasta la
cantidad de preguntas y respuestas en Stack Overflow, TensorFlow.js no está a la sombra de
ninguna de las bibliotecas de la competencia.

1.3.3 ¿Cómo utiliza el mundo TensorFlow.js?


No hay testimonio más convincente del poder y la popularidad de una biblioteca que la forma
en que se utiliza en aplicaciones del mundo real. Algunas aplicaciones notables de
TensorFlow.js incluyen las siguientes:
- Project Magenta de Google usa TensorFlow.js para ejecutar RNN y otros tipos de redes
neuronales profundas para generar partituras musicales y sonidos de instrumentos
novedosos en el navegador (https://magenta.tensorflow.org/demos/).
32 CPASADO1Aprendizaje profundo y JavaScript

- Dan Shiffman y sus colegas de la Universidad de Nueva York crearon ML5.js, una API de alto nivel
y fácil de usar para varios modelos de aprendizaje profundo listos para usar para el navegador,
como la detección de objetos y la transferencia de estilo de imagen (https://ml5js.org). Abhishek
- Singh, un desarrollador de código abierto, creó una interfaz basada en navegador que traduce el
lenguaje de señas estadounidense a voz para ayudar a las personas que no pueden hablar ni
escuchar a usar altavoces inteligentes como Amazon Echo.11
- Canvas Friends es una aplicación web similar a un juego basada en TensorFlow.js que ayuda a los
usuarios a mejorar sus habilidades artísticas y de dibujo (www.y8.com/games/canvas_friends).
-MetaCar, un simulador de automóvil autónomo que se ejecuta en el navegador, utiliza Tensor-Flow.js

para implementar algoritmos de aprendizaje por refuerzo que son fundamentales para sus
simulaciones (www.metacar-project.com).
- Clinic doctor, una aplicación basada en Node.js que supervisa el rendimiento de los
programas del lado del servidor, implementó un modelo oculto de Markov con
TensorFlow.js y lo usa para detectar picos en el uso de la CPU.12
-Vea la galería de TensorFlow.js de otras aplicaciones destacadas creadas por la
comunidad de código abierto enhttps://github.com/tensorflow/tfjs/blob/master/
GALERÍA.md.

1.3.4 Lo que este libro le enseñará y no le enseñará sobre TensorFlow.js


Al estudiar los materiales de este libro, debería poder crear aplicaciones como las
siguientes con TensorFlow.js:
- Un sitio web que clasifica las imágenes subidas por un usuario
- Redes neuronales profundas que incorporan datos de imagen y audio de sensores conectados al
navegador y realizan tareas de aprendizaje automático en tiempo real, como reconocimiento y
aprendizaje de transferencia, en ellos
- IA de lenguaje natural del lado del cliente, como un clasificador de opiniones de comentarios para ayudar con la

moderación de comentarios

- Un entrenador de modelo de aprendizaje automático de Node.js (backend) que utiliza datos a escala de
gigabytes y aceleración de GPU
- Un alumno de refuerzo impulsado por TensorFlow.js que puede resolver problemas de juego y
control a pequeña escala
-Un tablero para ilustrar los aspectos internos de los modelos entrenados y los resultados de los

experimentos de aprendizaje automático

Es importante destacar que no solo sabrá cómo crear y ejecutar dichas aplicaciones, sino que también
comprenderá cómo funcionan. Por ejemplo, tendrá un conocimiento práctico de las estrategias y
limitaciones involucradas en la creación de modelos de aprendizaje profundo para varios

11Abhishek Singh, "Hacer que Alexa responda al lenguaje de señas usando su cámara web y TensorFlow.js"
Medio, 8 de agosto de 2018,http://mng.bz/4eEa.
12Andreas Madsen, "Clinic.js Doctor ahora es más avanzado con TensorFlow.js"Clínica.jsblog, 22 de agosto de 2018,
http://mng.bz/Q06w.
Ejercicios 33

tipos de problemas, así como los pasos y errores en el entrenamiento y la implementación de dichos
modelos.
El aprendizaje automático es un campo amplio; TensorFlow.js es una biblioteca versátil. Por lo tanto,
algunas aplicaciones son completamente factibles con la tecnología TensorFlow.js existente, pero van
más allá de lo que se cubre en el libro. Los ejemplos son:

- Entrenamiento distribuido de alto rendimiento de redes neuronales profundas que


involucran una gran cantidad de datos (del orden de terabytes) en el entorno Node.js
-Técnicas de redes no neuronales, como SVM, árboles de decisión y bosques
aleatorios
- Aplicaciones avanzadas de aprendizaje profundo, como motores de resumen de texto que
reducen documentos grandes en unas pocas oraciones representativas, motores de imagen a
texto que generan resumen de texto a partir de imágenes de entrada y modelos de imágenes
generativas que mejoran la resolución de las imágenes de entrada.

Sin embargo, este libro le brindará conocimientos básicos sobre aprendizaje profundo con los que
estará preparado para aprender sobre el código y los artículos relacionados con esas aplicaciones
avanzadas.
Como cualquier otra tecnología, TensorFlow.js tiene sus límites. Algunas tareas están más allá
de lo que puede hacer. Aunque es probable que estos límites se superen en el futuro, es bueno
saber dónde están los límites al momento de escribir:

-Ejecución de modelos de aprendizaje profundo con requisitos de memoria que exceden los límites de RAM y

WebGL en una pestaña del navegador. Para la inferencia en el navegador, esto generalmente significa un
modelo con un tamaño de peso total superior a ~ 100 MB. Para el entrenamiento, se requiere más
memoria y potencia informática, por lo que es posible que incluso los modelos más pequeños sean
demasiado lentos para entrenar en una pestaña del navegador. El entrenamiento de modelos también
suele implicar una mayor cantidad de datos que la inferencia, que es otro factor limitante que debe
tenerse en cuenta al evaluar la viabilidad del entrenamiento en el navegador.
- Crear un alumno de refuerzo de alto nivel, como el que puede derrotar a los jugadores
humanos en el juego de Go.
- Entrenamiento de modelos de aprendizaje profundo con una configuración distribuida (multimáquina) usando

Node.js.

Ejercicios
1 Ya sea que sea un desarrollador de JavaScript frontend o un desarrollador de Node.js, según lo
que aprendió en este capítulo, haga una lluvia de ideas sobre algunos casos posibles en los que
puede aplicar el aprendizaje automático al sistema en el que está trabajando para hacerlo más
inteligente. Para inspirarse, consulte las tablas 1.1 y 1.2, así como la sección 1.3.3. Algunos
ejemplos adicionales incluyen lo siguiente:
a Un sitio web de moda que vende accesorios como anteojos de sol captura imágenes de los rostros de
los usuarios usando la cámara web y detecta puntos de referencia faciales usando una red neuronal
profunda que se ejecuta en TensorFlow.js. Los puntos de referencia detectados se utilizan luego para
sintetizar una imagen de las gafas de sol superpuestas en la cara del usuario.
34 CPASADO1Aprendizaje profundo y JavaScript

para simular una experiencia de prueba en la página web. La experiencia es realista porque la
prueba simulada puede ejecutarse con una latencia baja y una velocidad de fotogramas alta
gracias a la inferencia del lado del cliente. Se respeta la privacidad de los datos del usuario
porque la imagen facial capturada nunca sale del navegador.
B Una aplicación deportiva móvil escrita en React Native (una biblioteca de JavaScript
multiplataforma para crear aplicaciones móviles nativas) realiza un seguimiento del ejercicio
de los usuarios. Con la API de HTML5, la aplicación accede a datos en tiempo real del
giroscopio y el acelerómetro del teléfono. Los datos se ejecutan a través de un modelo
impulsado por TensorFlow.js que detecta automáticamente el tipo de actividad actual del
usuario (por ejemplo, descansar, caminar, trotar o correr).
C Una extensión del navegador detecta automáticamente si la persona que usa el dispositivo es un
niño o un adulto (mediante el uso de imágenes capturadas desde la cámara web a una velocidad de
cuadro de una vez cada 5 segundos y un modelo de visión por computadora impulsado por
TensorFlow.js) y usa la información para bloquear o permitir el acceso a ciertos sitios web en
consecuencia.
D Un entorno de programación basado en navegador utiliza una red neuronal recurrente
implementada con TensorFlow.js para detectar errores tipográficos en los comentarios del código.
mi Una aplicación del lado del servidor basada en Node.js que coordina un servicio de logística de carga
utiliza señales en tiempo real, como el estado del transportista, el tipo y la cantidad de carga, la
fecha/hora y la información de tráfico para predecir la hora estimada de llegada (ETA) para cada uno.
transacción. Las canalizaciones de entrenamiento e inferencia están todas escritas en Node.js,
usando TensorFlow.js, lo que simplifica la pila del servidor.

Resumen
- La IA es el estudio de la automatización de tareas cognitivas. El aprendizaje automático es un subcampo
de la IA en el que las reglas para realizar una tarea, como la clasificación de imágenes, se descubren
automáticamente aprendiendo de ejemplos en los datos de entrenamiento.
- Un problema central en el aprendizaje automático es cómo transformar la representación
original de los datos en una representación más adecuada para resolver la tarea.
- Las redes neuronales son un enfoque en el aprendizaje automático en el que la transformación de
la representación de datos se realiza mediante pasos sucesivos (o capas) de operaciones
matemáticas. El campo del aprendizaje profundo se refiere a las redes neuronales profundas,
redes neuronales con muchas capas.
- Gracias a las mejoras en el hardware, la mayor disponibilidad de datos etiquetados y los avances en los
algoritmos, el campo del aprendizaje profundo ha logrado un progreso asombroso desde principios de
la década de 2010, resolviendo problemas que antes no tenían solución y creando nuevas y
emocionantes oportunidades.
- JavaScript y el navegador web son un entorno adecuado para implementar y entrenar
redes neuronales profundas.
- TensorFlow.js, el tema central de este libro, es una biblioteca de código abierto completa,
versátil y potente para el aprendizaje profundo en JavaScript.
Parte 2

Una tierna introducción


a TensorFlow.js

H Habiendo cubierto los fundamentos, en esta parte del libro nos sumergimos
en el aprendizaje automático de manera práctica, armados con TensorFlow.js.
Comenzamos en el capítulo 2 con una tarea simple de aprendizaje automático, la
regresión (predicción de un solo número), y avanzamos hacia tareas más sofisticadas,
como la clasificación binaria y multiclase en los capítulos 3 y 4. Al mismo tiempo que los
tipos de tareas, también verá una suave progresión desde datos simples (matrices
planas de números) a otros más complejos (imágenes y sonidos). La base matemática
de métodos como la retropropagación se presentará junto con problemas concretos y
el código que los resuelve. Evitamos las matemáticas formales en favor de
explicaciones, diagramas y pseudocódigos más intuitivos. El Capítulo 5 analiza el
aprendizaje por transferencia, una reutilización eficiente de redes neuronales
previamente entrenadas para adaptarse a nuevos datos,
Empezando:
Regresión lineal simple
en TensorFlow.js

Este capítulo cubre


- Un ejemplo mínimo de una red neuronal para la simple tarea
de aprendizaje automático de regresión lineal
- Tensores y operaciones de tensor

- Optimización básica de redes neuronales

A nadie le gusta esperar, y es especialmente molesto esperar cuando no sabemos cuánto tiempo
tendremos que esperar. Cualquier diseñador de experiencia de usuario le dirá que si no puede ocultar
la demora, lo mejor que puede hacer es darle al usuario una estimación confiable del tiempo de
espera. Estimar los retrasos esperados es un problema de predicción, y la biblioteca TensorFlow.js se
puede usar para crear una predicción precisa del tiempo de descarga, sensible al contexto y al
usuario, lo que nos permite crear experiencias claras y confiables que respetan el tiempo y la atención
del usuario.
En este capítulo, utilizando un problema simple de predicción del tiempo de descarga como nuestro
ejemplo motivador, presentaremos los componentes principales de un modelo completo de aprendizaje
automático. Cubriremos tensores, modelado y optimización desde una perspectiva práctica.

37
38 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

punto de vista para que pueda construir intuiciones sobre lo que son, cómo funcionan y
cómo usarlos apropiadamente.
Una comprensión completa de los aspectos internos del aprendizaje profundo, el tipo que un
investigador dedicado desarrollaría durante años de estudio, requiere familiaridad con muchos
temas matemáticos. Sin embargo, para el profesional del aprendizaje profundo, la experiencia en
álgebra lineal, cálculo diferencial y estadísticas de espacios de alta dimensión es útil pero no
necesaria, incluso para construir sistemas complejos de alto rendimiento. Nuestro objetivo en este
capítulo, ya lo largo de este libro, es introducir temas técnicos según sea necesario utilizando
código, en lugar de notación matemática, cuando sea posible. Nuestro objetivo es transmitir una
comprensión intuitiva de la maquinaria y su propósito sin necesidad de experiencia en el dominio.

2.1 Ejemplo 1: Predecir la duración de una descarga


usando TensorFlow.js
¡Entremos de inmediato! Construiremos una red neuronal mínima que utilice la biblioteca
TensorFlow.js (a veces abreviada como tfjs) para predecir los tiempos de descarga dado el tamaño
de la descarga. A menos que ya tenga experiencia con TensorFlow.js o bibliotecas similares, no
entenderá todo sobre este primer ejemplo de inmediato, y eso está bien. Cada tema presentado
aquí se tratará en detalle en los próximos capítulos, ¡así que no se preocupe si algunas partes le
parecen arbitrarias o mágicas! Tenemos que empezar en alguna parte. Comenzaremos escribiendo
un programa corto que acepte un tamaño de archivo como entrada y genere un tiempo previsto
para descargar el archivo.

2.1.1 Resumen del proyecto: predicción de la duración

Al estudiar un sistema de aprendizaje automático por primera vez, es posible que se sienta
intimidado por la variedad de nuevos conceptos y la jerga. Por lo tanto, es útil observar
primero todo el flujo de trabajo. El esquema general de este ejemplo se ilustra en la figura
2.1, y es un patrón que veremos repetido en los ejemplos de este libro.
En primer lugar, accederemos a nuestros datos de entrenamiento. En el aprendizaje automático, los
datos se pueden leer desde el disco, descargar a través de la red, generar o simplemente codificar. En
este ejemplo, tomamos el último enfoque porque es conveniente y solo tratamos con una pequeña
cantidad de datos. En segundo lugar, convertiremos los datos en tensores, para que puedan incorporarse
a nuestro modelo. El siguiente paso es crear un modelo que, como vimos en el capítulo 1, es similar a
diseñar una función entrenable adecuada: una función que asigna datos de entrada a cosas que estamos
tratando de predecir. En este caso, los datos de entrada y los objetivos de predicción son números. Una
vez que nuestro modelo y los datos estén disponibles, entrenaremos el modelo, monitoreando sus
métricas informadas a medida que avanza. Finalmente, usaremos el modelo entrenado para hacer
predicciones sobre datos que aún no hemos visto y medir la precisión del modelo.
Procederemos a través de cada una de estas fases con fragmentos de código ejecutable de copiar y
pegar y explicaciones tanto de la teoría como de las herramientas.
Ejemplo 1: Predecir la duración de una descarga usando TensorFlow.js 39

Obtener datos de entrenamiento

Convertir datos a tensores

Crear modelo

listados2.3 y 2.4

Ajustar modelo a datos

Usar modelo en datos nuevos

Figura 2.1 Descripción general de los principales pasos involucrados en el sistema de predicción
del tiempo de descarga, nuestro primer ejemplo

2.1.2 Una nota sobre las listas de códigos y las interacciones de la consola

El código de este libro se presentará en dos formatos. El primer formato, el código.listado,


presenta código estructural que encontrarás en los repositorios de código referenciados. Cada
listado tiene un título y un número. Por ejemplo, el listado 2.1 contiene un fragmento HTML muy
breve que puede copiar textualmente en un archivo, por ejemplo, /tmp/tmp.html, en su
computadora y luego abrirlo en su navegador web en file:///tmp/tmp. html, aunque no hará mucho
por sí mismo.
El segundo formato de código es elinteracción de la consola. Estos bloques más informales están
destinados a transmitir interacciones de ejemplo en un REPL de JavaScript,1como el navegador

1
Read-eval-print-loop, también conocido como intérprete interactivo o shell. El REPL nos permite interactuar activamente con
nuestro código para interrogar variables y probar funciones.
40 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

Consola de JavaScript (Cmd-Opt-J, Ctrl+Shift+J o F12 en Chrome, pero su navegador/SO puede ser
diferente). Las interacciones de la consola se indican con un signo mayor que precedente, como lo
que vemos en Chrome o Firefox, y sus resultados se presentan en la siguiente línea, al igual que en
la consola. Por ejemplo, la siguiente interacción crea una matriz e imprime el valor. El resultado que
ve en su consola de JavaScript puede ser ligeramente diferente, pero la esencia debería ser la
misma:

> sea a = ['hola', 'mundo', 2 * 1009]


> un;
(3) ["hola", "mundo", 2018]

La mejor manera de probar, ejecutar y aprender de las listas de códigos de este libro es clonar los
repositorios a los que se hace referencia y luego jugar con ellos. Durante el desarrollo de este libro,
hicimos uso frecuente de CodePen como un repositorio simple, interactivo y compartible (http://
codepen.io). Por ejemplo, el listado 2.1 está disponible para que juegues en codepen.io/tfjs-book/
pen/VEVMbx. Cuando navegue hasta CodePen, debería ejecutarse automáticamente. Debería
poder ver la salida impresa en la consola. Haga clic en Consola en la parte inferior izquierda para
abrir la consola. Si el CodePen no se ejecuta automáticamente, intente hacer un cambio pequeño e
intrascendente, como agregar un espacio al final, para iniciarlo.

Los listados de esta sección están disponibles en esta colección de CodePen:códec


. io/colección/Xzwavm/. CodePen funciona bien donde hay un solo archivo JavaScript, pero
nuestros ejemplos más grandes y estructurados se mantienen en los repositorios de GitHub,
que verá en ejemplos posteriores. Para este ejemplo, recomendamos leer esta sección y
luego jugar con los CodePens asociados en orden.

2.1.3 Crear y formatear los datos


Estimemos cuánto tiempo llevará descargar un archivo en una máquina, dado solo su tamaño en MB. Primero
usaremos un conjunto de datos creado previamente, pero, si está motivado, puede crear un conjunto de datos
similar, modelando las estadísticas de red de su propio sistema.

Listado 2.1 Codificando los datos de entrenamiento y prueba (de CodePen 2-a)

<guión src='https://cdn.jsdelivr.net/npm/@tensorflow/ tfjs@latest '></guión> <guión>

const datostren = {
tamaño MB: [0.080, 9.000, 0.001, 0.100, 8.000,
5.000, 0.100, 6.000, 0.050, 0.500, 0.002, 2.000,
0.005, 10.00, 0.010, 7.000, 6.000, 5.000, 1.000,
1.000],
tiempoSec: [0.135, 0.739, 0.067, 0.126, 0.646,
0,435, 0,069, 0,497, 0,068, 0,116, 0,070, 0,289,
0,076, 0,744, 0,083, 0,560, 0,480, 0,399, 0,153,
0,149]
};
const testData = {
tamaño MB: [5.000, 0.200, 0.001, 9.000, 0.002,
0,020, 0,008, 4,000, 0,001, 1,000, 0,005, 0,080,
0,800, 0,200, 0,050, 7,000, 0,005, 0,002, 8,000,
0,008],
Ejemplo 1: Predecir la duración de una descarga usando TensorFlow.js 41

tiempoSec: [0.425, 0.098, 0.052, 0.686, 0.066,


0,078, 0,070, 0,375, 0,058, 0,136, 0,052, 0,063,
0,183, 0,087, 0,066, 0,558, 0,066, 0,068, 0,610,
0,057]
};
</script>

En la lista de código HTML anterior, hemos optado por incluir explícitamente el <guion> etiquetas,
que ilustran cómo cargar la versión más reciente de la biblioteca TensorFlow.js usando @más
recientesufijo (al momento de escribir este código, este código se ejecutaba con tfjs 0.13.5). Más
adelante entraremos en más detalles sobre las diferentes formas de importar TensorFlow.js en su
aplicación, pero en el futuro, el <guion>se asumirán las etiquetas. El primer script carga el paquete
TensorFlow y define el símbolotf,que proporciona una forma de hacer referencia a los nombres en
TensorFlow. Por ejemplo,tf.añadir()hace referencia a la operación de adición de TensorFlow, que
suma dos tensores. En el futuro, supondremos que elt.f.El símbolo se carga y está disponible en el
espacio de nombres global, por ejemplo, al obtener el script TensorFlow.js como anteriormente.

El listado 2.1 crea dos constantes,trenDatosydatos de prueba,cada uno representa 20 muestras


de cuánto tiempo llevó descargar un archivo (tiempoSeg)y el tamaño de ese archivo (tamaño MB).los
elementos entamaño MBy los que están entiempoSegtienen correspondencia uno a uno. Por
ejemplo, el primer elemento detamaño MBentrenDatoses de 0,080 MB, y la descarga de ese archivo
tomó 0,135 segundos, es decir, el primer elemento detiempoSeg—Etcétera. Nuestro objetivo en
este ejemplo será estimartiempo seg,dado solotamaño MB.En este primer ejemplo, estamos
creando los datos directamente codificándolos en nuestro código. Este enfoque es conveniente
para este ejemplo simple, pero se volverá difícil de manejar muy rápidamente cuando crezca el
tamaño del conjunto de datos. Los ejemplos futuros ilustrarán cómo transmitir datos desde un
almacenamiento externo o a través de la red.
Volvamos a los datos. En el diagrama de la figura 2.2, podemos ver que existe una relación muy predecible,
aunque imperfecta, entre el tamaño y el tiempo de descarga. Los datos en la vida real son ruidosos, pero parece
que deberíamos poder hacer una estimación lineal bastante buena de la duración dado el tamaño del archivo. A
juzgar por el ojo, la duración debería ser de aproximadamente 0,1 segundos cuando el tamaño del archivo es
cero y luego aumentar a aproximadamente 0,07 segundos por cada MB adicional. Recuerde del capítulo 1 que
cada par de entrada-salida a veces se denominaejemplo. La salida a menudo se denominaobjetivo, mientras que
los elementos de la entrada a menudo se denominancaracteristicas. En nuestro caso aquí, cada uno de nuestros
40 ejemplos tiene exactamente una característica, tamaño MB,y un objetivo numérico,tiempoSeg.

En el listado 2.1, es posible que haya notado que los datos se dividen en dos subconjuntos, a saber
trenDatosydatos de prueba. trenDatoses el conjunto de entrenamiento. Contiene los ejemplos en los que se
entrenará el modelo.datos de pruebaes el conjunto de prueba. Lo usaremos para juzgar qué tan bien se
entrena el modelo después de completar el entrenamiento. Si entrenáramos y evaluáramos usando
exactamente los mismos datos, sería como hacer una prueba después de haber visto las respuestas. En el
caso más extremo, el modelo teóricamente podría memorizar eltiempoSegvalor para cadatamaño MBen
los datos de entrenamiento—no es un muy buen algoritmo de aprendizaje. El resultado no sería un buen
juez del desempeño futuro porque es poco probable que los valores
42 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

Duración de la descarga del archivo

trenDatos
datos de prueba

0.6
Tiempo (seg)

0.4

0.2

0
0 0 4 6 8 10
Tamaño (MB)

Figura 2.2 Duración de la descarga medida frente al tamaño del archivo. Si está interesado, en este
punto, en cómo crear gráficos como este, el código se encuentra en CodePencodepen.io/tfjsbook/
pen/dgQVze.

de las características de entrada futuras serán todas exactamente las mismas en las que se ha entrenado
el modelo.
Por lo tanto, el flujo de trabajo será el siguiente. Primero ajustaremos la red neuronal en los
datos de entrenamiento para hacer predicciones precisas detiempoSegdadotamaño MB.Luego, le
pediremos a la red que produzca predicciones paratamaño MBusando los datos de prueba, y
mediremos qué tan cerca están esas predicciones detiempoSeg.Pero primero, tendremos que
convertir estos datos a un formato que TensorFlow.js entienda, y este será nuestro primer ejemplo
de uso de tensores. El código del listado 2.2 muestra el primer uso de funciones bajo elf.*espacio
de nombres que verá en este libro. Aquí, vemos métodos para convertir datos almacenados en
estructuras de datos de JavaScript sin procesar en tensores.
Aunque el uso es bastante sencillo, aquellos lectores que deseen obtener una base más
sólida en estas API deben leer el apéndice B, que cubre no solo las funciones de creación de
tensor comotf.tensor2d(),sino también funciones que realizan operaciones que transforman y
combinan tensores, y patrones de cómo los tipos de datos comunes del mundo real, como
imágenes y videos, se empaquetan convencionalmente en tensores. No profundizamos en la
API de bajo nivel en el texto principal porque el material es algo seco y no está vinculado a
problemas de ejemplo específicos.

Listado 2.2 Convirtiendo datos en tensores (de CodePen 2-b)

const entrenarTensores = {
sizeMB: tf.tensor2d(trainData.sizeMB, [20, 1]), timeSec:
tf.tensor2d(trainData.timeSec, [20, 1])
};
El [20,1]aquí está la "forma" del tensor. Más adelante se explicará más,
const pruebaTensores = {
pero aquí esta forma significa que queremos interpretar la lista de
números como 20 muestras, donde cada muestra es1número. Si el
la forma es obvia, por ejemplo, a partir de la estructura de los datos
matriz, este argumento se puede omitir.
Ejemplo 1: Predecir la duración de una descarga usando TensorFlow.js 43

sizeMB: tf.tensor2d(testData.sizeMB, [20, 1]), timeSec:


tf.tensor2d(testData.timeSec, [20, 1])
};

En general, todos los sistemas de aprendizaje automático actuales utilizan tensores como
estructura de datos básica. Los tensores son fundamentales para el campo, tan fundamentales que
TensorFlow y TensorFlow.js llevan su nombre. Un recordatorio rápido del capítulo 1: en esencia, un
tensor es un contenedor de datos, casi siempre datos numéricos. Por lo tanto, se puede considerar
como un contenedor de números. Es posible que ya esté familiarizado con los vectores y las
matrices, que son tensores 1D y 2D, respectivamente. Los tensores son una generalización de
matrices a un número arbitrario de dimensiones. El número de dimensiones y el tamaño de cada
dimensión se llama el tensorforma. Por ejemplo, una matriz de 3 × 4 es un tensor con forma [3, 4].
Un vector de longitud 10 es un tensor 1D con forma [10].
En el contexto de los tensores, una dimensión a menudo se denominaeje. En TensorFlow.js, los
tensores son la representación común que permite que los componentes se comuniquen y
trabajen entre sí, ya sea en CPU, GPU u otro hardware. Tendremos más que decir sobre los
tensores y sus casos de uso comunes a medida que surja la necesidad, pero por ahora,
continuemos con nuestro proyecto de predicción.

2.1.4 Definición de un modelo simple

En el contexto del aprendizaje profundo, la función de las características de entrada a los objetivos se
conoce comomodelo. La función modelo toma características, ejecuta un cálculo y produce predicciones.
El modelo que estamos construyendo aquí es una función que toma el tamaño de un archivo como
duraciones de entrada y salida (ver figura 2.2). En el lenguaje del aprendizaje profundo, a veces usamosla
red como sinónimo de modelo. Nuestro primer modelo será una implementación deregresión lineal.
Regresión, en el contexto del aprendizaje automático, significa que el modelo generará
números con valores reales e intentará igualar los objetivos de entrenamiento; esto se opone a la
clasificación, que genera elecciones de un conjunto de opciones. En una tarea de regresión, un
modelo que genere números más cercanos al objetivo es mejor que un modelo que genere
números más lejanos. Si nuestro modelo predice que un archivo de 1 MB tardará unos 0,15
segundos, es mejor (como podemos ver en la figura 2.2) que si nuestro modelo predice que un
archivo de 1 MB tardará unos 600 segundos.
La regresión lineal es un tipo específico de regresión en el que la salida, en función de la
entrada, se puede ilustrar como una línea recta (o, por analogía, un plano plano en un espacio de
mayor dimensión cuando hay múltiples características de entrada). Una propiedad importante de
los modelos es que sonajustable. Esto significa que el cálculo de entrada-salida se puede ajustar.
Usamos esta propiedad para ajustar el modelo para "ajustar" mejor los datos. En el caso lineal, la
relación de entrada-salida del modelo siempre es una línea recta, pero podemos ajustar la
pendiente y la intersección con el eje y.
Construyamos nuestra primera red para tener una idea de esto.

Listado 2.3 Construyendo un modelo de regresión lineal (de CodePen 2-c)

const modelo = tf.secuencial(); modelo.add(tf.layers.dense({inputShape: [1],


unidades: 1}));
44 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

El bloque de construcción central de las redes neuronales es elcapa, un módulo de procesamiento


de datos que puede considerar como una función ajustable de tensor a tensor. Aquí, nuestra red
consta de una sola capa densa. Esta capa tiene una restricción en la forma del tensor de entrada,
según lo define el parámetroforma de entrada: [1].Aquí, significa que la capa espera una entrada en
forma de tensor 1D con exactamente un valor. La salida de la capa densa es siempre un tensor 1D
para cada ejemplo, pero el tamaño de esa dimensión está controlado por elunidadesparámetro de
configuración. En este caso, queremos un solo número de salida porque estamos tratando de
predecir exactamente un número, a saber, eltiempoSeg.
En esencia, la capa densa es una suma múltiple ajustable entre cada entrada y cada salida.
Como solo hay una entrada y una salida, este modelo es el simple y = metro * x + segundoecuación
lineal que quizás recuerdes de las matemáticas de la escuela secundaria. La capa densa llama
internamentemetrolosnúcleoyBlosparcialidad, como se ilustra en la figura 2.3. En este caso, hemos
construido un modelo lineal para la relación entre la entrada (tamaño MB)y la salida (tiempoSec):

tiempoSeg =núcleo*tamaño MB +parcialidad

Hay cuatro términos en esta ecuación. Dos de ellos son fijos en lo que se refiere al entrenamiento
del modelo: los valores detamaño MBytiempoSegestán determinados por los datos de
entrenamiento (ver listado 2.1). Los otros dos términos, kernel y bias, son los parámetros del
modelo. Sus valores se eligen aleatoriamente cuando se crea el modelo. Esos valores aleatorios no
darán buenas predicciones de la duración de la descarga. Para que sucedan predicciones decentes,
debemos buscar buenos valores del núcleo y sesgo al permitir que el modelo aprenda de los datos.
Esta búsqueda es laproceso de entrenamiento.
Para encontrar una buena configuración para el núcleo y el sesgo (colectivamente, elpesos) necesitamos dos
cosas:

- Una medida que nos dice qué tan bien lo estamos haciendo en una determinada configuración de los pesos. Un

- método para actualizar los valores de los pesos para que la próxima vez lo hagamos mejor de lo que lo estamos

haciendo actualmente, de acuerdo con la medida mencionada anteriormente.

Modelo

capa densa

Tensor de entrada núcleo Tensor de salida


forma: [1] forma: [1]
tamaño MB tiempoSeg
Parcialidad

Figura 2.3 Una ilustración de nuestro modelo de regresión lineal simple. El modelo tiene exactamente una capa. Los
parámetros ajustables del modelo (o pesos), el kernel y el sesgo, se muestran dentro de la capa densa.
Ejemplo 1: Predecir la duración de una descarga usando TensorFlow.js 45

Esto nos lleva al siguiente paso para resolver el problema de regresión lineal. Para que la red esté
lista para el entrenamiento, debemos elegir la medida y el método de actualización, que
corresponden a los dos elementos necesarios enumerados anteriormente. Esto se hace como parte
de lo que TensorFlow.js llama elcompilación de modelospaso, que lleva

- Afunción de pérdida—Un error de medida. Así es como la red mide su desempeño en los datos de
entrenamiento y se orienta en la dirección correcta. Menos pérdida es mejor. A medida que
entrenamos, deberíamos poder trazar la pérdida a lo largo del tiempo y ver cómo disminuye. Si
nuestro modelo entrena durante mucho tiempo y la pérdida no disminuye, podría significar que
nuestro modelo no está aprendiendo a adaptarse a los datos. A lo largo de este libro, aprenderá a
depurar problemas como este.
- Unoptimizador—El algoritmo mediante el cual la red actualizará sus pesos (kernel y
bias, en este caso) en función de los datos y la función de pérdida.

El propósito exacto de la función de pérdida y el optimizador, y cómo tomar buenas


decisiones para ellos, se explorará a fondo en los próximos capítulos. Pero por
ahora, las siguientes opciones servirán.

Listado 2.4 Configurando opciones de entrenamiento: compilación del modelo (de CodePen 2-c)

model.compile({optimizador: 'sgd', pérdida: 'meanAbsoluteError'});

llamamos alcompilarmétodo en nuestro modelo, especificando 'sgd'como nuestro optimizador y


'meanAbsoluteError'como nuestra pérdida. 'error absoluto medio'significa que nuestra función de pérdida
calculará qué tan lejos están nuestras predicciones de los objetivos, tomará sus valores absolutos
(haciéndolos todos positivos) y luego devolverá el promedio de esos valores:

meanAbsoluteError = promedio (absoluto (modelOutput - objetivos))

Por ejemplo, dado


salida del modelo = [1.1, 2.2, 3.3, 3.6] objetivos
= [1.0, 2.0, 3.0, 4.0]

luego,

error absoluto medio = promedio ([|1.1 - 1.0|, |2.2 - 2.0|,


|3.3 - 3.0|, |3.6 - 4.0|])

= promedio ([0.1, 0.2, 0.3, 0.4])

= 0,25

Si nuestro modelo hace muy malas predicciones que están muy lejos de los objetivos,
entonces el error absoluto medioserá muy grande. Por el contrario, lo mejor que podemos
hacer es obtener todas las predicciones exactamente correctas, en cuyo caso la diferencia
entre la salida de nuestro modelo y los objetivos sería cero y, por lo tanto, la pérdida (laerror
absoluto medio)sería cero.
lossgden el listado 2.4 significadescenso de gradiente estocástico, que describiremos un poco
más en la sección 2.2. Brevemente, significa que usaremos el cálculo para determinar qué
46 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

ajustes que debemos hacer a los pesos para reducir la pérdida; luego haremos esos
ajustes y repetiremos el proceso.
Nuestro modelo ya está listo para adaptarse a nuestros datos de entrenamiento.

2.1.5 Ajuste del modelo a los datos de entrenamiento

El entrenamiento de un modelo en TensorFlow.js se realiza llamando al modeloencajar()método.


Ajustamos el modelo a los datos de entrenamiento. Aquí, pasamos en eltamaño MBtensor como nuestra
entrada y eltiempoSegtensor como nuestra salida deseada. También pasamos un objeto de configuración
con unépocascampo que especifica que nos gustaría revisar nuestros datos de entrenamiento
exactamente 10 veces. En el aprendizaje profundo, cada iteración a través del conjunto de entrenamiento
completo se denominaépoca.

Listado 2.5 Ajuste de un modelo de regresión lineal (de CodePen 2-c)

(función asíncrona() {
espera model.fit(trainTensors.sizeMB,
entrenarTensores.timeSec,
{épocas: 10});
})();

losencajar()El método a menudo puede ser de larga duración, con una duración de segundos o minutos. Por lo
tanto, utilizamos elasíncrono/esperacaracterística de ES2017/ES8 para que esta función se pueda usar de una
manera que no bloquee el subproceso principal de la interfaz de usuario cuando se ejecuta en el navegador. Esto
es similar a otras funciones potencialmente de larga ejecución en JavaScript, comorecuperación asíncrona.Aquí
esperamos a queencajar()llamar para terminar antes de continuar, usando la expresión de función asíncrona
inmediatamente invocada2patrón, pero los ejemplos futuros se entrenarán en segundo plano mientras se realiza
otro trabajo en el subproceso de primer plano.
Una vez que nuestro modelo haya terminado de ajustarse, querremos ver si funcionó.
Fundamentalmente, evaluaremos el modelo con datos que no se usaron durante el entrenamiento. Este
tema de separar los datos de prueba de los datos de entrenamiento (y, por lo tanto, evitar el
entrenamiento en los datos de prueba) es algo que surgirá una y otra vez en este libro. Es una parte
importante del flujo de trabajo de aprendizaje automático que debe internalizar.
Los modelosevaluar()El método calcula la función de pérdida según se aplica a las
características y objetivos de ejemplo proporcionados. es similar a laencajar()método
que calcula la misma pérdida, peroevaluar()no actualiza los pesos del modelo. Usamos
evaluar()para estimar la calidad del modelo en los datos de prueba, para tener una idea
de cómo se comportaría el modelo en la aplicación futura:
> modelo.evaluar(testTensors.sizeMB, testTensors.timeSec).print(); Tensor

0.31778740882873535

Aquí, vemos que la pérdida, promediada a través de los datos de prueba, es de aproximadamente 0,318. Dado que, de
forma predeterminada, los modelos se entrenan a partir de un estado inicial aleatorio, obtendrá un valor diferente.

2Para obtener más información sobre las expresiones de función invocadas inmediatamente, consultehttp://mng.bz/RPOZ.
Ejemplo 1: Predecir la duración de una descarga usando TensorFlow.js 47

Otra forma de decir lo mismo es que el error absoluto medio (MAE) de este modelo es de poco más de 0,3
segundos. ¿Es esto bueno? ¿Es mejor que simplemente estimar una constante? Una buena constante que
podríamos elegir es el retraso promedio. Veamos qué tipo de error obtendríamos al usar el soporte de
TensorFlow.js para operaciones matemáticas en tensores. Primero, calcularemos el tiempo promedio de
descarga, calculado sobre nuestro conjunto de entrenamiento:

> const avgDelaySec = tf.mean(trainData.timeSec);


> avgDelaySec.print();
Tensor
0.2950500249862671

A continuación, calculemos elerror absoluto mediomanualmente. MAE es simplemente el valor


promedio de qué tan lejos estaba nuestra predicción del valor real. usaremostf.sub()para
calcular la diferencia entre los objetivos de prueba y nuestra predicción (constante) ytf.abs()
para tomar el valor absoluto (ya que a veces seremos demasiado bajos y otras demasiado
altos), y luego tomar el promedio contf.media:

> tf.mean(tf.abs(tf.sub(testData.timeSec, 0.295))).print(); Tensor

0.22020000219345093

Consulte el cuadro de información 2.1 para saber cómo realizar el mismo cálculo utilizando la API de encadenamiento conciso.

ICAJA NFO2.1 API de encadenamiento de tensores


Además de la API estándar, en la que las funciones de tensor están disponibles bajo elt.f. espacio
de nombres, la mayoría de las funciones de tensor también están disponibles desde los propios
objetos de tensor, lo que le permite escribir en un estilo de encadenamiento si lo prefiere. El
siguiente código es funcionalmente idéntico alerror absoluto mediocálculo en el texto principal:

// patrón de API de encadenamiento


> testData.timeSec.sub(0.295).abs().mean().print(); Tensor

0.22020000219345093

Parece que el retraso medio es de unos 0,295 segundos y que adivinar siempre el valor medio da
una mejor estimación que nuestra red. ¡Esto significa que la precisión de nuestro modelo es incluso
peor que la de un enfoque trivial de sentido común! ¿Podemos hacerlo mejor? Es posible que no
hayamos entrenado durante suficientes épocas. Recuerde que durante el entrenamiento, los
valores de kernel y bias se actualizan paso a paso. En este caso, cada época es un paso. Si el
modelo se entrena solo para una pequeña cantidad de épocas (pasos), es posible que los valores
de los parámetros no tengan la oportunidad de acercarse al óptimo. Entrenemos nuestro modelo
unos cuantos ciclos más y volvamos a evaluar:

> modelo.ajuste(trenTensores.tamañoMB, Asegúrese de esperar a que se resuelva


entrenarTensores.timeSec, la promesa devuelta por model.fit antes
{épocas: 200}); de ejecutar model.evaluate.

> modelo.evaluar(testTensors.sizeMB, testTensors.timeSec).print();


48 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

Tensor
0.04879039153456688

¡Mucho mejor! Parece que antes estábamosdesajustar, lo que significa que nuestro modelo no se había
adaptado lo suficiente a los datos de entrenamiento. Ahora nuestras estimaciones están dentro de los
0,05 segundos, en promedio. Somos cuatro veces más precisos que adivinar ingenuamente la media. En
este libro, ofreceremos orientación sobre cómo evitar la inadecuación, así como el problema más
insidioso desobreajuste, donde se ajusta el modelodemasiadoa los datos de entrenamiento y no
generaliza bien a los datos que no ha visto.

2.1.6 Usando nuestro modelo entrenado para hacer predicciones

¡Vale genial! Ahora tenemos un modelo que puede hacer predicciones precisas del tiempo de descarga
dado un tamaño de entrada, pero ¿cómo lo usamos? La respuesta es la del modelo.predecir() método:

> const smallFileMB = 1;


> const granArchivoMB = 100;
> const granArchivoMB = 10000;
> model.predict(tf.tensor2d([[smallFileMB], [archivograndeMB],
[hugeFileMB]])).print();
Tensor
[[0.1373825 ],
[7.2438402 ],
[717.8896484]]

Aquí vemos que nuestro modelo predice que la descarga de un archivo de 10 000 MB tardará unos 718
segundos. Tenga en cuenta que no teníamos ningún ejemplo en nuestros datos de entrenamiento cerca de este
tamaño. En general, la extrapolación a valores muy por fuera de los datos de entrenamiento es muy arriesgada,
pero con un problema tan simple, puede ser preciso. . . siempre y cuando no nos encontremos con nuevas
complicaciones con los búferes de memoria, la conectividad de entrada-salida, etc. Sería mejor si pudiéramos
recolectar más datos de entrenamiento en este rango.
Vemos también que necesitábamos envolver nuestras variables de entrada en un tensor de
forma apropiada. En el listado 2.3, definimos elforma de entradaser [1],por lo que el modelo espera
que cada ejemplo tenga esa forma. Ambas cosasencajar()ypredecir()trabajar con varios ejemplos a la
vez. Para proveernortemuestras, las apilamos juntas en un solo tensor de entrada, que por lo tanto
debe tener la forma [n, 1].Si lo hubiésemos olvidado y, en su lugar, hubiésemos proporcionado un
tensor con la forma incorrecta al modelo, habríamos obtenido un error de forma, como el siguiente
código:

> model.predict(tf.tensor1d([pequeñoFileMB, bigFileMB, enormeFileMB])).print(); Error no detectado:


error al verificar: se esperaba que dense_Dense1_input tuviera 2
dimensión(es), pero obtuvo una matriz con forma [3]

¡Cuidado con este tipo de desajuste de forma porque es un tipo de error muy común!
Ejemplo 1: Predecir la duración de una descarga usando TensorFlow.js 49

2.1.7 Resumen de nuestro primer ejemplo


Para este pequeño ejemplo, es posible ilustrar el resultado del modelo. La Figura 2.4 muestra
la salida del modelo (tiempoSeg)en función de la entrada (tamaño MB)para los modelos en
cuatro puntos del proceso, comenzando por el desfasado en 10 épocas y el convergente.
Vemos que el modelo convergente se ajusta mucho a los datos. Si está interesado, en este
punto, en explorar cómo trazar datos como los de la figura 2.4, visite CodePen encodepen.io/
tfjs-book/pen/VEVMMd.

Resultado de ajuste del modelo

0.8 trenDatos
datos de prueba

Modelo después de 10 épocas

0.6 Modelo después de 20 épocas

Modelo después de 100 épocas

Modelo después de 200 épocas


Tiempo (seg)

0.4

0.2

0
0 2 4 6 8 10
Tamaño (MB)

Figura 2.4 Ajuste del modelo lineal después del entrenamiento para 10, 20, 100 y 200 épocas

Esto concluye nuestro primer ejemplo. Acabas de ver cómo puedes construir, entrenar y evaluar un
modelo TensorFlow.js en muy pocas líneas de código JavaScript (ver listado 2.6). En la siguiente sección,
profundizaremos un poco más en lo que sucede dentro demodelo.ajuste.

Listado 2.6 Definición, entrenamiento, evaluación y predicción del modelo

const model = tf.secuencial([tf.layers.dense({inputShape: [1], units: 1})]); model.compile({optimizador:


'sgd', pérdida: 'meanAbsoluteError'});
(async () => espera model.fit(trainTensors.sizeMB,
entrenarTensores.timeSec,
{épocas: 10}))();
modelo.evaluar(testTensors.sizeMB, testTensors.timeSec);
modelo.predecir(tf.tensor2d([[7.8]])).print();
50 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

2.2 Inside Model.fit(): Descenso de gradiente de disección


del ejemplo 1
En la sección anterior, creamos un modelo simple y lo ajustamos a algunos datos de entrenamiento, lo
que demuestra que podemos hacer predicciones razonablemente precisas del tiempo de descarga dado
el tamaño del archivo. No es la red neuronal más impresionante, pero funciona exactamente de la misma
manera que los sistemas más grandes y mucho más complicados que construiremos. Vimos que ajustarlo
para 10 épocas no era muy bueno, pero ajustarlo para 200 épocas producía un modelo de calidad.3
Entremos un poco más en detalle para comprender exactamente lo que sucede debajo del capó cuando
se entrena al modelo.

2.2.1 Las intuiciones detrás de la optimización de gradiente descendente

Recuerde que nuestro modelo simple de una capa se ajusta a una función linealf(entrada),definido como

salida =núcleo*entrada +parcialidad

donde el núcleo y el sesgo son parámetros ajustables (los pesos) de la capa densa. Estos
pesos contienen la información aprendida por la red a partir de la exposición a los datos de
entrenamiento.
Inicialmente, estos pesos se llenan con pequeños valores aleatorios (un paso llamado
inicialización aleatoria). Por supuesto, no hay razón para esperar quenúcleo * entrada + sesgo
producirá algo útil cuando el núcleo y el sesgo sean aleatorios. Usando nuestra imaginación,
podemos imaginar cómo cambiará el valor de MAE a través de diferentes opciones de estos
parámetros. Esperamos que la pérdida sea baja cuando se aproximen a la pendiente y la
intersección de la línea que percibimos en la figura 2.4, y que la pérdida empeore a medida que los
parámetros describen líneas muy diferentes. Este concepto, la pérdida en función de todos los
parámetros ajustables, se conoce comosuperficie de pérdida.
Dado que este es un pequeño ejemplo, y solo tenemos dos parámetros ajustables y un solo objetivo,
es posible ilustrar la superficie de pérdida como un gráfico de contorno 2D, como muestra la figura 2.5.
Esta superficie de pérdida tiene una bonita forma de cuenco, con un mínimo global en la parte inferior del
cuenco que representa la mejor configuración de parámetros. Sin embargo, en general, la superficie de
pérdida de un modelo de aprendizaje profundo es mucho más compleja que esta. Tendrá muchas más de
dos dimensiones y podría tener muchos mínimos locales, es decir, puntos que son más bajos que
cualquier cosa cercana pero no los más bajos en general.
Vemos que esta superficie de pérdida tiene forma de cuenco, con el valor mejor (más bajo) en algún lugar
alrededor de {sesgo: 0,08, kernel: 0,07}.Esto se ajusta a la geometría de la línea implícita en nuestros datos, donde
el tiempo de descarga es de aproximadamente 0,10 segundos, incluso cuando el tamaño del archivo es cercano a
cero. La inicialización aleatoria de nuestro modelo nos inicia en una configuración de parámetros aleatoria,
análoga a una ubicación aleatoria en este mapa, a partir de la cual calculamos nuestra pérdida inicial. A
continuación, ajustamos gradualmente los parámetros en función de una señal de retroalimentación. Este ajuste
gradual, también llamadocapacitación, es el "aprendizaje" en "aprendizaje automático". Esto sucede dentro de un
bucle de entrenamiento—ilustrado en la figura 2.6.

3
Tenga en cuenta que para un modelo lineal simple como este, existen soluciones simples, eficientes y de forma cerrada. Sin embargo, este
método de optimización seguirá funcionando incluso para los modelos más complicados que presentamos más adelante.
51

Superficie de pérdida

0.35

0.1

0.3

0.05 0.25
Parcialidad

0.2

0
0.15

0.1
– 0.05

0.05

– 0,1
– 0.05 0 0.05 0.1

Núcleo

Figura 2.5 La superficie de pérdida ilustra la pérdida, mostrada contra los parámetros ajustables del modelo, como
un gráfico de contorno. Con esta vista de pájaro, vemos que una elección de{sesgo: 0.08, núcleo: 0.07} (marcado
con una X blanca) sería una opción razonable para pérdidas bajas. Rara vez tenemos el lujo de poder probartodosla
configuración de parámetros para construir un mapa como este, pero si lo hiciéramos, la optimización sería muy
fácil; ¡simplemente elija los parámetros correspondientes a la pérdida más baja!

4 Hay un modelo con 1


pesas.w.

Determine el gradiente de la
Aplicar el modelo a un nuevo lote
pérdida con respecto aw.
de datosX,para obtener una nueva
Actualizarwligeramente para
saliday_pred.
reducir la pérdida.

Calcule la pérdida entre


3 y_predy los verdaderos 2
valores,y_verdadero.

Figura 2.6 Diagrama de flujo que ilustra el bucle de entrenamiento, que actualiza el modelo a través
del descenso de gradiente
52 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

La figura 2.6 ilustra cómo el ciclo de entrenamiento itera a través de estos pasos todo el tiempo que sea
necesario:

1 Dibuja unlotede muestras de entrenamientoXy objetivos correspondientesy_verdadero.Un lote es


simplemente una cantidad de ejemplos de entrada reunidos como un tensor. El número de
ejemplos en un lote se llamatamaño del lote. En el aprendizaje profundo práctico, a menudo se
configura como una potencia de 2, como 128 o 256. Los ejemplos se agrupan para aprovechar la
potencia de procesamiento paralelo de la GPU y hacer que los valores calculados de los
gradientes sean más estables (consulte la sección 2.2). .2 para más detalles).
2 Ejecutar la red enX (un paso llamadopase adelantado) para obtener predicciones
y_pred.
3 Calcule la pérdida de la red en el lote, una medida del desajuste entre
y_verdaderoyy_pred.Recuerde que la función de pérdida se especifica cuando
modelo.compilar()se llama.
4 Actualice todos los pesos (parámetros) en la red de manera que reduzca ligeramente
la pérdida en este lote. Las actualizaciones detalladas de los pesos individuales son
administradas por el optimizador, otra opción que especificamos durante el
modelo.compilar()llamada.

Si puede reducir su pérdida en cada paso, eventualmente terminará con una red con poca
pérdida en los datos de entrenamiento. La red ha "aprendido" a asignar sus entradas a los
objetivos correctos. De lejos, puede parecer magia, pero reducido a estos pasos elementales
resulta sencillo.
La única parte difícil es el paso 4: ¿cómo puede determinar qué pesos deben aumentarse,
cuáles deben disminuirse y en cuánto? Podríamos simplemente adivinar y verificar, y solo
aceptar actualizaciones que realmente reduzcan la pérdida. Tal algoritmo podría funcionar
para un problema simple como este, pero sería muy lento. Para problemas más grandes,
cuando estamos optimizando millones de pesos, la probabilidad de seleccionar
aleatoriamente una buena dirección se vuelve muy pequeña. Un enfoque mucho mejor es
aprovechar el hecho de que todas las operaciones utilizadas en la red sondiferenciabley para
calcular eldegradadode la pérdida con respecto a los parámetros de la red.
¿Qué es un gradiente? En lugar de definirlo con precisión (lo que requiere algo de cálculo), podemos
describirlo intuitivamente de la siguiente manera:

Una dirección tal que si mueve los pesos un poco en esa dirección, aumentará la función de
pérdida más rápido, entre todas las direcciones posibles.

Aunque esta definición no es demasiado técnica, todavía queda mucho por desglosar, así que
intentemos desglosarlo:

- Primero, el gradiente es un vector. Tiene el mismo número de elementos que los pesos.
Representa una dirección en el espacio de todas las elecciones de los valores de peso. Si los
pesos de su modelo consisten en dos números, como es el caso de nuestra red de regresión
lineal simple, entonces el gradiente es un vector 2D. Los modelos de aprendizaje profundo
suelen tener miles o millones de dimensiones, y los gradientes de estos modelos son
vectores (direcciones) con miles o millones de elementos.
Inside Model.fit(): Descenso de gradiente de disección del ejemplo 1 53

- En segundo lugar, el gradiente depende de los valores de peso actuales. En otras palabras, diferentes
valores de peso producirán diferentes gradientes. Esto queda claro en la figura 2.5, en la que la dirección
que desciende más rápidamente depende de dónde se encuentre en la superficie de pérdida. En el borde
izquierdo, debemos ir a la derecha. Cerca del fondo, debemos ir hacia arriba, y así sucesivamente.

- Finalmente, la definición matemática de un gradiente especifica una dirección a lo largo de la cual


la función de pérdidaaumenta. Por supuesto, cuando entrenamos redes neuronales, queremos
que la pérdidadisminución. Es por eso que debemos mover los pesos en la dirección opuestoel
gradiente

Considere, a modo de analogía, una caminata en una cadena montañosa. Imagina que deseamos
viajar a un lugar con la altitud más baja. En esta analogía, podemos cambiar nuestra altitud
moviéndonos en cualquier dirección definida por los ejes este-oeste y norte-sur. Deberíamos
interpretar el primer punto como diciendo que el gradiente de nuestra altitud es la dirección más
empinada hacia arriba dada la pendiente bajo nuestros pies. La segunda viñeta es un tanto obvia, e
indica que la dirección más empinada hacia arriba depende de nuestra posición actual. Finalmente,
si deseamos ir a poca altura, debemos dar pasos en la direcciónopuesto el gradiente

Este proceso de formación se llama acertadamentedescenso de gradiente. Recuerda en el listado 2.4, cuando
especificamos nuestro optimizador de modelo con la configuraciónoptimizador: 'sgd'?La porción de descenso de
gradiente del descenso de gradiente estocástico ahora debería estar clara. La parte "estocástica" solo significa
que extraemos muestras aleatorias de los datos de entrenamiento durante cada paso de descenso de gradiente
para mayor eficiencia, en lugar de usar cada muestra de datos de entrenamiento en cada paso. El descenso de
gradiente estocástico es simplemente una modificación del descenso de gradiente para la eficiencia
computacional.
Ahora tenemos herramientas para una explicación más completa de cómo funciona la optimización y
por qué 200 épocas fueron mejores que 10 para nuestro modelo de estimación del tiempo de descarga.
La figura 2.7 ilustra cómo el algoritmo de descenso de gradiente sigue un camino por nuestra superficie
de pérdida para encontrar una configuración de peso que se ajuste bien a nuestros datos de
entrenamiento. El gráfico de contorno en el panel A de la figura 2.7 muestra la misma superficie de
pérdida que antes, ampliada un poco y ahora superpuesta con la ruta seguida por el algoritmo de
descenso de gradiente. El camino comienza en elinicialización aleatoria—un lugar aleatorio en la imagen.
¡Tenemos que elegir un lugar al azar para comenzar ya que no conocemos el óptimo de antemano! Varios
otros puntos de interés se mencionan a lo largo del camino, ilustrando las posiciones correspondientes a
los modelos de ajuste insuficiente y adecuado. El panel B de la figura 2.7 muestra un gráfico de la pérdida
del modelo en función del paso, destacando los puntos de interés análogos. El Panel C ilustra los modelos
usando los pesos como instantáneas en los pasos resaltados en B.
Nuestro modelo de regresión lineal simple es el único modelo en este libro en el que tendremos el lujo
de visualizar el proceso de descenso de gradiente de esta manera vívida. Pero cuando nos encontremos
con modelos más complejos más adelante, tenga en cuenta que la esencia del descenso de gradientes
sigue siendo la misma: es simplemente descender iterativamente por la pendiente de una superficie
complicada y de alta dimensión, con la esperanza de terminar en un lugar con muy baja. pérdida.
54 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

A B
Superficie de pérdida Pérdida vs. época

0.3
0.35
0.1 0.25
0.3

0.25 0.2
0.05
Parcialidad

Pérdida
0.2 0.15
0
0.15
0.1

– 0.05
0.1
0.05
0.05
– 0,1
0
– 0.05 0 0.05 0.1 0 50 100 150 200
Núcleo época #

C
Resultado de ajuste del modelo

0.8 trenDatos
datos de prueba

Modelo después de 10 épocas

0.6 Modelo después de 20 épocas


Modelo después de 100 épocas
Tiempo (seg)

Modelo después de 200 épocas

0.4

0.2

0
0 2 4 6 8 10
Tamaño (MB)

Figura 2.7 Panel A: la realización de 200 pasos moderados mediante el descenso de gradiente guía la configuración de los parámetros al óptimo
local. Las anotaciones resaltan los pesos y valores iniciales después de 20, 100 y 200 épocas. Panel B: un gráfico de la pérdida en función de la
época, destacando la pérdida en los mismos puntos. Panel C: la función detamaño MB paratiempoSeg, representado por el modelo ajustado
después de 10, 20, 100 y 200 épocas de entrenamiento, repetido aquí para que pueda comparar fácilmente la posición de la superficie de
pérdida y el resultado del modelo. Vercodepen.io/tfjs-book/pen/JmerMMpara jugar con este código.

En nuestro esfuerzo inicial, usamos el tamaño de paso predeterminado (determinado por eltasa de
aprendizaje predeterminada), pero al recorrer nuestros datos limitados solo 10 veces, no hubo
suficientes pasos para alcanzar el óptimo; 200 pasos fueron suficientes. En general, ¿cómo sabe
cómo establecer la tasa de aprendizaje o cómo saber cuándo se realiza el entrenamiento? Hay
algunas reglas generales útiles, que cubriremos a lo largo de este libro, pero no hay
Inside Model.fit(): Descenso de gradiente de disección del ejemplo 1 55

regla dura y rápida que siempre lo mantendrá fuera de problemas. Si usamos una tasa de aprendizaje
demasiado pequeña y terminamos condemasiado pequeñaun paso, no alcanzaremos los parámetros
óptimos en un tiempo razonable. Por el contrario, si usamos una tasa de aprendizaje demasiado grande
y, por lo tanto,demasiado grandede un paso, nos saltaremos por completo el mínimo e incluso podemos
terminar con una pérdida mayor que el lugar que dejamos. Esto hará que los parámetros de nuestro
modelo oscilen salvajemente alrededor del óptimo en lugar de acercarse a él rápidamente de una manera
directa. La figura 2.8 ilustra lo que sucede cuando nuestro paso de gradiente es demasiado grande. En
casos más extremos, las altas tasas de aprendizaje harán que los valores de los parámetros diverjan y
lleguen al infinito, lo que a su vez generará valores NaN (no un número) en los pesos, arruinando
completamente su modelo.

Una tasa de aprendizaje demasiado grande salta más allá del mínimo
a un punto con mayor error.

Comienzo
0.35

0.1

0.3

0.05 0.25
Parcialidad

0.2

0
0.15

0.1
– 0.05
Después de una época
0.05

– 0,1
– 0.05 0 0.05 0.1

Núcleo

Figura 2.8 Cuando la tasa de aprendizaje es demasiado alta, el paso de gradiente será demasiado grande y los
nuevos parámetros pueden ser peores que los anteriores. Esto podría conducir a un comportamiento oscilante o
alguna otra inestabilidad que resulte en infinitos o NaN. Puede intentar aumentar la tasa de aprendizaje en el
código de CodePen a 0,5 o más para ver este comportamiento.
56 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

2.2.2 Retropropagación: descenso de gradiente interior


En la sección anterior, explicamos cómo el tamaño de paso de las actualizaciones de peso afecta el
proceso de descenso de gradiente. Sin embargo, no hemos discutido cómo eldireccionesde las
actualizaciones se calculan. Las direcciones son fundamentales para el proceso de aprendizaje de la
red neuronal. Están determinados por los gradientes con respecto a los pesos, y el algoritmo para
calcular los gradientes se llamaretropropagación. Inventada en la década de 1960, la
retropropagación es una de las bases de las redes neuronales y el aprendizaje profundo. En esta
sección, usaremos un ejemplo simple para mostrar cómo funciona la retropropagación. Tenga en
cuenta que esta sección es para lectores que deseen comprender la retropropagación. No es
necesario si solo desea aplicar el algoritmo usando TensorFlow.js, porque todos estos mecanismos
están muy bien escondidos bajo eltf.Modelo.ajuste()API; puede omitir esta sección y continuar
leyendo en la sección 2.3.
Considere el modelo lineal modelo simple

y' = v * x,

dondeXes la característica de entrada ytues la salida predicha, yves el único parámetro de


peso del modelo que se actualiza durante la retropropagación. Supongamos que estamos
usando el error cuadrático como función de pérdida; entonces tenemos la siguiente relación
entre pérdida, v, x,yy (el valor objetivo real):

pérdida = cuadrado(y' - y) = cuadrado(v * x - y)

Supongamos los siguientes valores concretos: las dos entradas sonx = 2yy = 5,y el valor del
peso esv = 0.La pérdida se puede calcular entonces como 25. Esto se muestra paso a paso en
la figura 2.9. Cada cuadrado gris en el panel A representa una entrada (es decir, elXy ely).
Cada caja blanca es una operación. Hay un total de tres operaciones. Los bordes que
conectan las operaciones (y el que conecta el peso sintonizablevcon la primera operación)
están etiquetadosmi1,mi2, ymi3.
Un paso importante de la retropropagación es determinar la siguiente cantidad:

Asumiendo todo lo demás (Xyyen este caso) permanece igual, ¿cuánto cambio en el valor de la pérdida
obtendremos sivse incrementa en una cantidad unitaria?

Esta cantidad se denominael gradiente de pérdida con respecto av.¿Por qué necesitamos este gradiente?
Porque una vez que lo tenemos, podemos modificarven la direcciónopuestoa él, por lo que podemos
obtener una disminución en el valor de la pérdida. Tenga en cuenta que no necesitamos el gradiente de
pérdida con respecto aXoy,porqueXyyno necesitan ser actualizados: son los datos de entrada y son fijos.

Este gradiente se calcula paso a paso, comenzando desde el valor de pérdida y volviendo hacia
la variablev,como se ilustra en el panel B de la figura 2.9. La dirección en la que se lleva a cabo el
cálculo es la razón por la que este algoritmo se llama "propagación hacia atrás". Vamos a caminar a
través de los pasos. Cada uno de los siguientes pasos corresponde a una flecha en la figura:

- En el borde etiquetadopérdida,partimos de un valor de gradiente de 1. Esto es hacer el punto trivial, "un


aumento de unidad enpérdidacorresponde a un incremento unitario enpérdidasí mismo."
Inside Model.fit(): Descenso de gradiente de disección del ejemplo 1 57

A
–5
-y

2 mi2= 0 mi3= –5 pérdida = 25


X * + cuadrado

mi1= 0
v

B
-y

mi2 – 10 mi3 – 10 pérdida 1

X * + cuadrado

mi1 – 20

Figura 2.9 Ilustración del algoritmo de retropropagación a través de un modelo lineal simple con solo un
peso actualizable (v). Panel A: pase hacia adelante en el modelo: el valor de pérdida se calcula a partir del
peso (v) y las entradas (Xyy). Panel B: pase hacia atrás: el gradiente de pérdida con respecto a vse calcula
paso a paso, desde la pérdida hastav.

- En el borde etiquetadomi3, calculamos el gradiente de pérdida con respecto al cambio


unitario del valor actual enmi3. Debido a que la operación intermedia es un cuadrado,
y por cálculo básico sabemos que la derivada (gradiente en el caso de una variable) de
(mi3)2con respecto ami3es2 * mi3, obtenemos un valor de gradiente de 2 * -5 = -10.El
valor -10 se multiplica con el gradiente de antes (es decir, 1) para obtener el gradiente
en el bordemi3: -10. Esta es la cantidad de aumento en la pérdida que obtendremos si
mi3se incrementa en 1. Como habrás podido observar, la regla que utilizamos para
pasar del gradiente de la pérdida respecto de un borde al respecto del borde siguiente
es multiplicar el gradiente anterior por el gradiente calculado localmente en el nodo
actual. Esta regla a veces se conoce como la cadena de reglas.

- en el bordemi2, calculamos el gradiente demi3con respecto ami2. Porque esto es un simple


agregaroperación, el gradiente es simplemente 1, independientemente del otro valor de entrada
(-y).Multiplicando este 1 con el gradiente en el bordemi3, obtenemos el degradado en el bordemi2,
es decir, -10.
58 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

- en el bordemi1, calculamos el gradiente demi2con respecto ami1. La operación


aquí es una multiplicación entreXyv,es decir,x * v.Entonces, el gradiente demi2
con respecto ami1(es decir, con respecto av)esX,o 2. El valor de 2 se multiplica con
el gradiente en el bordemi2para obtener el gradiente final:2 * -10 = -20.

Hasta aquí hemos obtenido el gradiente de pérdida con respecto av:es -20. Para aplicar el descenso de
gradiente, necesitamos multiplicar el negativo de este gradiente con la tasa de aprendizaje. Suponga que
la tasa de aprendizaje es 0.01. Luego obtenemos una actualización de gradiente de

- (-20) * 0,01 = 0,2

Esta es la actualización que aplicaremosven este paso del entrenamiento:

v = 0 + 0,2 = 0,2

Como puedes ver, porque tenemosx = 2yy = 5,y la función a ajustar esy' = v * x,el valor óptimo
deves5/2 = 2,5.Después de un paso de entrenamiento, el valor dev cambia de 0 a 0.2. En otras
palabras, el pesovse acerca un poco más al valor deseado. Se acercará más y más en los
pasos de entrenamiento subsiguientes (ignorando cualquier ruido en los datos de
entrenamiento), que se basará en el mismo algoritmo de retropropagación descrito
anteriormente.
El ejemplo anterior se hizo intencionalmente simple para que sea fácil de seguir.
Aunque el ejemplo captura la esencia de la retropropagación, la retropropagación que
ocurre en el entrenamiento real de la red neuronal es diferente en los siguientes
aspectos:
-En lugar de proporcionar un ejemplo de entrenamiento simple (x = 2yy = 5,en nuestro caso), generalmente

se proporciona un lote de muchos ejemplos de entrada simultáneamente. El valor de pérdida utilizado


para derivar el gradiente es una media aritmética de los valores de pérdida para todos los ejemplos
individuales.

- Las variables que se actualizan generalmente tienen muchos más elementos. Entonces, en lugar de
hacer una derivada simple de una variable como acabamos de hacer, a menudo se involucra el cálculo
matricial.

- En lugar de tener que calcular el gradiente para una sola variable, generalmente se
involucran múltiples variables. La figura 2.10 muestra un ejemplo, que es un modelo lineal
un poco más complejo con dos variables para optimizar. Además dek,el modelo tiene un
término de sesgo:y' = k * x + b.Aquí, hay dos gradientes para calcular, uno paraky uno para
B.Ambos caminos de retropropagación parten de la pérdida. Comparten algunos bordes
comunes y forman una estructura similar a un árbol.

Nuestro tratamiento de la retropropagación en esta sección es casual y de alto nivel. Si desea obtener una
comprensión más profunda de las matemáticas y los algoritmos de la retropropagación, consulte los enlaces en
el cuadro de información 2.2.
En este punto, debería tener una comprensión bastante buena de lo que sucede al ajustar un modelo
simple a los datos de entrenamiento, así que dejemos de lado nuestro pequeño problema de predicción
del tiempo de descarga y usemos TensorFlow.js para abordar algo un poco más desafiante. En
Regresión lineal con múltiples características de entrada 59

-y

pérdida
X * + + cuadrado

Figura 2.10 Dibujo esquemático que muestra la retropropagación desde la pérdida hasta dos pesos
actualizables (kyB).

ICAJA NFO2.2 Lecturas adicionales sobre descenso de gradiente y retropropagación


El cálculo diferencial detrás de la optimización de redes neuronales es definitivamente interesante
y da una idea de cómo se comportan estos algoritmos; pero más allá de lo básico, definitivamente
esnoun requisito para el profesional del aprendizaje automático, de la misma manera que
comprender las complejidades del protocolo TCP/IP es útil pero no fundamental para comprender
cómo crear una aplicación web moderna. Invitamos al lector curioso a explorar los excelentes
recursos aquí para construir una comprensión más profunda de las matemáticas de la
optimización basada en gradientes en redes:

- Ilustración de scrollytelling de demostración de retropropagación:http://mng.bz/2J4g


- Stanford CS231 conferencia 4 notas del curso sobre retropropagación:http://cs231n.
github.io/optimización-2/
-Andrej Karpathy "Guía de hackers para redes neuronales":http://karpatía.github
. io/redes neuronales/

En la siguiente sección, construiremos un modelo para predecir con precisión el precio de los bienes raíces a partir de
múltiples características de entrada simultáneamente.

2.3 Regresión lineal con múltiples funciones de entrada


En nuestro primer ejemplo, solo teníamos una característica de entrada,tamaño MB,con el que
predecir nuestro target,tiempoSeg.Un escenario mucho más común es tener múltiples funciones de
entrada, no saber exactamente cuáles son las más predictivas y cuáles solo están vagamente
relacionadas con el objetivo, y usarlas todas simultáneamente y dejar que el algoritmo de
aprendizaje las resuelva. En esta sección, abordaremos este problema más complicado.
60 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

Al final de esta sección, usted


- Comprenda cómo construir un modelo que tome y aprenda de múltiples características de
entrada.
- Use Yarn, Git y la estructura estándar de empaquetado de proyectos de JavaScript para crear y ejecutar
una aplicación web con aprendizaje automático.
- Saber normalizar los datos para estabilizar el proceso de aprendizaje.
- Tener una idea de cómo usartf.Modelo.ajuste()devoluciones de llamada para actualizar una interfaz de usuario web durante el entrenamiento.

2.3.1 El conjunto de datos de precios de la vivienda de Boston

El conjunto de datos de precios de la vivienda de Boston4es una colección de 500 registros simples de
bienes raíces recopilados en Boston, Massachusetts, y sus alrededores, a fines de la década de 1970. Se
ha utilizado como un conjunto de datos estándar para estadísticas introductorias y problemas de
aprendizaje automático durante décadas. Cada registro independiente en el conjunto de datos incluye
medidas numéricas de un vecindario de Boston, incluido, por ejemplo, el tamaño típico de las casas, qué
tan lejos está la región de la autopista más cercana, si el área tiene propiedades frente al mar, etc. La
Tabla 2.1 proporciona la lista ordenada precisa de características, junto con el valor promedio de cada
característica.

Tabla 2.1 Características del conjunto de datos de vivienda de Boston

Rasgo Significar Rango


Índice nombre corto Descripción de la característica valor (máximo minimo)

0 CRIMEN Tasa de criminalidad 3.62 88,9

1 ZN Proporción de terreno residencial zonificado para lotes de más 11.4 100


de 25,000 pies cuadrados.

2 INDUS Proporción de acres comerciales no minoristas (industria) en 11.2 27.3


la ciudad

3 CHÁS Si el área está o no al lado del río Charles 0.0694 1.0

4 NOX Concentración de óxido nítrico (partes por 10 millones) 0.555 0.49

5 RM Promedio de cuartos por vivienda 6.28 5.2

6 AÑOS Porción de unidades ocupadas por sus propietarios construidas antes 68.6 97.1
de 1940

7 DIS Distancias ponderadas a cinco centros de empleo de 3.80 11.0


Boston

8 RAD Índice de accesibilidad a vías radiales 9.55 23.0

4
David Harrison y Daniel Rubinfeld, “Los precios hedónicos de la vivienda y la demanda de aire limpio”,Revista de
Economía y Gestión Ambiental, vol. 5, 1978, págs. 81 a 102,http://mng.bz/1wvX.
Regresión lineal con múltiples características de entrada 61

Tabla 2.1 Características del conjunto de datos de vivienda de Boston(continuado)

Rasgo Significar Rango


Índice nombre corto Descripción de la característica valor (máximo minimo)

9 IMPUESTO Tasa de impuesto por US$10,000 408.0 524.0

10 PTRATIO Proporción alumno-maestro 18.5 9.40

11 LSTAT Porcentaje de hombres que trabajan sin 12.7 36.2


educación secundaria

12 MEDV Valor medio de viviendas ocupadas por sus propietarios en 22.5 45.0
unidades de $1,000

En esta sección, construiremos, entrenaremos y evaluaremos un sistema de aprendizaje para estimar el


valor medio de los precios de la vivienda en un vecindario (MEDV) dadas todas las características de
entrada del vecindario. Puede imaginarlo como un sistema para estimar el precio de los bienes inmuebles
a partir de propiedades de vecindario medibles.

2.3.2 Obtener y ejecutar el proyecto de vivienda de Boston desde GitHub


Debido a que este problema es un poco más grande que el ejemplo de predicción del tiempo de descarga y tiene
más piezas en movimiento, comenzaremos brindando la solución en forma de un repositorio de código de
trabajo y luego lo guiaremos a través de él. Si ya es un experto en el flujo de trabajo de control de código fuente
de Git y la administración de paquetes npm/Yarn, es posible que desee leer rápidamente esta subsección. Se
proporciona más información sobre la estructura básica del proyecto JavaScript en el cuadro de información 2.3.

Comenzaremos clonando el repositorio del proyecto desde su fuente en GitHub5para obtener


una copia de los archivos HTML, JavaScript y de configuración necesarios para el proyecto. Excepto
los más simples, que están alojados en CodePen, todos los ejemplos de este libro se recopilan en
uno de los dos repositorios de Git y luego se separan por directorio dentro del repositorio. Los dos
repositorios son tensorflow/tfjs-examples y tensorflow/tfjsmodels, ambos alojados en GitHub. Los
siguientes comandos clonarán localmente el repositorio que necesitamos para este ejemplo y
cambiarán el directorio de trabajo al proyecto de predicción de viviendas de Boston:

git clonhttps://github.com/tensorflow/tfjs-examples.git tfjs-examples/


boston-vivienda
discos compactos

5
Los ejemplos de este libro son de código abierto y están alojados en github.com y codepen.io. Si desea un repaso sobre
cómo usar las herramientas de control de fuente de Git, GitHhub tiene un tutorial bien hecho que comienza en https://
help.github.com/articles/set-up-git. Si ve un error o desea ayudar aclarando algo, puede enviar correcciones a través de
las solicitudes de extracción de GitHub.
62 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

ICAJA NFO2.3 Estructura básica del proyecto JavaScript de los ejemplos utilizados en este libro

La estructura de proyecto estándar que usaremos para los ejemplos de este libro incluye tres tipos
importantes de archivos. El primero es HTML. Los archivos HTML que usaremos serán básicos y servirán
principalmente como una estructura básica para contener algunos componentes. Por lo general, habrá un
solo archivo HTML, titulado index.html, que incluirá algunosdivisión etiquetas, quizás algunos elementos
de la interfaz de usuario y una etiqueta fuente para extraer el código JavaScript, como index.js.

El código JavaScript generalmente se modulará en varios archivos para promover una buena
legibilidad y estilo. En el caso de este proyecto de vivienda de Boston, el código que trata con la
actualización de elementos visuales reside en ui.js, y el código para descargar los datos está en
data.js. Ambos están referenciados a través deimportardeclaraciones de index.js.

El tercer tipo de archivo importante con el que trabajaremos es el archivo .json del paquete
de metadatos, un requisito del administrador de paquetes npm (www.npmjs.com). Si no ha
trabajado antes con npm o Yarn, le recomendamos hojear la documentación de "inicio" de
npm enhttps://docs.npmjs.com/about-npmy familiarizarse lo suficiente como para poder
compilar y ejecutar el código de ejemplo. Usaremos Yarn como nuestro administrador de
paquetes (https://yarnpkg.com/en/), pero debería poder sustituir npm por Yarn si se adapta
mejor a sus necesidades.

Dentro del repositorio, tome nota de los siguientes archivos importantes:

- índice.html—El archivo HTML raíz, que proporciona la raíz DOM y llama a los scripts
de JavaScript
- índice.js—El archivo JavaScript raíz, que carga los datos, define el modelo y el ciclo de
entrenamiento, y especifica los elementos de la interfaz de usuario
-datos.js—Implementación de las estructuras necesarias para descargar y acceder al
conjunto de datos de viviendas de Boston
- interfaz de usuario.js—Implementación de los ganchos de UI para conectar elementos de UI a acciones;
especificación de la configuración de la trama
-normalización.js—Rutinas numéricas para, por ejemplo, restar la media de los
datos
- paquete.json—Definición de paquete npm estándar que describe qué dependencias
son necesarias para compilar y ejecutar esta demostración (¡como Tensor-Flow.js!)

Tenga en cuenta que no seguimos la práctica estándar de colocar archivos HTML y archivos JavaScript en
subdirectorios específicos de tipo. Este patrón, aunque es la mejor práctica para repositorios más
grandes, oscurece más de lo que aclara para ejemplos más pequeños como los que usaremos para este
libro o los que puede encontrar engithub.com/tensorflow/tfjs-ejemplos.

Para ejecutar la demostración, use Yarn:

hilo y reloj de hilo

Esto debería abrir una nueva pestaña en su navegador apuntando a un puerto enservidor local,que
ejecutará el ejemplo. Si su navegador no reacciona automáticamente, puede navegar a
Regresión lineal con múltiples características de entrada 63

la salida URL en la línea de comando. Al hacer clic en el botón etiquetado Train Linear Regressor, se
activará la rutina para construir un modelo lineal y ajustarlo a los datos de vivienda de Boston, y
luego generará un gráfico animado de la pérdida en los conjuntos de datos de entrenamiento y
prueba después de cada época, como ilustra la figura 2.11.

Regresión multivariante

Separar

500 pérdida de tren

Pérdida de validación

400
Pérdida

300

200

100

0
0 40 80 120 160 200
Época

Pérdida final del conjunto de trenes: 21,9864

Pérdida final del conjunto de validación: 31,1396


Figura 2.11 El ejemplo de
Pérdida del conjunto de prueba: 25.3206 regresión lineal de
Pérdida de referencia (error medio cuadrado)es 85.58 Bostonhousing de tfjs-examples

El resto de esta sección repasará los puntos importantes en la construcción de esta demostración
de aplicación web de regresión lineal de vivienda de Boston. Primero revisaremos cómo se
recopilan y procesan los datos para que funcionen con TensorFlow.js. Luego nos enfocaremos en la
construcción, entrenamiento y evaluación del modelo; y, finalmente, mostraremos cómo usar el
modelo para predicciones en vivo en la página web.

2.3.3 Acceso a los datos de viviendas de Boston


En nuestro primer proyecto, en el listado 2.1, codificamos nuestros datos como matrices de JavaScript y los
convertimos en tensores usando eltf.tensor2dfunción. La codificación dura está bien para una pequeña
demostración, pero claramente no se adapta a aplicaciones más grandes. En general, los desarrolladores de
JavaScript encontrarán que sus datos están ubicados en algún formato serializado en alguna URL (que puede ser
local). Por ejemplo, los datos de viviendas de Boston están disponibles pública y gratuitamente en formato CSV
desde Google Cloud en las siguientes URL:
64 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

- https://storage.googleapis.com/tfjs-examples/multivariate-linear-regression/
data/train-data.csv
- https://storage.googleapis.com/tfjs-examples/multivariate-linear-regression/
data/train-target.csv
- https://storage.googleapis.com/tfjs-examples/multivariate-linear-regression/
data/test-data.csv
- https://storage.googleapis.com/tfjs-examples/multivariate-linear-regression/
data/test-target.csv

Los datos se dividieron previamente mediante la asignación aleatoria de muestras en porciones de


entrenamiento y prueba. Alrededor de dos tercios de las muestras están en la división de entrenamiento y el
tercio restante está reservado para evaluar de forma independiente el modelo entrenado. Además, para cada
división, la función de destino se ha separado en un archivo CSV aparte de las otras funciones, lo que da como
resultado los cuatro nombres de archivo enumerados en la tabla 2.2.

Tabla 2.2 Nombres de archivo por división y contenido para el conjunto de datos de viviendas de Boston

Características (12 números) Objetivo (1 número)

División de prueba de tren Capacitación tren-datos.csv tren-objetivo.csv

Pruebas prueba-datos.csv test-target.csv

Para incorporarlos a nuestra aplicación, necesitaremos poder descargar estos datos y convertirlos
en un tensor del tipo y la forma apropiados. El proyecto de vivienda de Boston define una clase
BostonViviendaDataseten data.js para este propósito. Esta clase abstrae la operación de transmisión
del conjunto de datos y proporciona una API para recuperar los datos sin procesar como matrices
numéricas. Internamente, la clase utiliza la biblioteca pública Papa Parse de código abierto (
www.papaparse.com) para transmitir y analizar los archivos CSV remotos. Una vez que el archivo
ha sido cargado y analizado, la biblioteca devuelve una matriz de matrices de números. Luego se
convierte en un tensor usando la misma API que en el primer ejemplo, según la siguiente lista, una
muestra ligeramente recortada de index.js enfocada en los bits relevantes.

Listado 2.7 Convirtiendo los datos de viviendas de Boston a tensores en index.js

// Inicializar un objeto BostonHousingDataset definido en data.js. const bostonData =


new BostonHousingDataset();
tensores constantes = {};

// Convertir los datos csv cargados, de tipo número[][] en tensores 2d. exportar const
arraysToTensors = () => {
tensores.rawTrainFeatures =tf.tensor2d(bostonData.trainFeatures);
tensores.trainTarget =tf.tensor2d(datosboston.trenObjetivo);
tensores.rawTestFeatures =tf.tensor2d(bostonData.testFeatures); tensores.testTarget
=tf.tensor2d(datosboston.objetivo de prueba);
}

// Activar los datos para que se carguen de forma asíncrona una vez que se haya cargado la página.
Regresión lineal con múltiples características de entrada sesenta y cinco

let tensores;
documento.addEventListener('DOMContentLoaded', asíncrono () => {
esperar bostonData.loadData();
matrices a tensores ();
}, falso);

2.3.4 Definición precisa del problema de la vivienda en Boston

Ahora que tenemos acceso a nuestros datos en la forma que queremos, es un buen momento para
aclarar nuestra tarea con mayor precisión. Dijimos que nos gustaría predecir el MEDV de los otros
campos, pero ¿cómo decidiremos si estamos haciendo un buen trabajo? ¿Cómo podemos distinguir
un buen modelo de uno aún mejor?
La métrica que usamos en nuestro primer ejemplo,error absoluto medio,cuenta todos los errores
por igual. Si solo hubiera 10 muestras, e hiciéramos predicciones para las 10, y acertáramos
exactamente en 9 de ellas, pero nos equivocamos por 30 en la décima muestra, la error absoluto
mediosería 3 (porque 30/10 es 3). Si, en cambio, nuestras predicciones estuvieran equivocadas por
3 en todas y cada una de las muestras, elerror absoluto medioseguiría siendo 3. Este principio de
"igualdad de errores" puede parecer la única opción obviamente correcta, pero hay buenas
razones para elegir métricas de pérdida distintas deerror absoluto medio.
Otra opción es ponderar los errores grandes más que los errores pequeños. Podríamos, en
lugar de tomar el valor promedio del error absoluto, tomar el valor promedio de laal cuadrado
error.
Continuando con el estudio de caso con las 10 muestras, este enfoque del error cuadrático
medio (MSE) ve una pérdida menor al estar equivocado por 3 en cada ejemplo (10 × 32= 90) que
estar equivocado por 30 en solo un ejemplo (1 × 302= 900). Debido a la sensibilidad a los grandes
errores, el error cuadrático puede ser más sensible a los valores atípicos de la muestra que el error
absoluto. Un optimizador que ajuste modelos para minimizar el MSE preferirá modelos que
sistemáticamente cometen pequeños errores sobre modelos que ocasionalmente dan muy malas
estimaciones. ¡Obviamente, ambas medidas de error preferirían modelos que no cometan ningún
error! Sin embargo, si su aplicación puede ser sensible a valores atípicos muy incorrectos, MSE
podría ser una mejor opción que MAE. Hay otras razones técnicas por las que podría seleccionar
MSE o MAE, pero no son importantes en este momento. En este ejemplo, usaremos MSE para
variedad, pero MAE también sería suficiente.
Antes de continuar, debemos encontrar una estimación de referencia de la pérdida. Si no conocemos
el error a partir de una estimación muy simple, entonces no estamos equipados para evaluarlo a partir de
un modelo más complicado. Usaremos el precio promedio de bienes raíces como sustituto de nuestra
"mejor suposición ingenua" y calcularemos cuál sería el error de adivinar siempre ese valor.

Listado 2.8 Cálculo de la pérdida de referencia al adivinar el precio medio

export const computarBaseline = () => { Calcula el precio medio


const avgPrice = tf.mean(tensors.trainTarget); console.log(`Precio promedio:
${avgPrice.dataSync()[0]}`);

línea de base constante = Calcula el error cuadrático medio de los


tf.mean(tf.pow(tf.sub( datos de prueba. Las llamadas sub(),
tensores.testTarget, avgPrice), 2)); pow y mean() son los pasos de
calcular el error cuadrático medio.
66 CPASADO2 Primeros pasos: regresión lineal simple en TensorFlow.js

consola.log( Imprime el
`Pérdida de línea base: ${baseline.dataSync()[0]}`); valor de la pérdida

};

Debido a que TensorFlow.js optimiza su cálculo mediante la programación en la GPU, es posible


que la CPU no siempre tenga acceso a los tensores. las llamadas asincronización de datosen el
listado 2.8, dígale a TensorFlow.js que termine de calcular el tensor y extraiga el valor de la GPU a la
CPU, para que pueda imprimirse o compartirse con una operación que no sea de TensorFlow.
Cuando se ejecuta, el código del listado 2.8 produce lo siguiente en la consola:

Precio promedio: 22.768770217895508


Pérdida de referencia: 85.58282470703125

Esto nos dice que la tasa de error ingenuo es de aproximadamente 85,58. Si tuviéramos que construir un modelo
que siempre generara 22,77, este modelo lograría un MSE de 85,58 en los datos de prueba. Nuevamente, tenga
en cuenta que calculamos la métrica en los datos de entrenamiento y la evaluamos en los datos de prueba para
evitar sesgos injustos.
La mediaal cuadradoel error es 85,58, por lo que debemos sacar la raíz cuadrada para obtener el error
promedio. La raíz cuadrada de 85,58 es aproximadamente 9,25. Por lo tanto, podemos decir que esperamos que
nuestra estimación (constante) se desvíe (por encima o por debajo) en alrededor de 9,25 en promedio. Dado que
los valores, según la tabla 2.1, están en miles de dólares estadounidenses, estimar una constante significa que
nos equivocaremos en unos 9250 dólares estadounidenses. Si esto fuera lo suficientemente bueno para nuestra
aplicación, ¡podríamos detenernos aquí! El practicante sabio del aprendizaje automático sabe cuándo evitar una
complejidad innecesaria. Supongamos que nuestra aplicación de estimación de precios debe estar más cerca que
esto. Procederemos ajustando un modelo lineal a nuestros datos para ver si podemos lograr un MSE mejor que
85.58.

2.3.5 Una ligera desviación hacia la normalización de datos

Al observar las características de las viviendas de Boston, vemos una amplia gama de valores. NOX
oscila entre 0,4 y 0,9, mientras que TAX va de 180 a 711. Para ajustar una regresión lineal, el
optimizador intentará encontrar un peso para cada característica de modo que la suma de las
características multiplicada por los pesos sea aproximadamente igual al precio de la vivienda.
Recuerde que para encontrar estos pesos, el optimizador busca, siguiendo un gradiente en el
espacio de pesos. Si algunas características se escalan de manera muy diferente a otras, entonces
ciertos pesos serán mucho más sensibles que otros. Un movimiento muy pequeño en una dirección
cambiará la salida más que un movimiento muy grande en una dirección diferente. Esto puede
causar inestabilidad y dificulta el ajuste del modelo.
Para contrarrestar esto, primeronormalizarnuestros datos. Esto significa que escalaremos
nuestras características para que tengan media cero y desviación estándar unitaria. Este tipo de
normalización es común y también puede denominarsetransformación estándaronormalización de
puntuación z. El algoritmo para hacer esto es simple: primero calculamos la media de cada
característica y la restamos del valor original para que la característica tenga un valor promedio de
cero. Luego calculamos la desviación estándar de la característica con el valor medio restado y
hacemos una división por eso. En pseudocódigo,

característica normalizada = (característica - media(característica)) / std(característica)


Regresión lineal con múltiples características de entrada 67

Por ejemplo, cuando la característica es [10, 20, 30, 40],la versión normalizada sería
aproximadamente [-1,3, -0,4, 0,4, 1,3],que claramente tiene una media de cero; a simple vista, la
desviación estándar es de aproximadamente uno. En el ejemplo de la vivienda de Boston, el código
de normalización se factoriza en un archivo separado, normalization.js, cuyo contenido se
encuentra en el listado 2.9. Aquí, vemos dos funciones, una para calcular la media y la desviación
estándar de un tensor de rango 2 proporcionado y la otra para normalizar un tensor dada la media
y la desviación estándar precalculadas proporcionadas.

Listado 2.9 Normalización de datos: media cero, desviación estándar unitaria

/**
* Calcula la media y la desviación estándar de cada columna de una matriz.
*
* @param {Tensor2d} data Conjunto de datos a partir del cual calcular la media y
* estándar de cada columna de forma independiente.
*
* @returns {Object} Contiene la media y estándar de cada vector
* columna como tensores 1d.
*/
función de exportación determineMeanAndStddev(datos) {
const dataMean = data.mean(0); const diffFromMean =
data.sub(dataMean);
const squaredDiffFromMean = diffFromMean.square(); varianza
const = squaredDiffFromMean.mean(0); const std = varianza.sqrt();

volver {media, std};


}
/**
* Dada la media esperada y la desviación estándar, normaliza un conjunto de datos por
* restando la media y dividiendo por la desviación estándar.
*
* @param {Tensor2d} data: Datos a normalizar.
* Forma: [numSamples, numFeatures].
* @param {Tensor1d} mean: Media esperada de los datos. Forma [numFeatures].
* @param {Tensor1d} std: std esperado de los datos. Forma [numCaracterísticas]
*
* @returns {Tensor2d}: Tensor de la misma forma que los datos, pero cada columna
* normalizado para tener media cero y desviación estándar unitaria.
*/
función de exportación normalizeTensor(data, dataMean, dataStd) {
devuelve data.sub(dataMean).div(dataStd);
}

Profundicemos un poco en estas funciones. La funcióndeterminarMeanAndStddevtoma como


entradadatos,que es un tensor de rango 2. Por convención, la primera dimensión es lamuestras
dimensión: cada índice corresponde a una muestra única e independiente. La segunda dimensión
es larasgodimensión: sus 12 elementos corresponden a las 12 características de entrada (como
CRIM, ZN, INDUS, etc.). Como queremos calcular la media de cada característica de forma
independiente, llamamos

const dataMean = data.mean(0);


68 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

los0en esta llamada significa que la media debe tomarse sobre la dimensión del índice 0 (primera).
Recordar quedatoses un tensor de rango 2 y por lo tanto tiene dos dimensiones (o ejes). El primer
eje, el eje "lote", es la dimensión de la muestra. A medida que nos movemos del primero al
segundo al tercer elemento a lo largo de ese eje, nos referimos a diferentes muestras o, en nuestro
caso, diferentes piezas de bienes raíces. La segunda dimensión es la dimensión característica. A
medida que pasamos del primer al segundo elemento en esta dimensión, nos referimos a
diferentes características, como CRIM, ZN e INDUS, de la tabla 2.1. Cuando tomamos la media a lo
largo del eje 0, estamos tomando el promedio sobre la dirección de la muestra. El resultado es un
tensor de rango 1 con solo el eje de características restante. Tenemos la media de cada
característica. Si, en cambio, tomáramos la media sobre el eje 1, aún obtendríamos un tensor de
rango 1, pero el eje restante sería la dimensión de la muestra. Los valores corresponderían al valor
medio de cada bien inmueble, lo que no tendría sentido para nuestra aplicación. Cuando trabaje
con sus ejes, tenga cuidado de estar haciendo sus cálculos en la dirección correcta, ya que esta es
una fuente común de errores.
Efectivamente, si establecemos un punto de interrupción6aquí, podemos usar la consola de JavaScript para
explorar los valores medios calculados y vemos valores medios bastante cercanos a los que calculamos para todo
el conjunto de datos. Esto significa que nuestra muestra de capacitación fue representativa:

> dataMean.shape
[12]
> mediadatos.print();
[3.3603415, 10.6891899, 11.2934837, 0.0600601, 0.5571442, 6.2656188, 68.2264328, 3.7099338,
9.6336336, 409.2792969, 18.4480471]5, 4123.56]

En la siguiente línea, restamos (usandotf.sub)la media de nuestros datos para obtener una versión
centrada de los datos:

const diffFromMean = data.sub(dataMean);

Si no estabas prestando el 100% de atención, esta línea podría haber escondido una pequeña y
encantadora pieza de magia. Verás,datoses un tensor de rango 2 con forma [333, 12],tiempomedia
de datos es un tensor de rango 1 con forma [12].En general, no es posible sustraer dos tensores con
formas diferentes. Sin embargo, en este caso, TensorFlow usa la transmisión para expandir la
forma del segundo tensor, de hecho, repitiéndolo 333 veces, haciendo exactamente lo que el
usuario pretendía sin que lo deletreara. Esta ganancia de usabilidad es útil, pero a veces las reglas
sobre qué formas son compatibles para la transmisión pueden ser un poco confusas. Si está
interesado en los detalles de la transmisión, sumérjase directamente en el cuadro de información
2.4.
Las próximas líneas deldeterminarMeanAndStddevla función no tiene nuevas sorpresas:
tf.cuadrado()multiplica cada elemento por sí mismo, mientras quetf.sqrt()toma la raíz cuadrada de
los elementos. La API detallada para cada método está documentada en TensorFlow.js

6
Las instrucciones para establecer un punto de interrupción en Chrome están aquí:http://mng.bz/rPQJ. Si necesita instrucciones para los
puntos de interrupción en Firefox, Edge u otro navegador, simplemente puede buscar "cómo establecer un punto de interrupción" utilizando
su motor de búsqueda favorito.
Regresión lineal con múltiples características de entrada 69

Figura 2.12 La documentación de la API de TensorFlow.js enjs.tensorflow.orgte permite explorar e interactuar con
la API de TensorFlow directamente dentro de la documentación. Esto hace que sea simple y rápido comprender los
usos funcionales y los casos extremos complicados.

referencia de la API,https://js.tensorflow.org/api/latest/. La página de documentación también tiene


widgets editables en vivo que le permiten explorar cómo funcionan las funciones con sus propios valores
de parámetros, como se ilustra en la figura 2.12.
En este ejemplo, hemos escrito nuestro código priorizando la claridad de la exposición, pero el
determinarMeanAndStddevLa función se puede expresar de manera mucho más concisa:

const std = data.sub(data.mean(0)).square().mean().sqrt();

Debería poder ver que TensorFlow nos permite expresar una gran cantidad de cálculos
numéricos sin mucho código repetitivo.

ICAJA NFO2.4 Radiodifusión


Considere una operación tensorial comoC = tf.algunaOperación(A, B),dondeAyBson tensores.
Cuando sea posible, y si no hay ambigüedad, el tensor más pequeño se transmitirá para que
coincida con la forma del tensor más grande. La transmisión consta de dos pasos:

1 ejes (llamadosejes de difusión) se agregan al tensor más pequeño para que coincida con el rango
del tensor más grande.
2 El tensor más pequeño se repite junto con estos nuevos ejes para que coincida con la forma
completa del tensor más grande.
70 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

(continuado)
En términos de implementación, en realidad no se crea ningún tensor nuevo porque sería
terriblemente ineficiente. La operación de repetición es completamente virtual: ocurre a
nivel algorítmico en lugar de a nivel de memoria. Pero pensar en el tensor más pequeño que
se repite a lo largo del nuevo eje es un modelo mental útil.

Con la transmisión, generalmente puede aplicar operaciones de elementos de dos tensores


si un tensor tiene forma (a, b, …, n, n + 1, … m)y el otro tiene forma (n, n + 1, …, m).La
transmisión se realizará automáticamente para el eje.aa través den - 1.Por ejemplo, el
siguiente ejemplo aplica el elemento-sabiomáximooperación sobre dos tensores aleatorios
de diferentes formas a través de la transmisión:

x es un tensor aleatorio con


x = tf.uniforme aleatorio([64, 3, 11, 9]); y = forma [64, 3,11,9].

tf.uniforme aleatorio([11, 9]); y es un tensor aleatorio con forma [11,9].


z = tf.máximo(x, y); La salida z tiene forma [64, 3,11,9] como x.

2.3.6 Regresión lineal sobre los datos de vivienda de Boston


Nuestros datos están normalizados y hemos realizado el trabajo de datos de diligencia debida para
calcular una línea de base razonable; el siguiente paso es construir y ajustar un modelo para ver si
podemos superar la línea de base. En el listado 2.10, definimos un modelo de regresión lineal como lo
hicimos en la sección 2.1 (de index.js). El código es notablemente similar; la única diferencia que vemos
con el modelo de predicción del tiempo de descarga está en elforma de entradaconfiguración, que ahora
acepta vectores de longitud 12 en lugar de 1. La única capa densa todavía tieneunidades: 1, indicando que
un solo número es la salida.

Listado 2.10 Definición de un modelo de regresión lineal para viviendas en Boston

export const linearRegressionModel = () => {


const modelo = tf.secuencial();
modelo.add(tf.layers.dense(
{inputShape: [bostonData.numFeatures], unidades: 1})); modelo de
retorno;
};

Recuerde que después de definir nuestro modelo, pero antes de comenzar a entrenar, debemos
especificar la pérdida y el optimizador a través de una llamada amodelo.compilar.En el listado 2.11,
vemos que el 'error medio cuadrado'se especifica la pérdida y que el optimizador está utilizando una
tasa de aprendizaje personalizada. En nuestro ejemplo anterior, el parámetro del optimizador se
estableció en la cadena 'sgd',pero ahora estf.train.sgd(TARIFA_DE_APRENDIZAJE).Esta función de
fábrica devolverá un objeto que representa el algoritmo de optimización de descenso de gradiente
estocástico, pero parametrizado con nuestra tasa de aprendizaje personalizada. Este es un patrón
común en Tensor-Flow.js, tomado de Keras, y lo verá adoptado para muchas opciones
configurables. Para parámetros predeterminados estándar y bien conocidos, un valor centinela de
cadena puede sustituir el tipo de objeto requerido, y TensorFlow.js sustituirá la cadena por
Regresión lineal con múltiples características de entrada 71

el objeto requerido con buenos parámetros predeterminados. En este caso, 'sgd'sería reemplazado por
tf.tren.sgd(0.01).Cuando se necesitan personalizaciones adicionales, el usuario puede construir el objeto a través
de la función de fábrica y proporcionar el valor personalizado requerido. Esto permite que el código sea conciso
en la mayoría de los casos, pero permite que el usuario avanzado anule los comportamientos predeterminados
cuando sea necesario.

Listado 2.11 Compilación de modelos para viviendas en Boston (de index.js)

const TASA_DE_APRENDIZAJE = 0.01;


modelo.compilar({
optimizador: tf.train.sgd(LEARNING_RATE), loss:
'meanSquaredError'});

Ahora podemos entrenar nuestro modelo con el conjunto de datos de entrenamiento. En los listados 2.12
a 2.14, usaremos algunas características adicionales delmodelo.fit()llamada, pero esencialmente está
haciendo lo mismo que en la figura 2.6. En cada paso, selecciona un número de nuevas muestras de
Las características (tensores.trenCaracterísticas)y objetivos (tensores.trainTarget),calcular
calcula la pérdida y luego actualiza los pesos internos para reducir esa pérdida. El proceso se repetirá
paraNUM_ÉPOCASpases completos a través de los datos de entrenamiento y seleccionará TAMAÑO DEL
LOTEmuestras en cada paso.

Listado 2.12 Entrenando nuestro modelo con los datos de vivienda de Boston

esperar model.fit(tensors.trainFeatures, tensors.trainTarget, {


tamaño de lote: BATCH_SIZE
épocas: NUM_EPOCHS,
});

En la aplicación web de viviendas de Boston, ilustramos un gráfico de la pérdida de entrenamiento a medida que el modelo entrena.

Esto requiere el uso de lamodelo.fit()función de devolución de llamada para actualizar la interfaz de usuario. los modelo.fit()La API

de devolución de llamada permite al usuario proporcionar funciones de devolución de llamada, que se ejecutarán en eventos

específicos. La lista completa de disparadores de devolución de llamada, a partir de la versión 0.12.0,

esonTrainBegin, onTrainEnd, onEpochBegin, onEpochEnd, onBatchBegin,y enBatchEnd.

Listado 2.13 Callbacks enmodelo.fit()

dejar entrenarLoss;
esperar model.fit(tensors.trainFeatures, tensors.trainTarget, {
tamaño de lote: BATCH_SIZE,
épocas: NUM_EPOCHS,
devoluciones de llamada: {

onEpochEnd: asíncrono (época, registros) => {


esperar ui.updateStatus(
`Época ${época + 1} de ${NUM_EPOCHS} completada. )̀; trainLoss =
logs.loss;
esperar ui.plotData(época, trainLoss);
}
}
});
72 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

Una última personalización nueva introducida aquí es hacer uso de datos de validación. La validación es
un concepto de aprendizaje automático que merece una pequeña explicación. En el ejemplo anterior del
tiempo de descarga, separamos nuestros datos de entrenamiento de nuestros datos de prueba porque
queríamos una estimación imparcial de cómo se desempeñará nuestro modelo en datos nuevos e
invisibles. Sin embargo, normalmente lo que sucede es que hay otra división llamadadatos de validación.
Los datos de validación están separados tanto de los datos de entrenamiento como de los datos de
prueba. ¿Para qué se utilizan los datos de validación? El ingeniero de aprendizaje automático verá el
resultado en los datos de validación y utilizará ese resultado para cambiar ciertas configuraciones del
modelo.7para mejorar la precisión de los datos de validación. Todo esto está muy bien. Sin embargo, si
este ciclo se realiza suficientes veces, entonces estamos ajustando los datos de validación. Si usamos los
mismos datos de validación para evaluar la precisión final del modelo, el resultado de la evaluación final
ya no será generalizable, en el sentido de que el modelo ya ha visto los datos, y no se garantiza que el
resultado de la evaluación refleje cómo funcionará el modelo en datos no vistos en el futuro. Este es el
propósito de separar la validación de los datos de prueba. La idea es que ajustaremos nuestro modelo a
los datos de entrenamiento y ajustaremos sus hiperparámetros en función de las evaluaciones de los
datos de validación. Cuando hayamos terminado y estemos satisfechos con el proceso, evaluaremos el
modelo solo una vez en los datos de prueba para una evaluación final,

Resumamos qué son los conjuntos de entrenamiento, validación y prueba y cómo usarlos en
TensorFlow.js. No todos los proyectos harán uso de los tres tipos de datos. Con frecuencia, las
exploraciones rápidas o los proyectos de investigación utilizarán solo datos de capacitación y validación y
no reservarán un conjunto de datos "puros" para la prueba. Si bien es menos riguroso, este es a veces el
mejor uso de los recursos limitados:

- Datos de entrenamiento—Para ajustar los pesos del modelo con pendiente descendente

– Uso en TensorFlow.js: Por lo general, los datos de entrenamiento se emplean utilizando los
argumentos principales (Xyy)para llamadas aModel.fit(x, y, config). Datos de validación—Para
- seleccionar la estructura del modelo y los hiperparámetros
– Uso en TensorFlow.js:Modelo.fit()tiene dos formas de especificar datos de validación, tanto
como parámetros para elconfiguraciónargumento. Si usted, el usuario, tiene datos explícitos
para usar para la validación, esto puede especificarse comoconfig.validationData.Si, en cambio,
desea que el marco divida algunos de los datos de entrenamiento y los use como datos de
validación, especifique la fracción que se usará enconfig.validationSplit. El marco se encargará
de no usar los datos de validación para entrenar el modelo, por lo que no hay superposición.

- Prueba de datos—Para obtener una estimación final e imparcial del rendimiento del modelo

– Uso en TensorFlow.js: Los datos de evaluación se exponen al sistema pasándolos


como elXyyargumentos paraModel.evaluate(x, y, config).

7
Los ejemplos de esas configuraciones incluyen la cantidad de capas en el modelo, los tamaños de las capas, el tipo de
optimizador y la tasa de aprendizaje que se usará durante el entrenamiento, etc. Se conocen como modelos.
hiperparámetros, que trataremos con mayor detalle en el apartado 3.1.2 del próximo capítulo.
Regresión lineal con múltiples características de entrada 73

En el listado 2.14, la pérdida de validación se calcula junto con la pérdida de entrenamiento. losvalidación-
Split: 0.2campo instruye almodelo.fit()maquinaria para seleccionar el último 20% de los datos de
entrenamiento para usarlos como datos de validación. Estos datos no se utilizarán para el entrenamiento
(no afecta al descenso de pendientes).

Listado 2.14 Incluyendo datos de validación enmodelo.fit()

dejar pérdida de tren;


dejarvalLoss;
esperar model.fit(tensors.trainFeatures, tensors.trainTarget, {
tamaño de lote: BATCH_SIZE,
épocas: NUM_EPOCHS,
división de validación: 0.2,
devoluciones de llamada: {

onEpochEnd: asíncrono (época, registros) => {


esperar ui.updateStatus(
`Época ${época + 1} de ${NUM_EPOCHS} completado.̀ ); pérdida de
tren = registros.pérdida;
valLoss = registros.val_loss;
esperar ui.plotData(época, trainLoss,pérdida de valor);
}
}
});

Entrenar este modelo a 200 épocas lleva aproximadamente 11 segundos en el navegador de una computadora
portátil moderna. Ahora podemos evaluar el modelo en nuestro conjunto de prueba para ver si es mejor que la
línea de base. La siguiente lista muestra cómo usarmodelo.evaluar()para recopilar el rendimiento del modelo en
nuestros datos de prueba reservados y luego llamar a nuestras rutinas de IU personalizadas para actualizar la
vista.

Listado 2.15 Evaluación de nuestro modelo en los datos de prueba y actualización de la interfaz de usuario (de index.js)

await ui.updateStatus('Ejecutándose en datos de prueba...'); const


resultado = modelo.evaluar(
tensors.testFeatures, tensors.testTarget, {batchSize: BATCH_SIZE}); const testLoss =
resultado.dataSync()[0];
esperar ui.updateStatus(
`Pérdida final del conjunto de trenes: ${trainLoss.toFixed(4)}\n` + `Pérdida
final del conjunto de validación: ${valLoss.toFixed(4)}\n` + `Pérdida del
conjunto de prueba: ${testLoss. aFijo(4)}`);

Aquí,modelo.evaluar()devuelve un escalar (recuerde, un tensor de rango 0) que contiene la pérdida calculada


sobre el conjunto de prueba.
Debido a la aleatoriedad involucrada en el descenso de gradientes, es posible que obtenga resultados
diferentes, pero los siguientes resultados son típicos:

- Pérdida de conjunto de tren final: 21,9864 Pérdida de

- conjunto de validación final: 31,1396 Pérdida de

- conjunto de prueba: 25,3206

- Pérdida de referencia: 85,58


74 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

Vemos a partir de esto que nuestra estimación final e imparcial de nuestro error es de aproximadamente
25,3, que es mucho mejor que nuestra línea de base ingenua de 85,6. Recuerde que nuestro error se
calcula usandoerror medio cuadrado.Tomando la raíz cuadrada, vemos que la estimación de la línea de
base estaba típicamente desviada en más de 9.2, mientras que el modelo lineal está desviado por solo
alrededor de 5.0. ¡Una gran mejora! Si fuéramos los únicos en el mundo con acceso a esta información,
¡podríamos ser los mejores inversores inmobiliarios de Boston en 1978! A menos que, de alguna manera,
alguien pudiera construir una estimación aún más precisa. . .
Si se ha dejado llevar por la curiosidad y ha hecho clic en Train Neural Network Regressor,
ya lo sabe.muchomejores estimaciones son posibles. En el próximo capítulo, presentaremos
modelos profundos no lineales para mostrar cómo son posibles tales proezas.

2.4 Cómo interpretar su modelo


Ahora que hemos entrenado nuestro modelo y es capaz de hacer predicciones razonables, es
natural preguntarse qué ha aprendido. ¿Hay alguna forma de mirar dentro del modelo para ver
cómo entiende los datos? Cuando el modelo predice un precio específico para un insumo, ¿es
posible que encuentre una explicación comprensible de por qué obtiene ese valor? Para el caso
general de grandes redes profundas, la comprensión del modelo, también conocida como
interpretabilidad del modelo, sigue siendo un área de investigación activa, llenando muchos
carteles y charlas en conferencias académicas. Pero para este modelo de regresión lineal simple, es
bastante simple.
Al final de esta sección, usted
- Ser capaz de extraer los pesos aprendidos de un modelo.
- Ser capaz de interpretar esos pesos y sopesarlos contra sus intuiciones por lo que
los pesosdeberíanser.

2.4.1 Extraer el significado de los pesos aprendidos


El modelo lineal simple que construimos en la sección 2.3 contiene 13 parámetros aprendidos, contenidos
en un núcleo y un sesgo, al igual que nuestro primer modelo lineal en la sección 2.1.3:

salida =núcleo· caracteristicas +parcialidad

Los valores del núcleo y el sesgo se aprenden mientras se ajusta el modelo. En contraste con el
escalarfunción lineal aprendida en la sección 2.1.3, aquí, las características y el kernel son ambos
vectores, y el "·”signo indica elproducto Interno, una generalización de la multiplicación escalar a
vectores. El producto interior, también conocido como elproducto punto, es simplemente la suma
de los productos de los elementos coincidentes. El pseudocódigo del listado 2.16 define el producto
interno con mayor precisión.
De esto debemos deducir que existe una relación entre los elementos de las
características y los elementos del kernel. Para cada elemento de característica individual,
como "Índice de criminalidad" y "Concentración de óxido nítrico", como se indica en la tabla
2.1, hay un número aprendido asociado en el núcleo. Cada valor nos dice algo sobre lo que el
modelo ha aprendido sobre esta función y cómo la función influye en el resultado.
Traducido del inglés al español - www.onlinedoctranslator.com

Cómo interpretar tu modelo 75

Listado 2.16 Pseudocódigo de producto interno

function ProductoInterior(a, b) {
salida = 0;
for (sea i = 0 ; i < a.longitud ; i++) {
salida += a[i] * b[i];
}
salida de retorno;
}

Por ejemplo, si el modelo aprende quenúcleo[yo]es positivo, significa que la salida será mayor
si elcaracterística[i]el valor es mayor. Viceversa, si el modelo aprende que núcleo[j]es negativo,
entonces un valor mayor decaracterística[j]reduce la salida prevista. Un valor aprendido que
es muy pequeño en magnitud indica que el modelo cree que la característica asociada tiene
poco impacto en la predicción, mientras que un valor aprendido con una gran magnitud
indica que el modelo pone un gran énfasis en la característica y los pequeños cambios en la
característica valor tendrá un impacto comparativamente grande en la predicción.8

Para hacer esto concreto, los cinco valores principales de características, por valor absoluto, se imprimen en
la figura 2.13 para una ejecución en el área de salida del ejemplo de vivienda de Boston. Las ejecuciones
posteriores pueden aprender diferentes valores debido a la aleatoriedad de la inicialización. Podemos ver que los
valores son negativos para las características que esperaríamos que reflejen negativamente el precio de los
bienes inmuebles, como la tasa a la que los residentes locales abandonan la escuela y la distancia de los bienes
inmuebles a los lugares de trabajo deseables. Los pesos aprendidos son positivos para las características que
esperaríamos que se correlacionen directamente con el precio, como la cantidad de habitaciones en la
propiedad.

tasa de abandono escolar – 3.8119


Figura 2.13 Clasificados por valor absoluto, estos son los
Distancia para viajar – 3.7278
cinco pesos principales aprendidos en una ejecución del
Número de habitaciones por casa 2.8451 modelo lineal en el problema de predicción de vivienda de
Boston. Tenga en cuenta los valores negativos de las
Distancia a la autopista 2.2949
características que esperaría que se reflejaran negativamente
Concentración de óxido nítrico – 2.1190 en el precio de la vivienda.

2.4.2 Extracción de pesos internos del modelo


La estructura modular del modelo aprendido facilita la extracción de los pesos relevantes;
podemos acceder a ellos directamente, pero hay algunos niveles de API que deben
alcanzarse para obtener los valores sin procesar. Es importante tener en cuenta que, dado
que el valor puede estar en la GPU y la comunicación entre dispositivos es costosa, la
solicitud de dichos valores es asíncrona. El código en negrita del listado 2.17 es una adición al

8
Tenga en cuenta que comparar magnitudes de esta manera solo es posible si las características se han normalizado, como lo hemos hecho para el
conjunto de datos de viviendas de Boston.
76 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

modelo.fitdevoluciones de llamada, extendiendo el listado 2.14 para ilustrar los pesos aprendidos después
de cada época. Veremos las llamadas a la API paso a paso.
Dado el modelo, primero deseamos acceder a la capa correcta. Esto es fácil porque solo
hay una capa en este modelo, por lo que podemos manejarlo enmodelo.capas[0].Ahora que
tenemos la capa, podemos acceder a los pesos internos conobtenerPesos(),que devuelve una
matriz de los pesos. Para el caso de una capa densa, esta siempre contendrá dos pesos, el
kernel y el bias, en ese orden. Por lo tanto, podemos acceder al tensor correcto en

> modelo.capas[0].getWeights()[0]

Ahora que tenemos el tensor correcto, podemos acceder a su contenido con una llamada a
sudatos() método. Debido a la naturaleza asíncrona de GPU-comunicación de la CPU,datos()es
asíncrono y devuelve una promesa del valor del tensor, no el valor real. En el listado 2.17, se
pasó una devolución de llamada alluego()método de la promesa une los valores del tensor a
una variable llamadakernelAsArr.Si elconsola.log()declaración no está comentada,
declaraciones como las siguientes, que enumeran los valores del kernel, se registran en la
consola una vez por cada época:

> Matriz Flotante32(12) [-0.44015952944755554, 0.8829045295715332,


0.11802537739276886, 0.9555914402008057, - 1.6466193199157715,
3.386948347091675, - 0.36070501804351807, - 3.0381457805633545,
1.4347705841064453, - 1.3844640254974365, - 1.4223048686981201,
- 3.795234441757202]

Listado 2.17 Accediendo a los valores del modelo interno

dejar pérdida de tren;


dejarvalLoss;
esperar model.fit(tensors.trainFeatures, tensors.trainTarget, {
tamaño de lote: BATCH_SIZE,
épocas: NUM_EPOCHS,
división de validación: 0.2,
devoluciones de llamada: {

onEpochEnd: asíncrono (época, registros) => {


esperar ui.updateStatus(
`Época ${época + 1} de ${NUM_EPOCHS} completado.̀ ); pérdida de
tren = registros.pérdida;
pérdida de valor = registros.val_loss;
esperar ui.plotData(epoch, trainLoss, valLoss);
modelo.capas[0].getWeights()[0].data().then(kernelAsArr => {
// consola.log(kernelAsArr);
const weightsList = describeKerenelElements(kernelAsArr);
ui.updateWeightDescription(weightsList);
});
}
}
});
Ejercicios 77

2.4.3 Advertencias sobre la interpretabilidad

Los pesos de la figura 2.13 cuentan una historia. Como lector humano, podría mirar esto y decir que el modelo ha
aprendido que la característica "Número de habitaciones por casa" se correlaciona positivamente con el precio de
salida o que la característica AGE de bienes raíces, que no se incluye debido a su menor magnitud absoluta, es de
menor importancia que esas primeras cinco características. Debido a la forma en que a nuestras mentes les
gusta contar historias, es común llevar esto demasiado lejos e imaginar que estos números dicen más de lo que
respalda la evidencia. Por ejemplo, una forma en que este tipo de análisis puede fallar es si dos características de
entrada están fuertemente correlacionadas.
Considere un ejemplo hipotético en el que la misma característica se incluye dos veces, quizás
por accidente. Llámelos FEAT1 y FEAT2. Imagine que los pesos aprendidos para las dos
características son 10 y -5. Podría inclinarse a decir que aumentar FEAT1 conduce a mayores
resultados, mientras que FEAT2 hace lo contrario. Sin embargo, dado que las características son
equivalentes, el modelo generaría exactamente los mismos valores si se invirtieran los pesos.

Otra advertencia a tener en cuenta es la diferencia entre correlación y causalidad. Imagine un


modelo simple en el que deseamos predecir qué tan fuerte está lloviendo afuera a partir de qué
tan mojado está nuestro techo. Si tuviéramos una medida de la humedad del techo,
probablemente podríamos hacer una predicción de cuánta lluvia ha habido en la última hora. ¡Sin
embargo, no pudimos salpicar agua sobre el sensor para que lloviera!

Ejercicios
1 Se seleccionó el problema de estimación de tiempo codificado en la sección 2.1 porque los datos
son aproximadamente lineales. Otros conjuntos de datos tendrán diferentes superficies de
pérdida y dinámicas durante el ajuste. Es posible que desee intentar sustituir sus propios datos
aquí para explorar cómo reacciona el modelo. Es posible que deba jugar con la tasa de
aprendizaje, la inicialización o la normalización para que el modelo converja en algo interesante.
2 En la sección 2.3.5, dedicamos un tiempo a describir por qué la normalización es
importante y cómo normalizar los datos de entrada para tener una media cero y una
varianza unitaria. Debería poder modificar el ejemplo para eliminar la normalización y
ver que el modelo ya no entrena. También debería poder modificar la rutina de
normalización para tener, por ejemplo, una media distinta de 0 o una desviación
estándar que sea menor, pero no tan baja. Algunas normalizaciones funcionarán y
otras conducirán a un modelo que nunca converge.
3 Es bien sabido que algunas características del conjunto de datos de precios de la vivienda de
Boston son másproféticodel objetivo que otros. Algunas de las funciones son simplemente ruido
en el sentido de que no contienen información útil para predecir los precios de la vivienda. Si
tuviéramos que eliminar todas las características menos una, ¿cuál deberíamos conservar? ¿Qué
pasaría si mantuviéramos dos características: cómo podemos seleccionar cuáles? Juega con el
código en el ejemplo de vivienda de Boston para explorar esto.
4 Describa cómo el descenso de gradiente permite la optimización de un modelo al actualizar los
pesos de una manera mejor que aleatoria.
78 CPASADO2Primeros pasos: regresión lineal simple en TensorFlow.js

5 El ejemplo de la vivienda de Boston muestra los cinco pesos principales por magnitud
absoluta. Intente modificar el código para imprimir las funciones asociadas con los
pesos pequeños. ¿Te imaginas por qué esos pesos son pequeños? Si alguien te
preguntara por qué esos pesos eran los que eran, ¿qué le dirías? ¿Qué tipo de
precauciones le diría a esa persona sobre cómo interpretar los valores?

Resumen
- Es sencillo crear, entrenar y evaluar un modelo de aprendizaje automático simple en cinco
líneas de JavaScript usando TensorFlow.js.
- El descenso de gradiente, la estructura básica del algoritmo detrás del aprendizaje profundo, es
conceptualmente simple y en realidad solo significa actualizar repetidamente los parámetros del
modelo en pequeños pasos en la dirección calculada que mejoraría más el ajuste del modelo. La
- superficie de pérdida de un modelo ilustra qué tan bien se ajusta el modelo a una cuadrícula de
valores de parámetros. La superficie de pérdida generalmente no se puede calcular debido a la
alta dimensionalidad del espacio de parámetros, pero es ilustrativa para pensar y da intuición
sobre cómo funciona el aprendizaje automático.
- Una sola capa densa es suficiente para resolver algunos problemas simples y puede lograr un
rendimiento razonable en un problema de precios de bienes raíces.
Agregando no linealidad:
Más allá de las sumas ponderadas

Este capítulo cubre


- Qué es la no linealidad y cómo la no linealidad en capas ocultas de
una red neuronal mejora la capacidad de la red y conduce a mejores
precisiones de predicción
- Qué son los hiperparámetros y métodos para ajustarlos
- Clasificación binaria a través de la no linealidad en la capa de salida,
presentada con el ejemplo de detección de sitio web de phishing

- Clasificación multiclase y en qué se diferencia de la clasificación


binaria, presentada con el ejemplo de la flor del iris

En este capítulo, construirá sobre la base establecida en el capítulo 2 para permitir que sus
redes neuronales aprendan mapeos más complicados, desde características hasta etiquetas. La
principal mejora que presentaremos esno linealidad—un mapeo entre entrada y salida que no
es una simple suma ponderada de los elementos de la entrada. La no linealidad mejora el poder
de representación de las redes neuronales y, cuando se usa correctamente, mejora la precisión
de la predicción en muchos problemas. Ilustraremos este punto al continuar utilizando el
conjunto de datos de viviendas de Boston. Además, este capítulo

79
80 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

introducirá una mirada más profunda asobre-ydesajustarpara ayudarlo a entrenar modelos que no solo se
desempeñen bien en los datos de entrenamiento, sino que también logren una buena precisión en los datos que
los modelos no han visto durante el entrenamiento, que es lo que finalmente cuenta en términos de calidad de
los modelos.

3.1 No linealidad: qué es y para qué sirve


Retomemos donde lo dejamos con el ejemplo de la vivienda en Boston del último capítulo. Usando una
sola capa densa, vio modelos entrenados que conducían a MSE correspondientes a estimaciones erróneas
de aproximadamente US $ 5,000. ¿Podemos hacerlo mejor? La respuesta es sí. Para hacer un mejor
modelo para los datos de viviendas de Boston, le agregamos una capa más densa, como se muestra en la
siguiente lista de códigos (de index.js del ejemplo de viviendas de Boston).

Listado 3.1 Definición de una red neuronal de dos capas para el problema de la vivienda en Boston

función de exportación multiLayerPerceptronRegressionModel1Hidden() {


const modelo = tf.secuencial();
modelo.add(tf.layers.dense({ Especifica cómo deben inicializarse los valores del
inputShape: [bostonData.numFeatures], unidades: kernel; consulte la sección 3.1.2 para una
50, discusión de cómo se elige esto a través de la
activación: 'sigmoide', optimización de hiperparámetros.

kernelInitializer: 'leCunNormal' }));

modelo.add(tf.layers.dense({unidades: 1})); Agrega una capa oculta

Resumen Modelo();
Imprime un resumen de texto
modelo de retorno;
de la topología del modelo.
};

Para ver este modelo en acción, primero ejecute elhilo y reloj de hilocomo se mencionó en el
capítulo 2. Una vez que la página web esté abierta, haga clic en el botón Train Neural Network
Regressor (1 Hidden Layer) en la interfaz de usuario para comenzar el entrenamiento del modelo.
El modelo es una red de dos capas. La primera capa es una capa densa con 50 unidades.
También está configurado para tener una activación personalizada y un inicializador de kernel, que
discutiremos en la sección 3.1.2. Esta capa es unocultoporque su salida no se ve directamente
desde fuera del modelo. La segunda capa es una capa densa con la activación predeterminada (la
activación lineal) y es estructuralmente la misma capa que usamos en el modelo lineal puro del
capítulo 2. Esta capa es unaproduccióncapa porque su salida es la salida final del modelo y es lo
que devuelve el modelopredecir()método. Es posible que haya notado que el nombre de la función
en el código se refiere al modelo como unperceptrón multicapa(MPL). Este es un término usado
con frecuencia que describe las redes neuronales que 1) tienen una topología simple sin bucles (lo
que se conoce comoredes neuronales feedforward) y 2) tienen al menos una capa oculta. Todos los
modelos que verá en este capítulo cumplen con esta definición.
losResumen Modelo()La llamada en el listado 3.1 es nueva. Es una herramienta de diagnóstico/informes
que imprime la topología de los modelos de TensorFlow.js en la consola (ya sea en la herramienta de
desarrollo del navegador o en la salida estándar en Node.js). Esto es lo que generó el modelo de dos
capas:
No linealidad: qué es y para qué sirve 81

_________________________________________________________________ Capa (tipo)


Forma de salida Parámetro #
================================================== ===============
dense_Dense1 (Denso) [nulo, 50] 650
________________________________________________________________ denso_Dense2 (Denso)
[nulo, 1] 51
================================================== ===============
Parámetros totales: 701
Parámetros entrenables: 701
Parámetros no entrenables: 0

La información clave en el resumen incluye


- Los nombres y tipos de las capas (primera columna).
- La forma de salida para cada capa (segunda columna). Estas formas casi siempre contienen
una dimensión nula como la primera dimensión (lote), que representa un tamaño de lote
variable e indeterminado.
-El número de parámetros de peso para cada capa (tercera columna). Este es un recuento
de todos los números individuales que componen los pesos de la capa. Para capas con
más de un peso, esta es una suma de todos los pesos. Por ejemplo, la primera capa
densa en este ejemplo contiene dos pesos: un núcleo de forma [12, 50]y un sesgo de
forma [50],llevando a12 * 50 + 50 = 650parámetros El número total de parámetros de
- peso del modelo (en la parte inferior del resumen), seguido de un desglose de cuántos
de los parámetros se pueden entrenar y cuántos no se pueden entrenar. Los modelos
que hemos visto hasta ahora contienen solo parámetros entrenables, que pertenecen
a los pesos del modelo que se actualizan cuandotf.Modelo.ajuste()se llama.
Discutiremos los pesos no entrenables cuando hablemos sobre el aprendizaje de
transferencia y el ajuste fino del modelo en el capítulo 5.

losResumen Modelo()La salida del modelo puramente lineal del capítulo 2 es la siguiente. En
comparación con el modelo lineal, nuestro modelo de dos capas contiene aproximadamente 54
veces más parámetros de peso. La mayoría de los pesos adicionales provienen de la capa oculta
agregada:

_________________________________________________________________ Capa (tipo)


Forma de salida Parámetro #
================================================== ===============
dense_Dense3 (Denso) [nulo, 1] 13
================================================== ===============
Parámetros totales: 13
Parámetros entrenables: 13
Parámetros no entrenables: 0

Debido a que el modelo de dos capas contiene más capas y parámetros de peso, su entrenamiento e
inferencia consumen más tiempo y recursos de cómputo. ¿Vale la pena ganar en precisión este costo
adicional? Cuando entrenamos este modelo para 200 épocas, terminamos con MSE finales en el conjunto
de prueba que caen en el rango de 14–15 (variabilidad debido a la aleatoriedad de la inicialización), en
comparación con una pérdida del conjunto de prueba de aproximadamente 25 del conjunto de prueba.
Modelo lineal. Nuestro nuevo modelo termina con una estimación errónea de US$3,700–$3,900 versus el
82 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

aproximadamente $ 5,000 errores de estimación que vimos con los intentos puramente lineales. Esta es
una mejora significativa.

3.1.1 Construyendo la intuición para la no linealidad en redes neuronales

¿Por qué mejora la precisión? La clave es la mayor complejidad del modelo, como muestra la figura
3.1. Primero, hay una capa adicional de neuronas, que es la capa oculta. En segundo lugar, la capa
oculta contiene un no linealfunción de activación(como se especifica por activación: 'sigmoide'en el
código), que está representado por las casillas cuadradas en el panel B de la figura 3.1. Una función
de activación1es una transformación elemento por elemento. La función sigmoidea es una no
linealidad de "aplastamiento", en el sentido de que "aplasta" todos los valores reales de –infinito a
+infinito en un rango mucho más pequeño (0 a +1, en este caso). Su ecuación matemática y gráfica
se muestran en la figura 3.2. Tomemos como ejemplo la capa densa oculta. Supongamos que el
resultado de la multiplicación y suma de matrices con el sesgo es un tensor 2D que consta de la
siguiente matriz de valores aleatorios:

[[1.0], [0.5], …, [0.0]],


El resultado final de la capa densa se obtiene llamando al sigmoide (S)funcionan en cada uno
de los 50 elementos individualmente, dando

[[S(1.0)], [S(0.5)], …, [S(0.0)]] = [[0.731], [0.622], …, [0.0]]

¿Por qué se llama esta función?no lineal?Intuitivamente, la gráfica de la función de


activación no es una línea recta. Por ejemplo, sigmoid es una curva (figura 3.2, panel
izquierdo) y relu es una concatenación de dos segmentos de línea (figura 3.2, panel
derecho). Aunque sigmoid y relu no son lineales, una de sus propiedades es que son
suaves y diferenciables en cada punto, lo que hace posible realizar retropropagación.2a
través de ellos. Sin esta propiedad, no sería posible entrenar un modelo con capas que
contengan esta activación.
Además de la función sigmoidea, en el aprendizaje profundo se utilizan con frecuencia
algunos otros tipos de funciones no lineales diferenciables. Estos incluyen relu y tangente
hiperbólica (o tanh). Los describiremos en detalle cuando los encontremos en ejemplos
posteriores.

1
El terminofunción de activaciónse originó a partir del estudio de las neuronas biológicas, que se comunican entre sí a
través delos potenciales de acción(picos de voltaje en sus membranas celulares). Una neurona biológica típica recibe
entradas de varias neuronas aguas arriba a través de puntos de contacto llamadossinapsis. Las neuronas aguas arriba
disparan potenciales de acción a diferentes velocidades, lo que conduce a la liberación de neurotransmisores y a la
apertura o cierre de canales iónicos en las sinapsis. Esto, a su vez, conduce a una variación del voltaje en la membrana de
la neurona receptora. Esto no es diferente al tipo de suma ponderada que se ve para una unidad en la capa densa. Solo
cuando el potencial excede un cierto umbral, la neurona receptora producirá potenciales de acción (es decir, se "activará")
y, por lo tanto, afectará el estado de las neuronas posteriores. En este sentido, la función de activación de una neurona
biológica típica es algo similar a la función relu (figura 3.2, panel derecho), que consiste en una “zona muerta” por debajo
de cierto umbral de la entrada y aumenta linealmente con la entrada por encima de la entrada. umbral (al menos hasta
cierto nivel de saturación,
2
Consulte la sección 2.2.2 si necesita un repaso sobre la retropropagación.
No linealidad: qué es y para qué sirve 83

B.neuronal de dos capas


red con
UN.regresión lineal interno no lineal
modelo activación

Figura 3.1 El modelo de regresión lineal (panel A) y la red neuronal de dos capas (panel
B) creados para el conjunto de datos de viviendas de Boston. En aras de la claridad,
redujimos la cantidad de entidades de entrada de 12 a 3 y la cantidad de unidades de la
capa oculta de 50 a 5 en el panel B. Cada modelo tiene solo una unidad de salida
porque los modelos resuelven un univariante (uno- número objetivo) problema de
regresión. El panel B ilustra la activación no lineal (sigmoidea) de la capa oculta del
modelo.

Figura 3.2 Dos funciones de activación no lineal de uso frecuente para redes neuronales profundas. Izquierda: la
función sigmoideaS(x) = 1 / (1 + e - x). Derecha: la función de unidad lineal rectificada (relu)relu(x) = {0:x < 0, x:x >=
0}
84 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

norteONLINEALIDAD Y CAPACIDAD DEL MODELO


¿Por qué la no linealidad mejora la precisión de nuestro modelo? Las funciones no lineales nos permiten
representar una familia más diversa de relaciones de entrada-salida. Muchas relaciones en el mundo real
son aproximadamente lineales, como el problema del tiempo de descarga que vimos en el último
capítulo. Pero muchos otros no lo son. Es fácil concebir ejemplos de relaciones no lineales. Considere la
relación entre la altura de una persona y su edad. La altura varía aproximadamente linealmente con la
edad solo hasta cierto punto, donde se dobla y se estanca. Como otro escenario totalmente razonable, los
precios de la vivienda pueden variar de manera negativa con la tasa de criminalidad del vecindario solo si
la tasa de criminalidad está dentro de un cierto rango. Un modelo puramente lineal, como el que
desarrollamos en el último capítulo, no puede modelar con precisión este tipo de relación, mientras que
la no linealidad sigmoidea es mucho más adecuada para modelar esta relación. Por supuesto, la relación
tasa de delincuencia-precio de la vivienda se parece más a una función sigmoidea invertida (decreciente)
que a la función original creciente del panel izquierdo de la figura 3.2. Pero nuestra red neuronal no tiene
problemas para modelar esta relación porque la activación sigmoidea está precedida y seguida por
funciones lineales con pesos ajustables.
Pero al reemplazar la activación lineal con una no lineal como sigmoide, ¿perdemos la capacidad
de aprender cualquier relación lineal que pueda estar presente en los datos? Por suerte, la
respuesta es no. Esto se debe a que parte de la función sigmoidea (la parte cercana al centro) está
bastante cerca de ser una línea recta. Otras activaciones no lineales de uso frecuente, como tanh y
relu, también contienen partes lineales o casi lineales. Si las relaciones entre ciertos elementos de
la entrada y los de la salida son aproximadamente lineales, es muy posible que una capa densa con
una activación no lineal aprenda los pesos y sesgos adecuados para utilizar las partes casi lineales
de la función de activación. Por lo tanto, agregar activación no lineal a una capa densa conduce a
una ganancia neta en la amplitud de las relaciones de entrada-salida que puede aprender.

Además, las funciones no lineales se diferencian de las lineales en que las funciones no lineales
en cascada conducen a conjuntos más ricos de funciones no lineales. Aquí,en cascadase refiere a
pasar la salida de una función como entrada a otra. Supongamos que hay dos funciones lineales,

f(x) = k1 * x + b1

y
g(x) = k2 * x + b2

Poner en cascada las dos funciones equivale a definir una nueva funciónH:

h(x) = g(f(x)) = k2 * (k1 * x + b1) + b2 = (k2 * k1) * x + (k2 * b1 + b2)

Como puedes ver,hsigue siendo una función lineal. Simplemente tiene un núcleo diferente (pendiente) y
un sesgo diferente (intersección) de los def1yf2.La pendiente es ahora (k2 * k1),y el sesgo es ahora (k2 * b1
+ b2).La conexión en cascada de cualquier número de funciones lineales siempre da como resultado una
función lineal.
Sin embargo, considere una función de activación no lineal de uso frecuente: relu. En la parte
inferior de la figura 3.3, ilustramos lo que sucede cuando pones en cascada dos funciones relu
No linealidad: qué es y para qué sirve 85

con escala lineal. Al poner en cascada dos funciones relu escaladas, obtenemos una función que no
se parece en nada a relu. Tiene una nueva forma (algo así como una pendiente descendente
flanqueada por dos secciones planas en este caso). La conexión en cascada adicional de la función
de paso con otras funciones relu brindará un conjunto de funciones aún más diverso, como una
función de "ventana", una función que consta de varias ventanas, funciones con ventanas apiladas
sobre ventanas más anchas, etc. en la figura 3.3). Hay una gama notablemente rica de formas de
función que puede crear mediante la cascada de no linealidades como relu (una de las funciones de
activación más utilizadas). Pero, ¿qué tiene esto que ver con las redes neuronales? En esencia, las
redes neuronales son funciones en cascada. Cada capa de una red neuronal se puede ver como
una función, y el apilamiento de capas equivale a conectar en cascada estas funciones para formar
una función más compleja que es la propia red neuronal. Esto debería dejar en claro por qué incluir
funciones de activación no lineales aumenta el rango de relaciones de entrada-salida que el
modelo es capaz de aprender. Esto también le brinda una comprensión intuitiva detrás del truco
usado con frecuencia de "agregar más capas a una red neuronal profunda" y por qué a menudo
(¡pero no siempre!) conduce a modelos que pueden ajustarse mejor al conjunto de datos.

Funciones lineales en cascada

f(x) = 2 * x g(x) = 1 – x h(x) = g(f(x)) = 1 – 2 * x

1 1

1
0.5

Funciones de relu en cascada

f(x) = relu(2 * x) g(x) = relu(1 – x) h(x) = g(f(x)) = relu(1 – relu(2 * x))

1 1

1 0.5

Figura 3.3 Funciones lineales en cascada (arriba) y funciones no lineales (abajo). Las funciones lineales en cascada
siempre conducen a funciones lineales, aunque con nuevas pendientes e intersecciones. Las funciones no lineales en
cascada (como relu en este ejemplo) conducen a funciones no lineales con formas novedosas, como la función de
"paso hacia abajo" en este ejemplo. Esto ejemplifica por qué las activaciones no lineales y la cascada de ellas en las
redes neuronales conducen a un mayor poder de representación (es decir, capacidad).
86 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

El rango de relaciones de entrada-salida que un modelo de aprendizaje automático es capaz de aprender


a menudo se conoce como el modelo.capacidad. De la discusión anterior sobre la no linealidad, podemos
ver que una red neuronal con capas ocultas y funciones de activación no lineales tiene una mayor
capacidad en comparación con un regresor lineal. Esto explica por qué nuestra red de dos capas logra
una precisión de conjunto de prueba superior en comparación con el modelo de regresión lineal.

Podría preguntarse, dado que las funciones de activación no lineales en cascada conducen a una
mayor capacidad (como en la parte inferior de la figura 3.3), ¿podemos obtener un mejor modelo para el
problema de la vivienda de Boston agregando más capas ocultas a la red neuronal? losmulti-
LayerPerceptronRegressionModel2Hidden()función en index.js, que está conectado a la
botón titulado Train Neural Network Regressor (2 Hidden Layers), hace exactamente eso. Consulte
el siguiente extracto de código (de index.js del ejemplo de vivienda de Boston).

Listado 3.2 Definición de una red neuronal de tres capas para el problema de la vivienda en Boston

función de exportación multiLayerPerceptronRegressionModel2Hidden() {


const modelo = tf.secuencial();
modelo.add(tf.layers.dense({
forma de entrada: [datosboston.numFeatures],
unidades: 50, agrega el primero

activación: 'sigmoideo', capa oculta


kernelInitializer: 'leCunNormal'
}));
modelo.add(tf.layers.dense({
unidades: 50,
agrega otro
activación: 'sigmoide', capa oculta
kernelInitializer: 'leCunNormal' }));

model.add(tf.layers.dense({unidades: 1}));

Resumen Modelo();
Imprime un resumen de texto
modelo de retorno;
de la topología del modelo.
};

En elresumen()impresión (no se muestra), puede ver que el modelo contiene tres capas, es
decir, una más que el modelo en el listado 3.1. También tiene una cantidad significativamente
mayor de parámetros: 3251 en comparación con 701 en el modelo de dos capas. Los 2550
parámetros de peso adicionales se deben a la inclusión de la segunda capa oculta, que
consiste en un núcleo de forma [50, 50]y un sesgo de forma [50].
Al repetir el entrenamiento del modelo varias veces, podemos tener una idea del rango del conjunto
de prueba final (es decir, evaluación) MSE de las redes de tres capas: aproximadamente 10,8–13,4. Esto
corresponde a una estimación errónea de $3280 a $3660, que supera la de la red de dos capas ($3700 a
$3900). Por lo tanto, nuevamente hemos mejorado la precisión de predicción de nuestro modelo al
agregar capas ocultas no lineales y, por lo tanto, mejorar su capacidad.

AANULAR LA FALACIA DE APILAR CAPAS SIN NO LINEALIDAD


Otra forma de ver la importancia de la activación no lineal para el modelo mejorado de
vivienda de Boston es eliminarla del modelo. El listado 3.3 es igual al listado 3.1, excepto
que se comenta la línea que especifica la función de activación sigmoidea.
No linealidad: qué es y para qué sirve 87

fuera. Eliminar la activación personalizada hace que la capa tenga la activación lineal
predeterminada. Otros aspectos del modelo, incluido el número de capas y los parámetros
de peso, no cambian.

Listado 3.3 Una red neuronal de dos capas sin activación no lineal

función de exportación multiLayerPerceptronRegressionModel1Hidden() {


const modelo = tf.secuencial();
modelo.add(tf.layers.dense({
inputShape: [bostonData.numFeatures], unidades:
50,
// activación: 'sigmoid', kernelInitializer: Desactiva la función de activación no lineal
'leCunNormal' }));

model.add(tf.layers.dense({unidades: 1}));

Resumen Modelo();
modelo de retorno;
};

¿Cómo afecta este cambio al aprendizaje del modelo? Como puede averiguar haciendo clic en el
botón Train Neural Network Regressor (1 Hidden Layer) nuevamente en la interfaz de usuario, el
MSE en la prueba sube a aproximadamente 25, en comparación con el rango de 14 a 15 cuando se
incluyó la activación sigmoide. En otras palabras, ¡el modelo de dos capas sin la activación
sigmoidea funciona casi igual que el regresor lineal de una capa!
Esto confirma nuestro razonamiento sobre las funciones lineales en cascada. Al eliminar la
activación no lineal de la primera capa, obtenemos un modelo que es una cascada de dos
funciones lineales. Como hemos demostrado antes, el resultado es otra función lineal sin ningún
aumento en la capacidad del modelo. Por lo tanto, no sorprende que terminemos con la misma
precisión que el modelo lineal. Esto trae a colación un problema común en la construcción de redes
neuronales multicapa:asegúrese de incluir activaciones no lineales en las capas ocultas. De lo
contrario, se desperdiciarán tiempo y recursos de cálculo, con aumentos potenciales en la
inestabilidad numérica (observe las curvas de pérdida más ondulantes en el panel B de la figura
3.4). Más adelante, veremos que esto se aplica no solo a las densas sino también a otros tipos de
capas, como las capas convolucionales.

norteONLINEALIDAD E INTERPRETABILIDAD DEL MODELO


En el capítulo 2, mostramos que una vez que se entrenó un modelo lineal en el conjunto de datos
de viviendas de Boston, pudimos examinar sus pesos e interpretar sus parámetros individuales de
una manera razonablemente significativa. Por ejemplo, el peso que corresponde al rasgo “número
promedio de cuartos por vivienda” tuvo un valor positivo, y el peso que corresponde al rasgo “tasa
de criminalidad” tuvo un valor negativo. Los signos de dichos pesos reflejan la relación positiva o
negativa esperada entre el precio de la vivienda y las características respectivas. Sus magnitudes
también insinúan la importancia relativa que el modelo asigna a las diversas características. Dado
lo que acaba de aprender en este capítulo, una pregunta natural es: con un modelo no lineal que
contiene una o más capas ocultas, ¿sigue siendo posible llegar a una interpretación comprensible e
intuitiva de sus valores de peso?
88 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

A B
Separar
400 Separar

pérdida de tren
pérdida de tren 350 Pérdida de validación
200 Pérdida de validación
300

150 250
Pérdida

Pérdida
200
100 150
100
50
50

0 0
0 40 80 120 160 200 0 40 80 120 160 200
Época Época
Pérdida final del conjunto de trenes: 27,7744
Pérdida final del conjunto de trenes: 9,2346
Pérdida final del conjunto de validación: 45,7087
Pérdida final del conjunto de validación: 8,0922
Pérdida del conjunto de prueba: 26.0527
Pérdida del conjunto de prueba: 15.6055

Pérdida de referencia (error medio cuadrado)es 85.58 Pérdida de referencia (error medio cuadrado)es 85.58

Figura 3.4 Comparación de los resultados del entrenamiento con (panel A) y sin (panel B) la activación sigmoidea. Tenga en cuenta que la
eliminación de la activación sigmoidea conduce a valores de pérdida final más altos en los conjuntos de entrenamiento, validación y
evaluación (un nivel comparable al modelo puramente lineal anterior) y a curvas de pérdida menos uniformes. Tenga en cuenta que las
escalas del eje y son diferentes entre las dos gráficas.

La API para acceder a los valores de peso es exactamente la misma entre un modelo no lineal y un
modelo lineal: solo usa elobtenerPesos()en el objeto del modelo o en sus objetos de capa
constituyentes. Tome el MLP en el listado 3.1, por ejemplo, puede insertar la siguiente línea
después de que termine el entrenamiento del modelo (justo después delmodelo.fit()llamada):

modelo.capas[0].getWeights()[0].print();

Esta línea imprime el valor del núcleo de la primera capa (es decir, la capa oculta). Este es uno
de los cuatro tensores de peso en el modelo, los otros tres son el sesgo de la capa oculta y el
kernel y el sesgo de la capa de salida. Una cosa a tener en cuenta sobre la impresión es que
tiene un tamaño mayor que el núcleo que vimos al imprimir el núcleo del modelo lineal:

Tensor
[[-0.5701274, -0.1643915, -0.0009151, ..., 0.313205 , -0.3253246],
[-0.4400523, -0.0081632, -0.2673715, -0.2673715, ..., 0.1735748, 0.0864024], [0.6294659,
0.1240944, -0.2472516, ..., 0.2181769, 0.1706504], [0.9084488, 0.0130388, -0.3142847, ...,
0.4063887, 0.2205501], [0.331214, -0.5040522, 0.1784604, ..., 0.3022115, -0.1997144],
[-0.9726604, -0.173905, 0.8167523, 0.8167523, ..., -0.0406454, -0.4347956], [-0.2426955,
0.3274118, -0.3496988, ..., 0.5623314, 0.2339328], [-1.6335299, -1.1270424, 0.618491, ...,
-0.0868887, -0.0868887], [-0.1577617, 0.4981289, 0.4981289, -0.1368523, ..., 0.3636355,
-0.0784487], [- 0.5824679, -0.1883982, -0.483655, -0.483655, ..., 0,0026836, -0.0549298],
[-0.6993552, -0.1317919, -0.4666585, -0.4666585, ..., 0.2831602, -0.2487895], [0.0448515,
-0.6925298, 0.4925298, 0.4945385, ..., ..., -0.3133179, -0.0241681]]
No linealidad: qué es y para qué sirve 89

Esto se debe a que la capa oculta consta de 50 unidades, lo que conduce a un tamaño de peso de
[18, 50].Este núcleo tiene 900 parámetros de peso individuales, en comparación con el12 + 1 = 13
parámetros en el núcleo del modelo lineal. ¿Podemos asignar un significado a cada uno de los
parámetros de peso individuales? En general, la respuesta es no. Esto se debe a que no hay un
significado fácilmente identificable para ninguna de las 50 salidas de la capa oculta. Estas son las
dimensiones del espacio de alta dimensión creado para que el modelo pueda aprender (descubrir
automáticamente) relaciones no lineales en él. La mente humana no es muy buena para hacer un
seguimiento de las relaciones no lineales en espacios de dimensiones tan altas. En general, es muy
difícil escribir algunas oraciones en términos sencillos para describir qué hace cada una de las
unidades de la capa oculta o para explicar cómo contribuye a la predicción final de la red neuronal
profunda.
Además, tenga en cuenta que el modelo aquí tiene solo una capa oculta. Las relaciones se vuelven aún
más oscuras y difíciles de describir cuando hay varias capas ocultas apiladas una encima de la otra (como
es el caso del modelo definido en el listado 3.2). Aunque hay esfuerzos de investigación para encontrar
mejores formas de interpretar el significado de las capas ocultas de las redes neuronales profundas,3y se
están haciendo progresos para algunas clases de modelos,4es justo decir que las redes neuronales
profundas son más difíciles de interpretar en comparación con las redes neuronales superficiales y ciertos
tipos de modelos de aprendizaje automático de redes no neuronales (como los árboles de decisión). Al
elegir un modelo profundo en lugar de uno superficial, esencialmente estamos intercambiando algo de
interpretabilidad por una mayor capacidad del modelo.

3.1.2 Hiperparámetros y optimización de hiperparámetros


Nuestra discusión de las capas ocultas en los listados 3.1 y 3.2 se ha centrado en la activación no
lineal (sigmoide). Sin embargo, otros parámetros de configuración para esta capa también son
importantes para garantizar un buen resultado de entrenamiento de este modelo. Estos incluyen el
número de unidades (50) y el kernel's 'leCunNormal'inicialización Esta última es una forma especial
de generar los números aleatorios que entran en el valor inicial del núcleo en función del tamaño
de la entrada. Es distinto del inicializador de kernel predeterminado. ('glorotNormal'),que utiliza los
tamaños de entrada y salida. Las preguntas naturales que se deben hacer son: ¿Por qué usar este
inicializador de kernel personalizado en particular en lugar del predeterminado? ¿Por qué usar 50
unidades (en lugar de, digamos, 30)? Estas elecciones se realizan para garantizar la mejor calidad
de modelo posible o casi la mejor posible mediante la prueba repetida de varias combinaciones de
parámetros.
Los parámetros como el número de unidades, los inicializadores del núcleo y la activación son
hiperparámetrosdel modelo El nombre "hiperparámetros" significa el hecho de que estos parámetros son
distintos de los parámetros de peso del modelo, que se actualizan automáticamente a través de la
retropropagación durante el entrenamiento (es decir,Modelo.fit()llamadas). Una vez

3
Marco Tulio Ribeiro, Sameer Singh y Carlos Guestrin, "Explicaciones agnósticas del modelo local interpretable (LIME): una
introducción", O'Reilly, 12 de agosto de 2016,http://mng.bz/j5vP.
4
Chris Olah et al., "Los componentes básicos de la interpretabilidad", Distill, 6 de marzo de 2018,https://distill.pub/2018/bloques-de-
construccion/.
90 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

los hiperparámetros han sido seleccionados para un modelo, no cambian durante el proceso
de entrenamiento. A menudo determinan el número y el tamaño de los parámetros de peso
(por ejemplo, considere elunidadescampo para una capa densa), los valores iniciales de los
parámetros de peso (considere elkernelInitializercampo), y cómo se actualizan durante el
entrenamiento (considere eloptimizadorcampo pasado aModelo.compilar()). Por lo tanto, están
en un nivel superior a los parámetros de peso. De ahí el nombre de "hiperparámetro".

Además de los tamaños de las capas y el tipo de inicializadores de peso, existen muchos otros
tipos de hiperparámetros para un modelo y su entrenamiento, como

- La cantidad de capas densas en un modelo, como las de los listados 3.1 y 3.2. Qué
- tipo de inicializador usar para el núcleo de una capa densa.
- Si se debe utilizar alguna regularización de peso (ver sección 8.1) y, en caso afirmativo, el factor de
regularización
- Si incluir capas de deserción (consulte la sección 4.3.2, por ejemplo) y, de ser así, la
tasa de deserción
- El tipo de optimizador utilizado para el entrenamiento (como 'sgd'versus 'Adán';ver cuadro de
información 3.1)
- Para cuántas épocas entrenar el modelo La
- tasa de aprendizaje del optimizador
- Si la tasa de aprendizaje del optimizador debe reducirse gradualmente a medida que
avanza el entrenamiento y, de ser así, a qué tasa
- El tamaño del lote para el entrenamiento.

Los últimos cinco ejemplos enumerados son algo especiales en el sentido de que no están
relacionados con la arquitectura del modelo per se; en cambio, son configuraciones del proceso de
entrenamiento del modelo. No obstante, afectan el resultado del entrenamiento y, por lo tanto, se
tratan como hiperparámetros. Para los modelos que constan de tipos de capas más diversos (como
las capas convolucionales y recurrentes, discutidas en los capítulos 4, 5 y 9), existen aún más
hiperparámetros potencialmente sintonizables. Por lo tanto, está claro por qué incluso un modelo
simple de aprendizaje profundo puede tener docenas de hiperparámetros ajustables.
El proceso de selección de buenos valores de hiperparámetros se conoce como
optimización de hiperparámetrosoajuste de hiperparámetros. El objetivo de la optimización
de hiperparámetros es encontrar un conjunto de parámetros que conduzca a la menor
pérdida de validación después del entrenamiento. Desafortunadamente, actualmente no
existe un algoritmo definitivo que pueda determinar los mejores hiperparámetros dado un
conjunto de datos y la tarea de aprendizaje automático involucrada. La dificultad radica en
que muchos de los hiperparámetros son discretos, por lo que el valor de pérdida de
validación no es diferenciable con respecto a ellos. Por ejemplo, el número de unidades en
una capa densa y el número de capas densas en un modelo son números enteros; el tipo de
optimizador es un parámetro categórico. Incluso para los hiperparámetros que son continuos
y frente a los cuales la pérdida de validación es diferenciable (por ejemplo, factores de
regularización),
No linealidad: qué es y para qué sirve 91

realizar descenso de gradiente en el espacio de tales hiperparámetros. La optimización de


hiperparámetros sigue siendo un área activa de investigación, a la que los profesionales del
aprendizaje profundo deben prestar atención.
Dada la falta de una metodología o herramienta estándar y lista para usar para la optimización de
hiperparámetros, los profesionales del aprendizaje profundo a menudo usan los siguientes tres enfoques.
Primero, si el problema en cuestión es similar a un problema bien estudiado (digamos, cualquiera de los
ejemplos que puede encontrar en este libro), puede comenzar aplicando un modelo similar en su
problema y "heredar" los hiperparámetros. Más tarde, puede buscar en un espacio de hiperparámetros
relativamente pequeño alrededor de ese punto de partida.
En segundo lugar, los profesionales con suficiente experiencia pueden tener intuición y
conjeturas informadas sobre cuáles pueden ser hiperparámetros razonablemente buenos para un
problema determinado. Incluso tales elecciones subjetivas casi nunca son óptimas: forman buenos
puntos de partida y pueden facilitar ajustes posteriores.
En tercer lugar, para los casos en los que solo hay una pequeña cantidad de hiperparámetros
para optimizar (por ejemplo, menos de cuatro), podemos usar la búsqueda en cuadrícula, es decir,
iterar exhaustivamente sobre una cantidad de combinaciones de hiperparámetros, entrenar un
modelo hasta completar para cada uno. de ellos, registrando la pérdida de validación y tomando la
combinación de hiperparámetros que produce la menor pérdida de validación. Por ejemplo,
suponga que los únicos dos hiperparámetros para ajustar son 1) el número de unidades en una
capa densa y 2) la tasa de aprendizaje; puede seleccionar un conjunto de unidades ({10, 20, 50, 100,
200})y un conjunto de tasas de aprendizaje ({1e-5, 1e-4, 1e-3, 1e-2})y realiza un cruce de los dos
conjuntos, lo que lleva a un total de5 * 4 = 20combinaciones de hiperparámetros para buscar. Si
tuviera que implementar la búsqueda de cuadrícula usted mismo, el pseudocódigo podría
parecerse a la siguiente lista.

Listado 3.4 Pseudocódigo para una búsqueda simple en grilla de hiperparámetros

función hiperparámetroGridSearch():
para unidades de [10, 20, 50, 100, 200]:
para el aprendizaje Tasa de [1e-5, 1e-4, 1e-3, 1e-2]:
Cree un modelo cuya capa densa consista en `unidades` unidades Entrene el modelo
con un optimizador con `learningRate`
Calcule la pérdida de validación final como la pérdida de validación
si la pérdida de validación < minValidationLoss
minValidationLoss := ValidaciónPérdida
mejoresUnidades := unidades
mejortasadeaprendizaje := tasa de aprendizaje

volver [mejoresunidades, mejortasa de aprendizaje]

¿Cómo se seleccionan los rangos de estos hiperparámetros? Bueno, hay otro lugar donde el aprendizaje
profundo no puede proporcionar una respuesta formal. Estos rangos generalmente se basan en la
experiencia y la intuición del practicante de aprendizaje profundo. También pueden estar limitados por
los recursos de computación. Por ejemplo, una capa densa con demasiadas unidades puede hacer que el
modelo sea demasiado lento para entrenar o ejecutar durante la inferencia.
A menudo, hay una mayor cantidad de hiperparámetros para optimizar, en la medida en que se vuelve
demasiado costoso desde el punto de vista computacional para buscar en el exponencialmente.
92 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

número creciente de combinaciones de hiperparámetros. En tales casos, debe usar métodos más
sofisticados que la búsqueda en cuadrícula, como la búsqueda aleatoria.5y bayesiano6
métodos.

3.2 No linealidad en la salida: Modelos para la clasificación


Los dos ejemplos que hemos visto hasta ahora son tareas de regresión en las que intentamos predecir un
valor numérico (como el tiempo de descarga o el precio medio de la vivienda). Sin embargo, otra tarea
común en el aprendizaje automático es la clasificación. Algunas tareas de clasificación sonclasificación
binaria, donde el objetivo es la respuesta a una pregunta de sí/no. El mundo de la tecnología está lleno de
este tipo de problemas, incluyendo

- Si un correo electrónico dado es o no spam


- Si una determinada transacción con tarjeta de crédito es legítima o fraudulenta. Si una
- muestra de audio de un segundo de duración contiene una palabra hablada específica. Si
- dos imágenes de huellas digitales coinciden (provienen del mismo dedo de la misma
persona).

Otro tipo de problema de clasificación es unclasificación multiclasetarea, para la que también


abundan los ejemplos:

- Si un artículo de noticias trata sobre deportes, clima, juegos, política u otros temas
generales.
- Si una imagen es un gato, un perro, una pala, etc.
- Dados los datos de trazo de un lápiz óptico electrónico, determinar qué es un carácter
escrito a mano
- En el escenario de usar el aprendizaje automático para jugar un videojuego simple tipo
Atari, determinar en cuál de las cuatro direcciones posibles (arriba, abajo, izquierda y
derecha) debe ir el personaje del juego a continuación, dado el estado actual del juego.

3.2.1 ¿Qué es la clasificación binaria?


Comenzaremos con un caso simple de clasificación binaria. Dados algunos datos, queremos una decisión
de sí/no. Para nuestro ejemplo motivador, hablaremos sobre el conjunto de datos del sitio web de
phishing.7La tarea es, dada una colección de características sobre una página web y su URL, predecir si la
página web se usa parasuplantación de identidad(haciéndose pasar por otro sitio con el objetivo de robar
información confidencial de los usuarios).
El conjunto de datos contiene 30 características, todas binarias (representadas como los valores
– 1 y 1) o ternarias (representadas como –1, 0 y 1). En lugar de enumerar todos los individuos

5
James Bergstra y Yoshua Bengio, “Búsqueda aleatoria para la optimización de hiperparámetros”,Revista de investigación de
aprendizaje automático, vol. 13, 2012, págs. 281–305,http://mng.bz/WOg1.
6
Will Koehrsen, “Una explicación conceptual de la optimización de hiperparámetros bayesianos para el aprendizaje automático,
Hacia la ciencia de datos, 24 de junio de 2018,http://mng.bz/8zQw.
7
Rami M. Mohammad, Fadi Thabtah y Lee McCluskey, "Características de los sitios web de phishing"http://mng.bz/E1KO.
No linealidad en la salida: Modelos para la clasificación 93

características como hicimos para el conjunto de datos de viviendas de Boston, aquí presentamos algunas características

representativas:

-CON_DIRECCIÓN_IP—Si se utiliza una dirección IP como alternativa a un nombre


de dominio (valor binario: {-1, 1})
-SERVICIO_CORTE—Si está utilizando un servicio de acortamiento de URL o no
(valor binario: {1, -1})
- SSLFINAL_ESTADO—Si 1) la URL usa HTTPS y el emisor es de confianza, 2) usa
HTTPS pero el emisor no es de confianza, o 3) no se usa HTTPS (ternaria
valor: {-1, 0, 1})
El conjunto de datos consta de aproximadamente 5500 ejemplos de entrenamiento y un número igual de
ejemplos de prueba. En el conjunto de entrenamiento, aproximadamente el 45% de los ejemplos son positivos
(verdaderas páginas web de phishing). El porcentaje de ejemplos positivos es casi el mismo en el conjunto de
prueba.
Este es el tipo de conjunto de datos más fácil con el que trabajar: las características de los datos ya
están en un rango constante, por lo que no es necesario normalizar sus medias y desviaciones estándar
como hicimos con el conjunto de datos de viviendas de Boston. Además, tenemos una gran cantidad de
ejemplos de entrenamiento relativos tanto a la cantidad de características como a la cantidad de
predicciones posibles (dos, sí o no). Tomado como un todo, este es un buen control de cordura de que es
un conjunto de datos con el que podemos trabajar. Si quisiéramos pasar más tiempo investigando
nuestros datos, podríamos hacer verificaciones de correlación de características por pares para saber si
tenemos información redundante; sin embargo, esto es algo que nuestro modelo puede tolerar.
Dado que nuestros datos se parecen a los que usamos (posnormalización) para Bostonhousing,
nuestro modelo inicial se basa en la misma estructura. El código de ejemplo para este problema
está disponible en la carpeta de phishing del sitio web del repositorio tfjs-examples. Puede verificar
y ejecutar el ejemplo de la siguiente manera:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


website-phishing
discos compactos

hilo y reloj de hilo

Listado 3.5 Definición de un modelo de clasificación binaria para la detección de phishing (de index.js)

const modelo = tf.secuencial();


modelo.add(tf.layers.dense({
forma de entrada: [datos.numFeatures],
unidades: 100,
activación:'sigmoideo'
}));
model.add(tf.layers.dense({unidades: 100, activación: 'sigmoide'}));
model.add(tf.layers.dense({unidades: 1, activación: 'sigmoide'})); modelo.compilar({

optimizador: 'adam',
pérdida: 'binaryCrossentropy',
métricas: ['precisión']
});

Este modelo tiene muchas similitudes con la red multicapa que construimos para el problema
de la vivienda en Boston. Comienza con dos capas ocultas, y ambas usan el sigmoide
94 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

activación. El último (salida) tiene exactamente 1 unidad, lo que significa que el modelo genera un solo
número para cada ejemplo de entrada. Sin embargo, una diferencia clave aquí es que la última capa de
nuestro modelo para la detección de phishing tiene una activación sigmoidea en lugar de la activación
lineal predeterminada como en el modelo de vivienda de Boston. Esto significa que nuestro modelo está
restringido a generar números entre 0 y 1, lo que es diferente al modelo de vivienda de Boston, que
podría generar cualquier número flotante.
Anteriormente, hemos visto activaciones sigmoideas para capas ocultas que ayudan a aumentar la
capacidad del modelo. Pero, ¿por qué usamos la activación sigmoidea en la salida de este nuevo modelo?
Esto tiene que ver con la naturaleza de clasificación binaria del problema que tenemos entre manos. Para
la clasificación binaria, generalmente queremos que el modelo produzca una suposición de la
probabilidad de la clase positiva, es decir, qué tan probable es que el modelo "piense" que un ejemplo
dado pertenece a la clase positiva. Como recordará de las matemáticas de la escuela secundaria, una
probabilidad es siempre un número entre 0 y 1. Al hacer que el modelo siempre genere un valor de
probabilidad estimado, obtenemos dos beneficios:

- Capta el grado de apoyo a la clasificación asignada. Un valor sigmoideo de 0,5 indica una
incertidumbre completa, en la que cualquier clasificación es igualmente compatible. Un
valor de 0,6 indica que, si bien el sistema predice la clasificación positiva, solo tiene un
respaldo débil. Un valor de 0,99 significa que el modelo está bastante seguro de que el
ejemplo pertenece a la clase positiva, y así sucesivamente. Por lo tanto, hacemos que sea
fácil y directo convertir la salida del modelo en una respuesta final (por ejemplo,
simplemente establecer el umbral de la salida en un valor dado, digamos 0.5). Ahora
imagine lo difícil que sería encontrar ese umbral si el rango de salida del modelo puede
variar ampliamente.
- También facilitamos la creación de una función de pérdida diferenciable que, dada la
salida del modelo y las etiquetas objetivo binarias verdaderas, produce un número
que es una medida de cuánto falló el modelo en la marca. Para el último punto,
elaboraremos más cuando examinemos la entropía cruzada binaria real utilizada por
este modelo.

Sin embargo, la pregunta es cómo forzar la salida de la red neuronal en el rango de [0, 1].La
última capa de una red neuronal, que suele ser una capa densa, realiza la multiplicación de
matrices (matMul)y adición de sesgo (sesgoAñadir)operaciones con su entrada. No existen
restricciones intrínsecas en elMatMulo lasesgoAñadiroperación que garantiza un [0, 1]rango
en el resultado. Agregando una no linealidad aplastante como sigmoide al resultado de
MatMulysesgoAñadires una forma natural de lograr el [0, 1]rango.
Otro aspecto del código del listado 3.5 que es nuevo para usted es el tipo de optimizador: 'Adán',
que es diferente del 'sgd'optimizador utilizado en ejemplos anteriores. Como esAdándiferente de
sgd?Como recordará de la sección 2.2.2 del último capítulo, el sgdEl optimizador siempre multiplica
los gradientes obtenidos a través de la retropropagación por un número fijo (su tasa de
aprendizaje multiplicada por -1) para calcular las actualizaciones de los pesos del modelo. Este
enfoque tiene algunos inconvenientes, incluida la convergencia lenta hacia el mínimo de pérdida
cuando se elige una tasa de aprendizaje pequeña y caminos en "zigzag" en el espacio de peso
cuando la forma de la (hiper)superficie de pérdida tiene ciertas características especiales.
No linealidad en la salida: Modelos para la clasificación 95

propiedades losAdánEl optimizador tiene como objetivo abordar estas deficiencias desgdmediante el uso de un
factor de multiplicación que varía con el historial de los gradientes (de iteraciones de entrenamiento anteriores)
de manera inteligente. Además, utiliza diferentes factores de multiplicación para diferentes parámetros de peso
del modelo. Como resultado,Adángeneralmente conduce a una mejor convergencia y menos dependencia en la
elección de la tasa de aprendizaje en comparación consgden una variedad de tipos de modelos de aprendizaje
profundo; por lo tanto, es una opción popular de optimizador. La biblioteca Tensor-Flow.js proporciona otros
tipos de optimizadores, algunos de los cuales también son populares (comormsprop).La tabla en el cuadro de
información 3.1 ofrece una breve descripción de ellos.

ICAJA NFO3.1 Optimizadores compatibles con TensorFlow.js


La siguiente tabla resume las API de los tipos de optimizadores más utilizados en
TensorFlow.js, junto con una explicación simple e intuitiva para cada uno de ellos.

Optimizadores de uso común y sus API en TensorFlow.js

Nombre API (cadena) API (función) Descripción

estocástico 'sgd' tf.tren.sgd El optimizador más simple, siempre


degradado usando la tasa de aprendizaje como
descenso (SGD) multiplicador de gradientes

Impulso 'impulso' tf.tren.impulso Acumula gradientes pasados de tal


manera que la actualización de un
parámetro de peso se vuelve más
rápida cuando los gradientes pasados
del parámetro se alinean más en la
misma dirección y se vuelve más lenta
cuando cambian mucho de dirección

RMSProp 'rmsprop' tf.tren.rmsprop Escala el factor de multiplicación de


manera diferente para diferentes
parámetros de peso del modelo mediante
el seguimiento de un historial reciente del
valor RMS (raíz cuadrática media) de cada
gradiente de peso; de ahí su nombre

AdaDelta 'adadelta' tf.tren.adadelta Escala la tasa de aprendizaje para


cada parámetro de peso individual de
manera similar a RMSProp

ADÁN 'Adán' tf.tren.adam Puede entenderse como una


combinación del enfoque de tasa de
aprendizaje adaptativo de AdaDelta y
el método de impulso

adamax 'adamax' tf.tren.adamax Similar a ADAM, pero realiza un seguimiento


de las magnitudes de los gradientes
utilizando un algoritmo ligeramente diferente
96 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

(continuado)
Una pregunta obvia es qué optimizador debe usar dado el problema de aprendizaje automático y
el modelo en el que está trabajando. Desafortunadamente, todavía no hay consenso en el campo
del aprendizaje profundo (¡razón por la cual TensorFlow.js proporciona todos los optimizadores
enumerados en la tabla anterior!). En la práctica, debe comenzar con los populares, incluidos Adán
yrmsprop.Con suficiente tiempo y recursos informáticos, también puede tratar el optimizador
como un hiperparámetro y encontrar la opción que le brinde el mejor resultado de entrenamiento
a través del ajuste de hiperparámetros (consulte la sección 3.1.2).

3.2.2 Medición de la calidad de los clasificadores binarios: precisión,


recuperación, exactitud y curvas ROC
En un problema de clasificación binaria, emitimos uno de dos valores: 0/1, sí/no, etc. En un
sentido más abstracto, hablaremos de los aspectos positivos y negativos. Cuando nuestra red
adivina, es correcta o incorrecta, por lo que tenemos cuatro escenarios posibles para la
etiqueta real del ejemplo de entrada y la salida de la red, como muestra la tabla 3.1.

Tabla 3.1 Los cuatro tipos de clasificación dan como resultado un problema de clasificación binaria

Predicción

Positivo Negativo

Positivo Verdadero positivo (VP) Falso negativo (FN)


Verdad
Negativo Falso positivo (FP) Verdadero negativo (TN)

Los verdaderos positivos (TP) y los verdaderos negativos (TN) son donde el modelo predijo la
respuesta correcta; los falsos positivos (FP) y los falsos negativos (FN) son donde el modelo se
equivocó. Si llenamos las cuatro celdas con conteos, obtenemos unmatriz de confusión; la tabla 3.2
muestra uno hipotético para nuestro problema de detección de phishing.

Tabla 3.2 La matriz de confusión de un problema hipotético de clasificación binaria

Predicción

Positivo Negativo

Positivo 4 2
Verdad
Negativo 1 93

En nuestros resultados hipotéticos de nuestros ejemplos de phishing, vemos que identificamos


correctamente cuatro páginas web de phishing, pasamos por alto dos y tuvimos una falsa alarma.
Veamos ahora las diferentes métricas comunes para expresar este rendimiento.
No linealidad en la salida: Modelos para la clasificación 97

Exactitudes la métrica más simple. Cuantifica qué porcentaje de los ejemplos se clasifican
correctamente:

Precisión = (#TP + #TN) / #ejemplos = (#TP + #TN) / (#TP + #TN + #FP + #FN)

En nuestro ejemplo particular,

Precisión = (4 + 93) / 100 = 97%

La precisión es un concepto fácil de comunicar y fácil de entender. Sin embargo, puede ser
engañoso: a menudo, en una tarea de clasificación binaria, no tenemos distribuciones iguales de
ejemplos positivos y negativos. A menudo nos encontramos en una situación en la que hay muchos
menos ejemplos positivos que negativos (por ejemplo, la mayoría de los enlaces no son phishing, la
mayoría de las partes no son defectuosas, etc.). ¡Si solo 5 de cada 100 enlaces son phishing, nuestra
red siempre podría predecir lo falso y obtener un 95% de precisión! Dicho de esa manera, la
precisión parece una medida muy mala para nuestro sistema. La alta precisión siempre suena bien,
pero a menudo es engañosa. Es bueno monitorear, pero sería muy malo usarlo como una función
de pérdida.
El siguiente par de métricas intenta capturar la sutileza que falta en la precisión:precisióny
recordar. En la discusión que sigue, normalmente también estamos pensando en problemas en los
que un positivo implica que se requiere una acción adicional (un enlace está resaltado, una
publicación está marcada para revisión manual), mientras que un negativo indica el status quo.
Estas métricas se enfocan en los diferentes tipos de “mal” que podría tener nuestra predicción.
Precisiónes la proporción de predicciones positivas hechas por el modelo que en realidad son
positivas:

precisión = #TP / (#TP + #FP)

Con nuestros números de la matriz de confusión, calcularíamos

precisión = 4 / (4 + 1) = 80%

Al igual que la precisión, generalmente es posible jugar con precisión. Puede hacer que su modelo sea
muy conservador al emitir predicciones positivas, por ejemplo, etiquetando solo los ejemplos de entrada
con una salida sigmoidea muy alta (por ejemplo, >0,95, en lugar del valor predeterminado >0,5) como
positivos. Por lo general, esto hará que la precisión aumente, pero hacerlo probablemente hará que el
modelo pierda muchos ejemplos positivos reales (etiquetándolos como negativos). El último costo es
capturado por la métrica que a menudo acompaña y complementa la precisión, a saber, el recuerdo.

Recordares la proporción de ejemplos positivos reales que el modelo clasifica como


positivos:

recordar = #TP / (#TP + #FN)

Con los datos de ejemplo, obtenemos un resultado de

recuerdo = 4 / (4 + 2) = 66,7%

De todos los aspectos positivos en el conjunto de muestra, ¿cuántos encontró el modelo? Normalmente será una
decisión consciente aceptar una tasa más alta de falsas alarmas para reducir la posibilidad de perder
98 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

algo. Para jugar con esta métrica, simplemente declararía todos los ejemplos como positivos; Debido a
que los falsos positivos no entran en la ecuación, puede obtener un 100 % de recuperación a costa de una
menor precisión.
Como podemos ver, es bastante fácil crear un sistema que puntúe muy bien en exactitud,
recuperación o precisión. En los problemas de clasificación binaria del mundo real, a menudo es
difícil obtener una buena precisión y recuperación al mismo tiempo. (Si fuera fácil hacerlo, tendría
un problema simple y probablemente no necesitaría usar el aprendizaje automático en primer
lugar). La precisión y la memoria consisten en ajustar el modelo en los lugares difíciles donde existe
una incertidumbre fundamental. sobre cuál debería ser la respuesta correcta. Verá métricas más
matizadas y combinadas, comoPrecisión al X% de recuperación, siendo X algo así como el 90 %,
¿cuál es la precisión si estamos sintonizados para encontrar al menos el X % de los positivos? Por
ejemplo, en la figura 3.5, vemos que después de 400 épocas de entrenamiento, nuestro modelo de
detección de phishing puede lograr una precisión del 96,8 % y una recuperación del 92,9 % cuando
la salida de probabilidad del modelo tiene un umbral de 0,5.
Como hemos aludido brevemente, una realización importante es que el umbral aplicado
en la salida sigmoidea para seleccionar predicciones positivas no tiene que ser exactamente
0,5. De hecho, dependiendo de las circunstancias, puede ser mejor establecerlo en un valor
superior a 0,5 (pero inferior a 1) o inferior a 0,5 (pero superior a 0). Bajar el umbral hace

TensorFlow.js: clasificación de URL de sitios web como phishy o normales

0.8 Separar 1.0 Separar

pérdida de tren pérdida de tren


0.9
0.7 Pérdida de validación Pérdida de validación

0.8
0.6
0.7
0.5
0.6
Exactitud
Pérdida

0.4 0.5

0.4
0.3
0.3
0.2
0.2
0.1
0.1

0.0 0.0
0 50 100 150 200 250 300 350 400 0 50 100 150 200 250 300 350 400
Época Época

Pérdida final del conjunto de trenes: 0,0493 Precisión: 0,9801


Pérdida final del conjunto de validación: 0,1402 Precisión: 0,9521
Pérdida del equipo de prueba: 0,1317 precisión: 0,9555
Precisión: 0.9675
Recuperación: 0.9289

Tasa de falsos positivos (FPR): 0,0240 Área


bajo la curva (AUC): 0,981150715602107

Figura 3.5 Un ejemplo de resultado de una ronda de entrenamiento del modelo para la detección de páginas web de phishing.
Preste atención a las diversas métricas en la parte inferior: precisión, recuperación y FPR. El área bajo la curva (AUC) se analiza en la
sección 3.2.3.
No linealidad en la salida: Modelos para la clasificación 99

el modelo es más liberal al etiquetar las entradas como positivas, lo que conduce a un mayor recuerdo pero
probablemente a una menor precisión. Por otro lado, elevar el umbral hace que el modelo sea más cauteloso al
etiquetar las entradas como positivas, lo que generalmente conduce a una mayor precisión pero probablemente
a una menor recuperación. Entonces, podemos ver que existe una compensación entre precisión y recuperación,
y esta compensación es difícil de cuantificar con cualquiera de las métricas de las que hemos hablado hasta
ahora. Afortunadamente, la rica historia de investigación sobre la clasificación binaria nos ha brindado mejores
formas de cuantificar y visualizar esta relación de compensación. La curva ROC que discutiremos a continuación
es una herramienta de este tipo que se usa con frecuencia.

3.2.3 La curva ROC: mostrando las compensaciones en la clasificación binaria

Las curvas ROC se utilizan en una amplia gama de problemas de ingeniería que involucran la
clasificación binaria o la detección de ciertos tipos de eventos. El nombre completo,Característica
Operativa del Receptor, es un término de la era temprana del radar. Hoy en día, casi nunca verá el
nombre ampliado. La Figura 3.6 es una curva ROC de muestra para nuestra aplicación.
Como habrá notado en las etiquetas de los ejes de la figura 3.6, las curvas ROC no se crean
exactamente al graficar las métricas de precisión y recuperación entre sí. En cambio, se basan en
dos métricas ligeramente diferentes. El eje horizontal de una curva ROC es untasa de falsos
positivos(FPR), definido como

FPR = #FP / (#FP + #TN)

curva ROC
1.0 Época
Época 001
0.9
Época 003
Época 005
0.8
Época 100
0.7 Época 200
Época 300
0.6 Época 400
TPR

0.5

0.4

0.3

0.2

0.1

0.0
0,0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 0,8 0,9 1,0
FPR

Figura 3.6 Un conjunto de ROC de muestra trazados durante el entrenamiento del modelo de
detección de phishing. Cada curva es para un número de época diferente. Las curvas muestran una
mejora gradual en la calidad del modelo de clasificación binaria a medida que avanza el
entrenamiento.
100 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

El eje vertical de una curva ROC es eltasa positiva verdadera(TPR), definido como

TPR = #TP / (#TP + #FN) = recuperar

TPR tiene exactamente la misma definición que recordar; es solo un nombre diferente para la
misma métrica. Sin embargo, FPR es algo nuevo. Su denominador es un recuento de todos los
casos en los que la clase real del ejemplo es negativa; su numerador es un recuento de todos los
casos de falsos positivos. En otras palabras, FPR es la proporción de ejemplos realmente negativos
que se clasifican erróneamente como positivos, que es la probabilidad de que ocurra algo
comúnmente conocido comofalsa alarma. La tabla 3.3 resume las métricas más comunes que
encontrará en un problema de clasificación binaria.

Tabla 3.3 Métricas comúnmente vistas para un problema de clasificación binaria

Cómo se usa en ROC o


Nombre de la métrica Definición curvas de precisión/recuperación

Exactitud (#TP + #TN) / (#TP + (No utilizado por los ROC)


#TN + #FP + #FN)

Precisión #TP / (#TP + #FP) El eje vertical de una curva de precisión/


recuperación

Recuperación/sensibilidad/tasa de #TP / (#TP + #FN) El eje vertical de una curva ROC (como en la figura
verdaderos positivos (TPR) 3.6), o el eje horizontal de una curva de precisión/
recuperación

Tasa de falsos positivos (FPR) #FP / (#FP + #TN) El eje horizontal de una curva ROC (ver
figura 3.6)

Área bajo la curva (AUC) Calculado mediante (No lo utilizan los ROC, sino que se calcula a
integración numérica bajo la partir de los ROC)
curva ROC; ver listado 3.7 para
un ejemplo

Las siete curvas ROC de la figura 3.6 se realizan al comienzo de siete épocas de entrenamiento
diferentes, desde la primera época (época 001) hasta la última (época 400). Cada uno de ellos se
crea en base a las predicciones del modelo sobre los datos de prueba (no los datos de
entrenamiento). El listado 3.6 muestra los detalles de cómo se hace esto con elonEpochBegin
devolución de llamada de laModelo.fit()API. Este enfoque le permite realizar análisis y
visualizaciones interesantes en el modelo en medio de una llamada de capacitación sin necesidad
de escribir un porbucle o uso múltipleModelo.fit()llamadas

Listado 3.6 Usando callback para renderizar curvas ROC en medio del entrenamiento del modelo

espera model.fit(trainData.data, trainData.target, {


tamaño del lote,
épocas,
división de validación: 0.2,
devoluciones de llamada: {

onEpochBegin: asíncrono (época) => {


if ((época + 1)% 100 === 0 ||
No linealidad en la salida: Modelos para la clasificación 101

época === 0 || época === 2 || época === 4) {


Dibuja ROC cada pocas épocas
const probs = model.predict(testData.data);
drawROC(testData.target, probs, epoch);
}
},
onEpochEnd: asíncrono (época, registros) => {
esperar ui.updateStatus(
`Época ${época + 1} de ${épocas} completadas.`);
trainLogs.push(registros);
ui.plotLosses(trenLogs);
ui.plotAccuracies(trenLogs);
}
}
});

El cuerpo de la función.dibujarROC()contiene los detalles de cómo se hace un ROC (ver


listado 3.7). Hace lo siguiente:
- Varía el umbral en la salida sigmoidea (probabilidades) de la red neuronal para obtener
diferentes conjuntos de resultados de clasificación
- Para cada resultado de clasificación, utilícelo junto con las etiquetas reales
(objetivos) para calcular el TPR y el FPR
- Traza los TPR contra los FPR para formar la curva ROC

Como muestra la figura 3.6, al inicio del entrenamiento (época 001), a medida que los pesos del
modelo se inicializan aleatoriamente, la curva ROC se acerca mucho a una línea diagonal que
conecta el punto (0, 0) con el punto (1, 1) . Así es como se ven las adivinanzas aleatorias. A medida
que avanza el entrenamiento, las curvas ROC se empujan cada vez más hacia la esquina superior
izquierda, un lugar donde el FPR está cerca de 0 y el TPR está cerca de 1. Si nos enfocamos en
cualquier nivel dado de FPR, tal como 0.1, vemos un aumento monótono en el valor TPR
correspondiente a medida que avanzamos en el entrenamiento. En pocas palabras, esto significa
que a medida que avanza el entrenamiento, podemos lograr un nivel de recuperación (TPR) cada
vez más alto si estamos anclados a un nivel fijo de falsa alarma (FPR).
La República de China "ideal" es una curva tan doblada hacia la esquina superior izquierda que se convierte
en una-8forma. En este escenario, puede obtener 100% TPR y 0% FPR, que es el "Santo Grial" para cualquier
clasificador binario. Sin embargo, con problemas reales, solo podemos mejorar el modelo para empujar la curva
ROC cada vez más cerca de la esquina superior izquierda; el ideal teórico en la esquina superior izquierda nunca
se puede lograr.
Con base en esta discusión sobre la forma de la curva ROC y sus implicaciones, podemos
ver que es posible cuantificar qué tan buena es una curva ROC observando el área debajo de
ella, es decir, cuánto del espacio en la unidad cuadrada está encerrado por la curva ROC y el
eje x. Esto se llama elárea bajo la curva(AUC) y también se calcula mediante el código del
listado 3.7. Esta métrica es mejor que la precisión, el recuerdo y la exactitud en el sentido de
que tiene en cuenta el equilibrio entre falsos positivos y falsos

La letra griega gamma.


8
102 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

negativos La ROC para adivinar al azar (la línea diagonal) tiene un AUC de 0.5, mientras que la-La ROC
ideal en forma de A tiene un AUC de 1,0. Nuestro modelo de detección de phishing alcanza un AUC de
0,981 después del entrenamiento.

Listado 3.7 El código para calcular y representar una curva ROC y el AUC

function dibujarROC(objetivos, problemas, época) {


volver tf.ordenado(() => {
umbrales constantes = [
manualmente
0,0, 0,05, 0,1, 0,15, 0,2, 0,25, 0,3, 0,35, 0,4, 0,45,
conjunto seleccionado de
0,5, 0,55, 0,6, 0,65, 0,7, 0,75, 0,8, 0,85, 0,9, 0,92, 0,94, 0,96,
probabilidad
0,98, 1,0
umbrales
];
constante tprs = []; // Tasas positivas verdaderas. //
constante fprs = []; Tasas de falsos positivos.
sea área = 0; TasaPositivafalsa()
for (sea i = 0; i < umbrales.longitud; ++i) { calcula el falso
const umbral = umbrales[i]; const tasa positiva por
umbralPredicciones = comparando el
Convierte el utils.binarize(problemas, umbral).as1D(); const fpr = predicciones y reales
tasaFalsoPositivo( objetivos Se define en el
probabilidad en
objetivos, mismo archivo.
predicciones
a través de umbralPredicciones).arraySync();
umbralización const tpr = tf.metrics.recall(targets, threshPredictions).arraySync(); fprs.push(fpr);

tprs.push(tpr);

si (yo > 0) {
area += (tprs[i] + tprs[i - 1]) * (fprs[i - 1] - fprs[i]) / 2;
}
}
ui.plotROC(fprs, tprs, época); Se acumula en el área
zona de retorno; para el cálculo del AUC

});
}

Además de visualizar las características de un clasificador binario, el ROC también nos ayuda a
tomar decisiones sensatas sobre cómo seleccionar el umbral de probabilidad en situaciones del
mundo real. Por ejemplo, imagina que somos una empresa comercial que desarrolla el detector de
phishing como un servicio. ¿Queremos hacer uno de los siguientes?

- Haga que el umbral sea relativamente bajo porque perder un sitio web de phishing real nos costará
mucho en términos de responsabilidad o pérdida de contratos.

-Haga que el umbral sea relativamente alto porque somos más reacios a las quejas
presentadas por usuarios cuyos sitios web normales están bloqueados porque el modelo
los clasifica como phishy.

Cada valor de umbral corresponde a un punto en la curva ROC. Cuando aumentamos el umbral
gradualmente de 0 a 1, nos movemos desde la esquina superior derecha de la gráfica (donde FPR y
TPR son ambos 1) a la esquina inferior izquierda de la gráfica (donde FPR y TPR son ambos 0). En
problemas reales de ingeniería, la decisión de qué punto seleccionar en el
No linealidad en la salida: Modelos para la clasificación 103

La curva ROC siempre se basa en sopesar costos reales opuestos de este tipo, y puede variar
para diferentes clientes y en diferentes etapas de desarrollo comercial.
Además de la curva ROC, otra visualización de clasificación binaria comúnmente utilizada es lacurva de
recuperación de precisión(a veces llamada curva P/R, mencionada brevemente en la tabla 3.3). A diferencia de la
curva ROC, una recuperación de precisión traza la precisión contra la recuperación. Dado que las curvas de
recuperación de precisión son conceptualmente similares a las curvas ROC, no profundizaremos en ellas aquí.

Una cosa que vale la pena señalar en el listado 3.7 es el uso detf.ordenado().Esta función
garantiza que los tensores creados dentro de la función anónima que se le pasa como argumentos
se eliminen correctamente, por lo que no seguirán ocupando la memoria de WebGL. En el
navegador, TensorFlow.js no puede administrar la memoria de los tensores creados por el usuario,
principalmente debido a la falta de finalización de objetos en JavaScript y la falta de recolección de
elementos no utilizados para las texturas WebGL que subyacen en los tensores de TensorFlow.js. Si
dichos tensores intermedios no se limpian correctamente, se producirá una fuga de memoria
WebGL. Si se permite que dichas fugas de memoria continúen durante el tiempo suficiente,
eventualmente resultarán en errores de falta de memoria de WebGL. La sección 1.3 del apéndice B
contiene un tutorial detallado sobre la administración de memoria en TensorFlow.js. También hay
ejercicios sobre este tema en la sección 1.5 del apéndice B. Si planea definir funciones
personalizadas al componer TensorFlow.

3.2.4 Entropía cruzada binaria: la función de pérdida para la clasificación binaria


Hasta ahora, hemos hablado sobre algunas métricas diferentes que cuantifican diferentes aspectos del
rendimiento de un clasificador binario, como la exactitud, la precisión y la recuperación (tabla 3.3). Pero
no hemos hablado de una métrica importante, una que es diferenciable y puede generar gradientes que
respaldan el entrenamiento de descenso de gradiente del modelo. Este es el binariaCrosentropíaque
vimos brevemente en el listado 3.5 y que aún no hemos explicado:

modelo.compilar({
optimizador: 'adam',
pérdida: 'binaryCrossentropy',
métricas: ['precisión']
});

En primer lugar, podría preguntarse, ¿por qué no podemos simplemente tomar la exactitud, la precisión,
la recuperación o quizás incluso el AUC y usarlo como la función de pérdida? Después de todo, estas
métricas son comprensibles. Además, en los problemas de regresión que vimos anteriormente, usamos
MSE, una métrica bastante comprensible, como función de pérdida para el entrenamiento directo. La
respuesta es que ninguna de estas métricas de clasificación binaria puede producir los gradientes que
necesitamos para el entrenamiento. Tome la métrica de precisión, por ejemplo: para ver por qué no es
compatible con gradientes, tenga en cuenta el hecho de que calcular la precisión requiere determinar
cuáles de las predicciones del modelo son positivas y cuáles son negativas (consulte la primera fila en la
tabla 3.3). Para ello, es necesario aplicar unfunción de umbral, que convierte la salida sigmoidea del
modelo en predicciones binarias. Aquí está el quid del problema: aunque la función de umbralización (o
función de pasoen términos más técnicos) es diferenciable casi
104 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

en todas partes (“casi” porque no es diferenciable en el “punto de salto” en 0.5), la derivada siempre
es exactamente cero (ver figura 3.7)! ¿Qué sucede si intenta hacer retropropagación a través de
esta función de umbral? Sus gradientes terminarán siendo todos ceros porque en algún momento,
los valores de gradiente aguas arriba deben multiplicarse con estos derivados todos cero de la
función de paso. En pocas palabras, si se elige la precisión (o precisión, recuperación, AUC, etc.)
como la pérdida, las secciones planas de la función de paso subyacente hacen imposible que el
procedimiento de entrenamiento sepa dónde moverse en el espacio de peso para disminuir. el
valor de la pérdida.

Diferenciable pero gradiente = 0


paso

1
Figura 3.7 La función de paso utilizada para
convertir la salida de probabilidad de un
modelo de clasificación binaria es
diferenciable en casi todas partes.
Desafortunadamente, el gradiente
0 0.5 1 (derivada) en cada punto diferenciable
pags es exactamente cero.

Por lo tanto, usar la precisión como función de pérdida no nos permite calcular gradientes útiles y,
por lo tanto, nos impide obtener actualizaciones significativas de los pesos del modelo. La misma
limitación se aplica a métricas que incluyen precisión, recuperación, FPR y AUC. Si bien estas
métricas son útiles para que los humanos comprendan el comportamiento de un clasificador
binario, son inútiles para el proceso de entrenamiento de estos modelos.
La función de pérdida que usamos para una tarea de clasificación binaria esentropía cruzada
binaria, que corresponde al 'binariaCrossentropía'configuración en nuestro código de modelo de
detección de phishing (listados 3.5 y 3.6). Algorítmicamente, podemos definir la entropía cruzada
binaria con el siguiente pseudocódigo.9

Listado 3.8 El pseudocódigo para la función de pérdida de entropía cruzada binaria9

función binaryCrossentropy(truthLabel, prob):


si etiquetaVerdad es 1:
volver -log(probable)
demás:
volver -log(1 - prob)

9
El código real parabinariaCrosentropíanecesita protegerse contra los casos en los queproblemao1 - problemaes
exactamente cero, lo que conduciría a infinito si el valor se pasa directamente a laIniciar sesiónfunción. Esto se hace
sumando un número positivo muy pequeño (como1e-6,comúnmente conocido como "épsilon" o un "factor fudge") para
problemay 1 - problemaantes de pasarlos a la función de registro.
No linealidad en la salida: Modelos para la clasificación 105

En este pseudocódigo,etiqueta de verdades un número que toma los valores 0-1 e indica si el
ejemplo de entrada tiene una etiqueta negativa (0) o positiva (1) en realidad.problemaes la
probabilidad de que el ejemplo pertenezca a la clase positiva, como predice el modelo. Tenga en
cuenta que a diferencia deetiqueta de verdad, problemaSe espera que sea un número real que
puede tomar cualquier valor entre 0 y 1.Iniciar sesiónes el logaritmo natural, conmi(2.718) como
base, que quizás recuerdes de las matemáticas de la escuela secundaria. el cuerpo del
binariaCrosentropíacontiene una bifurcación lógica if-else, que realiza diferentes cálculos
dependiendo de sietiqueta de verdades 0 o 1. La figura 3.8 representa los dos casos en el mismo
gráfico.
Al observar los gráficos de la figura 3.8, recuerde que los valores más bajos son mejores porque se
trata de una función de pérdida. Las cosas importantes a tener en cuenta sobre la función de pérdida son
las siguientes:

- Sietiqueta de verdades 1, un valor deproblemamás cerca de 1,0 conduce a un valor de función de


pérdida más bajo. Esto tiene sentido porque cuando el ejemplo es realmente positivo, queremos
que el modelo genere una probabilidad lo más cercana posible a 1.0. Y viceversa: si eletiqueta de
verdades 0, el valor de pérdida es menor cuando el valor de probabilidad está más cerca de 0. Esto
también tiene sentido porque, en ese caso, queremos que el modelo genere una probabilidad lo
más cercana posible a 0.
- A diferencia de la función de umbral binario que se muestra en la figura 3.7, estas curvas tienen pendientes

distintas de cero en cada punto, lo que conduce a gradientes distintos de cero. Esta es la razón por la que es

adecuado para el entrenamiento de modelos basado en retropropagación.

14
etiqueta de verdad = 1

etiqueta de verdad = 0

12

10
Entropía cruzada binaria

0
0 0.2 0.4 0.6 0.8 1
problema

Figura 3.8 La función de pérdida de entropía cruzada binaria. Los dos casos (
etiqueta de verdad = 1yetiqueta de verdad = 0) se grafican por separado,
reflejando la bifurcación lógica if-else del listado 3.8.
106 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

Una pregunta que podría hacerse es, ¿por qué no repetir lo que hicimos para el modelo de
regresión simplemente pretender que los valores 0-1 son objetivos de regresión y usar MSE como
la función de pérdida? Después de todo, MSE es diferenciable, y calcular el MSE entre la etiqueta de
verdad y la probabilidad produciría derivadas distintas de cero comoCruzentropía binaria. La
respuesta tiene que ver con el hecho de que MSE tiene "rendimientos decrecientes" en los límites.
Por ejemplo, en la tabla 3.4, enumeramos losbinariaCrosentropíay valores de pérdida MSE para un
número deproblemavalores cuandoetiqueta de verdades 1. Comoproblemase acerca a 1 (el valor
deseado), el MSE disminuye cada vez más lentamente en comparación conCruzentropía binaria.
Como resultado, no es tan bueno para “animar” al modelo a producir una mayor (más cercana a 1)
problemavalor cuandoproblemaya está bastante cerca de 1 (por ejemplo, 0,9). Asimismo, cuando
etiqueta de verdades 0, MSE no es tan bueno comobinariaCrosentropíaen la generación de
gradientes que empujan el modeloproblemasalida hacia 0 tampoco.
Esto muestra otro aspecto en el que los problemas de clasificación binaria son diferentes de los
problemas de regresión: para un problema de clasificación binaria, la pérdida (crossentropía binaria)
y las métricas (exactitud, precisión, etc.) son diferentes, mientras que normalmente son las mismas
para un problema de regresión (por ejemplo,error medio cuadrado).Como veremos en la siguiente
sección, los problemas de clasificación multiclase también involucran diferentes funciones de
pérdida y métricas.

Tabla 3.4 Comparación de valores de entropía cruzada binaria y MSE para


resultados hipotéticos de clasificación binaria

cruz binaria
etiqueta de verdad problema entropía MSE

1 0.1 2.302 0.81

1 0.5 0.693 0.25

1 0.9 0.100 0.01

1 0.99 0.010 0.0001

1 0.999 0.001 0.000001

1 1 0 0

3.3 Clasificación multiclase


En la sección 3.2, exploramos cómo estructurar un problema de clasificación binaria; ahora haremos un
breve aparte sobre cómo manejarclasificación no binaria—es decir, tareas de clasificación que involucran
tres o más clases.10El conjunto de datos que usaremos para ilustrar la clasificación multiclase es

10 Es importante no confundirmulticlaseclasificación conmultietiquetaclasificación. En la clasificación multilabel, un


ejemplo de entrada individual puede corresponder a múltiples clases de salida. Un ejemplo es detectar la presencia de varios tipos
de objetos en una imagen de entrada. Una imagen puede incluir solo una persona; otra imagen puede incluir una persona, un
automóvil y un animal. Se requiere un clasificador de etiquetas múltiples para generar una salida que represente todas las clases
que son aplicables al ejemplo de entrada, sin importar si hay una o más de una de esas clases. Esta sección no se ocupa de la
clasificación multietiqueta. En su lugar, nos centramos en la clasificación multiclase de una sola etiqueta más simple, en la que
cada ejemplo de entrada corresponde exactamente a una clase de salida entre > 2 clases posibles.
Clasificación multiclase 107

losconjunto de datos de flor de iris, un famoso conjunto de datos con su origen en el campo de la
estadística (ver https://en.wikipedia.org/wiki/Iris_flower_data_set). Este conjunto de datos se centra
en tres especies de la flor del iris, llamadasiris setosa,iris versicolor, yiris virgen. Estas tres especies
se pueden distinguir entre sí en función de sus formas y tamaños. A principios del siglo XX, Ronald
Fisher, un estadístico británico, midió la longitud y el ancho de los pétalos y sépalos (diferentes
partes de la flor) de 150 muestras de iris. Este conjunto de datos está equilibrado: hay exactamente
50 muestras para cada etiqueta objetivo.
En este problema, nuestro modelo toma como entrada cuatro características numéricas (longitud del
pétalo, ancho del pétalo, longitud del sépalo y ancho del sépalo) e intenta predecir una etiqueta objetivo
(una de las tres especies). El ejemplo está disponible en la carpeta iris de tfjs-examples, que puede
consultar y ejecutar con estos comandos:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-ejemplos/iris


discos compactos

hilo y reloj de hilo

3.3.1 Codificación one-hot de datos categóricos


Antes de estudiar el modelo que resuelve el problema de clasificación del iris, debemos resaltar la
forma en que se representa el objetivo categórico (especies) en esta tarea de clasificación
multiclasificación. Todos los ejemplos de aprendizaje automático que hemos visto en este libro
hasta ahora involucran una representación más simple de objetivos, como el número único en el
problema de predicción del tiempo de descarga y el problema de la vivienda de Boston, así como la
representación 0-1 de binario objetivos en el problema de detección de phishing. Sin embargo, en
el problema del iris, las tres especies de flores están representadas de una manera un poco menos
familiar llamadacodificación one-hot. Abra data.js y notará esta línea:

const ys = tf.oneHot(tf.tensor1d(shuffledTargets).toInt(), IRIS_NUM_CLASSES);

Aquí,Objetivos barajadoses una matriz simple de JavaScript que consiste en las etiquetas de
números enteros para los ejemplos en un orden aleatorio. Todos sus elementos tienen valores 0, 1
y 2, lo que refleja las tres especies de iris en el conjunto de datos. Se convierte en un tensor 1D de
tipo int32 a través de la tf.tensor1d(objetivos barajados).toInt()llamada. El tensor 1D resultante se
pasa luego altf.oneHot()función, que devuelve un tensor 2D de la forma [numEjemplos,
IRIS_NUM_CLASES]. numEjemploses el número de ejemplos queobjetivoscontiene,
yIRIS_NUM_CLASESes simplemente la constante 3. Puede examinar los valores reales de objetivosy
síagregando algunas líneas de impresión justo debajo de la línea citada anteriormente, es decir,
algo así como

const ys = tf.oneHot(tf.tensor1d(shuffledTargets).toInt(), IRIS_NUM_CLASSES); // Se agregaron líneas para


imprimir los valores de `targets` y ỳs`. console.log('Valor de objetivos:', objetivos);

ys.print();11

11a diferencia deobjetivo, síno es una matriz de JavaScript simple. En cambio, es un objeto tensor respaldado por memoria GPU. Ahí-
por lo tanto, el archivo console.log normal no mostrará su valor. losimprimir()El método es específicamente para recuperar los valores de la
GPU, formatearlos de una manera que tenga en cuenta la forma y sea amigable para los humanos, y registrarlos en la consola.
108 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

Una vez que haya realizado estos cambios, el proceso de empaquetado de paquetes iniciado por
YarnrelojEl comando en su terminal reconstruirá automáticamente los archivos web. Luego, puede
abrir la herramienta de desarrollo en la pestaña del navegador que se usa para ver esta
demostración y actualizar la página. Los mensajes impresos de laconsola.log()yimprimir()las
llamadas se registrarán en la consola de la herramienta de desarrollo. Los mensajes impresos que
verá se verán así:

Valor de los objetivos: (50) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,


0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Tensor
[[1, 0, 0],
[1, 0, 0],
[1, 0, 0],
...,
[1, 0, 0],
[1, 0, 0],
[1, 0, 0]]

o
Valor de los objetivos: (50) [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1]

Tensor
[[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
...,
[0, 1, 0],
[0, 1, 0],
[0, 1, 0]]

Etcétera. Para describir esto en palabras, para un ejemplo con la etiqueta de número entero 0,
obtienes una fila de valores [1, 0, 0];para un ejemplo con la etiqueta entera 1, obtienes una fila de
valores [0, 1, 0],Etcétera. Este es un ejemplo simple y claro de codificación one-hot: convierte una
etiqueta entera en un vector que consta de valores cero excepto en el índice que corresponde a la
etiqueta, donde el valor es 1. La longitud del vector es igual a la número de todas las categorías
posibles. El hecho de que haya un solo valor 1 en el vector es precisamente la razón por la cual este
esquema de codificación se llama "one-hot".
Esta codificación puede parecerle innecesariamente complicada. ¿Por qué usar tres números
para representar una categoría cuando un solo número podría hacer el trabajo? ¿Por qué elegimos
esto en lugar de la codificación de índice de entero único más simple y económica? Esto puede
entenderse desde dos ángulos diferentes.
En primer lugar, es mucho más fácil para una red neuronal generar un valor continuo de tipo
flotante que uno entero. Tampoco es elegante aplicar el redondeo en la salida de tipo flotante. Un
enfoque mucho más elegante y natural es que la última capa de la red neuronal genere unos pocos
números de tipo flotante separados, cada uno restringido a estar en el [0, 1]intervalo a través de
una función de activación cuidadosamente elegida similar a la activación sigmoidea
Clasificación multiclase 109

función que usamos para la clasificación binaria. En este enfoque, cada número es la estimación del
modelo de la probabilidad de que el ejemplo de entrada pertenezca a la clase correspondiente. Esto es
exactamente para lo que sirve la codificación one-hot: es la "respuesta correcta" para los puntajes de
probabilidad, que el modelo debe tratar de ajustar a través de su proceso de entrenamiento.
En segundo lugar, al codificar una categoría como un número entero, implícitamente creamos
un orden entre las clases. Por ejemplo, podemos etiquetariris setosacomo 0,iris versicolorcomo 1, y
iris virgencomo 2. Pero los esquemas de orden como este son a menudo artificiales e injustificados.
Por ejemplo, este esquema de numeración implica quesetosaestá "más cerca" deversicolorque a
virginica, lo que puede no ser cierto. Las redes neuronales operan con números reales y se basan
en operaciones matemáticas como la multiplicación y la suma. Por lo tanto, son sensibles a la
magnitud de los números y su orden. Si las categorías se codifican como un solo número, se
convierte en una relación adicional no lineal que la red neuronal debe aprender. Por el contrario,
las categorías codificadas en caliente no implican ningún orden implícito y, por lo tanto, no gravan
la capacidad de aprendizaje de una red neuronal de esta manera.
Como veremos en el capítulo 9, la codificación one-hot no solo se usa para los objetivos de salida de las redes
neuronales, sino que también se aplica cuando los datos categóricos forman las entradas de las redes
neuronales.

3.3.2 Activación Softmax


Con una comprensión de cómo se representan las características de entrada y el destino de salida,
ahora estamos listos para ver el código que define nuestro modelo (de iris/index.js).

Listado 3.9 La red neuronal multicapa para la clasificación de flores de iris

const modelo = tf.secuencial();


modelo.add(tf.layers.dense(
{unidades: 10, activación: 'sigmoide', forma de entrada: [xTrain.shape[1]]}));
model.add(tf.layers.dense({unidades: 3, activación: 'softmax'})); Resumen Modelo();

optimizador const = tf.train.adam(params.learningRate);


modelo.compilar({
optimizador: optimizador,
pérdida: 'categoricalCrossentropy',
métricas: ['precisión'],
});

El modelo definido en el listado 3.9 conduce al siguiente resumen:

_________________________________________________________________ Capa (tipo)


Forma de salida Parámetro #
================================================== ===============
dense_Dense1 (Denso) [nulo, 10] 50
________________________________________________________________ denso_Dense2 (Denso)
[nulo,3] 33
================================================== ===============
Parámetros totales: 83
Parámetros entrenables: 83
Parámetros no entrenables:
________________________________________________________________
110 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

Como se puede ver en el resumen impreso, este es un modelo bastante simple con un número
relativamente pequeño (83) de parámetros de peso. La forma de salida [nulo, 3]corresponde a la
codificación one-hot del destino categórico. La activación utilizada para la última capa, a saber
softmax, está diseñado específicamente para el problema de clasificación multiclase. La definición
matemática de softmax se puede escribir como el siguiente pseudocódigo:

softmax([x1, x2, …, xn]) =


[exp(x1) / (exp(x1) + exp(x2) + … + exp(xn)),
exp(x2) / (exp(x1) + exp(x2) + … + exp(xn)), …,

exp(xn) / (exp(x1) + exp(x2) + … + exp(xn))]

A diferencia de la función de activación sigmoidea que hemos visto, la función de activación


softmax no es elemento por elemento porque cada elemento del vector de entrada se transforma
de una manera que depende de todos los demás elementos. Específicamente, cada elemento de la
entrada se convierte a su exponencial natural (laExpfunción, conmi=2.718 como base). Luego, el
exponencial se divide por la suma de los exponenciales de todos los elementos. ¿Qué hace esto?
Primero, asegura que cada número esté en el intervalo entre 0 y 1. Segundo, está garantizado que
todos los elementos del vector de salida suman 1. Esta es una propiedad deseable porque 1) las
salidas pueden interpretarse como puntajes de probabilidad asignados a las clases, y 2) para ser
compatibles con la función categórica de pérdida de entropía cruzada, las salidas deben satisfacer
esta propiedad. Tercero, la definición asegura que un elemento más grande en el vector de entrada
se mapea a un elemento más grande en el vector de salida. Para dar un ejemplo concreto, suponga
que la multiplicación de matrices y la suma de sesgos en la última capa densa produce un vector de

[-3, 0, -8]

Su longitud es 3 porque la capa densa está configurada para tener 3 unidades. Tenga en cuenta
que los elementos son números flotantes sin restricciones a ningún rango en particular. La
activación softmax convertirá el vector en

[0.0474107, 0.9522698, 0.0003195]

Puede verificar esto usted mismo ejecutando el siguiente código TensorFlow.js (por ejemplo,
en la consola de devtool cuando la página apunta a js.tensorflow.org):

const x = tf.tensor1d([-3, 0, -8]);


tf.softmax(x).print();

Los tres elementos de la salida de la función softmax 1) están todos en el [0, 1]intervalo, 2) suma a 1
y 3) se ordenan de manera que coincida con el orden en el vector de entrada. Como resultado de
estas propiedades, la salida puede interpretarse como los valores de probabilidad asignados (por
el modelo) a todas las clases posibles. En el fragmento de código anterior, a la segunda categoría
se le asigna la probabilidad más alta, mientras que a la primera se le asigna la más baja.

Como consecuencia, al usar una salida de un clasificador multiclase de este tipo,


puede elegir el índice del elemento softmax más alto como decisión final, es decir, un
Clasificación multiclase 111

decisión sobre a qué clase pertenece la entrada. Esto se puede lograr usando el método
argMax().Por ejemplo, este es un extracto de index.js:

const predecirOut = modelo.predecir(entrada);


const ganador = datos.IRIS_CLASSES[predictOut.argMax(-1).dataSync()[0]];

predecires un tensor 2D de forma [numEjemplos, 3].llamando a suargMax0El método


hace que la forma se reduzca a [numEjemplo].El valor del argumento –1 indica que
argMax()debe buscar valores máximos a lo largo de la última dimensión y devolver sus
índices. Por ejemplo, supongamospredecirtiene el siguiente valor:
[[0 , 0.6, 0.4],
[0.8, 0, 0.2]]

Luego,argMax(-1)devolverá un tensor que indica que los valores máximos a lo largo de la última
(segunda) dimensión se encuentran en los índices 1 y 0 para el primer y segundo ejemplo,
respectivamente:

[1, 0]

3.3.3 Entropía cruzada categórica: la función de pérdida


para la clasificación multiclase
En el ejemplo de clasificación binaria, vimos cómo se usaba la entropía cruzada binaria como
función de pérdida y por qué otras métricas más interpretables por humanos, como la precisión y
la recuperación, no se podían usar como función de pérdida. La situación para la clasificación
multiclase es bastante análoga. Existe una métrica sencilla, la precisión, que es la fracción de
ejemplos que el modelo clasifica correctamente. Esta métrica es importante para que los humanos
entiendan qué tan bien está funcionando el modelo y se usa en este fragmento de código en el
listado 3.9:

modelo.compilar({
optimizador: optimizador,
pérdida: 'categoricalCrossentropy',
métricas: ['precisión'],
});

Sin embargo, la precisión es una mala elección para la función de pérdida porque sufre el mismo
problema de gradiente cero que la precisión en la clasificación binaria. Por lo tanto, la gente ha
ideado una función de pérdida especial para la clasificación multiclase:entropía cruzada categórica.
Es simplemente una generalización de la entropía cruzada binaria en los casos donde hay más de
dos categorías.

Listado 3.10 Pseudocódigo para pérdida de entropía cruzada categórica

función categoricalCrossentropy(oneHotTruth, probs):


for i in (0 hasta la longitud de oneHotTruth)
si oneHotTruth(i) es igual a 1
return -log(problemas[i]);
112 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

En el pseudocódigo del listado anterior,unoHotTruthes la codificación one-hot de la clase real


del ejemplo de entrada.problemases la salida de probabilidad softmax del modelo. La
conclusión clave de este pseudocódigo es que, en lo que respecta a la entropía cruzada
categórica, solo un elemento deproblemasimporta, y ese es el elemento cuyos índices
corresponden a la clase real. Los otros elementos deproblemaspueden variar todo lo que
quieran, pero mientras no cambien el elemento de la clase real, no afectará la entropía
cruzada categórica. Para ese elemento particular deproblemas,cuanto más se acerque a 1,
menor será el valor de la entropía cruzada. Al igual que la entropía cruzada binaria, la
entropía cruzada categórica está directamente disponible como una función bajo eltf.métricas
espacio de nombres, y puede usarlo para calcular la entropía cruzada categórica de ejemplos
simples pero ilustrativos. Por ejemplo, con el siguiente código, puede crear una etiqueta de
verdad hipotética codificada en caliente y una hipotéticaproblemasvector y calcule el valor de
entropía cruzada categórica correspondiente:

const 1, 0]); const probs =


tf.tensor1d([0.2, 0.5, 0.3]);
tf.metrics.categoricalCrossentropy(oneHotTruth, problemas).print();

Esto le da una respuesta de aproximadamente 0,693. Esto significa que cuando la


probabilidad asignada por el modelo a la clase real es 0.5,Cruzentropía categóricatiene un
valor de 0.693. Puede verificarlo contra el pseudocódigo en el listado 3.10. También puede
intentar subir o bajar el valor de 0,5 para ver cómoCruzentropía categórica cambios (por
ejemplo, véase el cuadro 3.5). La tabla también incluye una columna que muestra el MSE
entre la etiqueta de verdad one-hot y laproblemasvector.

Tabla 3.5 Los valores de la entropía cruzada categórica bajo diferentes salidas de
probabilidad. Sin pérdida de generalidad, todos los ejemplos (fila) se basan en un
caso en el que hay tres clases (como es el caso del conjunto de datos de la flor del
iris), y la clase real es la segunda.

uno-caliente problemas Categórico


etiqueta de verdad (salida softmax) entropía cruzada MSE

[0, 1, 0] [0,2, 0,5, 0,3] 0.693 0.127

[0, 1, 0] [0,0, 0,5, 0,5] 0.693 0.167

[0, 1, 0] [0,0, 0,9, 0,1] 0.105 0.006

[0, 1, 0] [0,1, 0,9, 0,0] 0.105 0.006

[0, 1, 0] [0,0, 0,99, 0,01] 0.010 0.00006

Al comparar las filas 1 y 2 o comparar las filas 3 y 4 en esta tabla, debe quedar claro que
cambiar los elementos deproblemasque no corresponden a la clase real no altera la entropía
cruzada binaria, aunque puede alterar el MSE entre la etiqueta de verdad única yproblemas
Además, como en la entropía cruzada binaria, MSE muestra un rendimiento disminuido
cuando elproblemasvalor para la clase real se aproxima a 1, y por lo tanto no es bueno en
Clasificación multiclase 113

fomentando que el valor de probabilidad de la clase correcta suba como entropía categórica en este
régimen. Estas son las razones por las que la entropía cruzada categórica es más adecuada como función
de pérdida que la MSE para problemas de clasificación multiclase.

3.3.4 Matriz de confusión: análisis detallado de


la clasificación multiclase
Al hacer clic en el botón Entrenar modelo desde cero en la página web del ejemplo, puede obtener un
modelo entrenado en unos segundos. Como muestra la figura 3.9, el modelo alcanza una precisión casi
perfecta después de 40 épocas de entrenamiento. Esto refleja el hecho de que el conjunto de datos del iris
es pequeño con límites relativamente bien definidos entre las clases en el espacio de características.

La parte inferior de la figura 3.9 muestra una forma adicional de caracterizar el comportamiento
de un clasificador multiclase, llamadomatriz de confusión. Una matriz de confusión desglosa los
resultados de un clasificador multiclase según sus clases reales y las clases predichas del modelo.
Es una matriz cuadrada de forma [numClasses, numClasses].El elemento en los índices [yo, j] (fila i y
columna j) es el número de ejemplos que pertenecen a la claseIy se predicen como clasejpor el
modelo Por tanto, los elementos diagonales de una matriz de confusión corresponden a ejemplos
correctamente clasificados. Un clasificador multiclase perfecto debería producir una matriz de
confusión sin elementos distintos de cero fuera de la diagonal. Este es exactamente el caso de la
matriz de confusión de la figura 3.9.
Además de mostrar la matriz de confusión final, el ejemplo del iris también dibuja la matriz de
confusión al final de cada época de entrenamiento, usando elenTrainEnd()llamar de vuelta.

Pérdida Exactitud
Serie 1.000000
pérdida
1.0 0.960317
Serie
1.0 val_loss 0.8 cuenta

val_acc
Valor

Valor

0.6
0.5 0.4
0.2
0.0 0.0
0 5 10 15 20 25 30 35 40 0 5 10 15 20 25 30 35 40
Iteración Iteración

Matriz de confusión (en conjunto de validación)

Contar
Iris-setosa 8 0 0
8
Etiqueta

Iris-versicolor 0 8 0
Iris virginica 0 0 8
0 Figura 3.9 Un resultado típico del entrenamiento
Iris-setosa

Iris virginica
Iris-versicolor

el modelo iris durante 40 épocas. Arriba a la izquierda: la


función de pérdida graficada contra épocas de
entrenamiento. Arriba a la derecha: la precisión trazada
contra épocas de entrenamiento. Abajo: la matriz de
Predicción confusión.
114 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

Matriz de confusión (en conjunto de validación)

Contar
Iris-setosa 8 0 0
8

Etiqueta
Iris-versicolor 0 3 5
Iris virginica 0 0 8
0 Figura 3.10 Un ejemplo de un

Iris-setosa

Iris virginica
Iris-versicolor
matriz de confusión "imperfecta", en la que hay
elementos distintos de cero fuera de la diagonal.
Esta matriz de confusión se genera después de solo
2 épocas, antes de que el entrenamiento
Predicción convergiera.

En épocas tempranas, es posible que vea una matriz de confusión menos perfecta que la de la
figura 3.9. La matriz de confusión de la figura 3.10 muestra que 8 de los 24 ejemplos de entrada
estaban mal clasificados, lo que corresponde a una precisión del 66,7 %. Sin embargo, la matriz de
confusión nos dice más que un solo número: muestra qué clases involucran la mayor cantidad de
errores y cuáles involucran menos. En este ejemplo particular, todas las flores de la segunda clase
están mal clasificadas (ya sea como primera o tercera clase), mientras que las flores de la primera y
tercera clase siempre se clasifican correctamente. Por lo tanto, puede ver que en la clasificación
multiclase, una matriz de confusión es una medida más informativa que simplemente la precisión,
al igual que la precisión y la recuperación juntas forman una medida más completa que la precisión
en la clasificación binaria. Las matrices de confusión pueden proporcionar información que ayude a
la toma de decisiones relacionadas con el modelo y el proceso de formación. Por ejemplo, cometer
algunos tipos de errores puede resultar más costoso que confundir otros pares de clases. Tal vez
confundir un sitio de deportes con un sitio de juegos sea un problema menor que confundir un
sitio de deportes con una estafa de phishing. En esos casos, puede ajustar los hiperparámetros del
modelo para minimizar los errores más costosos.
Todos los modelos que hemos visto hasta ahora toman una matriz de números como entradas. En
otras palabras, cada ejemplo de entrada se representa como una lista simple de números, cuya longitud
es fija, y el orden de los elementos no importa siempre que sean consistentes para todos los ejemplos
que se alimentan al modelo. Si bien este tipo de modelo cubre un gran subconjunto de problemas
importantes y prácticos de aprendizaje automático, está lejos de ser el único tipo. En los próximos
capítulos, veremos tipos de datos de entrada más complejos, incluidas imágenes y secuencias. En el
capítulo 4, comenzaremos con imágenes, un tipo de datos de entrada ubicuo y ampliamente útil para el
cual se han desarrollado poderosas estructuras de redes neuronales para llevar la precisión de los
modelos de aprendizaje automático a niveles sobrehumanos.

Ejercicios
1 Al crear redes neuronales para el problema de la vivienda en Boston, nos detuvimos en un
modelo con dos capas ocultas. Dado lo que dijimos sobre las funciones no lineales en
cascada que conducen a una mayor capacidad de los modelos, ¿agregar más capas ocultas
al modelo conducirá a una mejor precisión de la evaluación? Pruebe esto modificando
index.js y volviendo a ejecutar el entrenamiento y la evaluación.
Resumen 115

a ¿Cuál es el factor que impide que más capas ocultas mejoren la precisión de la
evaluación?
B ¿Qué te hace llegar a esta conclusión? (Sugerencia: mire el error en el conjunto de
entrenamiento).
2 Mira cómo el código del listado 3.6 usa elonEpochBegindevolución de llamada para calcular y
dibujar una curva ROC al comienzo de cada época de entrenamiento. ¿Puede seguir este patrón y
hacer algunas modificaciones en el cuerpo de la función de devolución de llamada para que
pueda imprimir los valores de precisión y recuperación (calculados en el conjunto de prueba) al
comienzo de cada época? Describa cómo cambian estos valores a medida que avanza el
entrenamiento.
3 Estudie el código del listado 3.7 y comprenda cómo calcula la curva ROC. ¿Puedes seguir
este ejemplo y escribir una nueva función, llamadadibujarPrecisión-RecuperarCurva(),que,
como su nombre lo indica, calcula y genera una curva de recuperación de precisión? Una
vez que haya terminado de escribir la función, llámela desde elonEpoch-Begindevolución de
llamada para que se pueda trazar una curva de recuperación de precisión junto con la curva
ROC al comienzo de cada época de entrenamiento. Es posible que deba realizar algunos
cambios o adiciones a ui.js.
4 Suponga que le dicen el FPR y el TPR de los resultados de un clasificador binario.
Con esos dos números, ¿es posible que calcule la precisión general? Si no es así,
¿qué información adicional necesita?
5 Las definiciones de entropía cruzada binaria (sección 3.2.4) y entropía cruzada categórica
(sección 3.3.3) se basan ambas en el logaritmo natural (el logaritmo de base mi). ¿Y si
cambiamos la definición para que usen el logaritmo de base 10? ¿Cómo afectaría eso al
entrenamiento y la inferencia de clasificadores binarios y multiclase? Convierta el
6 pseudocódigo para la búsqueda de cuadrícula de hiperparámetros en el listado 3.4 en
código JavaScript real y use el código para realizar la optimización de hiperparámetros para
el modelo de vivienda de Boston de dos capas en el listado 3.1. Específicamente, ajuste el
número de unidades de la capa oculta y la tasa de aprendizaje. Siéntase libre de decidir
sobre los rangos de unidades y la tasa de aprendizaje para buscar. Tenga en cuenta que los
ingenieros de aprendizaje automático generalmente usan secuencias aproximadamente
geométricas (es decir, logarítmicas) para estas búsquedas (por ejemplo, unidades = 2, 5, 10,
20, 50, 100, 200, . . .).

Resumen
- Las tareas de clasificación son diferentes de las tareas de regresión en que implican hacer
predicciones discretas.
- Hay dos tipos de clasificación: binaria y multiclase. En la clasificación binaria, hay
dos clases posibles para una entrada dada, mientras que en la clasificación
multiclase, hay tres o más.
- La clasificación binaria generalmente puede verse como la detección de cierto tipo de evento u
objeto de importancia, llamados positivos, entre todos los ejemplos de entrada. Cuándo
116 CPASADO3Agregar no linealidad: más allá de las sumas ponderadas

Visto de esta manera, podemos usar métricas como precisión, recuperación y FPR, además de la
exactitud, para cuantificar varios aspectos del comportamiento de un clasificador binario. La
- compensación entre la necesidad de capturar todos los ejemplos positivos y la necesidad de
minimizar los falsos positivos (falsas alarmas) es común en las tareas de clasificación binaria. La
curva ROC, junto con la métrica AUC asociada, es una técnica que nos ayuda a cuantificar y
visualizar esta relación.
- Una red neuronal creada para la clasificación binaria debe usar la activación sigmoidea en su
última capa (de salida) y usar la entropía cruzada binaria como función de pérdida durante el
entrenamiento.
- Para crear una red neuronal para la clasificación multiclase, el objetivo de salida generalmente se
representa mediante una codificación one-hot. La red neuronal debe usar la activación softmax
en su capa de salida y entrenarse usando la función de pérdida de entropía cruzada categórica.

- Para la clasificación multiclase, las matrices de confusión pueden proporcionar información


más detallada sobre los errores cometidos por el modelo que la precisión.
-La Tabla 3.6 resume las metodologías recomendadas para los tipos más comunes de
problemas de aprendizaje automático que hemos visto hasta ahora (regresión,
clasificación binaria y clasificación multiclase).
-Los hiperparámetros son configuraciones relacionadas con la estructura de un modelo de

aprendizaje automático, las propiedades de su capa y su proceso de entrenamiento. Son distintos


de los parámetros de peso del modelo en que 1) no cambian durante el proceso de
entrenamiento del modelo y 2) a menudo son discretos. La optimización de hiperparámetros es el
proceso en el que se buscan los valores de los hiperparámetros para minimizar una pérdida en el
conjunto de datos de validación. La optimización de hiperparámetros sigue siendo un área activa
de investigación. Actualmente, los métodos más utilizados incluyen la búsqueda en cuadrícula, la
búsqueda aleatoria y los métodos bayesianos.

Tabla 3.6 Una descripción general de los tipos más comunes de tareas de aprendizaje automático, su función de activación de última capa
adecuada y su función de pérdida, así como las métricas que ayudan a cuantificar la calidad del modelo

Activación Métricas adecuadas


de salida apoyado durante Adicional
tipo de tarea capa Función de pérdida Llamadas a Model.fit() métrica

Regresión 'lineal' 'error medio cuadrado'o (igual que pérdida)


(por defecto) 'meanAbsoluteError'

Binario 'sigmoideo' 'binaryCrosentropy' 'exactitud' Precisión, recuerdo,


clasificación recuperación de precisión

curva, curva ROC,


ABC

etiqueta única, 'softmax' 'Crosentropía categórica' 'exactitud' Matriz de confusión


multiclase
clasificación
Reconocimiento de imágenes
y sonidos usando convnets

Este capítulo cubre


- Cómo las imágenes y otros datos de percepción, como el audio, se
representan como tensores multidimensionales

- Qué son las convnets, cómo funcionan y por qué son especialmente
adecuadas para tareas de aprendizaje automático que involucran
imágenes

- Cómo escribir y entrenar un convnet en TensorFlow.js para


resolver la tarea de clasificar dígitos escritos a mano
- Cómo entrenar modelos en Node.js para lograr velocidades de entrenamiento más

rápidas

- Cómo usar convnets en datos de audio para el reconocimiento de


palabras habladas

La revolución del aprendizaje profundo en curso comenzó con avances en las tareas de
reconocimiento de imágenes, como la competencia ImageNet. Existe una amplia gama de
problemas útiles y técnicamente interesantes que involucran imágenes, desde reconocer el
contenido de las imágenes hasta segmentar imágenes en partes significativas, y desde

117
118 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

desde localizar objetos en imágenes hasta sintetizar imágenes. Esta subárea del aprendizaje automático a
veces se denominavisión por computador.1Las técnicas de visión por computadora a menudo se
trasplantan a áreas que no tienen nada que ver con la visión o las imágenes (como el procesamiento del
lenguaje natural), que es una razón más por la que es importante estudiar el aprendizaje profundo para
la visión por computadora.2Pero antes de profundizar en los problemas de visión por computadora,
debemos analizar las formas en que se representan las imágenes en el aprendizaje profundo.

4.1 De vectores a tensores: representación de imágenes


En los dos capítulos anteriores, analizamos las tareas de aprendizaje automático que involucran entradas
numéricas. Por ejemplo, el problema de predicción de duración de descarga del capítulo 2 tomó un solo
número (tamaño de archivo) como entrada. La entrada en el problema de la vivienda de Boston fue una
serie de 12 números (número de habitaciones, tasa de criminalidad, etc.). Lo que estos problemas tienen
en común es el hecho de que cada ejemplo de entrada se puede representar como una matriz plana (no
anidada) de números, que corresponde a un tensor 1D en TensorFlow.js. Las imágenes se representan de
manera diferente en el aprendizaje profundo.
Para representar una imagen, usamos un tensor 3D. Las dos primeras dimensiones del tensor
son las dimensiones familiares de alto y ancho. El tercero es el canal de color. Por ejemplo, el color
a menudo se codifica como valores RGB. En este caso, cada uno de los tres colores es un canal, lo
que conduce a un tamaño de 3 a lo largo de la tercera dimensión. Si tenemos una imagen en color
codificada en RGB de tamaño 224 × 224 píxeles, podemos representarla como un tensor 3D de
tamaño [224, 224, 3].Las imágenes en algunos problemas de visión por computadora no tienen
color (por ejemplo, escala de grises). En esos casos, solo hay un canal que, si se representa como
un tensor 3D, conducirá a una forma de tensor de [alto, ancho, 1] (ver figura 4.1 para un ejemplo).3

Este modo de codificar una imagen se conoce comoaltura-ancho-canal (HWC). Para realizar un
aprendizaje profundo en imágenes, a menudo combinamos un conjunto de imágenes en un lote
para un cálculo en paralelo eficiente. Cuando se agrupan imágenes por lotes, la dimensión de las
imágenes individuales es siempre la primera dimensión. Esto es similar a cómo combinamos
tensores 1D en un tensor 2D por lotes en los capítulos 2 y 3. Por lo tanto, un lote de imágenes es un
tensor 4D, con cuatro dimensiones: número de imagen (N), altura (H), ancho (W ), y canal de color
(C), respectivamente. Este formato se conoce comoNHWC. Hay un formato alternativo, resultado de
una ordenación diferente de las cuatro dimensiones. Se llama NCHW. Como sugiere su nombre,
NCHW pone la dimensión del canal por delante de las dimensiones de alto y ancho. TensorFlow.js
puede manejar los formatos NHWC y NCHW. Pero solo usaremos el formato NHWC
predeterminado en este libro, por coherencia.

1
Tenga en cuenta que la visión por computadora es en sí misma un campo amplio, algunas partes del cual utilizan técnicas que no son de aprendizaje
automático más allá del alcance de este libro.
2
Los lectores que estén especialmente interesados en el aprendizaje profundo en visión artificial y quieran profundizar en el tema pueden
consultar el libro de Mohamed Elgendy,Aprendizaje profundo de Grokking para visión artificial, Publicaciones de Manning, en prensa.

3
Una alternativa es "aplanar" todos los píxeles de la imagen y sus valores de color asociados en un tensor 1D (una
matriz plana de números). Pero hacerlo dificulta la explotación de la asociación entre los canales de color de cada
píxel y las relaciones espaciales 2D entre píxeles.
De vectores a tensores: representación de imágenes 119

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

0.00 0.00 0.10 0.24 0.40 0.40 0.32 0.00

0.00 0.00 0,46 0.88 0,60 0.27 0.08 0.00

0.00 0.00 0.00 0,45 0.26 0.00 0.00 0.00

0.00 0.00 0.00 0.01 0.54 0.44 0.00 0.00

0.00 0.00 0.00 0.30 0.72 0.54 0.00 0.00

0.00 0.26 0,68 0.71 0.25 0.00 0.00 0.00

0.00 0.13 0.15 0.00 0.00 0.00 0.00 0.00

Figura 4.1 Representación de una imagen MNIST como tensores en aprendizaje profundo. En aras de la
visualización, redujimos el tamaño de la imagen MNIST de 28 × 28 a 8 × 8. La imagen es en escala de grises, lo que
conduce a una forma de canal de altura-anchura (HWC) de[8, 8, 1]. El canal de un solo color a lo largo de la última
dimensión se omite en este diagrama.

4.1.1 El conjunto de datos MNIST

El problema de visión artificial en el que nos centraremos en este capítulo es el MNIST4conjunto de datos de dígitos
escritos a mano. Este es un conjunto de datos tan importante y de uso frecuente que a menudo se lo conoce como el
"hola mundo" para la visión artificial y el aprendizaje profundo. El conjunto de datos MNIST es más antiguo y más
pequeño que la mayoría de los conjuntos de datos que encontrará en el aprendizaje profundo. Sin embargo, es bueno
estar familiarizado con él porque se usa ampliamente como ejemplo y, a menudo, sirve como una primera prueba para
nuevas técnicas de aprendizaje profundo.

Cada ejemplo en el conjunto de datos MNIST es una imagen en escala de grises de 28 × 28 (consulte la figura
4.1 para ver un ejemplo). Estas imágenes se convirtieron de escritura a mano real de los 10 dígitos del 0 al 9. El
tamaño de la imagen de 28 × 28 es suficiente para el reconocimiento confiable de estas formas simples, aunque
es más pequeño que los tamaños de imagen que se ven en los problemas típicos de visión por computadora.
Cada imagen va acompañada de una etiqueta definitiva, que indica cuál de los 10 dígitos posibles es realmente la
imagen. Como hemos visto en los conjuntos de datos de predicción del tiempo de descarga y viviendas de
Boston, los datos se dividen en un conjunto de entrenamiento y un conjunto de prueba. El conjunto de
entrenamiento consta de 60.000 imágenes, mientras que la prueba contiene 10.000 imágenes. El conjunto de
datos MNIST5está aproximadamente equilibrado, lo que significa que hay aproximadamente el mismo número
de ejemplos para las 10 categorías (es decir, los 10 dígitos).

4
MNIST significa NIST modificado. La parte "NIST" del nombre proviene del hecho de que el conjunto de datos se originó en el
Instituto Nacional de Estándares y Tecnología de EE. UU. alrededor de 1995. La parte "modificada" del nombre refleja la
modificación realizada en el conjunto de datos NIST original, que incluía 1) normalizar imágenes en el mismo ráster uniforme de 28
× 28 píxeles con suavizado para hacer que los subconjuntos de prueba y entrenamiento sean más homogéneos y 2) asegurarse de
que los conjuntos de escritores estén separados entre los subconjuntos de prueba y entrenamiento. Estas modificaciones hicieron
que el conjunto de datos fuera más fácil de trabajar y más apto para la evaluación objetiva de la precisión del modelo.
5
Véase Yann LeCun, Corinna Cortes y Christopher JC Burges, “La base de datos de dígitos escritos a mano del
MNIST”,http://yann.lecun.com/exdb/mnist/.
120 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

4.2 Tu primera convnet


Dada la representación de los datos de la imagen y las etiquetas, sabemos qué tipo de
entrada debe tomar una red neuronal que resuelva el conjunto de datos MNIST y qué tipo de
salida debe generar. La entrada a la red neuronal es un tensor de la forma de formato NHWC
[nulo, 28, 28, 1].La salida es un tensor de forma [nulo, 10],donde la segunda dimensión
corresponde a los 10 dígitos posibles. Esta es la codificación one-hot canónica de objetivos de
clasificación multiclase. Es lo mismo que la codificación one-hot de las especies de flores de
iris que vimos en el ejemplo de iris en el capítulo 3. Con este conocimiento, podemos
sumergirnos en los detalles de convnets (que, como recordatorio, es la abreviatura de redes
convolucionales ), el método elegido para tareas de clasificación de imágenes como MNIST.
La parte "convolucional" del nombre puede sonar aterradora. Es solo un tipo de operación
matemática, y la explicaremos en detalle.
El código está en la carpeta mnist de tfjs-examples. Al igual que los ejemplos anteriores, puede
acceder y ejecutar el código de la siguiente manera:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-ejemplos/


mnist
discos compactos

hilo y reloj de hilo


El listado 4.1 es un extracto del archivo de código principal index.js en el ejemplo mnist. Es una
función que crea la convnet que usamos para resolver el problema MNIST. El número de capas en
este modelo secuencial (siete) es significativamente mayor que en los ejemplos que hemos visto
hasta ahora (entre una y tres capas).

Listado 4.1 Definición de un modelo convolucional para el conjunto de datos MNIST

función crearConvModel() {
const modelo = tf.secuencial();

modelo.add(tf.layers.conv2d({
forma de entrada: [IMAGE_H, IMAGE_W, 1],
kernelSize: 3, Primera capa conv2d
filtros: 16,
activación: 'relú'
}));
modelo.add(tf.layers.maxPooling2d({
tamaño de la piscina: 2,
zancadas: 2 Agrupación después de la convolución

}));

modelo.add(tf.layers.conv2d({ Repetición de "motivo" de

kernelSize: 3, filtros: 32, activación: 'relu'})); conv2d-maxPooling2d


model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2}));

modelo.add(tf.layers.flatten()); Aplana el tensor para prepararlo para capas densas


modelo.add(tf.layers.dense({
unidades: 64, Utiliza la activación softmax para
activación: 'relu' el problema de clasificación multiclase
}));
model.add(tf.layers.dense({unidades: 10, activación: 'softmax'}));
Tu primera convnet 121

Resumen Modelo(); Imprime un resumen de texto del modelo.


modelo de retorno;
}

El modelo secuencial construido por el código del listado 4.1 consta de siete capas, creadas una a
una a través de laagregar()llamadas a métodos. Antes de examinar las operaciones detalladas
realizadas por cada capa, veamos la arquitectura general del modelo, que se muestra en la figura
4.2. Como muestra el diagrama, las primeras cinco capas del modelo incluyen un patrón repetitivo
de grupos de capas conv2d-maxPooling2d, seguido de una capa plana. Los grupos de capas
conv2d-maxPooling2d son el caballo de batalla de la extracción de características. Cada una de las
capas transforma una imagen de entrada en una de salida. Una capa conv2d opera a través de un
núcleo convolucional, que se "desliza" sobre las dimensiones de alto y ancho de la imagen de
entrada. En cada posición de deslizamiento, se multiplica con los píxeles de entrada y los productos
se suman y se alimentan a través de una no linealidad. Esto produce un píxel en la imagen de
salida. Las capas maxPooling2d funcionan de manera similar pero sin kernel. Al pasar los datos de
la imagen de entrada a través de las sucesivas capas de convolución y agrupación, obtenemos
tensores que se vuelven cada vez más pequeños y más abstractos en el espacio de características.
La salida de la última capa de agrupación se transforma en un tensor 1D a través del aplanamiento.
El tensor 1D aplanado luego pasa a la capa densa (no se muestra en el diagrama).

convolucional
núcleo 1 convolucional
máx. máx.
Circunvolución núcleo 2
puesta en común
Circunvolución puesta en común Aplastamiento
resultado 1
resultado
resultado 2 resultado 2 resultado
Imagen de entrada

Para
denso
capas
(MLP)

conv2d maxPooling2d conv2d maxPooling2d aplanar

Figura 4.2 Una descripción general de alto nivel de la arquitectura de una convnet simple del tipo construido por el código en el
listado 4.1. En esta figura, los tamaños de las imágenes y los tensores intermedios se hacen más pequeños que los tamaños reales
en el modelo definido en el listado 4.1 con fines ilustrativos. También lo son los tamaños de los núcleos convolucionales. También
tenga en cuenta que este diagrama muestra un solo canal en cada tensor 4D intermedio, mientras que los tensores intermedios
en el modelo real tienen múltiples canales.

Puede pensar en el convnet como un MLP construido sobre el preprocesamiento convolucional y


de agrupación. El MLP es exactamente del mismo tipo que el que hemos visto en los problemas de
detección de phishing y alojamiento de Boston: simplemente está hecho de capas densas con
activaciones no lineales. Lo que es diferente en el convnet aquí es que la entrada al MLP es la salida
de las capas conv2d y maxPooling2d en cascada. Estas capas están diseñadas específicamente para
entradas de imágenes para extraer características útiles de ellas. Esta arquitectura se descubrió a
través de años de investigación en redes neuronales: conduce a una precisión significativamente
mejor que alimentar los valores de píxeles de las imágenes directamente en un MLP.
122 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

Con esta comprensión de alto nivel del convnet MNIST, profundicemos ahora en el
funcionamiento interno de las capas del modelo.

4.2.1 capa conv2d


La primera capa es una capa conv2d, que realiza convolución 2D. Esta es la primera capa
convolucional que ves en este libro. ¿Qué hace? conv2d es una transformación de imagen a
imagen: transforma un tensor de imagen 4D (NHWC) en otro tensor de imagen 4D, posiblemente
con una altura, un ancho y una cantidad de canales diferentes. (Puede parecer extraño que conv2d
funcione con tensores 4D, pero tenga en cuenta que hay dos dimensiones adicionales, una para
ejemplos por lotes y otra para canales). Intuitivamente, puede entenderse como un grupo de
"filtros de Photoshop" simples.6que conducen a efectos de imagen como desenfoque y nitidez.
Estos efectos se realizan con convolución 2D, lo que implica deslizar un pequeño parche de píxeles
(elnúcleo convolucional, o simplementenúcleo) sobre la imagen de entrada. En cada posición de
deslizamiento, el kernel se multiplica con el pequeño parche de la imagen de entrada con el que se
superpone, píxel por píxel. Luego, los productos píxel por píxel se suman para formar píxeles en la
imagen resultante.
En comparación con una capa densa, una capa conv2d tiene más parámetros de
configuración. kernelSizeyfiltrosson dos parámetros clave de la capa conv2d. Para entender su
significado, necesitamos describir cómo funciona la convolución 2D a nivel conceptual.
La figura 4.3 ilustra la convolución 2D con mayor detalle. Aquí, suponemos que el tensor
de la imagen de entrada (arriba a la izquierda) consiste en un ejemplo simple para que
podamos dibujarlo fácilmente en papel. Suponemos que la operación conv2d está
configurada comotamaño del núcleo = 3y filtros = 3.Debido al hecho de que la imagen de
entrada tiene dos canales de color (un número un tanto inusual de canales solo con fines
ilustrativos), el núcleo es un tensor de forma 3D [3, 3, 2, 3].Los primeros dos números (3 y 3)
son la altura y el ancho del grano, determinados porkernelSize.La tercera dimensión (2) es el
número de canales de entrada. ¿Qué es la cuarta dimensión (3)? Es el número de filtros, que
es igual a la última dimensión del tensor de salida de conv2d.
Si la salida se considera como un tensor de imagen (¡una forma totalmente válida de ver esto!),
entonces los filtros pueden entenderse como el número de canales en la salida. A diferencia de la imagen
de entrada, los canales en el tensor de salida en realidad no tienen que ver con los colores. En cambio,
representan diferentes características visuales de la imagen de entrada, aprendidas de los datos de
entrenamiento. Por ejemplo, algunos filtros pueden ser sensibles a los límites de línea recta entre las
regiones claras y oscuras en una determinada orientación, mientras que otros pueden ser sensibles a las
esquinas formadas por un color marrón, etc. Más sobre eso más adelante.
La acción de "deslizamiento" mencionada anteriormente se representa como la extracción de
pequeños parches de la imagen de entrada. Cada parche tiene una altura y un ancho igual akernelSize (3
en este caso). Dado que la imagen de entrada tiene una altura de 4, solo hay dos posiciones de
deslizamiento posibles a lo largo de la dimensión de altura porque debemos asegurarnos de que la

6
Debemos esta analogía a la charla de Ashi Krishnan titulada “Aprendizaje profundo en JS” en JSConf EU 2018:http://mng.bz/VPa0.
Tu primera convnet 123

Alt
ura
en de
ag im
e la im ag
od en
ch
An
1,3
1,2 2,3
Imagen 1,1 2,2
2, 1
profundidad

1,3

1,2 2,3

1,1 2,2

Núcleo convolucional
2,1 @ profundidad de salida 0
Píxeles en la salida

ΣK·X Producción Producción

ancho altura

@ profundidad de salida 1
Producción

profundidad

ΣK·X

@ profundidad de salida 2

ΣK·X

Figura 4.3 Cómo funciona una capa conv2D, con un ejemplo. Para simplificar, se supone que el tensor de entrada (arriba a
la izquierda) contiene solo una imagen y, por lo tanto, es un tensor 3D. Sus dimensiones son alto, ancho y profundidad
(canales de color). La dimensión del lote se omite por simplicidad. La profundidad del tensor de la imagen de entrada se
establece en 2 por simplicidad. Tenga en cuenta que la altura y el ancho de la imagen (4 y 5) son mucho más pequeños
que los de una imagen real típica. La profundidad (2) es menor que el valor más típico de 3 o 4 (por ejemplo, para RGB o
RGBA). Suponiendo que elfiltrospropiedad (el número de filtros) de la capa conv2D es 3,kernelSizees[3, 3], yzancadas es[
1, 1], el primer paso para realizar la convolución 2D es deslizar las dimensiones de alto y ancho y extraer pequeños
parches de la imagen original. Cada parche tiene una altura de 3 y un ancho de 3, coincidiendo con la capa tamaño del
filtro; también tiene la misma profundidad que la imagen original. En el segundo paso, se calcula un producto punto entre
cada parche de 3 × 3 × 2 y el kernel convolucional (es decir, "filtros"). La figura 4.4 da más detalles sobre cada operación de
producto escalar. El núcleo es un tensor 4D y consta de tres filtros 3D. El producto punto entre el parche de imagen con el
filtro se produce por separado para los tres filtros. El parche de la imagen se multiplica con el filtro píxel por píxel y los
productos se suman, lo que conduce a un píxel en el tensor de salida. Debido a que hay tres filtros en el kernel, cada
parche de imagen se convierte en una pila de tres píxeles. Esta operación de producto punto se realiza sobre todos los
parches de imagen y las pilas resultantes de tres píxeles se fusionan como el tensor de salida, que tiene una forma de[2, 3,
3]en este caso.

La ventana de 3 × 3 no cae fuera de los límites de la imagen de entrada. De manera similar, el ancho (5) de
la imagen de entrada nos da tres posibles posiciones de deslizamiento a lo largo de la dimensión del
ancho. Por lo tanto, terminamos con2 × 3 = 6parches de imagen extraídos.
En cada posición de la ventana deslizante, se produce una operación de producto escalar. Recuerde
que el núcleo convolucional tiene una forma de [3, 3, 2, 3].Podemos dividir el tensor 4D a lo largo de la
última dimensión en tres tensores 3D separados, cada uno de los cuales tiene una forma de [3, 3, 2],como
lo muestran las líneas hash en la figura 4.3. Tomamos el parche de la imagen y uno
124 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

de los tensores 3D, multiplíquelos píxel por píxel y sume todos los3 * 3 * 2 = 18 valores para
obtener un píxel en el tensor de salida. La figura 4.4 ilustra el paso del producto punto con mayor
detalle. No es una coincidencia que el parche de la imagen y la porción del kernel convolucional
tengan exactamente la misma forma: extrajimos los parches de la imagen en función de la forma
del kernel. Esta operación de multiplicar y sumar se repite para las tres porciones del núcleo, lo que
da un conjunto de tres números. Luego, esta operación de producto escalar se repite para los
parches de imagen restantes, lo que da las seis columnas de tres cubos en la figura. Estas
columnas finalmente se combinan para formar la salida, que tiene una forma HWC de [2, 3, 3].

–1 0 –1

0 0 1

–1 1 2 – 12 0 –8

Σ
X 0 0 25 43

12 14 8 –2 10 30

7 21 25

2 10 15

Figura 4.4 Una ilustración de la operación de producto escalar (es decir,


multiplicar y sumar) en la operación de convolución 2D, un paso en el flujo de
trabajo completo descrito en la figura 4.3. En aras de la ilustración, se supone
que el parche de imagen (x) contiene solo un canal de color. El parche de la
imagen tiene una forma de[3, 3, 1], es decir, el mismo que el tamaño de la
rebanada del kernel convolucional (K). El primer paso es la multiplicación
elemento por elemento, que produce otro[3, 3, 1]tensor. Los elementos del
nuevo tensor se suman (representados por el-), y la suma es el resultado.

Al igual que una capa densa, una capa conv2d tiene un término de polarización, que se suma al
resultado de la convolución. Además, una capa conv2d generalmente se configura para tener una
función de activación no lineal. En este ejemplo, usamos relu. Recuerde que en la sección del
capítulo 3 "Evitar la falacia de apilar capas sin no linealidad", advertimos que apilar dos capas
densas sin no linealidad es matemáticamente equivalente a usar una sola capa densa. Una nota de
advertencia similar se aplica a las capas conv2d: apilar dos capas de este tipo sin una activación no
lineal es matemáticamente equivalente a usar una sola capa conv2d con un kernel más grande y,
por lo tanto, es una forma ineficiente de construir una convnet que debe evitarse.

¡Uf! Eso es todo por los detalles de cómo funcionan las capas conv2d. Demos un paso atrás y veamos
lo que realmente logra conv2d. En pocas palabras, es una forma especial de transformar un
Traducido del inglés al español - www.onlinedoctranslator.com

Tu primera convnet 125

imagen de entrada en una imagen de salida. La imagen de salida generalmente tendrá una altura y
un ancho más pequeños en comparación con la entrada. La reducción de tamaño depende de la
tamaño del núcleoconfiguración. La imagen de salida puede tener menos, más o los mismos canales
que la entrada, lo cual está determinado por lafiltrosconfiguración.
Así que conv2d es una transformación de imagen a imagen. Dos características clave de la transformación
conv2d son la localidad y el uso compartido de parámetros:

- Localidadse refiere a la propiedad de que el valor de un píxel determinado en la imagen de salida se ve


afectado solo por una pequeña parte de la imagen de entrada, en lugar de por todos los píxeles de la
imagen de entrada. El tamaño de ese parche eskernelSize.Esto es lo que diferencia a conv2d de las capas
densas: en una capa densa, cada elemento de salida se ve afectado por cada elemento de entrada. En
otras palabras, los elementos de entrada y los elementos de salida están "densamente conectados" en
una capa densa (de ahí su nombre). Entonces, podemos decir que una capa conv2d está "escasamente
conectada". Mientras que las capas densas aprenden patrones globales en la entrada, las capas
convolucionales aprenden patrones locales, patrones dentro de la pequeña ventana del núcleo.

- Compartir parámetrosse refiere a la propiedad de que la forma en que el píxel de salida A se ve


afectado por su pequeño parche de entrada es exactamente la misma que la forma en que el píxel
de salida B se ve afectado por su parche de entrada. Esto se debe a que el producto escalar en
cada posición deslizante usa el mismo kernel convolucional (figura 4.3).

Debido a la localidad y el uso compartido de parámetros, una capa conv2d es una transformación
de imagen a imagen altamente eficiente en términos de la cantidad de parámetros requeridos. En
particular, el tamaño del kernel convolucional no cambia con la altura o el ancho de la imagen de
entrada. Volviendo a la primera capa conv2d del listado 4.1, el kernel tiene una forma de
[tamaño del kernel, tamaño del kernel, 1, filtro] (es decir, [5, 5, 1, 8]),y por lo tanto un
total de 5 * 5 * 1 * 8 = 200 parámetros, independientemente de si las imágenes MNIST de
entrada son 28 × 28 o mucho más grandes. La salida de la primera capa conv2d tiene una
forma de [24, 24, 8] (omitiendo la dimensión del lote). Entonces, la capa conv2d transforma un
tensor que consta de 28 * 28 * 1 = 784 elementos en otro tensor de 24 * 24 * 8 = 4608
elementos. Si tuviéramos que implementar esta transformación con una capa densa,
¿cuántos parámetros estarán involucrados? La respuesta es 784 * 4,608 = 3,612,672 (sin
incluir el sesgo), que es aproximadamente 18 mil veces más que la capa conv2d. Este
experimento mental muestra la eficiencia de las capas convolucionales.
La belleza de la localidad de conv2d y el uso compartido de parámetros no radica solo en su eficiencia,
sino también en el hecho de que imita (de manera flexible) cómo funcionan los sistemas visuales
biológicos. Considere las neuronas en la retina. Cada neurona se ve afectada solo por un pequeño parche
en el campo de visión del ojo, llamadocampo receptivo. Dos neuronas ubicadas en diferentes lugares de
la retina responden a los patrones de luz en sus respectivos campos receptivos prácticamente de la
misma manera, lo que es análogo al intercambio de parámetros en una capa conv2d. Además, las capas
conv2d demuestran que funcionan bien para los problemas de visión por computadora, como pronto
apreciaremos en este ejemplo de MNIST. conv2d es una capa de red neuronal ordenada que lo tiene todo:
eficiencia, precisión y relevancia para la biología. No es de extrañar que sea tan ampliamente utilizado en
el aprendizaje profundo.
126 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

4.2.2 capa maxPooling2d


Habiendo examinado la capa conv2d, veamos la siguiente capa en el modelo secuencial: una capa
maxPooling2d. Al igual que conv2d, maxPooling2d es una especie de transformación de imagen a
imagen. Pero la transformación maxPooling2d es más simple en comparación con conv2d. Como
muestra la figura 4.5, simplemente calcula los valores máximos de píxel en pequeños parches de
imagen y los utiliza como valores de píxel en la salida. El código que define y agrega la capa
maxPooling2d es

model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2}));

12 14 8 4

7 21 25 dieciséis

2 10 15 10

30 24 14 32

Figura 4.5 Un ejemplo de cómo funciona una capa maxPooling2D. Este


ejemplo utiliza una imagen diminuta de 4 × 4 y supone que la capa
21 25 maxPooling2D está configurada para tener una tamaño de la piscina
de[2, 2]yzancadasde[2, 2]. La dimensión de profundidad no se muestra,
30 32 pero la operación de agrupación máxima ocurre independientemente
sobre las dimensiones.

En este caso particular, los parches de imagen tienen una altura y un ancho de 2 × 2 debido a la
especificacióntamaño de la piscinavalor de [2, 2].Los parches se extraen cada dos píxeles, a lo largo
de ambas dimensiones. Este espacio entre parches resulta de lazancadasvalor que usamos aquí: [2,
2].Como resultado, la imagen de salida, con una forma HWC de [12, 12, 8],es la mitad de la altura y
la mitad del ancho de la imagen de entrada (forma [24, 24, 8])pero tiene el mismo número de
canales.
Una capa maxPooling2d tiene dos propósitos principales en una convnet. Primero, hace que la
convnet sea menos sensible a la ubicación exacta de las características clave en la imagen de entrada. Por
ejemplo, queremos poder reconocer el dígito "8" independientemente de si se ha desplazado hacia la
izquierda o hacia la derecha desde el centro en la imagen de entrada de 28 × 28 (o se ha desplazado hacia
arriba o hacia abajo, para el caso), un propiedad llamadainvariancia posicional. Para comprender cómo la
capa maxPooling2d mejora la invariancia posicional, tenga en cuenta el hecho de que dentro de cada
parche de imagen en el que opera maxPooling2d, no importa dónde esté el píxel más brillante, siempre
que caiga en ese parche. Es cierto que una sola capa maxPooling2d no puede hacer mucho para hacer
que la convnet sea insensible a los cambios porque su ventana de agrupación es limitada. Sin embargo,
cuando se utilizan varias capas maxPooling2d en la misma convnet, funcionan juntas para lograr una
invariancia posicional considerablemente mayor. Esto es exactamente lo que se hace en nuestro modelo
MNIST, así como en prácticamente todas las convenciones prácticas, que contiene dos capas
maxPooling2d.
Tu primera convnet 127

Como experimento mental, considere lo que sucede cuando dos capas conv2d (llamadas
conv2d_1 y conv2d_2) se apilan directamente una encima de la otra sin una capa intermedia
maxPooling2d. Supongamos que cada una de las dos capas conv2d tiene unkernelSize de 3;
entonces cada píxel en el tensor de salida de conv2d_2 es una función de una región de 5 × 5 en la
entrada original de conv2d_1. Podemos decir que cada "neurona" de la capa conv2d_2 tiene un
campo receptivo de tamaño 5 × 5. ¿Qué sucede cuando hay una capa maxPooling2d intermedia
entre las dos capas conv2d (como es el caso de nuestra convnet MNIST)? El campo receptivo de las
neuronas de conv2d_2 se vuelve más grande: 11 × 11. Esto se debe a la operación de agrupación,
por supuesto. Cuando hay varias capas maxPooling2d presentes en una convnet, las capas en los
niveles más altos pueden tener amplios campos receptivos e invariancia posicional. En resumen,
¡pueden ver más amplio!
En segundo lugar, una capa maxPooling2d también reduce el tamaño de las dimensiones
de alto y ancho del tensor de entrada, lo que reduce significativamente la cantidad de
cómputo requerida en las capas posteriores y en todo el convnet en general. Por ejemplo, la
salida de la primera capa conv2d tiene un tensor de salida de forma [26, 26, 16].Después de
pasar por la capa maxPooling2d, la forma del tensor se convierte en [13, 13, 16],lo que reduce
el número de elementos tensoriales por un factor de 4. El convnet contiene otra capa max-
Pooling2d, que reduce aún más el tamaño de los pesos en las capas posteriores y el número
de operaciones matemáticas por elementos en esas capas.

4.2.3 Motivos repetitivos de convolución y agrupación


Habiendo examinado la primera capa de maxPooling2d, centremos nuestra atención en las siguientes
dos capas de la convnet, definidas por estas líneas en el listado 4.1:

modelo.add(tf.layers.conv2d(
{kernelSize: 3, filtros: 32, activación: 'relu'}));
model.add(tf.layers.maxPooling2d({poolSize: 2, strides: 2}));

Estas dos capas son una repetición exacta de las dos capas anteriores (excepto que la capa conv2d
tiene un valor mayor en sufiltrosconfiguración y no posee unaforma de entrada campo). Este tipo de
"motivo" casi repetitivo que consta de una capa convolucional y una capa de agrupación se ve con
frecuencia en convnets. Desempeña un papel crítico: extracción jerárquica de características. Para
entender lo que significa, considere un convnet entrenado para la tarea de clasificar animales en
imágenes. En las primeras etapas de la convnet, los filtros (es decir, los canales) en una capa
convolucional pueden codificar características geométricas de bajo nivel, como líneas rectas, líneas
curvas y esquinas. Estas características de bajo nivel se transforman en características más
complejas, como el ojo, la nariz y la oreja de un gato (consulte la figura 4.6). En el nivel superior de
la convnet, una capa puede tener filtros que codifican la presencia de un gato completo. Cuanto
más alto es el nivel, más abstracta es la representación y más eliminadas de los valores de nivel de
píxel son las características. Pero esas características abstractas son exactamente lo que se
requiere para lograr una buena precisión en la tarea de la convnet, por ejemplo, detectar un gato
cuando está presente en la imagen. Además, estas características no están hechas a mano, sino
que se extraen de los datos de forma automática a través del aprendizaje supervisado. Este es un
ejemplo por excelencia del tipo de transformación representacional capa por capa que describimos
como la esencia del aprendizaje profundo en el capítulo 1.
128 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

"gato"

Figura 4.6 Extracción jerárquica de características de una imagen de entrada por un convnet,
usando una imagen de gato como ejemplo. Tenga en cuenta que en este ejemplo, la entrada
a la red neuronal está en la parte inferior y la salida está en la parte superior.

4.2.4 Capas planas y densas


Una vez que el tensor de entrada ha pasado por los dos grupos de transformaciones
conv2d-maxPooling2d, se convierte en un tensor de forma HWC [4, 4, 16] (sin la
dimensión del lote). La siguiente capa en el convnet es una capa plana. Esta capa forma
un puente entre las capas anteriores de conv2d-maxPooling2d y las siguientes capas del
modelo secuencial.
El código para la capa plana es simple, ya que el constructor no requiere ningún
parámetro de configuración:

modelo.add(tf.layers.flatten());

Una capa plana "aplasta" un tensor multidimensional en un tensor 1D, preservando el


número total de elementos. En nuestro caso, el tensor 3D de forma [3, 3, 32]se aplana en un
tensor 1D [288] (sin la dimensión del lote). Una pregunta obvia para la operación de
aplastamiento es cómo ordenar los elementos, porque no existe un orden intrínseco en el
espacio 3D original. La respuesta es que ordenamos los elementos de tal manera que si baja
los elementos en el tensor 1D aplanado y observa cómo cambian sus índices originales (del
tensor 3D), el último índice cambia más rápido, el penúltimo índice cambia el segundo más
rápido, y así sucesivamente, mientras que el primer índice cambia el más lento. Esto se ilustra
en la figura 4.7.
Tu primera convnet 129

Figura 4.7 Cómo funciona una capa plana. Se supone una entrada de tensor 3D.
En aras de la simplicidad, dejamos que cada dimensión tenga un tamaño
pequeño de 2. Los índices de los elementos se muestran en las "caras" de los
cubos que representan los elementos. La capa flatten transforma el tensor 3D
en un tensor 1D mientras conserva el número total de elementos. El orden de
los elementos en el tensor 1D aplanado es tal que cuando baja los elementos del
tensor 1D de salida y examina sus índices originales en el tensor de entrada, la
última dimensión es la que cambia más rápido.

¿Para qué sirve la capa plana en nuestro convnet? Prepara el escenario para las capas densas
posteriores. Como aprendimos en los capítulos 2 y 3, una capa densa generalmente toma un
tensor 1D (excluyendo la dimensión del lote) como entrada debido a cómo funciona una capa
densa (sección 2.1.4).
Las siguientes dos líneas del código en el listado 4.1 agregan dos capas densas a la convnet:

model.add(tf.layers.dense({unidades: 64, activación: 'relu'}));


model.add(tf.layers.dense({unidades: 10, activación: 'softmax'}));

¿Por qué dos capas densas y no solo una? La misma razón que en el ejemplo de vivienda de
Boston y el ejemplo de detección de URL de phishing que vimos en el capítulo 3: agregar
capas con activación no lineal aumenta la capacidad de la red. De hecho, puede pensar que la
convnet consiste en dos modelos apilados uno encima del otro:

- Un modelo que contiene capas conv2d, maxPooling2d y flatten, que extrae características
visuales de las imágenes de entrada
- Un MLP con dos capas densas que usa las características extraídas para hacer predicciones de
clases de dígitos: esto es esencialmente para lo que son las dos capas densas

En el aprendizaje profundo, muchos modelos muestran este patrón de capas de extracción de características
seguidas de MLP para las predicciones finales. Veremos más ejemplos como este a lo largo del
130 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

resto de este libro, en modelos que van desde clasificadores de señales de audio hasta procesamiento de
lenguaje natural.

4.2.5 Entrenamiento de la convnet

Ahora que hemos definido con éxito la topología de la convnet, el siguiente paso es
entrenarlo y evaluar el resultado del entrenamiento. Para esto es el código en el siguiente
listado.

Listado 4.2 Entrenamiento y evaluación del convnet MNIST

optimizador const = 'rmsprop';


modelo.compilar({
optimizador,
pérdida: 'categoricalCrossentropy',
métricas: ['exactitud']
});

const tamaño de lote = 320; const


validaciónSplit = 0.15;
espera model.fit(trainData.xs, trainData.labels, {
tamaño del lote,
división de validación,
épocas: épocas de tren,
devoluciones de llamada: { Utiliza devoluciones de llamada para trazar la

onBatchEnd: asíncrono (lote, registros) => { precisión y la pérdida durante el entrenamiento

trenBatchCount++;
ui.logStatus(
`Entrenando... (` +
`${(trainBatchCount / totalNumBatches * 100).toFixed(1)}%` + ` completo). Para
detener el entrenamiento, actualice o cierre la página.`);
ui.plotLoss(trainBatchCount, logs.loss, 'train'); ui.plotAccuracy(trainBatchCount,
logs.acc, 'entrenar');
},
onEpochEnd: asíncrono (época, registros) => {
valAcc = registros.val_acc;
ui.plotLoss(trainBatchCount, logs.val_loss, 'validación');
ui.plotAccuracy(trainBatchCount, registros.val_acc, 'validación');
}
}
});
const testResult = modelo.evaluar( Evalúa la precisión del modelo usando
testData.xs, testData.labels); datos que el modelo no ha visto

Gran parte del código aquí se trata de actualizar la interfaz de usuario a medida que avanza el entrenamiento,
por ejemplo, para trazar cómo cambian los valores de pérdida y precisión. Esto es útil para monitorear el proceso
de entrenamiento pero no es estrictamente esencial para el entrenamiento de modelos. Destaquemos las partes
esenciales para el entrenamiento:

- datostren.xs (el primer argumento paramodelo.ajuste())contiene las imágenes MNIST de


entrada representadas como un tensor de forma NHWC [N, 28, 28, 1]
Tu primera convnet 131

- trainData.etiquetas (el segundo argumento demodelo.ajuste()).Esto incluye las etiquetas de


entrada, representadas como un tensor de forma 2D codificado one-hot [N, 10].
-La función de pérdida utilizada en elmodelo.compilar()llamada,cruz categórica-
entropía,lo cual es apropiado para problemas de clasificación multiclase como MNIST. Recuerde
que usamos la misma función de pérdida para el problema de clasificación de la flor del iris en el
capítulo 3.
- La función métrica especificada en elmodelo.compilar()llamada: 'exactitud'.Esta función mide
qué fracción de los ejemplos se clasifican correctamente, dado que la predicción se realiza
en base al elemento más grande entre los 10 elementos de la salida del convnet.
Nuevamente, esta es exactamente la misma métrica que usamos para el problema de las
noticias. Recuerde la diferencia entre la pérdida de entropía cruzada y la métrica de
precisión: la entropía cruzada es diferenciable y, por lo tanto, hace posible el entrenamiento
basado en retropropagación, mientras que la métrica de precisión no es diferenciable pero
es más fácilmente interpretable.
- lostamaño del loteparámetro especificado para elmodelo.fit()llamada. En general, el beneficio
de usar tamaños de lote más grandes es que produce una actualización de gradiente más
consistente y menos variable para los pesos del modelo que un tamaño de lote más
pequeño. Pero cuanto mayor sea el tamaño del lote, más memoria se requiere durante el
entrenamiento. También debe tener en cuenta que dada la misma cantidad de datos de
entrenamiento, un tamaño de lote más grande conduce a una pequeña cantidad de
actualizaciones de gradiente por época. Por lo tanto, si usa un tamaño de lote más grande,
asegúrese de aumentar la cantidad de épocas en consecuencia para no disminuir
inadvertidamente la cantidad de actualizaciones de peso durante el entrenamiento. Por lo
tanto, hay una compensación. Aquí, usamos un tamaño de lote relativamente pequeño de
64 porque necesitamos asegurarnos de que este ejemplo funcione en una amplia gama de
hardware. Al igual que otros parámetros,
- losvalidaciónDividirutilizado en elmodelo.fit()llamada. Esto permite que el proceso de
formación omita el último 15% detrainData.xsytrainData.etiquetaspara la validación durante
el entrenamiento. Como aprendió con los modelos sin imágenes anteriores, es importante
monitorear la pérdida de validación y la precisión durante el entrenamiento. Le da una idea
de si el modelo estásobreajuste. ¿Qué es el sobreajuste? En pocas palabras, es un estado en
el que el modelo presta demasiada atención a los detalles finos de los datos que ha visto
durante el entrenamiento, tanto que la precisión de su predicción sobre los datos no vistos
durante el entrenamiento se ve afectada negativamente. Es un concepto crítico en el
aprendizaje automático supervisado. Más adelante en el libro (capítulo 8), dedicaremos un
capítulo completo a cómo detectar y contrarrestar el sobreajuste.

modelo.fit()es una función asíncrona, por lo que necesitamos usaresperaren él si las acciones posteriores

dependen de la finalización de laencajar()llamada. Esto es exactamente lo que se hace aquí, ya que necesitamos
realizar una evaluación en el modelo usando un conjunto de datos de prueba después de que el modelo esté
132 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

entrenado. La evaluación se realiza mediante elmodelo.evaluar()método, que es síncrono. Los


datos alimentados amodelo.evaluar()esdatos de prueba,que tiene el mismo formato que el
trenDatosmencionado anteriormente, pero tiene un número menor de ejemplos. Estos ejemplos
nunca fueron vistos por la modelo durante elencajar()llamar, asegurando que el conjunto de datos
de prueba no se filtre en el resultado de la evaluación y que el resultado de la evaluación sea una
evaluación objetiva de la calidad del modelo.
Con este código, dejamos que el modelo entrene 10 épocas (especificadas en el cuadro de
entrada), lo que nos da las curvas de pérdida y precisión de la figura 4.8. Como muestran los
gráficos, la pérdida converge hacia el final de las épocas de entrenamiento, al igual que la
precisión. Los valores de pérdida de validación y precisión no se desvían demasiado de sus
contrapartes de entrenamiento, lo que indica que no hay un sobreajuste significativo en este caso.
El final modelo.evaluar()call proporciona una precisión cercana al 99,0 % (el valor real que obtenga
variará ligeramente de una ejecución a otra, debido a la inicialización aleatoria de pesos y la mezcla
aleatoria implícita de ejemplos durante el entrenamiento).

1
Entrenar Entrenar

2 Validación Validación
0.8

1.5
0.6
Exactitud
Pérdida

1
0.4

0.5
0.2

0
0
2000 4000 6000 2000 4000 6000
Lote # Lote #

Figura 4.8 Curvas de entrenamiento de la convnet MNIST. Se realizan diez épocas de entrenamiento, y cada época consta de
aproximadamente 800 lotes. Izquierda: valor de la pérdida. Derecha: valor de precisión. Los valores de los conjuntos de entrenamiento y
validación se muestran con diferentes colores, anchos de línea y símbolos de marcador. Las curvas de validación contienen menos
puntos de datos que las de entrenamiento porque, a diferencia de los lotes de entrenamiento, la validación se realiza solo al final de
cada época.

¿Qué tan bueno es 99.0%? Es pasable desde un punto de vista práctico, pero ciertamente no es el
estado del arte. Con más capas convolucionales, es posible alcanzar una precisión que alcanza el
99,5 % aumentando el número de capas convolucionales y de agrupación y el número de filtros en
el modelo. Sin embargo, la capacitación de esas convnets más grandes lleva mucho más tiempo en
el navegador, tanto que tiene sentido realizar la capacitación en un entorno con menos recursos
limitados como Node.js. Le mostraremos exactamente cómo hacerlo en la sección 4.3.

Desde un punto de vista teórico, recuerde que MNIST es un problema de clasificación de 10 factores.
Por lo tanto, la precisión del nivel de probabilidad (adivinación pura) es del 10 %; 99.0% es mucho mejor
que eso. Pero el nivel de probabilidad no es un listón muy alto. ¿Cómo mostramos el valor de conv2d?
Tu primera convnet 133

y maxPooling2d capas en el modelo? ¿Lo habríamos hecho tan bien si nos hubiéramos quedado
con las viejas capas densas?
Para responder a estas preguntas, podemos hacer un experimento. El código en index.js
contiene otra función para la creación de modelos llamadacrearModeloDenso().A diferencia delcrear-
ConvModel()función que vimos en el listado 4.1,crearModeloDense()crea un modelo secuencial hecho
solo de capas planas y densas, es decir, sin usar los nuevos tipos de capas que aprendimos en este
capítulo.crearModeloDense()se asegura de que la cantidad total de parámetros sea
aproximadamente igual entre el modelo denso que crea y la convnet que acabamos de entrenar,
aproximadamente 33 000, por lo que será una comparación más justa.

Listado 4.3 Un modelo plano y denso únicamente para MNIST, para compararlo con convnet

función crearModeloDense() {
const modelo = tf.secuencial(); model.add(tf.layers.flatten({inputShape: [IMAGE_H,
IMAGE_W, 1]})); model.add(tf.layers.dense({unidades: 42, activación: 'relu'}));
model.add(tf.layers.dense({unidades: 10, activación: 'softmax'})); Resumen Modelo();

modelo de retorno;
}

El resumen del modelo definido en el listado 4.3 es el siguiente:

_________________________________________________________________ Capa (tipo)


Forma de salida Parámetro #
================================================== ===============
flatten_Flatten1 (Flatten) [nulo, 784] 0
________________________________________________________________ denso_Dense1 (Denso)
[nulo, 42] 32970
________________________________________________________________ denso_Dense2 (Denso)
[nulo, 10] 430
================================================== ===============
Parámetros totales: 33400
Parámetros entrenables: 33400 Parámetros no entrenables: 0
_________________________________________________________________

Usando la misma configuración de entrenamiento, obtenemos resultados de entrenamiento como


se muestra en la figura 4.9 del modelo no convolucional. La precisión de la evaluación final que
obtenemos después de 10 épocas de entrenamiento es de alrededor del 97,0 %. La diferencia de
dos puntos porcentuales puede parecer pequeña, pero en términos de tasa de error, el modelo no
convolucional es tres veces peor que el convnet. Como ejercicio práctico, intente aumentar el
tamaño del modelo no convolucional aumentando elunidadesparámetro de la (primera) capa densa
oculta en elcrearModeloDense()función. Verá que, incluso con tamaños más grandes, es imposible
que el modelo solo denso logre una precisión a la par con el convnet. Esto le muestra el poder de
una convnet: mediante el uso compartido de parámetros y la explotación de la localidad de las
características visuales, las convnets pueden lograr una precisión superior en las tareas de visión
por computadora con una cantidad igual o menor de parámetros que las redes neuronales no
convolucionales.
134 CPASADO4 Reconocimiento de imágenes y sonidos usando convnets

2.5 1
Entrenar Entrenar

2 Validación Validación
0.8

1.5 0.6

Exactitud
Pérdida

1 0.4

0.5
0.2

0
0
2000 4000 6000 2000 4000 6000
Lote # Lote #

Figura 4.9 Igual que la figura 4.8, pero para un modelo no convolucional para el problema MNIST, creado por el
crearModeloDense()función en el listado 4.3

4.2.6 Usar una convnet para hacer predicciones


Ahora tenemos un convnet entrenado. ¿Cómo lo usamos para clasificar imágenes de dígitos
escritos a mano? Primero, necesita obtener los datos de la imagen. Hay varias formas en las que
los datos de imagen pueden estar disponibles para los modelos de TensorFlow.js. Los
enumeraremos y describiremos cuándo son aplicables.

CRELACIONANDO TENSORES DE IMAGEN DESDETYPEDARRAYOS


En algunos casos, los datos de imagen que desea ya están almacenados como JavaScriptMatriz tipificadas.
Este es el caso en el ejemplo tfjs-example/mnist en el que nos estamos centrando. Los detalles están en el
archivo data.js y no daremos más detalles sobre la maquinaria detallada. Dado un Matriz flotante32que
representa un MNIST de la longitud correcta (digamos, una variable llamada matriz de datos de imagen),
podemos convertirlo en un tensor 4D de la forma esperada por nuestro modelo con7

sea x = tf.tensor4d(imageDataArray, [1, 28, 28, 1]);


El segundo argumento en eltf.tensor4d()call especifica la forma del tensor que se va a crear.
Es necesario porque unMatriz flotante32 (o unMatriz tipificadaen general) es una estructura
plana sin información sobre las dimensiones de la imagen. El tamaño de la primera
dimensión es 1 porque estamos tratando con una sola imagen enimageDataArray.Como en
los ejemplos anteriores, el modelo siempre espera una dimensión de lote durante el
entrenamiento, la evaluación y la inferencia, sin importar si hay una sola imagen o más de
una. Si elMatriz flotante32contiene un lote de múltiples imágenes, también se puede convertir
en un solo tensor, donde el tamaño de la primera dimensión es igual al número de imágenes:

sea x = tf.tensor4d(imageDataArray, [numImages, 28, 28, 1]);

7
Consulte el apéndice B para obtener un tutorial más completo sobre cómo crear tensores utilizando la API de bajo nivel en
TensorFlow.js.
Tu primera convnet 135

TF.NAVEGADOR.DESDEPAGSIXELS: GAJUSTE DE TENSORES DE IMAGEN DESDEHTMLIMG,LIENZO,


O ELEMENTOS DE VÍDEO

La segunda forma de obtener tensores de imagen en el navegador es usar la función


TensorFlow.jstf.navegador.fromPixels()en elementos HTML que contienen datos de
imagen, esto incluyelienzo,yvideoelementos.
Por ejemplo, supongamos que una página web contiene unimagenelemento definido como

<img id="mi-imagen" src="foo.jpg"></img>

Puede obtener los datos de imagen que se muestran en laimagenelemento con una línea:

sea x = tf.browser.fromPixels(
document.getElementById('mi-imagen')).asType('float32');

Esto genera un tensor de forma [alto, ancho, 3],donde los tres canales son para la codificación
de color RGB. loscomoTipo0llamar al final es necesario porque tf.navegador.fromPixels()
devuelve un tensor de tipo int32, pero convnet espera tensores de tipo float32 como
entradas. La altura y el ancho están determinados por el tamaño de la imagenelemento. Si no
coincide con el alto y el ancho esperado por el modelo, puede cambiar los atributos de alto y
ancho delimagen(si eso no hace que la interfaz de usuario se vea mal, por supuesto) o
cambiar el tamaño del tensor detf.navegador.fromPixels()mediante el uso de uno de los dos
métodos de cambio de tamaño de imagen proporcionados por TensorFlow.js,
tf.image.resizeBilinear()otf.image.resizeNearestNeigbor():
x = tf.image.resizeBilinear(x, [newHeight, newWidth]);

tf.image.resizeBilinear()ytf.image.resizeNearestNeighbor()tener el
misma sintaxis, pero realizan el cambio de tamaño de la imagen con dos algoritmos diferentes. El primero usa la
interpolación bilineal para formar valores de píxeles en el nuevo tensor, mientras que el segundo realiza un
muestreo del vecino más cercano y, por lo general, es menos intensivo desde el punto de vista computacional
que la interpolación bilineal.
Tenga en cuenta que el tensor creado portf.navegador.fromPixels()no incluye una dimensión de
lote. Por lo tanto, si el tensor se va a introducir en un modelo TensorFlow.js, primero debe
expandirse en dimensión; por ejemplo,

x = x.expandDims();

expandirDims()toma un argumento de dimensión en general. Pero en este caso, el argumento se


puede omitir porque estamos expandiendo la primera dimensión, que es la predeterminada para
ese argumento.
Además deimagenelementos,tf.navegador.fromPixels()trabaja enlienzoyvideo
elementos de la misma manera. Aplicartf.navegador.fromPixels()enlienzoLos elementos son útiles para los
casos en los que el usuario puede alterar de forma interactiva el contenido de un lienzo antes de que un
modelo de TensorFlow.js use el contenido. Por ejemplo, imagine una aplicación de reconocimiento de
escritura a mano en línea o una aplicación de reconocimiento de formas dibujadas a mano en línea.
Aparte de las imágenes estáticas, aplicartf.navegador.fromPixels()envideoelementos es útil para obtener
datos de imagen cuadro por cuadro de una cámara web. Esto es exactamente lo que se hizo en la
demostración de Pac-Man que Nikhil Thorat y Daniel Smilkov dieron durante el TensorFlow.js inicial.
136 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

anuncio (verhttp://mng.bz/xl0e), la demostración de PoseNet,8y muchas otras aplicaciones web


basadas en TensorFlow.js que usan una cámara web. Puede leer el código fuente en GitHub en
http://mng.bz/ANYK.
Como hemos visto en los capítulos anteriores, se debe tener mucho cuidado para evitarsesgar (es
decir, desajuste) entre los datos de entrenamiento y los datos de inferencia. En este caso, nuestro convnet
MNIST está entrenado con tensores de imagen normalizados al rango entre 0 y 1. Por lo tanto, si los datos
en elXtensor tiene un rango diferente, digamos 0–255, como es común en los datos de imagen basados
en HTML, deberíamos normalizar los datos:

x = x.div(255);

Con los datos disponibles, ya estamos listos para llamarmodelo.predecir()para obtener las predicciones.
Consulte el siguiente listado.

Listado 4.4 Usando el convnet entrenado para inferencia

const pruebaEjemplos = 100;


const ejemplos = data.getTestData(testExamples);
Utiliza tf.tidy() para evitar
tf.ordenado(() => { pérdidas de memoria WebGL
const salida = modelo.predecir(ejemplos.xs);

eje constante = 1;
const etiquetas = Array.from(ejemplos.etiquetas.argMax(eje).dataSync()); const predicciones
= Array.from(
salida.argMax(eje).dataSync());
Llama a argMax() para
ui.showTestResults(ejemplos, predicciones, etiquetas); }); obtener la clase con la
mayor probabilidad

El código está escrito asumiendo que el lote de imágenes para la predicción ya está
disponible en un solo tensor, a saber,ejemplos.xs.Tiene una forma de [100, 28, 28, 1] (incluida
la dimensión del lote), donde la primera dimensión refleja el hecho de que hay 100 imágenes
en las que estamos ejecutando una predicción.modelo.predecir()devuelve un tensor 2D de
salida de forma [100, 10].La primera dimensión de la salida corresponde a los ejemplos,
mientras que la segunda dimensión corresponde a los 10 dígitos posibles. Cada fila del
tensor de salida incluye los valores de probabilidad asignados a los 10 dígitos para una
entrada de imagen determinada. Para determinar la predicción, necesitamos averiguar los
índices de los valores de máxima probabilidad, imagen por imagen. Esto se hace con las
líneas.

eje constante = 1;
const etiquetas = Array.from(ejemplos.etiquetas.argMax(eje).dataSync());

losargMax()La función devuelve los índices de los valores máximos a lo largo de un eje dado.
En este caso, este eje es la segunda dimensión,eje constante = 1.El valor de retorno de
argMax()es un tensor de forma [100, 1].Llamandosincronización de datos(),convertimos el [100,
1]- tensor en forma de longitud-100Matriz flotante32.Luegomatriz.desde()convierte el

8Dan Oved, "Estimación de la pose humana en tiempo real en el navegador con TensorFlow.js", Medium, 7 de mayo
2018,http://mng.bz/ZeOO.
Más allá de los navegadores: entrenar modelos más rápido con Node.js 137

Matriz flotante32en una matriz de JavaScript ordinaria que consta de 100 enteros entre 0 y 9. Esta matriz
de predicciones tiene un significado muy sencillo: son los resultados de clasificación realizados por el
modelo para las 100 imágenes de entrada. En el conjunto de datos MNIST, las etiquetas de destino
coinciden exactamente con el índice de salida. Por lo tanto, ni siquiera necesitamos convertir la matriz en
etiquetas de cadena. La matriz de predicciones es consumida por la siguiente línea, que llama a una
función de IU que presenta los resultados de la clasificación junto con las imágenes de prueba (consulte la
figura 4.10).

Figura 4.10 Algunos ejemplos de predicciones realizadas por el modelo después del entrenamiento, que se muestran junto con las imágenes de

entrada del MNIST

4.3 Más allá de los navegadores: entrenar modelos más rápido usando Node.js
En la sección anterior, entrenamos una convnet en el navegador y alcanzó una precisión de prueba del
99,0 %. En esta sección, crearemos un convnet más potente que nos dará una mayor precisión de prueba:
alrededor del 99,5%. Sin embargo, la precisión mejorada tiene un costo: una mayor cantidad de memoria
y computación consumida por el modelo durante el entrenamiento y la inferencia. El aumento en el costo
es más pronunciado durante el entrenamiento porque el entrenamiento implica retropropagación, que es
más intensivo desde el punto de vista computacional en comparación con las ejecuciones hacia adelante
que implica la inferencia. La convnet más grande será demasiado pesada y lenta para entrenar en la
mayoría de los entornos de navegador web.

4.3.1 Dependencias e importaciones para usar tfjs-node


¡Ingrese a la versión Node.js de TensorFlow.js! Se ejecuta en un entorno de back-end, sin obstáculos por
ninguna restricción de recursos como la de una pestaña del navegador. La versión de CPU de Node.js de
TensorFlow (nodo tfjspara abreviar de aquí en adelante) utiliza directamente las operaciones matemáticas
multiproceso escritas en C++ y utilizadas por la versión principal de Python de TensorFlow. Si tiene una
GPU habilitada para CUDA instalada en su máquina, tfjs-node también puede usar los núcleos
matemáticos acelerados por GPU escritos en CUDA, logrando ganancias aún mayores en velocidad.

El código para nuestra convnet MNIST mejorada se encuentra en el directorio mnist-node de


tfjsexamples. Como en los ejemplos que hemos visto, puedes usar los siguientes comandos para
acceder al código:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


mnist-nodo
discos compactos
138 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

Lo que es diferente de los ejemplos anteriores es que el ejemplo de mnist-node se ejecutará en una
terminal en lugar de un navegador web. Para descargar las dependencias, utilice elhilo mando.

Si examina el archivo package.json, puede ver la dependencia @tensorflow/tfjs-nodo.Con @


tensorflow/tfjs-nododeclarada como dependencia,hilodescargará automáticamente la
biblioteca compartida de C++ (con el nombre libtensorflow.so, libtensorflw . dylib o
libtensorflow.dll en los sistemas Linux, Mac o Windows, respectivamente) en su directorio de
módulos node_ para que TensorFlow.js la use.
Una vez elhiloel comando ha terminado de ejecutarse, puede iniciar el entrenamiento del modelo con

nodo principal.js

Suponemos que el binario del nodo está disponible en su ruta ya que ya instaló yarn
(consulte el apéndice A si necesita más información al respecto).
El flujo de trabajo que se acaba de describir le permitirá entrenar la convnet mejorada en su CPU. Si su
estación de trabajo y computadora portátil tienen una GPU habilitada para CUDA, también puede
entrenar el modelo en su GPU. Los pasos involucrados son los siguientes:

1 Instale las versiones correctas del controlador NVIDIA para su GPU.


2 Instale el kit de herramientas NVIDIA CUDA. Esta es la biblioteca que permite la computación
paralela de propósito general en la línea de GPU de NVIDIA.
3 Instale CuDNN, la biblioteca de NVIDIA para algoritmos de aprendizaje profundo de alto rendimiento
creados sobre CUDA (consulte el apéndice A para obtener más detalles sobre los pasos 1 a 3).
4 En package.json, reemplace el @tensorflow/tfjs-nododependencia con @tensorflow/tfjs-
nodo-gpu,pero mantenga el mismo número de versión porque los dos paquetes tienen
lanzamientos sincronizados.
5 Correrhilode nuevo, que descargará la biblioteca compartida que contiene las operaciones
matemáticas de CUDA para el uso de TensorFlow.js.
6 En main.js, reemplace la línea

require('@tensorflow/tfjs-nodo');
con
require('@tensorflow/tfjs-node-gpu');
7 Comience el entrenamiento de nuevo con

nodo principal.js

Si los pasos se realizan correctamente, su modelo avanzará rugiendo en su GPU CUDA, entrenando a una
velocidad que suele ser cinco veces la velocidad que puede obtener con la versión de CPU (tfjs-node). El
entrenamiento con la versión de CPU o GPU de tfjs-node es significativamente más rápido en
comparación con el entrenamiento del mismo modelo en el navegador.

TLLUVIA DE UN CONVENTO MEJORADO PARAMNISTEN TFJS-NODO


Una vez que se completa el entrenamiento en 20 épocas, el modelo debe mostrar una precisión de
prueba (o evaluación) final de aproximadamente 99,6 %, lo que supera el resultado anterior de 99,0 % que
logramos en la sección 4.2. Entonces, ¿cuáles son las diferencias entre este modelo basado en nodos
Más allá de los navegadores: entrenar modelos más rápido con Node.js 139

y el modelo basado en navegador que condujo a este aumento en la precisión? Después de todo, si
entrenas el mismo modelo en tfjs-node y la versión de navegador de TensorFlow.js usando los datos de
entrenamiento, deberías obtener los mismos resultados (excepto los efectos o la inicialización de pesos
aleatorios). Para responder a esta pregunta, veamos la definición del modelo basado en nodos. El modelo
se construye en el archivo model.js, que es importado por main.js.

Listado 4.5 Definiendo un convnet más grande para MNIST en Node.js

const modelo = tf.secuencial();


modelo.add(tf.layers.conv2d({
forma de entrada: [28, 28, 1],
filtros: 32,
tamaño del núcleo: 3,
activación: 'relú',
}));
modelo.add(tf.layers.conv2d({
filtros: 32,
tamaño del núcleo: 3,
activación: 'relú',
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
model.add(tf.layers.conv2d({
filtros: 64,
tamaño del núcleo: 3,
activación: 'relú',
}));
modelo.add(tf.layers.conv2d({
filtros: 64,
tamaño del núcleo: 3,
activación: 'relú',
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
model.add(tf.layers.flatten()); Agrega capas de abandono

modelo.add(tf.layers.dropout({rate: 0.25})); model.add(tf.layers.dense({unidades: para reducir el sobreajuste

512, activación: 'relu'})); modelo.add(tf.layers.dropout({rate: 0.5}));

model.add(tf.layers.dense({unidades: 10, activación: 'softmax'}));

Resumen Modelo();
modelo.compilar({
optimizador: 'rmsprop',
pérdida: 'categoricalCrossentropy',
métricas: ['precisión'],
});

El resumen del modelo es el siguiente:


_________________________________________________________________ Capa (tipo)
Forma de salida Parámetro #
================================================== ===============
conv2d_Conv2D1 (Conv2D) [nulo, 26, 26, 32] 320
_________________________________________________________________ conv2d_Conv2D2 (Conv2D)
[nulo, 24, 24, 32] 9248
_________________________________________________________________
140 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

max_pooling2d_MaxPooling2D1 [null,12,12,32] 0
_________________________________________________________________ conv2d_Conv2D3 (Conv2D)
[nulo,10,10,64] 18496
_________________________________________________________________ conv2d_Conv2D4 (Conv2D)
[nulo,8,8,64] 36928
_________________________________________________________________
max_pooling2d_MaxPooling2D2 [nulo,4,4,64] 0
_________________________________________________________________ flatten_Flatten1 (Flatten)
[nulo, 1024] 0
________________________________________________________________ abandono_Abandono1
(Abandono) [nulo, 1024] 0
________________________________________________________________ denso_Dense1 (Denso)
[nulo, 512] 524800
________________________________________________________________ abandono_Abandono2
(Abandono) [nulo, 512] 0
________________________________________________________________ denso_Dense2 (Denso)
[nulo, 10] 5130
================================================== ===============
Parámetros totales: 594922
Parámetros entrenables: 594922 Parámetros no entrenables: 0
_________________________________________________________________

Estas son las diferencias clave entre nuestro modelo tfjs-node y el modelo basado en navegador:

-El modelo basado en nodos tiene cuatro capas conv2d, una más en comparación con el modelo

basado en navegador.
- La capa densa oculta en el modelo basado en nodos tiene más unidades (512) en
comparación con la contraparte en el modelo basado en navegador (100).
- En general, el modelo basado en nodos tiene aproximadamente 18 veces más parámetros de peso que
el modelo basado en navegador.
- El modelo basado en nodos tiene dosAbandonarcapas insertadas entre las capas
planas y densas.

Las primeras tres diferencias en esta lista dan al modelo basado en nodos una mayor capacidad
que el modelo basado en navegador. También son los que hacen que el modelo basado en nodos
requiera demasiada memoria y cómputo para ser entrenado con una velocidad aceptable en el
navegador. Como aprendimos en el capítulo 3, con una mayor capacidad del modelo, existe un
mayor riesgo de sobreadaptación. El mayor riesgo de sobreajuste se reduce con la cuarta
diferencia, a saber, la inclusión de capas de abandono.

REDUCAR EL SOBREAJUSTE CON CAPAS DE SALIDA


Dropout es otro nuevo tipo de capa de TensorFlow.js que ha encontrado en este capítulo. Es una de
las formas más efectivas y ampliamente utilizadas para reducir el sobreajuste en las redes
neuronales profundas. Su funcionalidad se puede describir simplemente:

- Durante la fase de entrenamiento (duranteModelo.fit()llamadas), establece aleatoriamente una


fracción de los elementos en el tensor de entrada como cero (o "eliminados"), y el resultado es el
tensor de salida de la capa de abandono. A los efectos de este ejemplo, una capa de deserción
solo tiene un parámetro de configuración: la tasa de deserción (por ejemplo,
Más allá de los navegadores: entrenar modelos más rápido con Node.js 141

los doscalificarcampos como se muestra en el listado 4.5). Por ejemplo, suponga que una
capa de abandono está configurada para tener una tasa de abandono de 0,25 y el tensor de
entrada es un tensor 1D de valor [0,7, -0,3, 0,8, -0,4];el tensor de salida puede ser [0.7,
- 0,3, 0,0, 0,4]—con el 25% de los elementos del tensor de entrada seleccionados al azar y
establecidos en el valor 0. Durante la retropropagación, el tensor de gradiente en una capa de
abandono se ve afectado de manera similar por esta puesta a cero aleatoria.
- Durante la fase de inferencia (duranteModelo.predecir()yModelo.evaluar() llamadas), una
capa de abandono nonocero aleatoriamente elementos en el tensor de entrada. En su
lugar, la entrada simplemente se pasa como salida sin cambios (es decir, un mapeo de
identidad).

La figura 4.11 muestra un ejemplo de cómo funciona una capa de abandono con un tensor de entrada 2D en tiempo de
entrenamiento y tiempo de prueba.

–1 –1

Capacitación

fase 4 1

–1 0
–1 –1
0 3
4 1

–1 2
–1 –1 Figura 4.11 Un ejemplo de cómo funciona una capa de
exclusión. En este ejemplo, el tensor de entrada es 2D y tiene
–1 3
4 1 una forma de[4, 2]. La capa de abandono tiene su tasa
configurada como 0,25, lo que lleva a que el 25 % (es decir,
–1 2 dos de ocho) elementos del tensor de entrada se seleccionen
Inferencia
fase aleatoriamente y se establezcan en cero durante la fase de

–1 3 entrenamiento. Durante la fase de inferencia, la capa actúa


como un paso trivial.

Puede parecer extraño que un algoritmo tan simple sea una de las formas más efectivas de
combatir el sobreajuste. ¿Por qué funciona? Geoff Hinton, el inventor del algoritmo de abandono
(entre muchas otras cosas en las redes neuronales) dice que se inspiró en un mecanismo que
utilizan algunos bancos para evitar el fraude por parte de los empleados. En sus propias palabras,

Fui a mi banco. Los cajeros siguieron cambiando, y le pregunté a uno de ellos por qué. Dijo que no
sabía, pero que se movían mucho. Supuse que debía ser porque requeriría la cooperación entre los
empleados para defraudar con éxito al banco. Esto me hizo darme cuenta de que la eliminación
aleatoria de un subconjunto diferente de neuronas en cada ejemplo evitaría conspiraciones y, por
lo tanto, reduciría el sobreajuste.

Para poner esto en la jerga del aprendizaje profundo, la introducción de ruido en los valores de
salida de una capa rompe los patrones casuales que no son significativos con respecto a los
patrones reales en los datos (lo que Hinton denomina "conspiraciones"). En el ejercicio 3 al final de
este capítulo, debe intentar eliminar las dos capas de abandono del modelo basado en nodos.
142 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

convnet en model.js, vuelva a entrenar el modelo y vea cómo cambian las precisiones de entrenamiento,
validación y evaluación como resultado.
El Listado 4.6 muestra el código clave que usamos para entrenar y evaluar la convnet mejorada.
Si compara el código aquí con el del listado 4.2, puede apreciar la similitud entre los dos
fragmentos de código. Ambos están centrados alrededorModelo.fit()y Modelo.evaluar()llamadas La
sintaxis y el estilo son idénticos, excepto en lo que respecta a cómo se representan o muestran el
valor de pérdida, el valor de precisión y el progreso del entrenamiento en diferentes interfaces de
usuario (terminal versus navegador).
Esto muestra una característica importante de TensorFlow.js, un marco de aprendizaje
profundo de JavaScript que abarca la interfaz y el backend:

En cuanto a la creación y entrenamiento de modelos, el código que escribes en


TensorFlow.js es el mismo independientemente de si estás trabajando con el navegador
web o con Node.js.

Listado 4.6 Entrenamiento y evaluación del convnet mejorado en tfjs-node

espera model.fit(trainImages, trainLabels, {


épocas,
tamaño del lote,
validaciónDividir
});

const {imágenes: testImages, etiquetas: testLabels} = data.getTestData(); const evalOutput =


modelo.evaluar
Evalúa el modelo usando datos
imágenes de prueba, etiquetas de prueba);
que el modelo no ha visto
console.log('\nResultado de la evaluación:');
consola.log(
` Pérdida = ${evalOutput[0].dataSync()[0].toFixed(3)}; `+ `Precisión = $
{evalOutput[1].dataSync()[0].toFixed(3)}`);

4.3.2 Guardar el modelo de Node.js y cargarlo en el navegador


Entrenar su modelo consume recursos de CPU y GPU y lleva algo de tiempo. No querrás tirar
el fruto del entrenamiento. Sin guardar el modelo, tendrá que empezar desde cero la
próxima vez que ejecute main.js. Esta sección muestra cómo guardar el modelo después del
entrenamiento y exportar el modelo guardado como archivos en el disco (llamado controlo
unartefacto). También le mostraremos cómo importar el punto de control en el navegador,
reconstituirlo como modelo y usarlo para inferencias. La parte final de la principal()La función
en main.js consiste en el código de guardado del modelo en la siguiente lista.

Listado 4.7 Guardando el modelo entrenado en el sistema de archivos en tfjs-node

if (modeloSavePath != nulo) {
esperar model.save(`file://${modelSavePath}`); console.log(`Modelo
guardado en la ruta: ${modelSavePath}`);
}

lossalvar()metodo de lamodeloEl objeto se utiliza para guardar el modelo en un directorio de su


sistema de archivos. El método toma un solo argumento, que es una cadena de URL que comienza
Más allá de los navegadores: entrenar modelos más rápido con Node.js 143

con el archivo de esquema://. Tenga en cuenta que es posible guardar el modelo en el sistema de archivos
porque estamos usando tfjs-node. La versión de navegador de TensorFlow.js también proporciona la
modelo.guardar()API, pero no puede acceder directamente al sistema de archivos nativo de la máquina
porque el navegador lo prohíbe por razones de seguridad. Los destinos de almacenamiento que no sean
del sistema de archivos (como el almacenamiento local del navegador e IndexedDB) deberán usarse si
usamos TensorFlow.js en el navegador. Corresponden a esquemas de URL distintos de file://.
modelo.guardar()es una función asíncrona porque involucra entrada-salida de archivo o
red en general. Por lo tanto, usamos await en elsalvar()llamada. Suponermodelo-SavePathtiene
un valor /tmp/tfjs-node-mnist; después de lamodelo.guardar()completa la llamada, puede
examinar el contenido del directorio,

ls -lh /tmp/tfjs-node-mnist

que puede imprimir una lista de archivos como la siguiente:

- rw-r--r-- 1 grupo de usuarios 4.6K 14 de agosto 10:38 model.json


- rw-r--r-- 1 grupo de usuarios 2.3M 14 de agosto 10:38 pesos.bin

Allí, puedes ver dos archivos:

-model.json es un archivo JSON que contiene la topología guardada del modelo. Lo que
aquí se denomina "topología" incluye los tipos de capas que forman el modelo, sus
respectivos parámetros de configuración (comofiltrospara una capa conv2d ycalificar
para una capa de abandono), así como la forma en que las capas se conectan entre sí.
Las conexiones son simples para el convnet MNIST porque es un modelo secuencial.
Veremos modelos con patrones de conexión menos triviales, que también se pueden
guardar en disco conmodelo.guardar().
- Además de la topología del modelo, model.json también contiene un manifiesto de los
pesos del modelo. Esa parte enumera los nombres, formas y tipos de datos de todos los
pesos del modelo, además de las ubicaciones en las que se almacenan los valores de peso.
Esto nos lleva al segundo archivo: weights.bin.
Como su nombre lo indica, weights.bin es un archivo binario que almacena todos los
valores de peso del modelo. Es una corriente binaria plana sin demarcación de dónde
comienzan y terminan los pesos individuales. Esa "metainformación" está disponible en la
parte del manifiesto de pesos del objeto JSON en model.json.

Para cargar el modelo usando tfjs-node, puede usar eltf.loadLayersModel()método, apuntando


a la ubicación del archivo model.json (no se muestra en el código de ejemplo):

const modelo cargado = esperar tf.loadLayersModel('archivo:///tmp/tfjs-node-mnist');

tf.loadLayersModel()reconstituye el modelo deserializando los datos de topología guardados


en model.json. Luego,tf.loadLayersModel()lee los valores de ponderación binarios en
weights.bin utilizando el manifiesto en model.json y establece a la fuerza la ponderación del
modelo en esos valores. Me gustamodelo.guardar(), tf.loadLayersModel()es asíncrono, por lo
que usamos esperaral llamarlo aquí. Una vez que la llamada regresa, elmodelo cargadoEl
objeto es, para todos los efectos, equivalente al modelo creado y entrenado usando el código
Java-Script en los listados 4.5 y 4.6. Puede imprimir un resumen del modelo llamando a su
144 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

resumen()utilícelo para realizar inferencias llamando a supredecir()método, evalúe su exactitud usando el


evaluar()método, o incluso volver a entrenarlo usando elencajar() método. Si así lo desea, el modelo
también se puede guardar de nuevo. El flujo de trabajo de volver a entrenar y volver a guardar un modelo
cargado será relevante cuando hablemos sobre el aprendizaje de transferencia en el capítulo 5.

Lo dicho en el párrafo anterior también se aplica al entorno del navegador. Los archivos que
guardó se pueden usar para reconstituir el modelo en una página web. El modelo reconstituido es
compatible con la totalidadtf.LayersModel()flujo de trabajo, con la advertencia de que, si vuelve a
entrenar todo el modelo, será especialmente lento e ineficiente debido al gran tamaño de la
convnet mejorada. Lo único que es fundamentalmente diferente entre cargar un modelo en tfjs-
node y en el navegador es que debe usar un esquema de URL que no sea file:// en el navegador.
Por lo general, puede colocar los archivos model.json y weights.bin como archivos de activos
estáticos en un servidor HTTP. Suponga que su nombre de host es localhost y sus archivos se ven
en la ruta del servidor my/models/; puede usar la siguiente línea para cargar el modelo en el
navegador:

const modelo cargado =


esperar tf.loadLayersModel('http:///localhost/my/models/model.json');

Al manejar la carga del modelo basado en HTTP en el navegador,tf.loadLayersModel() llama a la


función de búsqueda integrada del navegador debajo del capó. Por lo tanto, tiene las siguientes
características y propiedades:

- Tanto http:// como https:// son compatibles.


- Se admiten rutas de servidor relativas. De hecho, si se utiliza una ruta relativa, se
puede omitir la parte http:// o https:// de la URL. Por ejemplo, si su página web está en
la ruta del servidor my/index.html y el archivo JSON de su modelo está en my/models/
model.json, puede usar la ruta relativa model/model.json:

const modelo cargado = esperar tf.loadLayersModel('modelos/modelo.json');

-Para especificar opciones adicionales para las solicitudes HTTP/HTTPS, el


tf.io.browserHTTPRequest()El método debe usarse en lugar del argumento de
cadena. Por ejemplo, para incluir credenciales y encabezados durante la carga del
modelo, puede hacer algo como
const modelo cargado = esperar tf.loadLayersModel(tf.io.browserHTTPRequest(
'http://foo.bar/path/to/model.json',
{credenciales: 'incluir', encabezados: {'clave_1': 'valor_1'}}));

4.4 Reconocimiento de palabras habladas: aplicación


de convnets en datos de audio
Hasta ahora, le hemos mostrado cómo usar convnets para realizar tareas de visión por computadora. Pero la
percepción humana no es sólo visión. El audio es una modalidad importante de datos de percepción y es
accesible a través de las API del navegador. ¿Cómo reconocer el contenido y el significado del habla y otros tipos
de sonidos? Sorprendentemente, las convnets no solo funcionan para la visión por computadora, sino que
también ayudan de manera significativa al aprendizaje automático relacionado con el audio.
Reconocimiento de palabras habladas: aplicación de convnets en datos de audio 145

En esta sección, verá cómo podemos resolver una tarea de audio relativamente
simple con una convnet similar a la que construimos para MNIST. La tarea consiste en
clasificar fragmentos breves de grabaciones de voz en unas 20 categorías de palabras.
Esta tarea es más simple que el tipo de reconocimiento de voz que puede ver en
dispositivos como Amazon Echo y Google Home. En particular, esos sistemas de
reconocimiento de voz involucran vocabularios más grandes que el usado en este
ejemplo. Además, procesan el habla conectada que consta de varias palabras habladas
en sucesión, mientras que nuestro ejemplo trata con palabras habladas una a la vez. Por
lo tanto, nuestro ejemplo no califica como un "reconocedor de voz"; en cambio, se
describe con mayor precisión como un "reconocedor de palabras" o "reconocedor de
comandos de voz". Sin embargo, nuestro ejemplo aún tiene usos prácticos (como
interfaces de usuario manos libres y funciones de accesibilidad).9

4.4.1 Espectrogramas: representación de sonidos como imágenes

Como en cualquier aplicación de aprendizaje profundo, si desea comprender cómo funciona el modelo, primero
debe comprender los datos. Para comprender cómo funcionan las conexiones de audio, primero debemos ver
cómo se representa el sonido como tensores. Recuerde de la física de la escuela secundaria que los sonidos son
patrones de cambios en la presión del aire. Un micrófono capta los cambios de presión del aire y los convierte en
señales eléctricas, que a su vez pueden ser digitalizadas por la tarjeta de sonido de una computadora. Los
navegadores web modernos cuentan con laWebAudioAPI, que se comunica con la tarjeta de sonido y brinda
acceso en tiempo real a las señales de audio digitalizadas (con el permiso otorgado por el usuario). Entonces,
desde el punto de vista de un programador de JavaScript, los sonidos son conjuntos de números con valores
reales. En el aprendizaje profundo, estas matrices de números generalmente se representan como tensores 1D.

Quizás se esté preguntando, ¿cómo pueden funcionar los tipos de convnets que hemos visto hasta
ahora en tensores 1D? ¿No se supone que operan con tensores que son al menos 2D? Las capas clave de
una convnet, incluidas conv2d y maxPooling2d, aprovechan las relaciones espaciales en espacios 2D.
resulta que suenalatarepresentarse como tipos especiales de imágenes llamadasespectrogramas. Los
espectrogramas no solo permiten aplicar convenciones sobre sonidos, sino que también tienen
justificaciones teóricas más allá del aprendizaje profundo.
Como muestra la figura 4.12, un espectrograma es una matriz 2D de números, que se pueden
mostrar como imágenes en escala de grises de la misma manera que las imágenes MNIST. La
dimensión horizontal es el tiempo, y la vertical es la frecuencia. Cada corte vertical de un
espectrograma es elespectrodel sonido dentro de una pequeña ventana de tiempo. Un espectro es
una descomposición de un sonido en diferentes componentes de frecuencia, que pueden
entenderse aproximadamente como diferentes "tonos". Así como un prisma puede dividir la luz en
varios colores, el sonido se puede descomponer mediante una operación matemática llamada
Transformada de Fourier en múltiples frecuencias. En pocas palabras, un espectrograma describe
cómo cambia el contenido de frecuencia del sonido en varias ventanas de tiempo cortas y sucesivas
(normalmente del orden de 20 milisegundos).

9
Ronan Collobert, Christian Puhrsch y Gabriel Synnaeve, "Wav2Letter: An End-to-End ConvNet-based Speech
Recognition System", presentado el 13 de septiembre de 2016,https://arxiv.org/abs/1609.03193.
146 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

Los espectrogramas son una representación adecuada del sonido por las siguientes razones. En
primer lugar, ahorran espacio: la cantidad de números flotantes en un espectrograma suele ser varias
veces menor que la cantidad de valores flotantes en la forma de onda sin procesar. En segundo lugar, en
un sentido amplio, los espectrogramas corresponden a cómo funciona la audición en biología. Una
estructura anatómica dentro del oído interno llamada cóclea esencialmente realiza la versión biológica de
la transformada de Fourier. Descompone los sonidos en diferentes frecuencias, que luego son captadas
por diferentes conjuntos de neuronas auditivas. En tercer lugar, la representación del espectrograma de
los sonidos del habla hace que los diferentes tipos de sonidos del habla sean más fáciles de distinguir
entre sí. Esto se muestra en los espectrogramas de voz de ejemplo en la figura 4.12: todas las vocales y
consonantes tienen diferentes patrones definitorios en sus espectrogramas. Hace decadas, Antes de la
adopción generalizada del aprendizaje automático, las personas que trabajaban en el reconocimiento de
voz en realidad intentaron elaborar reglas que detectaran diferentes vocales y consonantes a partir de
espectrogramas. El aprendizaje profundo nos ahorra los problemas y las lágrimas de tal artesanía.

"cero" "sí"
frecuencia Frecuencia
z yo:r UNED y mi s
5kHz 5kHz

Hora Hora
0 1 segundo
0 1 segundo

Figura 4.12 Espectrogramas de ejemplo de las palabras habladas aisladas "cero" y "sí". Un espectrograma es una representación conjunta de
tiempo y frecuencia del sonido. Puedes pensar en un espectrograma como un sonido representado como una imagen. Cada segmento a lo
largo del eje del tiempo (una columna de la imagen) es un breve momento (fotograma) en el tiempo; cada segmento a lo largo del eje de
frecuencia (una fila de la imagen) corresponde a un rango de frecuencia estrecho particular (tono). El valor en cada píxel de la imagen
representa la energía relativa del sonido en el intervalo de frecuencia dado en un momento dado. Los espectrogramas de esta figura se
representan de tal manera que un tono de gris más oscuro corresponde a una mayor cantidad de energía. Los diferentes sonidos del habla
tienen diferentes características definitorias. Por ejemplo, las consonantes sibilantes como "z" y "s" se caracterizan por una energía de estado
casi estable concentrada en frecuencias superiores a 2–3 kHz; Los sonidos de las vocales como "e" y "o" se caracterizan por franjas horizontales
(picos de energía) en el extremo inferior del espectro (< 3 kHz). Estos picos de energía se llamanformantesen acústica. Diferentes vocales tienen
diferentes frecuencias de formantes. Todas estas características distintivas de diferentes sonidos del habla pueden ser utilizadas por una red
profunda para el reconocimiento de palabras.

Detengámonos y pensemos por un momento. Mirando las imágenes MNIST en la figura 4.1 y los
espectrogramas de voz en la figura 4.12, debería poder apreciar la similitud entre los dos conjuntos de
datos. Ambos conjuntos de datos contienen patrones en un espacio de características 2D, que un par de
ojos entrenados deberían poder distinguir. Ambos conjuntos de datos muestran cierta aleatoriedad en la
ubicación detallada, el tamaño y los detalles de las características. Finalmente, ambas son tareas de
clasificación multicategoría. Si bien MNIST contiene 10 clases posibles, nuestro conjunto de datos de
comandos de voz contiene 20 (los 10 dígitos del 0 al 9, "arriba", "abajo", "izquierda", "derecha", "ir",
"detener", "sí" y “no”, además de la categoría de palabras “desconocidas” y ruido de fondo). Son
exactamente estas similitudes en la esencia de los conjuntos de datos las que hacen que los convnets
sean adecuados para la tarea de reconocimiento de comandos de voz.
Reconocimiento de palabras habladas: aplicación de convnets en datos de audio 147

Pero también hay algunas diferencias notables entre los dos conjuntos de datos. En primer lugar, las
grabaciones de audio en el conjunto de datos de comandos de voz tienen algo de ruido, como puede ver
en las motas de píxeles oscuros que no pertenecen al sonido del habla en los espectrogramas de ejemplo
de la figura 4.12. En segundo lugar, cada espectrograma en el conjunto de datos de comandos de voz
tiene un tamaño de 43 × 232, que es significativamente mayor en comparación con el tamaño de 28 × 28
de las imágenes individuales del MNIST. El tamaño del espectrograma es asimétrico entre las
dimensiones de tiempo y frecuencia. Estas diferencias se reflejarán en la convnet que usaremos en el
conjunto de datos de audio.
El código que define y entrena el convnet de comandos de voz vive en el repositorio de
tfjsmodels. Puede acceder al código con los siguientes comandos:

git clonar https://github.com/tensorflow/tfjs-models.git comandos de


voz/entrenamiento/navegador-fft
discos compactos

La creación y compilación del modelo está encapsulada en elcrearModelo() función


en model.ts.

Listado 4.8 Convnet para clasificar espectrogramas de comandos de voz

function createModel(inputShape: tf.Shape, numClasses: number) {


const modelo = tf.secuencial();
modelo.add(tf.layers.conv2d({
Repetición de motivos de
filtros: 8,
conv2d+maxPooling2d
tamaño del núcleo: [2, 8],
activación: 'relú',
forma de entrada
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); modelo.añadir(
tf.capas.conv2d({
filtros: 32,
tamaño del núcleo: [2, 4],
activación: 'relú'
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); modelo.añadir(

tf.capas.conv2d({
filtros: 32,
tamaño del núcleo: [2, 4],
activación: 'relú'
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]})); modelo.añadir(

tf.capas.conv2d({
filtros: 32,
multicapa tamaño del núcleo: [2, 4],
perceptrón activación: 'relú'
comienza }));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [1, 2]}));
modelo.add(tf.layers.flatten());
modelo.add(tf.layers.dropout({rate: 0.25})); model.add(tf.layers.dense({unidades:
Utiliza abandono para
2000, activación: 'relu'})); modelo.add(tf.layers.dropout({rate: 0.5})); reducir el sobreajuste
148 CPASADO4Reconocimiento de imágenes y sonidos usando convnets

model.add(tf.layers.dense({unidades: numClasses, activación: 'softmax'}));

modelo.compilar({
Configura la pérdida y la métrica para
pérdida: 'categoricalCrossentropy',
la clasificación multicategoría
optimizador: tf.train.sgd(0.01), métricas:
['precisión']
});
Resumen Modelo();
modelo de retorno;
}

La topología de nuestra convnet de audio se parece mucho a las convnets MNIST. El modelo
secuencial comienza con varios motivos repetitivos de capas conv2d emparejadas con capas max-
Pooling2d. La parte de agrupación de convolución del modelo termina en una capa plana, encima
de la cual se agrega un MLP. El MLP tiene dos capas densas. La capa densa oculta tiene una
activación relu, y la final (salida) tiene una activación softmax que se adapta a la tarea de
clasificación. El modelo está compilado para usarCruzentropía categórica como la función de pérdida
y emitir la métrica de precisión durante el entrenamiento y la evaluación. Esto es exactamente lo
mismo que los convenios MNIST porque ambos conjuntos de datos involucran una clasificación de
múltiples categorías. El convnet de audio también muestra algunas diferencias interesantes con
respecto al MNIST. En particular, elkernelSizepropiedades de las capas conv2d son rectangulares
(por ejemplo, [2, 8])en lugar de forma cuadrada. Estos valores se seleccionan para que coincidan
con la forma no cuadrada de los espectrogramas, que tienen una dimensión de frecuencia mayor
que la dimensión temporal.
Para entrenar el modelo, primero debe descargar el conjunto de datos de comandos de voz. El
conjunto de datos se originó a partir del conjunto de datos de comandos de voz recopilados por Pete
Warden, un ingeniero del equipo de Google Brain (verwww.tensorflow.org/tutorials/sequences/
audio_recognition). Se ha convertido a un formato de espectrograma específico del navegador:

rizo - fSsL https://storage.googleapis.com/learnjs-data/speechcommands/speech-commands-data-


v0.02-browser.tar.gz -o speech-commandsdata-v0.02-browser.tar.gz &&

alquitrán xzvf voz-comandos-datos-v0.02-navegador.tar.gz

Estos comandos descargarán y extraerán la versión del navegador del conjunto de datos del
comando de voz. Una vez que se han extraído los datos, puede iniciar el proceso de
capacitación con este comando:

hilo
hilo entrenar \
navegador de datos de comandos de voz/ \ /
tmp/modelo de comandos de voz/

El primer argumento a latren de hilopuntos de comando en la ubicación de los datos de


entrenamiento. Los siguientes argumentos especifican la ruta en la que se guardará el archivo
JSON del modelo, junto con el archivo de peso y el archivo JSON de metadatos. Al igual que cuando
entrenamos el convnet MNIST mejorado, el entrenamiento del convnet de audio ocurre en tfjs-
node, con el potencial de utilizar GPU. Debido a que los tamaños del conjunto de datos y el modelo
son más grandes que el convnet MNIST, el entrenamiento llevará más tiempo (del orden
Reconocimiento de palabras habladas: aplicación de convnets en datos de audio 149

de unas horas). Puede obtener una aceleración significativa del entrenamiento si tiene una GPU CUDA y
cambia el comando ligeramente para usar tfjs-node-gpu en lugar del tfjsnode predeterminado (que se
ejecuta solo en una CPU). Para hacer eso, simplemente agregue la bandera:GPUal comando anterior:

tren de hilo \
- - GPU \
navegador-de-datos-de-comandos-de- \
voz/ /tmp/modelo-de-comandos-de-voz/

Cuando finaliza el entrenamiento, el modelo debe alcanzar una precisión de evaluación final (prueba) de
aproximadamente el 94 %.
El modelo entrenado se guarda en la ruta especificada por la bandera en el
comando anterior. Al igual que el convnet MNIST que entrenamos con tfjs-node, el
modelo guardado se puede cargar en el navegador para servir. Sin embargo, debe
estar familiarizado con la API de audio web para poder adquirir datos del micrófono
y preprocesarlos en un formato que pueda usar el modelo. Para su comodidad,
escribimos una clase contenedora que no solo carga una convnet de audio
entrenada, sino que también se encarga de la ingesta y el preprocesamiento de
datos. Si está interesado en los mecanismos de la canalización de entrada de datos
de audio, puede estudiar el código subyacente en el repositorio Git de tfjs-model,
en la carpeta speech-commands/src. El contenedor está disponible a través de npm
con el nombre @tensorflow-models/speech-commands. Listado 4.

En la carpeta speech-commands/demo del repositorio de tfjs-models, puede encontrar un


ejemplo menos básico de cómo usar el paquete. Para clonar y ejecutar la demostración, ejecute los
siguientes comandos en el directorio de comandos de voz:

git clonar https://github.com/tensorflow/tfjs-models.git modelos tfjs/


comandos de voz
discos compactos

yarn && yarn publique-cd de


demostración local
hilado && hilado link-local && hilado reloj

losreloj de hiloEl comando abrirá automáticamente una nueva pestaña en su navegador web
predeterminado. Para ver el reconocedor de comandos de voz en acción, asegúrese de que su máquina
tenga un micrófono listo (que es lo que tienen la mayoría de las computadoras portátiles). Cada vez que
se reconozca una palabra dentro del vocabulario, se mostrará en la pantalla junto con el espectrograma
de un segundo de duración que contiene la palabra. Por lo tanto, este es un reconocimiento de una sola
palabra basado en el navegador en acción, impulsado por la API de WebAudio y una red profunda.
¿Seguramente no tiene la capacidad de reconocer el habla conectada con la gramática? Eso requerirá la
ayuda de otros tipos de bloques de construcción de redes neuronales capaces de procesar información
secuencial. Los visitaremos en el capítulo 8.
150 CPASADO4 Reconocimiento de imágenes y sonidos usando convnets

Listado 4.9 Ejemplo de uso del módulo @tensorflow-models/speech-commands

Crea una instancia del reconocedor de


Importa el módulo de comandos de voz.
comandos de voz que utiliza la transformada
Asegúrese de que aparezca como una
rápida de Fourier (FFT) integrada en el
dependencia en package.json.
navegador.

importar * como SpeechCommands desde


'@tensorflow-models/speech-commands'; Puede examinar qué etiquetas de
palabras (incluidas las etiquetas
reconocedor constante = "ruido de fondo" y "desconocido") es
SpeechCommands.create('BROWSER_FFT'); capaz de reconocer el modelo.

consola.log(recognizer.wordLabels());

reconocedor.escucha(resultado => {
dejar maxIndex; result.scores contiene las puntuaciones

let maxScore = -Infinito; de probabilidad que corresponden


areconocer.wordLabels().
result.scores.forEach((puntuación, i) => {
if (puntuación > puntuación máxima) {
maxÍndice = i; Encuentra el índice de la palabra
maxScore = puntuación; con la puntuación más alta
}
});
console.log(`Detectado palabra ${recognizer.wordLabels()[maxIndex]}`);
}, {
umbral de probabilidad: 0.75
});
setTimeout(() => reconocedor.stopStreaming(), 10e3);
Detiene el en línea
reconocimiento de transmisión
Inicia el reconocimiento de transmisión en línea. El primer
en10 segundos
argumento es una devolución de llamada, que se invocará
cada vez que se reconozca una palabra no desconocida y sin
ruido de fondo con una probabilidad superior al umbral (0,75
en este caso).

Ejercicios
1 El convnet para clasificar imágenes MNIST en el navegador (listado 4.1) tiene dos
grupos de capas conv2d y maxPooling2d. Modifique el código para reducir el número
a un solo grupo. Responde las siguientes preguntas:
a ¿Cómo afecta eso al número total de parámetros entrenables del convnet? ¿Cómo afecta
B eso a la velocidad de entrenamiento?
C ¿Cómo afecta eso a la precisión final obtenida por el convnet después del
entrenamiento?
2 Este ejercicio es similar al ejercicio 1. Pero en lugar de jugar con la cantidad de grupos
de capas conv2d-maxPooling2d, experimente con la cantidad de capas densas en la
parte MLP de la convnet en el listado 4.1. ¿Cómo cambia la cantidad total de
parámetros, la velocidad de entrenamiento y la precisión final si elimina la primera
capa densa y mantiene solo la segunda (salida)?
Resumen 151

3 Elimine la deserción de la convnet en mnist-node (listado 4.5) y vea qué sucede


con el proceso de capacitación y la precisión final de la prueba. ¿Por qué sucede
eso? ¿Qué muestra eso?
4 Como práctica usando eltf.navegador.fromPixels()método para extraer datos de imágenes de
elementos relacionados con imágenes y videos de una página web, intente lo siguiente:
a Utilizartf.navegador.fromPixels()para obtener un tensor que represente una imagen JPG en
color usando unimagenetiqueta.
– ¿Cuáles son la altura y el ancho del tensor de imagen devuelto por
tf.browser.fromPixels()?¿Qué determina la altura y el ancho?
– Cambie el tamaño de la imagen a una dimensión fija de 100 × 100 (alto × ancho) usando
tf.image.resizeBilinear().
– Repita el paso anterior, pero utilizando la función de cambio de tamaño alternativo
tf.image.resizeNearestNeighbor()en lugar de. ¿Puedes ver alguna diferencia-
diferencias entre los resultados de estas dos funciones de redimensionamiento?

B Cree un lienzo HTML y dibuje algunas formas arbitrarias en él usando funciones


comorect().O, si lo desea, puede usar bibliotecas más avanzadas como d3.js o
three.js para dibujar formas 2D y 3D más complicadas. Luego, obtenga los datos
del tensor de imagen del lienzo usandotf.navegador.fromPixels().

Resumen
-Convnets extrae características espaciales 2D de las imágenes de entrada con una jerarquía de
capas apiladas conv2d y maxPooling2d.
- Las capas conv2d son filtros espaciales multicanal ajustables. Tienen las propiedades de localidad
y uso compartido de parámetros, lo que los convierte en poderosos extractores de características
y transformaciones de representación eficientes.
- Las capas maxPooling2d reducen el tamaño del tensor de imagen de entrada al calcular el
máximo dentro de ventanas de tamaño fijo, logrando una mejor invariancia posicional. La
- “torre” conv2d-maxPooling2d de una convnet generalmente termina en una capa plana,
seguida de una MLP hecha de capas densas para tareas de clasificación o regresión.

- Con sus recursos restringidos, el navegador solo es adecuado para entrenar modelos
pequeños. Para entrenar modelos más grandes, se recomienda usar tfjs-node, la versión
Node.js de TensorFlow.js; tfjs-node puede usar los mismos núcleos paralelos de CPU y GPU
que usa la versión Python de TensorFlow.
- Con mayores capacidades del modelo, surgen mayores riesgos de sobreajuste. El sobreajuste se puede
mejorar agregando capas de abandono en una convnet. Las capas de abandono ponen a cero
aleatoriamente una fracción dada de los elementos de entrada durante el entrenamiento.
- Los convnets son útiles no solo para tareas de visión artificial. Cuando las señales de audio se
representan como espectrogramas, se les pueden aplicar convenciones para lograr también
buenas precisiones de clasificación.
Transferencia de aprendizaje: reutilización
de redes neuronales preentrenadas

Este capítulo cubre


- Qué es el aprendizaje por transferencia y por qué es mejor que
entrenar modelos desde cero para muchos tipos de problemas

- Cómo aprovechar el poder de extracción de características de los


convennets preentrenados de última generación al convertirlos de Keras a
TensorFlow.js

- Los mecanismos detallados de las técnicas de aprendizaje por transferencia,


incluida la congelación de capas, la creación de nuevos cabezales de transferencia
y el ajuste fino

- Cómo usar el aprendizaje por transferencia para entrenar un modelo


simple de detección de objetos en TensorFlow.js

En el capítulo 4, vimos cómo entrenar convnets para clasificar imágenes. Ahora considere el siguiente
escenario. Nuestro convnet para clasificar dígitos escritos a mano funciona mal para un usuario
porque su escritura a mano es muy diferente de los datos de entrenamiento originales. ¿Podemos
mejorar el modelo para servir mejor al usuario utilizando una pequeña cantidad de datos (digamos, 50
ejemplos) que podemos recopilar de ellos? Considere otro escenario:

152
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 153

un sitio web de comercio electrónico desea clasificar automáticamente las imágenes de los artículos cargados por
los usuarios. Pero ninguno de los convennets disponibles públicamente (como MobileNet1) están entrenados en
dichas imágenes de dominio específico. ¿Es posible usar un modelo de imagen disponible públicamente para
abordar el problema de la clasificación personalizada, dado un número modesto (digamos, unos pocos cientos)
de imágenes etiquetadas?
Afortunadamente, una técnica llamadatransferir el aprendizaje, el enfoque principal de este capítulo, puede ayudar a

resolver tareas como estas.

5.1 Introducción al aprendizaje por transferencia:


reutilización de modelos preentrenados
En esencia, el aprendizaje por transferencia consiste en acelerar una nueva tarea de aprendizaje mediante la
reutilización de los resultados del aprendizaje anterior. Implica el uso de un modelo ya entrenado en un conjunto
de datos para realizar unadiferente pero relacionadotarea de aprendizaje automático. El modelo ya entrenado se
conoce como elmodelo básico. Transferir el aprendizaje a veces implica volver a entrenar el modelo base y, a
veces, implica crear un nuevo modelo sobre el modelo base. Nos referimos al nuevo modelo como elmodelo de
transferencia. Como muestra la figura 5.1, la cantidad de datos utilizados para este proceso de reentrenamiento
suele ser mucho menor en comparación con los datos que se usaron para entrenar el modelo base (como en los
dos ejemplos dados al principio de este capítulo). Como tal, el aprendizaje de transferencia suele consumir
mucho menos tiempo y recursos en comparación con el proceso de capacitación del modelo base. Esto hace que
sea factible realizar transferencias de aprendizaje en un entorno con recursos restringidos, como el navegador
que usa TensorFlow.js. Y, por lo tanto, el aprendizaje por transferencia es un tema importante para los
estudiantes de TensorFlow.js.

Transferir
Inicial aprendiendo

Grande capacitación Base (más rápido y más ligero)

(largo y pesado) modelo Nuevo modelo


conjunto de datos

Menor
conjunto de datos

Figura 5.1 El flujo de trabajo general del aprendizaje por transferencia. Un gran conjunto de datos entra en el entrenamiento del modelo
base. Este proceso de entrenamiento inicial suele ser largo y computacionalmente pesado. Luego, el modelo base se vuelve a entrenar,
posiblemente convirtiéndose en parte de un nuevo modelo. El proceso de reentrenamiento generalmente involucra un conjunto de datos
mucho más pequeño que el original. El cómputo involucrado en el reentrenamiento es significativamente menor que el entrenamiento
inicial y puede ocurrir en un dispositivo perimetral, como una computadora portátil o un teléfono que ejecute TensorFlow.js.

1
Andrew G. Howard et al., "MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications",
presentado el 17 de abril de 2017,https://arxiv.org/abs/1704.04861.
154 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

La frase clave “diferente pero relacionado” en la descripción del aprendizaje por transferencia puede
significar diferentes cosas en diferentes casos:

- El primer escenario mencionado al principio de este capítulo consiste en adaptar un modelo a los
datos de un usuario específico. Aunque los datos son diferentes del conjunto de entrenamiento
original, la tarea es exactamente la misma: clasificar una imagen en 10 dígitos. Este tipo de
transferencia de aprendizaje se conoce comoadaptación del modelo. Otros problemas de
- transferencia de aprendizaje involucran objetivos (etiquetas) que son diferentes de los originales.
El escenario de clasificación de imágenes de mercancías mencionado al comienzo de este capítulo
pertenece a esta categoría.

¿Cuál es la ventaja de transferir el aprendizaje sobre entrenar un nuevo modelo desde cero?
La respuesta es doble:

-El aprendizaje por transferencia es más eficiente en términos tanto de la cantidad de datos que
requiere como de la cantidad de cómputo que requiere.
- Se basa en las ganancias del entrenamiento previo al reutilizar el poder de extracción de
características del modelo base.

Estos puntos son válidos independientemente del tipo de problema (por ejemplo, clasificación y
regresión). En el primer punto, el aprendizaje por transferencia utiliza los pesos entrenados del modelo
base (o un subconjunto de ellos). Como resultado, requiere menos datos de entrenamiento y tiempo de
entrenamiento para converger a un nivel dado de precisión en comparación con entrenar un nuevo
modelo desde cero. En este sentido, el aprendizaje por transferencia es similar a cómo los humanos
aprenden nuevas tareas: una vez que dominas una tarea (jugar un juego de cartas, por ejemplo),
aprender tareas similares (como jugar juegos de cartas similares) se vuelve significativamente más fácil y
rápido en el futuro. . El costo ahorrado del tiempo de capacitación puede parecer relativamente pequeño
para una red neuronal como la convnet que construimos para MNIST. Sin embargo, para modelos más
grandes entrenados en conjuntos de datos más grandes (como convnets a escala industrial entrenados
en terabytes de datos de imagen),
En el segundo punto, la idea central del aprendizaje por transferencia es la reutilización de los resultados de
capacitación anteriores. Mediante el aprendizaje de un conjunto de datos muy grande, la red neuronal original se
ha vuelto muy buena para extraer características útiles de los datos de entrada originales. Estas funciones serán
útiles para la nueva tarea siempre que los nuevos datos en la tarea de transferencia de aprendizaje no sean muy
diferentes de los datos originales. Los investigadores han reunido conjuntos de datos muy grandes para
dominios comunes de aprendizaje automático. En visión artificial, está Image-Net,2que contiene millones de
imágenes etiquetadas de unas mil categorías. Los investigadores de aprendizaje profundo han entrenado redes
de convención profundas utilizando el conjunto de datos de ImageNet, incluidos ResNet, Inception y MobileNet
(el último de los cuales pronto tendremos en nuestras manos). Debido a la gran cantidad y diversidad de
imágenes en ImageNet, las convenciones entrenadas en él son buenos extractores de características para tipos
generales de imágenes. Estos extractores de características serán útiles para trabajar con conjuntos de datos
pequeños como los de los escenarios antes mencionados, pero entrenar extractores de características tan
efectivos es imposible con pequeños conjuntos de datos.

2 No te confundas con el nombre. "ImageNet" se refiere a un conjunto de datos, no a una red neuronal.
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 155

conjuntos de datos como esos. Las oportunidades para transferir el aprendizaje también existen en otros dominios. Por
ejemplo, en el procesamiento del lenguaje natural, las personas han entrenado incrustaciones de palabras (es decir, la
representación vectorial de todas las palabras comunes en un idioma) en grandes corpus de texto que consisten en miles
de millones de palabras. Estas incrustaciones son útiles para tareas de comprensión de idiomas en las que hay disponibles
conjuntos de datos de texto mucho más pequeños. Sin más preámbulos, veamos cómo funciona el aprendizaje por
transferencia en la práctica a través de un ejemplo.

5.1.1 Transferencia de aprendizaje basada en formas de salida compatibles:


congelación de capas

Comencemos mirando un ejemplo relativamente simple. Entrenaremos un convnet solo en


los primeros cinco dígitos del conjunto de datos MNIST (0 a 4). Luego usaremos el modelo
resultante para reconocer los cinco dígitos restantes (del 5 al 9), que el modelo nunca vio
durante el entrenamiento original. Aunque este ejemplo es algo artificial, ilustra el flujo de
trabajo básico del aprendizaje por transferencia. El ejemplo se puede verificar y ejecutar con
los siguientes comandos:

git clonhttps://github.com/tensorflow/tfjs-examples.git tfjs-examples/


mnist-transfer-cnn
discos compactos

hilo y reloj de hilo

En la página web de demostración que se abre, inicie el proceso de transferencia de aprendizaje haciendo clic en
el botón Reentrenar. Puede ver que el proceso alcanza una precisión de alrededor del 96 % en el nuevo conjunto
de cinco dígitos (del 5 al 9), lo que lleva unos 30 segundos en una computadora portátil razonablemente potente.
Como mostraremos, esto es significativamente más rápido que la alternativa de aprendizaje sin transferencia (es
decir, entrenar un nuevo modelo desde cero). Veamos cómo se hace esto, paso a paso.
Nuestro ejemplo carga el modelo base preentrenado desde un servidor HTTP en lugar de
entrenarlo desde cero para no ocultar las partes clave del flujo de trabajo. Recuerde de la sección
4.3.3 que TensorFlow.js proporciona latf.loadLayersModel()método para cargar modelos
preentrenados. Esto se llama en el archivo loader.js:

const modelo = espera tf.loadLayersModel(url); Resumen


Modelo();

El resumen impreso del modelo se parece a la figura 5.2. Como puedes ver, este modelo consta de 12
capas.3Todos sus aproximadamente 600 000 parámetros de peso se pueden entrenar, al igual que todos
los modelos de TensorFlow.js que hemos visto hasta ahora. Tenga en cuenta quecargarLayersModel()carga
no solo la topología del modelo sino también todos sus valores de peso. Como resultado, el modelo
cargado está listo para predecir la clase de dígitos del 0 al 4. Sin embargo, no es así como usaremos el
modelo. En su lugar, entrenaremos al modelo para que reconozca nuevos dígitos (del 5 al 9).

3
Puede que no hayas visto laactivacióntipo de capa en este modelo. Las capas de activación son capas simples que
realizan solo una función de activación (como relu y softmax) en la entrada. Suponga que tiene una capa densa
con la activación predeterminada (lineal); apilar una capa de activación encima es equivalente a usar una capa
densa con la activación no predeterminada incluida. Esto último es lo que hicimos para los ejemplos del capítulo
4. Pero el primer estilo también se ve a veces. En TensorFlow.js, puede obtener una topología de modelo de este
tipo utilizando un código como el siguiente: const modelo = tf.secuencial(); model.add(tf.layers.dense({untis: 5,
inputShape})); model.add(tf.layers.activation({activation: 'relu'}).
156 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

________________________________________________________________ Capa (tipo)


Forma de salida Parámetro #
================================================== =============== conv2d_1
(Conv2D) [nulo, 26, 26, 32] 320
_________________________________________________________________ activación_1 (Activación)
[null,26,26,32] 0
_________________________________________________________________ conv2d_2 (Conv2D)
[nulo, 24, 24, 32] 9248
_________________________________________________________________ activación_2 (Activación)
[null,24,24,32] 0 Se establecerá como no entrenable
_________________________________________________________________ max_pooling2d_1 (congelado) durante
(MaxPooling2 [null,12,12,32] 0 transferir el aprendizaje
_________________________________________________________________
dropout_1 (Abandono) [nulo,12,12,32] 0
_________________________________________________________________ flatten_1 (Aplanar)
[nulo,4608] 0
________________________________________________________________ denso_1 (Denso)
[nulo, 128] 589952
_________________________________________________________________ activación_3 (Activación) [nulo,
128] 0
________________________________________________________________________________ deserción_2
(Abandono) [nulo, 128] 0
________________________________________________________________ denso_2 (Denso)
[nulo,5] 645
_________________________________________________________________ activación_4 (Activación) [nulo,
5] 0
================================================== =============== Parámetros
totales: 600165
Parámetros entrenables: 600165 Parámetros no entrenables: 0
_________________________________________________________________

Figura 5.2 Un resumen impreso del convnet para reconocimiento de imágenes MNIST y transferencia de aprendizaje

Mirando la función de devolución de llamada para el botón Reentrenar (en elreentrenarModelo()función de


index.js), notará algunas líneas de código que configuran elentrenablepropiedad de las primeras siete
capas del modelo parafalsosi la opción Congelar capas de entidades está seleccionada (está seleccionada
de forma predeterminada).
¿Qué hace eso? Por defecto, elentrenablepropiedad de cada una de las capas del modelo escierto
después de cargar el modelo a través delcargarLayersModel()método o creado desde cero. los
entrenablepropiedad se utiliza durante el entrenamiento (es decir, llamadas al encajar()o
ajusteDataset()método). Le dice al optimizador si los pesos de la capa deben actualizarse. De forma
predeterminada, los pesos de todas las capas de un modelo se actualizan durante el
entrenamiento. Pero si establece la propiedad enfalsopara algunas de las capas del modelo, los
pesos de esas capas seránnoactualizarse durante el entrenamiento. En la terminología de
TensorFlow.js, esas capas se convierteninentrenable, ocongelado. El código del listado 5.1 congela
las primeras siete capas del modelo, desde la capa conv2d de entrada hasta la capa plana, mientras
deja las últimas capas (las capas densas) entrenables.

Listado 5.1 “Congelando” las primeras capas de la convnet para transferencia de aprendizaje

const TrainingMode = ui.getTrainingMode(); if (trainingMode


=== 'congelar-características-capas') {
console.log('Congelando capas de características del modelo.'); para (sea
i = 0; i < 7; ++i) {
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 157

este.modelo.capas[i].entrenable = falso; Congela la capa


}
} else if (trainingMode === 'reinicializar-pesos') { Hace un nuevo modelo con
const returnString = falso; la misma topología que el
this.model = esperar tf.models.modelFromJSON({ anterior, pero con
modelTopology: this.model.toJSON(null, }); cadena de retorno) reinicializado
valores de peso

}
este.modelo.compilar({
pérdida: 'categoricalCrossentropy', La congelación no tendrá efecto
optimizador: tf.train.adam(0.01), métricas: durante las llamadas a fit() a menos que
['acc'], compile el modelo primero.
});
Imprime el resumen del modelo
este.modelo.resumen(); nuevamente después de compilar().
Debería ver que varios de los pesos del
modelo se han vuelto no entrenables.

Sin embargo, establecer las capasentrenablepropiedad por sí sola no es suficiente: si sólo


modifica laentrenablepropiedad y llamar al modeloencajar()método de inmediato, verá que los
pesos de esas capas aún se actualizan durante elencajar()llamada. tienes que llamar
Modelo.compilar()antes de llamarModelo.fit()para elentrenablepropiedad
los cambios surtan efecto, como se hace en el listado 5.1. Mencionamos anteriormente que el
compilar()call configura el optimizador, la función de pérdida y las métricas. Sin embargo, el método
también permite que el modelo actualice la lista de variables de peso que se actualizarán durante
esas llamadas. Después de lacompilar()llama, llamamosresumen()de nuevo para imprimir un nuevo
resumen del modelo. Como puede ver al comparar el nuevo resumen con el anterior en la figura
5.2, algunos de los pesos del modelo se vuelven no entrenables:

Parámetros totales: 600165


Parámetros entrenables: 590597
Parámetros no entrenables:9568

Puede verificar que la cantidad de parámetros no entrenables, 9568, es la suma de los


parámetros de peso en las dos capas congeladas con pesos (las dos capas conv2d). Tenga en
cuenta que algunas de las capas que congelamos no contienen ponderaciones (como la capa
maxPooling2d y la capa flatten) y, por lo tanto, no contribuyen al recuento de parámetros no
entrenables cuando están congeladas.
El código de transferencia de aprendizaje real se muestra en el listado 5.2. Aquí, usamos el mismo
encajar()método que hemos usado para entrenar modelos desde cero. En esta llamada, usamos el
Validación de datoscampo para obtener una medida de la precisión del modelo con los datos que no ha
visto durante el entrenamiento. Además, conectamos dos devoluciones de llamada alencajar()llamada, una
para actualizar la barra de progreso en la interfaz de usuario y la otra para trazar las curvas de pérdida y
precisión usando el módulo tfjs-vis (más detalles en el capítulo 7). Esto muestra un aspecto de laencajar()
API que no hemos mencionado antes: puede dar una devolución de llamada o una serie de múltiples
devoluciones de llamada a unencajar()llamada. En el último caso, se invocarán todas las devoluciones de
llamada (en el orden en que se especifican en la matriz) durante el entrenamiento.
158 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

Listado 5.2 UsandoModelo.fit()para realizar transferencia de aprendizaje

esperar this.model.fit(this.gte5TrainData.x, this.gte5TrainData.y, {


tamaño del lote: tamaño del lote,
épocas: épocas,
datos de validación: [este.gte5TestData.x, este.gte5TestData.y],
devoluciones de llamada: [
Se permite dar múltiples devoluciones de
ui.getProgressBarCallbackConfig(épocas),
llamada a una llamada fit().

tfVis.show.fitCallbacks(surfaceInfo, ['val_loss', 'val_acc'], {


zoomToFit: verdadero,
zoomToFitAccuracy: verdadero,
altura: 200, Utiliza tfjs-vis para trazar la
devoluciones de llamada: pérdida de validación y la precisión.
['onEpochEnd'], }), durante el aprendizaje por transferencia

]
});

¿Cómo resulta el resultado del aprendizaje por transferencia? Como puede ver en el panel A de la figura
5.3, alcanza una precisión de alrededor de 0,968 después de 10 épocas de entrenamiento, lo que lleva
aproximadamente 15 segundos en una computadora portátil relativamente actualizada; no está mal.
Pero, ¿cómo se compara esto con entrenar un modelo desde cero? Una forma en la que podemos
demostrar el valor de comenzar con un modelo preentrenado en lugar de comenzar desde cero es hacer
un experimento en el que reinicializamos aleatoriamente los pesos del modelo preentrenado justo antes
delencajar()llamada. Esto es lo que sucede si selecciona la opción Reinicializar pesos del menú desplegable
Modo de entrenamiento antes de hacer clic en el botón Reentrenar. El resultado se muestra en el panel B
de la misma figura.
Como puede ver al comparar el panel B con el panel A, la reinicialización aleatoria de los pesos
del modelo hace que la pérdida comience en un valor significativamente más alto (0,36 frente a
0,30) y que la precisión comience desde un valor significativamente más bajo (0,88 frente a 0,91). El
modelo reinicializado también termina con una precisión de validación final más baja (~0,954) que
el modelo que reutiliza pesos del modelo base (~0,968). Estas diferencias reflejan la ventaja del
aprendizaje por transferencia: al reutilizar pesos en las primeras capas (las capas de extracción de
características) del modelo, el modelo obtiene una buena ventaja en relación con aprender todo
desde cero. Esto se debe a que los datos encontrados en la tarea de transferencia de aprendizaje
son similares a los datos utilizados para entrenar el modelo original. Las imágenes de los dígitos
del 5 al 9 tienen mucho en común con las de los dígitos del 0 al 4: todas son imágenes en escala de
grises con fondo negro; tienen patrones visuales similares (trazos de anchos y curvaturas
comparables). Por lo tanto, las funciones que el modelo aprendió a extraer de los dígitos del 0 al 4
también resultan útiles para aprender a clasificar los nuevos dígitos (del 5 al 9).

¿Qué pasa si no congelamos los pesos de las capas de características? La opción No congelar
capas de entidades del menú desplegable Modo de entrenamiento le permite realizar este
experimento. El resultado se muestra en el panel C de la figura 5.3. Hay algunas diferencias
notables con los resultados del panel A:
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 159

A B
Serie Serie
0.30 val_loss 0.35 val_loss

0.25 0.30
Valor

Valor
0.25
0.20
0.20
0.15 0.132849
0.15
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
Época Época
0.967773 0,96 0.954102
Serie Serie
0,96 val_loss val_acc
0.94

0,92
Valor

Valor
0.94
0.90
0,92
0.88
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
Época Época

C D
Serie Serie
0.35 val_loss 0.35 A. transferir el aprendizaje
B. reinicialización

Pérdida de validación
0.30 0.30 C. sin congelación

0.249463
Valor

0.25 0.25

0.20 0.20

0.15 0.15
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
Época época #
Serie
0,96 Serie 0,96
0.945313 A. transferir el aprendizaje
Precisión de validación

val_loss B. reinicialización
0.94 0.94 C. sin congelación

0,92 0,92
Valor

0.90 0.90
0.88 0.88
0.86 0.86
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
Época época #

Figura 5.3 Las curvas de pérdida y validación para el aprendizaje de transferencia en la convnet MNIST. Panel A: las curvas obtenidas
con las primeras siete capas del modelo preentrenado congeladas. Panel B: las curvas obtenidas con todos los pesos del modelo
reinicializados aleatoriamente. Panel C: las curvas obtenidas sin congelar ninguna capa del modelo preentrenado. Tenga en cuenta
que los ejes Y difieren entre los tres paneles. Panel D: un gráfico multiserie que muestra las curvas de pérdida y precisión de los
paneles A–C en los mismos ejes para facilitar la comparación.

-Sin la congelación de la capa de características, el valor de pérdida comienza más alto (por ejemplo,

después de la primera época: 0,37 frente a 0,27); la precisión comienza más baja (0,87 frente a
0,91). ¿Por qué es este el caso? Cuando el modelo preentrenado comienza a entrenarse por
primera vez en el nuevo conjunto de datos, las predicciones contendrán una gran cantidad de
errores porque los pesos preentrenados generan predicciones esencialmente aleatorias para los
cinco dígitos nuevos. Como resultado, la función de pérdida tendrá valores muy altos y
pendientes pronunciadas. Esto hace que los gradientes calculados en las primeras fases del
entrenamiento sean muy grandes, lo que a su vez conduce a grandes fluctuaciones en todos los
160 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

pesos del modelo. Como resultado, los pesos de todas las capas experimentarán un período de
grandes fluctuaciones, lo que conduce a la mayor pérdida inicial que se ve en el panel C. En el
enfoque normal de aprendizaje por transferencia (panel A), las primeras capas del modelo están
congeladas y, por lo tanto, son "protegido" de estas grandes perturbaciones de peso iniciales. En
- parte debido a estas grandes perturbaciones iniciales, la precisión final lograda por el enfoque sin
congelamiento (~0.945, panel C) esnoapreciablemente más alto en comparación con el enfoque
normal de aprendizaje por transferencia con congelación de capa (~ 0.968, panel A).

- El entrenamiento lleva mucho más tiempo cuando ninguna de las capas del modelo está
congelada. Por ejemplo, en una de las computadoras portátiles que usamos, entrenar el modelo
con capas de características congeladas toma alrededor de 30 segundos, mientras que entrenar
el modelo sin ninguna capa congelada toma aproximadamente el doble (60 segundos). La Figura
5.4 ilustra la razón detrás de esto de una manera esquemática. Las capas congeladas se eliminan
de la ecuación durante la retropropagación, lo que hace que cada lote de la encajar()llamar para ir
mucho más rápido.

A B
y
y
Capas congeladas

X pérdida
X pérdida

v1 v1

v2 v2

v3 v3
v4
v4
v5
v5

Figura 5.4 Una explicación esquemática de por qué congelar algunas capas de un modelo acelera el entrenamiento. En esta figura, la ruta de
propagación hacia atrás se muestra con flechas negras que apuntan hacia la izquierda. Panel A: cuando ninguna capa está congelada, todos
los pesos del modelo (v1–v5) deben actualizarse durante cada paso de entrenamiento (cada lote) y, por lo tanto, estarán involucrados en la
retropropagación, representada por las flechas negras. Tenga en cuenta que las entidades (x) y los objetivos (y) nunca se incluyen en la
retropropagación porque no es necesario actualizar sus valores. Panel B: al congelar las primeras capas del modelo, un subconjunto de los
pesos (v1–v3) ya no forman parte de la retropropagación. En cambio, se vuelven análogas a x e y, que simplemente se tratan como constantes
que se tienen en cuenta en el cálculo de la pérdida. Como resultado, la cantidad de cómputo que se necesita para realizar la retropropagación
disminuye y la velocidad de entrenamiento aumenta.

Estos puntos justifican el enfoque de congelación de capas del aprendizaje por transferencia:
aprovecha las capas de extracción de características del modelo base y las protege de grandes
perturbaciones de peso durante las primeras fases del nuevo entrenamiento, logrando así una
mayor precisión en un entrenamiento más corto. período.
Dos comentarios finales antes de pasar a la siguiente sección. Primero, la adaptación del modelo, el
proceso de volver a entrenar un modelo para que funcione mejor con los datos de entrada de un usuario
en particular, utiliza técnicas muy similares a las que se muestran aquí, es decir, congelar el
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 161

capas base mientras permite que los pesos de las capas superiores se modifiquen a través del
entrenamiento en los datos específicos del usuario. Esto es a pesar del hecho de que el problema que
resolvimos en esta sección no involucraba datos de un usuario diferente, sino datos con etiquetas
diferentes. En segundo lugar, es posible que se pregunte cómo verificar que el peso de una capa
congelada (las capas conv2d, en este caso) sea el mismo antes y después de una capa congelada.encajar()
llamada. No es muy difícil hacer esta verificación. Te lo dejamos como ejercicio (ver ejercicio 2 al final de
este capítulo).

5.1.2 Transferir el aprendizaje en formas de salida incompatibles: Crear


un nuevo modelo usando salidas del modelo base
En el ejemplo de transferencia de aprendizaje visto en la sección anterior, el modelo base tenía la
misma forma de salida que la nueva forma de salida. Esta propiedad no se cumple en muchos
otros casos de transferencia de aprendizaje (ver figura 5.5). Por ejemplo, si desea utilizar el modelo
base entrenado inicialmente en los cinco dígitos para clasificarcuatronuevos dígitos, el enfoque
descrito anteriormente no funcionará. Un escenario más común es el siguiente: dada una convnet
profunda que ha sido entrenada en el conjunto de datos de clasificación de ImageNet que consta
de 1,000 clases de salida, tiene una tarea de clasificación de imágenes a la mano que involucra una
cantidad mucho menor de clases de salida (caso B en la figura 5.5). Tal vez sea un problema de
clasificación binaria, ya sea que la imagen contenga un rostro humano o no, o tal vez sea un
problema de clasificación de clases múltiples con solo un puñado de clases, qué tipo de artículo de
consumo contiene una imagen (recuerde el ejemplo al comienzo de este artículo). capítulo). En
tales casos, la forma de salida del modelo base no funciona para el nuevo problema.
En algunos casos, incluso elescribede la tarea de aprendizaje automático es diferente de aquella
en la que se ha entrenado el modelo base. Por ejemplo, puede realizar una tarea de regresión
(predecir un número, como en el caso C de la figura 5.5) aplicando el aprendizaje de transferencia
en el modelo base entrenado en una tarea de clasificación. En la sección 5.2, verá un uso aún más
intrigante del aprendizaje por transferencia: predecir una serie de números, en lugar de uno solo,
con el propósito de detectar y localizar objetos en imágenes.
Todos estos casos implican una forma de salida deseada que difiere de la del modelo
base. Esto hace que sea necesario construir un nuevo modelo. Pero debido a que estamos
haciendo transferencia de aprendizaje, el nuevo modelo no se creará desde cero. En su lugar,
utilizará el modelo base. Ilustraremos cómo hacer esto en el ejemplo webcam-transfer-
learning en el repositorio tfjs-examples.
Para ver este ejemplo en acción, asegúrese de que su máquina tenga una cámara frontal; el ejemplo
recopilará los datos para transferir el aprendizaje desde la cámara. La mayoría de las computadoras
portátiles y tabletas vienen con una cámara frontal incorporada en la actualidad. Sin embargo, si está
utilizando una computadora de escritorio, es posible que deba encontrar una cámara web y conectarla a
la máquina. Al igual que en los ejemplos anteriores, puede usar los siguientes comandos para verificar y
ejecutar la demostración:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


webcam-transferencia-aprendizaje
discos compactos
162 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

Original
UN.
Mismo
producción

forma & forma,


mismo
activación
activación
(sección 5.1.1)

Base B.
modelo
Diferente
forma,
mismo
activación
(secciones
5.1.2 y 5.1.3)

C.
Diferente
activación
(sección 5.2)

Figura 5.5 El aprendizaje de transferencia se puede dividir en tres tipos según si la forma de salida y la
activación del nuevo modelo son iguales o diferentes a las del modelo original. Caso A: la forma de salida y la
función de activación del nuevo modelo coinciden con las del modelo base. La transferencia del modelo
MNIST a nuevos dígitos en la sección 5.1.1 es un ejemplo de este tipo de transferencia de aprendizaje. Caso
B: el nuevo modelo tiene el mismo tipo de activación que el modelo base porque la tarea original y la nueva
tarea son del mismo tipo (por ejemplo, ambas son de clasificación multiclase). Sin embargo, las formas de
salida son diferentes (por ejemplo, la nueva tarea implica un número diferente de clases). Ejemplos de este
tipo de aprendizaje por transferencia se pueden encontrar en el apartado 5.1.2 (control de un videojuego al
estilo Pac-ManTM 4a través de una cámara web) y 5.1.3 (reconocimiento de un nuevo conjunto de palabras
habladas). Caso C: la nueva tarea es de un tipo diferente a la original (como regresión versus clasificación). El
modelo de detección de objetos basado en MobileNet es un ejemplo de este tipo.

Esta demostración divertida convierte su cámara web en un controlador de juegos al aplicar el


aprendizaje de transferencia en una implementación TensorFlow.js de MobileNet, y le permite jugar el
juego Pac-Man con ella. Repasemos los tres pasos necesarios para ejecutar la demostración: recopilación
de datos, aprendizaje de transferencia de modelos y juego.4
Los datos para la transferencia de aprendizaje se recopilan desde su cámara web. Una vez que
la demostración se esté ejecutando en su navegador, verá cuatro cuadrados negros en la parte
inferior derecha de la página. Están dispuestos de manera similar a los cuatro botones de dirección
en un controlador de Nintendo Family Computer. Corresponden a las cuatro clases que el modelo
será entrenado para reconocer en tiempo real. Estas cuatro clases corresponden a la

4Pac-Man es una marca registrada de Bandai Namco Entertainment Inc.


Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 163

cuatro direcciones en las que irá Pac-Man. Cuando hace clic y mantiene presionado uno de ellos, las
imágenes se recopilarán a través de la cámara web a una velocidad de 20 a 30 cuadros por segundo. Un
número debajo del cuadrado le indica cuántas imágenes se han recopilado para esta dirección del
controlador hasta el momento.
Para obtener la mejor calidad de transferencia de aprendizaje, asegúrese de 1) recopilar al menos 50
imágenes por clase y 2) mover y mover la cabeza y la cara un poco durante la recopilación de datos para
que las imágenes de capacitación contengan más diversidad, lo que beneficia la solidez del modelo que
obtendrá del aprendizaje por transferencia. En esta demostración, la mayoría de las personas giran la
cabeza en las cuatro direcciones (arriba, abajo, izquierda y derecha; consulte la figura 5.6) para indicar
hacia dónde debe ir Pac-Man. Pero puede usar cualquier posición de la cabeza, expresión facial o incluso
gestos con las manos que desee como imágenes de entrada, siempre que las entradas sean lo
suficientemente distintas visualmente de una clase a otra.5

Figura 5.6 La interfaz de usuario del ejemplo de aprendizaje de transferencia de cámara web5

Después de recopilar las imágenes de entrenamiento, haga clic en el botón Entrenar modelo, que
iniciará el proceso de aprendizaje por transferencia. Transferir el aprendizaje debe tomar solo unos
segundos. A medida que avanza, debería ver que el valor de pérdida que se muestra en la pantalla
se vuelve cada vez más pequeño hasta que alcanza un valor positivo muy pequeño (como 0.00010)
y deja de cambiar. En este punto, el modelo de transferencia de aprendizaje ha sido entrenado y
puede usarlo para jugar. Para iniciar el juego, simplemente haga clic en el botón Jugar y espere a
que se establezca el estado del juego. Luego, el modelo comenzará a realizar inferencias en tiempo
real en el flujo de imágenes de la cámara web. En cada cuadro de video, la clase ganadora (la clase
con el puntaje de probabilidad más alto asignado por el modelo de transferencia de aprendizaje) se
indicará en la parte inferior derecha de la interfaz de usuario con un resaltado amarillo brillante.
Además,

5
La interfaz de usuario de este ejemplo de aprendizaje de transferencia de cámara web es obra de Jimbo Wilson y Shan Carter. Una grabación
de video de este divertido ejemplo en acción está disponible enhttps://youtu.be/YB-kfeNIPCE?t=941.
164 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

Esta demostración puede parecer mágica para aquellos que no están familiarizados con el aprendizaje
automático, pero se basa nada más que en un algoritmo de transferencia de aprendizaje que utiliza MobileNet
para realizar una tarea de clasificación de cuatro clases. El algoritmo utiliza la pequeña cantidad de datos de
imagen recopilados a través de la cámara web. Esas imágenes se etiquetan convenientemente a través de la
acción de hacer clic y mantener presionada que realizó mientras recopilaba las imágenes. Gracias al poder del
aprendizaje por transferencia, este proceso no necesita muchos datos ni mucho tiempo de entrenamiento
(incluso funciona en un teléfono inteligente). Entonces, así es como funciona esta demostración en pocas
palabras. Si desea comprender los detalles técnicos, profundice con nosotros en el código subyacente de
TensorFlow.js en la siguiente sección.

DEEP SUMÉRGETE EN EL APRENDIZAJE DE TRANSFERENCIA DE CÁMARA WEB

El código del listado 5.3 (de webcam-transfer-learning/index.js) es responsable de cargar el modelo


base. En particular, cargamos una versión de MobileNet que puede ejecutarse de manera eficiente
en TensorFlow.js. El cuadro de información 5.1 describe cómo se convierte este modelo de la
biblioteca de aprendizaje profundo de Keras en Python. Tan pronto como se carga el modelo,
usamos el getLayer()método para hacerse con una de sus capas.getLayer()le permite especificar una
capa por su nombre ('conv_pw_13_relu'en este caso). Puede recordar otra forma de acceder a las
capas de un modelo de la sección 2.4.2, es decir, indexando las capas del modelo. capasatributo,
que contiene todas las capas del modelo como una matriz de JavaScript. Este enfoque es fácil de
usar solo cuando el modelo consta de un pequeño número de capas. El modelo de MobileNet que
estamos tratando aquí tiene 93 capas, lo que hace que ese enfoque sea frágil (por ejemplo, ¿qué
pasa si se agregan más capas al modelo en el futuro?). Por lo tanto, el nombre basadogetLayer()El
enfoque es más confiable, si asumimos que los autores de MobileNet mantendrán los nombres de
las capas clave sin cambios cuando publiquen nuevas versiones del modelo.

Listado 5.3 Cargando MobileNet y creando un modelo “truncado” a partir de él

función asíncrona cargarTruncatedMobileNet() {


const mobilenet = esperar tf.loadLayersModel(
'https://storage.googleapis.com/' +
'tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json');

const capa = mobilenet.getLayer( URL bajo


storage.google.com/tfjsmodels
'conv_pw_13_relu');
están diseñados para ser
return tf.modelo({
permanente y estable.
entradas: mobilenet.entradas,
salidas: capa.salida
}); Obtiene una capa intermedia de MobileNet. Esta capa
contiene características útiles para la tarea de
}
Crea un nuevo modelo que es el mismo clasificación de imágenes personalizadas.
como MobileNet excepto que termina
en la capa 'conv_pw_13_relu', es decir,
con las últimas capas (denominadas
la “cabeza”) truncada
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 165

ICAJA NFO5.1 Conversión de modelos de Python Keras a TensorFlow.


formato js
TensorFlow.js presenta un alto grado de compatibilidad e interoperabilidad con Keras, una de las
bibliotecas de aprendizaje profundo de Python más populares. Uno de los beneficios que se deriva de esta
compatibilidad es que puede utilizar muchas de las llamadas "aplicaciones" de Keras. Estas aplicaciones
son un conjunto de conexiones profundas preentrenadas (ver https://keras.io/aplicaciones/). Los autores
de Keras han entrenado minuciosamente estos convnets en grandes conjuntos de datos como ImageNet
y los han puesto a disposición a través de la biblioteca para que estén listos para su reutilización, incluida
la inferencia y el aprendizaje de transferencia, como lo estamos haciendo aquí. Para aquellos que usan
Keras en Python, importar una aplicación solo requiere una línea de código. Debido a la interoperabilidad
mencionada anteriormente, también es fácil para un usuario de TensorFlow.js usar estas aplicaciones.
Aquí están los pasos que toma:

1 Asegúrese de que el paquete de Python llamadotensorflowjsesta instalado. La forma más


fácil de instalarlo es a través de lapepitamando:

pip instalar tensorflowjs

2 Ejecute el siguiente código a través de un archivo fuente de Python o en un REPL de Python


interactivo como ipython:

importar kera
importar tensorflowjs como tfjs
modelo = keras.applications.mobilenet.MobileNet(alpha=0.25)
tfjs.converters.save_keras_model(modelo, '/tmp/mobilnet_0.25')

Las primeras dos líneas importan el requeridoquerasytensorflowjsmódulos. La tercera línea


carga MobileNet en un objeto Python (modelo).De hecho, puede imprimir un resumen del
modelo prácticamente de la misma manera que imprime el resumen de un modelo Tensor-
Flow.js: es decir,Resumen Modelo().Puede ver que la última capa del modelo (la salida del
modelo) tiene una forma de (Ninguno, 1000) (equivalente a [nulo, 1000]en JavaScript), que
refleja la tarea de clasificación de ImageNet de 1000 clases en la que se entrenó el modelo
de MobileNet. El argumento de la palabra clavealfa=0.25que especificamos para esta
llamada de constructor elige una versión de MobileNet que es más pequeña en tamaño.
Puede elegir valores mayores dealfa (tal como0,75, 1),y el mismo código de conversión
seguirá funcionando.

La última línea del fragmento de código anterior guarda el modelo en el directorio


especificado del disco mediante un método del módulo tensorflowjs. Después de que la línea
termine de ejecutarse, habrá un nuevo directorio en /tmp/mobilenet_0.25, con contenido
similar a

grupo1-fragmento1de6
grupo1-fragmento2de6
...
grupo1-fragmento6de6
modelo.json
166 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

(continuado)
Este es exactamente el mismo formato que el que vimos en la sección 4.3.3, cuando
mostramos cómo guardar un modelo TensorFlow.js entrenado en el disco usando susalvar()
método en la versión Node.js de TensorFlow.js. Por lo tanto, para los programas basados en
TensorFlow.js que cargan este modelo convertido desde el disco, el formato guardado es
idéntico a un modelo creado y entrenado en TensorFlow.js: simplemente puede llamar al
tf.loadLayersModel() y apunte a la ruta al archivo model.json (ya sea en el navegador o en
Node.js), que es exactamente lo que sucede en el listado 5.3.

El modelo de MobileNet cargado está listo para realizar la tarea de aprendizaje automático en la que se
entrenó originalmente el modelo: clasificar las imágenes de entrada en las 1000 clases del conjunto de
datos de ImageNet. Tenga en cuenta que este conjunto de datos en particular tiene un gran énfasis en los
animales, especialmente en varias razas de gatos y perros (¡lo que probablemente esté relacionado con la
abundancia de este tipo de imágenes en Internet!). Para aquellos interesados en este uso particular, el
ejemplo de MobileNet en el repositorio tfjs-example ilustra cómo hacerlo (https://github.com/tensorflow/
tfjs-examples/tree/master/mobilenet). Sin embargo, este uso directo de MobileNet no es en lo que nos
enfocamos en este capítulo; en su lugar, exploramos cómo usar MobileNet cargado para realizar
transferencias de aprendizaje.

lostfjs.converters.save_keras_model()El método mostrado anteriormente es capaz de


convertir y guardar no solo MobileNet sino también otras aplicaciones de Keras, como
DenseNet y NasNet. En el ejercicio 3 al final de este capítulo, practicará la conversión de
otra aplicación Keras (MobileNetV2) al formato TensorFlow.js y la cargará en el
navegador. Además, cabe señalar quetfjs.converters . guardar_keras_modelo()
generalmente se aplica a cualquier objeto de modelo que haya creado o entrenado en
Keras, no solo a los modelos dekeras.aplicaciones.

¿Qué hacemos con elconv_pw_13_relucapa una vez que lo consigamos? Creamos un nuevo
modelo que contiene las capas del modelo MobiletNet original desde su primera capa (de
entrada) hasta laconv_pw_13_relucapa. Esta es la primera vez que ve este tipo de construcción
de modelos en este libro, por lo que requiere una explicación cuidadosa. Para ello,
necesitamos introducir el concepto detensor simbólicoprimero.

Creación de modelos a partir de tensores simbólicos

Has visto tensores hasta ahora.Tensores el tipo de datos básico (también abreviado comotipo de d) en
TensorFlow.js. Un objeto tensor lleva valores numéricos concretos de una forma y un tipo determinados,
respaldados por almacenamiento en texturas WebGL (si se encuentra en un navegador habilitado para
WebGL) o memoria CPU/GPU (si se encuentra en Node.js). Sin embargo,Tensor simbólicoes otra clase
importante en TensorFlow.js. En lugar de contener valores concretos, un tensor simbólico especifica solo
una forma y un tipo de d. Se puede pensar en un tensor simbólico como una "ranura" o un "marcador de
posición", en el que se puede insertar un valor de tensor real más tarde, dado que el valor de tensor tiene
una forma y un tipo compatibles. En TensorFlow.js, un objeto de capa o modelo toma una o más entradas
(hasta ahora, solo ha visto casos de una entrada), y se representan como uno o más tensores simbólicos.

Usemos una analogía que podría ayudarte a entender un tensor simbólico. Considere una
función en un lenguaje de programación como Java o TypeScript (o cualquier otro
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 167

lenguaje escrito con el que está familiarizado). La función toma uno o más argumentos de entrada.
Cada argumento de una función tiene un tipo, que estipula qué tipo de variables se pueden pasar
como argumento. Sin embargo, el argumentosí mismono tiene ningún valor concreto. Por sí
mismo, el argumento es solo un marcador de posición. Un tensor simbólico es análogo a un
argumento de función: especifica qué tipo (combinación de forma6y dtype) de tensores pueden
usarse en esa ranura. Por paralelo, una función en un lenguaje de tipo estático tiene un tipo de
retorno. Esto es comparable al tensor simbólico de salida de un modelo o un objeto de capa. Es un
"modelo" para la forma y el tipo de los valores de tensor reales que generará el modelo o el objeto
de capa.
En TensorFlow.js, dos atributos importantes de un objeto modelo son sus entradas y salidas.
Cada uno de estos es una matriz de tensores simbólicos. Para un modelo con exactamente una
entrada y exactamente una salida, ambas matrices tienen una longitud de 1. De manera similar, un
objeto de capa tiene dos atributos: entrada y salida, cada uno de los cuales es un tensor simbólico.
Los tensores simbólicos se pueden utilizar para crear un nuevo modelo. Esta es una nueva forma
de crear modelos en TensorFlow.js, que es diferente del enfoque que ha visto antes: es decir, crear
modelos secuenciales contf.secuencial()y posteriores llamadas alagregar()método. En el nuevo
enfoque, usamos eltf.modelo()función, que toma un objeto de configuración con dos campos
obligatorios:entradasysalidas.losentradasSe requiere que el campo sea un tensor simbólico (o,
alternativamente, una matriz de tensores simbólicos), y lo mismo para el salidascampo. Por lo
tanto, podemos obtener los tensores simbólicos del modelo MobileNet original y alimentarlos a un
tf.modelo()llamada. El resultado es un nuevo modelo que consta de una parte del MobileNet
original.
Este proceso se ilustra esquemáticamente en la figura 5.7. (Tenga en cuenta que la figura
reduce el número de capas del modelo de MobileNet real en aras de un diagrama de aspecto
simple). Lo importante a tener en cuenta es que los tensores simbólicos tomados del modelo
original y entregados altf.modelo()llamada sonnoobjetos aislados. En cambio, llevan información
sobre a qué capas pertenecen y cómo las capas están conectadas entre sí. Para lectores
familiarizados con gráficos en estructura de datos, el modelo original es un gráfico de tensores
simbólicos, con los bordes de conexión siendo las capas. Al especificar las entradas y salidas del
nuevo modelo como tensores simbólicos en el modelo original, estamos extrayendo un subgráfico
del gráfico MobileNet original. El subgráfico, que se convierte en el nuevo modelo, contiene las
primeras (en particular, las primeras 87) capas de MobileNet, mientras que las últimas 6 capas
quedan fuera. Las últimas capas de una convnet profunda a veces se denominancabeza. que
estamos haciendo con eltf.modelo()La llamada se puede denominartruncandoel modelo. El
MobileNet truncado conserva las capas de extracción de características mientras descarta la
cabeza. ¿Por qué la cabeza contieneseis¿capas? Esto se debe a que esas capas son específicas de la
tarea de clasificación de 1000 clases en la que se entrenó originalmente MobileNet. Las capas no
son útiles para la tarea de clasificación de cuatro clases a la que nos enfrentamos.

6
Una diferencia entre la forma de un tensor y la forma de un tensor simbólico es que el primero siempre tiene
dimensiones completamente especificadas (como [8, 32, 20]),mientras que estos últimos pueden tener dimensiones
indeterminadas (como [nulo, nulo, 20]).Ya ha visto esto en la columna "Forma de salida" de los resúmenes del modelo.
168 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

modelo original

SimbólicoTensor0 SimbólicoTensor1 SimbólicoTensor2 Tensor Simbólico3


(aporte) (producción)

Capa 1

Capa 2

Capa 3
tf.modelo()
entradas salidas
llamada

Nuevo modelo

SimbólicoTensor0 SimbólicoTensor1 SimbólicoTensor2


(aporte) (producción)
Capa 1

Capa 2

Figura 5.7 Dibujo esquemático que explica cómo se crea el nuevo modelo ("truncado") a partir de MobileNet. Ver el
tf.modelo()llamar en el listado 5.3 para el código correspondiente. Cada capa tiene una entrada y una salida, las
cuales sonTensor simbólicoinstancias. En el modelo original, SymbolicTensor0 es la entrada de la primera capa y la
entrada de todo el modelo. Se utiliza como tensor simbólico de entrada del nuevo modelo. Además, tomamos el
tensor simbólico de salida de una capa intermedia (equivalente aconv_pw_13_relu) como el tensor de salida del
nuevo modelo. Por lo tanto, obtenemos un modelo que consta de las dos primeras capas del modelo original, que se
muestra en la parte inferior del diagrama. La última capa del modelo original, que es la capa de salida y, a veces, se
denomina cabeza del modelo, se descarta. Esta es la razón por la cual enfoques como este a veces se denominan
truncandoun modelo. Tenga en cuenta que este diagrama muestra modelos con un pequeño número de capas en
aras de la claridad. Lo que realmente sucede con el código del listado 5.3 implica un modelo con muchas más (93)
capas en comparación con el que se muestra en este diagrama.

Transferir el aprendizaje basado en incrustaciones

La salida de la MobileNet truncada es la activación de una capa intermedia de la MobileNet original.


7Pero, ¿cómo nos resulta útil la activación de la capa intermedia de MobileNet? La respuesta se
puede ver en la función que maneja los eventos de hacer clic y mantener presionado cada uno de
los cuatro cuadrados negros (listado 5.4). Cada vez que una imagen de entrada está disponible
desde la cámara web (a través de lacapturar()método), llamamos alpredecir()método de

7
Una pregunta frecuente sobre los modelos de TensorFlow.js es cómo obtener las activaciones de las capas intermedias. El
enfoque que mostramos aquí es la respuesta.
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 169

la MobileNet truncada y guarde la salida en un objeto llamadocontroladorDataset, que se utilizará


para transferir el aprendizaje más adelante.
Pero, ¿cómo interpretar la salida de MobileNet truncada? Para cada entrada de imagen, es un
tensor de forma [1, 7, 7, 256].No son las probabilidades de ningún problema de clasificación, ni
tampoco los valores pronosticados para ningún problema de regresión. Es una representación de
la imagen de entrada en un determinado espacio de alta dimensión. Este espacio tiene 7 * 7 * 256,
o aproximadamente 12,5k, dimensiones. Aunque el espacio tiene muchas dimensiones, es de
menor dimensión en comparación con la imagen original que, debido a las dimensiones de la
imagen de 224 × 224 y los tres canales de color, tiene 224 * 224 * 3-150k dimensiones. Por lo tanto,
la salida de MobileNet truncada se puede ver como una representación eficiente de la imagen. Este
tipo de representación de las entradas en una dimensión más baja a menudo se denomina
incrustación. Nuestro aprendizaje de transferencia se basará en las incrustaciones de los cuatro
conjuntos de imágenes recopiladas de la cámara web.

Listado 5.4 Obtención de incrustaciones de imágenes usando una MobileNet truncada

ui.setExampleHandler(etiqueta => {
tf.ordenado(() => {
Utiliza tf.tidy() para limpiar tensores
const img = webcam.capture();
intermedios como img. Consulte el
controladorDataset.addExample(
apéndice B, sección B.3 para obtener un
truncadoMobileNet.predict(img), etiqueta); tutorial sobre la administración de
memoria de TensorFlow.js en el navegador.

ui.drawThumb(img, etiqueta); });


Obtiene la activación interna de
MobileNet para la imagen de entrada
});

Ahora que tenemos una forma de obtener las incrustaciones de las imágenes de la cámara web, ¿cómo
las usamos para predecir a qué dirección corresponde una imagen determinada? Para esto, necesitamos
un nuevo modelo, uno que tome la incrustación como su entrada y genere los valores de probabilidad
para las cuatro clases de dirección. El código de la siguiente lista (de index.js) crea dicho modelo.

Listado 5.5 Predicción de la dirección del controlador usando incrustaciones de imágenes

modelo = tf.secuencial({
capas: [
tf.layers.flatten({
inputShape: truncadoMobileNet.outputs[0].shape.slice(1) }),

tf.capas.densas({ Aplana la incrustación [7, 7, 256] de


Una primera (oculta)
unidades: ui.getDenseUnits(), la MobileNet truncada. la rebanada(1)
capa densa con activación: 'relu', La operación descarta la primera
no lineal (relu) kernelInitializer: 'varianceScaling', useBias: dimensión (lote), que está presente en la salida
activación
verdadero forma pero no deseada por el atributo inputShape
}), del método de fábrica de la capa, por lo que

tf.capas.densas({ se puede utilizar con una capa densa.


unidades: NUM_CLASSES,
kernelInitializer: 'varianceScaling', useBias: false, El número de unidades de la última
capa debe corresponder al número de
clases que queremos predecir.
170 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

activación: 'softmax'
})
El número de unidades de la última
]
capa debe corresponder al número de
}); clases que queremos predecir.

Comparado con el MobileNet truncado, el nuevo modelo creado en el listado 5.5 tiene un tamaño
mucho más pequeño. Consta de sólo tres capas:

- La capa de entrada es una capa plana. Transforma la incrustación 3D del modelo


truncado en un tensor 1D que pueden tomar las capas densas posteriores. Hemos
visto usos similares de capas planas en los convenios MNIST en el capítulo 4. Dejamos
que su forma de entradahaga coincidir la forma de salida de la MobileNet truncada (sin
la dimensión del lote) porque el nuevo modelo recibirá incrustaciones que salen de la
MobileNet truncada.
- La segunda capa es una capa oculta. Está oculto porque no es ni la capa de
entrada ni la capa de salida del modelo. En su lugar, se intercala entre otras dos
capas para mejorar la capacidad del modelo. Esto es muy similar a los MLP que
encontró en el capítulo 3. Es una capa densa oculta con una activación de relu.
Recuerde que en la sección del capítulo 3 "Evitar la falacia de apilar capas sin no
linealidad", discutimos la importancia de usar una activación no lineal para capas
ocultas como esta.
- La tercera capa es la capa final (de salida) del nuevo modelo. Tiene una activación softmax
que se adapta al problema de clasificación multiclase al que nos enfrentamos (es decir,
cuatro clases: una para cada dirección de Pac-Man).

Por lo tanto, hemos construido esencialmente un MLP sobre las capas de extracción de características de
MobileNet. El MLP se puede considerar como un nuevo cabezal para MobileNet, aunque el extractor de
características (el MobileNet truncado) y el cabezal son dos modelos separados en este caso (consulte la
figura 5.8). Como resultado de la configuración de dos modelos, no es posible entrenar la nueva cabeza
directamente usando los tensores de imagen (de la forma [numEjemplos, 224, 224, 3]).En su lugar, el
nuevo jefe debe ser entrenado en las incrustaciones de las imágenes de la salida de la MobileNet
truncada. Afortunadamente, ya hemos recopilado esos tensores de incrustación (listado 5.4). Todo lo que
tenemos que hacer para entrenar al nuevo jefe es llamar a su encajar()método de los tensores de
incrustación. El código que hace eso dentro delentrenar() La función en index.js es sencilla y no daremos
más detalles al respecto.
Una vez finalizada la transferencia de aprendizaje, el modelo truncado y el nuevo cabezal se utilizarán
juntos para obtener puntuaciones de probabilidad a partir de las imágenes de entrada de la cámara web.
Puedes encontrar el código en elpredecir()función en index.js, que se muestra en el listado 5.6. En
particular, dospredecir()las llamadas están involucradas. La primera llamada convierte el tensor de
imagen en su incrustación utilizando MobileNet truncado; el segundo convierte la incrustación en
puntajes de probabilidad para las cuatro direcciones utilizando la nueva cabeza entrenada con
aprendizaje de transferencia. El código subsiguiente en el listado 5.6 obtiene el índice ganador (el índice
que corresponde al puntaje de probabilidad máxima entre las cuatro direcciones) y lo usa para dirigir el
Pac-Man y actualizar los estados de la interfaz de usuario. Como en el anterior
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 171

MobileNet

Truncamiento Forma:
[nulo,7,7,256]
MobileNet truncado
Imágenes
desde la cámara web

Nuevo jefe (MLP)

Forma:
[nulo, 12544]

Forma:
[nulo, 4]

a pac-man
aplanar denso1 denso2
lógica de control

Figura 5.8 Un esquema del algoritmo de aprendizaje por transferencia que subyace en el ejemplo de aprendizaje por transferencia
con cámara web

ejemplos, no cubrimos la parte de la interfaz de usuario del ejemplo porque no es fundamental para los
algoritmos de aprendizaje automático. Puede estudiar y jugar con el código de la interfaz de usuario a su
gusto utilizando el código de la siguiente lista.

Listado 5.6 Obtener la predicción de una imagen de entrada de cámara web después de transferir el aprendizaje

Captura un marco
desde la cámara web
función asíncrona predecir () {
ui.esPredecir();
Obtiene la incrustación
while (predice) {
de truncatedMobileNet
const clasepredicha = tf.tidy(() => {
const img = webcam.capture();
Convierte la incrustación en las
incrustación const = truncadoMobileNet.predict( puntuaciones de probabilidad de las

imagen); cuatro direcciones utilizando el

const predicciones = modelo.predecir(activación); nuevo modelo principal

devuelve predicciones.as1D().argMax(); });


Obtiene el índice de la puntuación
máxima de probabilidad

const classId = (esperar clase predicha.datos())[0];


Descarga el índice
clasepredicha.dispose(); de la GPU a la CPU
ui.predictClass(classId); esperar
tf.nextFrame(); Actualiza la interfaz de usuario de acuerdo con
} la dirección ganadora: dirige el Pac-Man y

ui.hechoPredecir(); actualiza otros estados de la interfaz de usuario,

} como el resaltado del "botón" correspondiente


en el controlador
172 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

Esto concluye nuestra discusión de la parte del ejemplo de aprendizaje de transferencia de cámara
web relevante para el algoritmo de aprendizaje de transferencia. Un aspecto interesante del
método que usamos en este ejemplo es que el proceso de entrenamiento e inferencia involucra
dos objetos de modelo separados. Esto es bueno para nuestro propósito educativo de ilustrar
cómo obtener incrustaciones de las capas intermedias de un modelo previamente entrenado. Otra
ventaja de este enfoque es que expone las incrustaciones y facilita la aplicación de técnicas de
aprendizaje automático que hacen uso directo de estas incrustaciones. Un ejemplo de tales
técnicas esk-vecinos más cercanos(kNN, discutido en el cuadro de información 5.2). Sin embargo,
exponer las incrustaciones directamente también puede verse como una deficiencia por las
siguientes razones:

- Conduce a un código un poco más complejo. Por ejemplo, la inferencia requiere dos
predecir()llamadas para realizar inferencias sobre una sola imagen.
- Supongamos que queremos guardar los modelos para usarlos en sesiones posteriores o para
convertirlos a una biblioteca que no sea TensorFlow.js. Luego, el modelo truncado y el nuevo modelo de
cabeza deben guardarse por separado, como dos artefactos separados.
- En algunos casos especiales, el aprendizaje de transferencia implicará la propagación
hacia atrás sobre ciertas partes del modelo base (como las primeras capas de la red
móvil truncada). Esto no es posible cuando la base y la cabeza son dos objetos
separados.

En la siguiente sección, mostraremos una forma de superar estas limitaciones formando un único objeto modelo
para el aprendizaje por transferencia. Será un modelo de extremo a extremo en el sentido de que puede
transformar los datos de entrada en el formato original en el resultado final deseado.

ICAJA NFOClasificación de 5.2 k-vecinos más cercanos basada en incrustaciones


Existen enfoques de redes no neuronales para resolver problemas de clasificación en el
aprendizaje automático. Uno de los más famosos es el algoritmo k-vecinos más cercanos (kNN). A
diferencia de las redes neuronales, el algoritmo kNN no implica un paso de entrenamiento y es
más fácil de entender.

Podemos describir cómo funciona la clasificación kNN en unas pocas oraciones:

1 Eliges un entero positivok(por ejemplo, 3).


2 Recopila una serie de ejemplos de referencia, cada uno etiquetado con la clase verdadera.
Por lo general, el número de ejemplos de referencia recopilados es al menos varias veces
mayor quek. Cada ejemplo se representa como una serie de números con valores reales, o
unvector. Este paso es similar a la recopilación de ejemplos de entrenamiento en el
enfoque de red neuronal.
3 Para predecir la clase de una nueva entrada, calcula las distancias entre la
representación vectorial de la nueva entrada y las de todos los ejemplos de
referencia. Luego ordenas las distancias. Al hacerlo, puede encontrar elk
ejemplos de referencia que son los más cercanos a la entrada en el espacio
vectorial. Estos son los llamados “kvecinos más cercanos” de la entrada (el
homónimo del algoritmo).
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 173

4 Miras las clases de loskvecinos más cercanos y usar la clase más común entre
ellos como predicción para la entrada. En otras palabras, dejas que el klos
vecinos más cercanos “votan” en la clase predicha.

Un ejemplo de este algoritmo se muestra en la siguiente figura.

Un ejemplo de clasificación kNN en un espacio de incrustación 2D. En este caso,k=3, y


hay dos clases (triángulos y círculos). Hay cinco ejemplos de referencia para la clase
de triángulos y siete para la clase de círculos. El ejemplo de entrada se representa
como un cuadrado. Los tres vecinos más cercanos a la entrada están indicados por los
segmentos de línea que los conectan con la entrada. Como dos de los tres vecinos
más cercanos son círculos, la clase predicha para el ejemplo de entrada será un
círculo.

Como puede ver en la descripción anterior, uno de los requisitos clave del algoritmo
kNN es que cada ejemplo de entrada se represente como un vector. Las incrustaciones
como la que obtuvimos de MobileNet truncada son buenas candidatas para este tipo
de representaciones vectoriales por dos razones. Primero, a menudo tienen una
dimensionalidad más baja en comparación con las entradas originales y, por lo tanto,
reducen la cantidad de almacenamiento y cómputo requerido por el cálculo de la
distancia. En segundo lugar, las incrustaciones suelen capturar características más
importantes en la entrada (como características geométricas importantes en las
imágenes; consulte la figura 4.5) e ignoran las menos importantes (por ejemplo, el
brillo y el tamaño) debido al hecho de que se han entrenado en una gran cantidad.
conjunto de datos de clasificación. En algunos casos,

En comparación con el enfoque de red neuronal, kNN no requiere ningún entrenamiento. En los
casos en que la cantidad de ejemplos de referencia no es demasiado grande y la dimensionalidad
de la entrada no es demasiado alta, usar kNN puede ser computacionalmente más eficiente que
entrenar una red neuronal y ejecutarla para inferencia.
174 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

(continuado)
Sin embargo, la inferencia de kNN no se adapta bien a la cantidad de datos. En particular,
dadonorteejemplos de referencia, un clasificador kNN debe calcularnortedistancias con el fin
de hacer una predicción para cada entrada.aCuándonortese vuelve grande, la cantidad de
cómputo puede volverse intratable. Por el contrario, la inferencia con una red neuronal no
cambia con la cantidad de datos de entrenamiento. Una vez que la red ha sido entrenada, no
importa cuántos ejemplos se incluyeron en el entrenamiento. La cantidad de cómputo que
toma el paso directo en la red es solo una función de la topología de la red.

Si está interesado en usar kNN para sus aplicaciones, consulte la biblioteca kNN acelerada
por WebGL creada sobre TensorFlow.js:http://mng.bz/2Jp8.

a
Pero vea los esfuerzos de investigación para diseñar algoritmos que se aproximen al algoritmo kNN, pero que se
ejecuten más rápido y se escalen mejor que kNN: Gal Yona, "Búsqueda rápida de imágenes casi duplicadas usando
hashing sensible a la localidad", Towards Data Science, 5 de mayo de 2018,http://mng.bz/1wm1.

5.1.3 Aprovechar al máximo el aprendizaje por transferencia a través de ajustes: un ejemplo de audio

En las secciones anteriores, los ejemplos de transferencia de aprendizaje trataron con entradas
visuales. En este ejemplo, mostraremos que el aprendizaje por transferencia también funciona en
datos de audio representados como imágenes de espectrograma. Recuerde que introdujimos el
convnet para reconocer comandos de voz (palabras cortas y aisladas) en la sección 4.4. El
reconocedor de comandos de voz que construimos era capaz de reconocer solo 18 palabras
diferentes (como "uno", "dos", "arriba" y "abajo"). ¿Qué pasa si quieres entrenar un reconocedor de
otras palabras? Tal vez su aplicación en particular requiera que el usuario diga palabras específicas
como "rojo" o "azul", o incluso palabras que elijan los propios usuarios; o quizás su aplicación está
destinada a usuarios que hablan idiomas distintos al inglés. Este es un ejemplo clásico de
transferencia de aprendizaje: con la pequeña cantidad de datos disponibles, ustedpudointente
entrenar un modelo completamente desde cero, pero usar un modelo previamente entrenado
como base le permite gastar menos tiempo y recursos de cómputo mientras obtiene un mayor
grado de precisión.

HCÓMO HACER TRANSFERIR EL APRENDIZAJE EN EL HABLA-APLICACIÓN DE EJEMPLO DE COMANDOS


Antes de describir cómo funciona el aprendizaje por transferencia en este ejemplo, será bueno que se familiarice
con el uso de la función de aprendizaje por transferencia a través de la interfaz de usuario. Para usar la interfaz
de usuario, asegúrese de que su máquina tenga un dispositivo de entrada de audio (un micrófono) conectado y
que el volumen de entrada de audio esté configurado en un valor distinto de cero en la configuración de su
sistema. Para descargar el código de la demo y ejecutarlo, haga lo siguiente (el mismo procedimiento que en la
sección 4.4.1):

git clonar https://github.com/tensorflow/tfjs-models.git modelos tfjs/


comandos de voz
discos compactos

hilo && hilo publicar-local


Traducido del inglés al español - www.onlinedoctranslator.com

Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 175

CD de demostración

hilado && hilado link-local && hilado reloj

Cuando se inicie la interfaz de usuario, responda Sí a la solicitud del navegador de su permiso para
acceder al micrófono. La Figura 5.9 muestra una captura de pantalla de ejemplo de la
demostración. Cuando se inicie, la página de demostración cargará automáticamente un modelo
de comandos de voz entrenado previamente desde Internet, usando eltf.loadLayersModel()método
que apunta a una URL HTTPS. Después de cargar el modelo, se habilitarán los botones Iniciar e
Introducir palabras de transferencia. Si hace clic en el botón Inicio, la demostración entrará en un
modo de inferencia en el que detecta las 18 palabras básicas (tal como se muestran en la pantalla)
de forma continua. Cada vez que se detecta una palabra, el cuadro de la palabra correspondiente
se iluminará en la pantalla. Sin embargo, si hace clic en el botón Introducir palabras de
transferencia, aparecerán varios botones adicionales en la pantalla. Estos botones se crean a partir
de las palabras separadas por comas en el cuadro de entrada de texto a la derecha. Las palabras
predeterminadas son "ruido", "rojo" y "verde". Estas son las palabras que el modelo de aprendizaje
por transferencia será entrenado para reconocer. Pero puede modificar el contenido del cuadro de
entrada si desea entrenar un modelo de transferencia para otras palabras, siempre que conserve el
elemento de "ruido". El elemento de "ruido" es especial, para el cual debe recopilar muestras de
ruido de fondo, es decir, muestras sin ningún sonido de voz en ellas. Esto permite que el modelo de
transferencia distinga los momentos en los que se habla una palabra de los momentos de silencio
(ruido de fondo). Al hacer clic en estos botones, la demostración grabará un fragmento de audio de
1 segundo del micrófono y mostrará su espectrograma junto al botón. los

Figura 5.9 Una captura de pantalla de ejemplo de la función


de transferencia de aprendizaje del ejemplo de comando de
voz. Aquí, el usuario ha ingresado un conjunto personalizado
de palabras para transferir el aprendizaje: "sentir", "sellar",
"ternera" y "celo", además del elemento "ruido" siempre
requerido. Además, el usuario ha recopilado 20 ejemplos
para cada una de las categorías de palabras y ruidos.
176 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

El número en el botón de palabra realiza un seguimiento de cuántos ejemplos ha recopilado para la palabra en
particular hasta el momento.
Como es el caso general en los problemas de aprendizaje automático, cuantos más datos pueda
recopilar (según lo permitan el tiempo y los recursos disponibles), mejor resultará el modelo entrenado.
La aplicación de ejemplo requiere al menos ocho ejemplos para cada palabra. Si no quiere o no puede
recopilar muestras de sonido usted mismo, puede descargar un conjunto de datos recopilados
previamente dehttp://mng.bz/POGY(tamaño del archivo: 9 MB) y cárguelo mediante el botón Cargar en la
sección Dataset IO de la interfaz de usuario.
Una vez que el conjunto de datos esté listo, ya sea mediante la carga de archivos o su propia colección de
muestras, se habilitará el botón Iniciar transferencia de aprendizaje. Puede hacer clic en el botón para iniciar el
entrenamiento del modelo de transferencia. La aplicación realiza una división de 3:1 en los espectrogramas de
audio que ha recopilado, de modo que un 75 % seleccionado al azar se usará para entrenamiento, mientras que
el 25 % restante se usará para validación.8La aplicación muestra los valores de pérdida y precisión del conjunto de
entrenamiento junto con los valores del conjunto de validación a medida que ocurre el aprendizaje de
transferencia. Una vez completada la capacitación, puede hacer clic en el botón Iniciar para permitir que la
demostración inicie un reconocimiento continuo de las palabras de transferencia, tiempo durante el cual puede
evaluar empíricamente la precisión del modelo de transferencia.
Debe experimentar con diferentes conjuntos de palabras y ver cómo afectan la precisión que puede
obtener después de transferir el aprendizaje con ellas. En el conjunto predeterminado, "rojo" y "verde",
las palabras son bastante distintas entre sí en cuanto a su contenido fonético. Por ejemplo, sus
consonantes de inicio son dos sonidos muy distintos, "r" y "g". Sus vocales también suenan bastante
distintas ("e" frente a "ee"); también lo hacen sus consonantes finales ("d" versus "n"). Por lo tanto,
debería poder obtener una precisión de validación casi perfecta al final de la capacitación de
transferencia, siempre que la cantidad de ejemplos que recopile para cada palabra no sea demasiado
pequeña (digamos> = 8), y no use un número de época que es demasiado pequeño (lo que conduce a un
ajuste insuficiente) o demasiado grande (lo que conduce a un ajuste excesivo; consulte el capítulo 8).

Para hacer que la tarea de aprendizaje por transferencia sea más desafiante para el modelo, use un
conjunto que consista en 1) palabras más confusas y 2) un vocabulario más amplio. Esto es lo que hicimos
para la captura de pantalla de la figura 5.9. Allí se utilizan un conjunto de cuatro palabras que suenan
similares entre sí: “sentir”, “sello”, “ternera” y “celo”. Estas palabras tienen vocales y consonantes finales
idénticas, así como cuatro consonantes de inicio de sonido similar. Incluso podrían confundir a un oyente
humano que no presta atención o a alguien que escucha a través de una línea telefónica defectuosa. A
partir de la curva de precisión en la parte inferior derecha de la figura, puede ver que no es una tarea fácil
para el modelo alcanzar una precisión superior al 90 %, por lo que una fase inicial de transferencia de
aprendizaje debe complementarse con una fase adicional desintonia FINA—es decir, un truco de
aprendizaje por transferencia.

8
Esta es la razón por la que la demostración requiere que recopile al menos ocho muestras por palabra. Con menos palabras, la cantidad de
muestras para cada palabra será pequeña en el conjunto de validación, lo que generará estimaciones de pérdida y precisión potencialmente
poco confiables.
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 177

DEEP INMERSIÓN EN FINE-SINTONIZANDO LA TRANSFERENCIA DE APRENDIZAJE

El ajuste fino es una técnica que lo ayuda a alcanzar niveles de precisión que no se pueden lograr
simplemente entrenando al nuevo jefe del modelo de transferencia. Si desea comprender cómo
funciona el ajuste fino, esta sección lo explica con mayor detalle. Habrá algunos puntos técnicos
para digerir. Pero la comprensión más profunda del aprendizaje de transferencia y la
implementación de TensorFlow.js relacionada que obtendrá valdrá la pena el esfuerzo.

Construyendo un modelo único para el aprendizaje por transferencia

Primero, necesitamos entender cómo la aplicación de aprendizaje por transferencia de voz crea el
modelo para el aprendizaje por transferencia. El código del listado 5.7 (de speech-commands/src/
browser_ fft_recognizer.ts) crea un modelo a partir del modelo base de comandos de voz (el que
aprendió en la sección 4.4.1). Primero encuentra la penúltima (la penúltima) capa densa del modelo
y obtiene su tensor simbólico de salida (base de salida truncadaen el código). Luego crea un nuevo
modelo de cabeza que consta de una sola capa densa. La forma de entrada de este nuevo cabezal
coincide con la forma delbase de salida truncadatensor simbólico, y su forma de salida coincide con
el número de palabras en el conjunto de datos de transferencia (cinco, en el caso de la figura 5.9).
La capa densa está configurada para usar la activación softmax, que se adapta a la tarea de
clasificación multiclase. (Tenga en cuenta que, a diferencia de la mayoría de las otras listas de
códigos en el libro, el siguiente código está escrito en TypeScript. Si no está familiarizado con
TypeScript, simplemente puede ignorar las notaciones de tipo comovacíoytf.Tensor Simbólico.)9

Listado 5.7 Creando el modelo de transferencia-aprendizaje como un solomodelo tfobjeto9

privado createTransferModelFromBaseModel(): void {


const capas = this.baseModel.layers; let layerIndex
= capas.longitud - 2; while (índice de capa >= 0) {

if (layers[layerIndex].getClassName().toLowerCase() === 'denso') {


descanso;
} Encuentra el penúltimo denso
índice de capa--; capa del modelo base
}
si (índice de capa < 0) {
throw new Error('No se puede encontrar una capa densa oculta en el modelo base.');
} Obtiene la capa que se descongelará durante
this.secondLastBaseDenseLayer = el ajuste posterior (ver listado 5.8)
capas[índicecapa];
const base de salida truncada = capas[índice de capa].salida como Encuentra el
tf.SymbolicTensor; tensor simbólico

this.transferHead = tf.capas.densas({
Crea la nueva cabeza
unidades: this.words.length,
del modelo.
activación: 'softmax',

9
Dos notas sobre esta lista de códigos: 1) El código está escrito en TypeScript porque es parte del código reutilizable.
Biblioteca @tensorflow-models/speech-commands. 2) Se han eliminado algunos códigos de verificación de errores de
este código en aras de la simplicidad.
178 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

inputShape: truncadoBaseOutput.shape.slice(1) })); Crea la nueva cabeza


del modelo.
salida de transferencia const =
this.transferHead.apply(truncatedBaseOutput) this.model = como tf.SymbolicTensor;

tf.model({entradas: this.baseModel.inputs, salidas: transferOutput});


}

"Aplica" la nueva cabeza en la salida del Utiliza la API tf.model() para crear un nuevo
modelo base truncado para obtener la modelo para transferir aprendizaje, especificando las
salida final del nuevo modelo como un entradas del modelo original como su entrada y el
tensor simbólico nuevo tensor simbólico como salida

El nuevo cabezal se utiliza de una forma novedosa: suaplicar()se llama al método usando el base de
salida truncadatensor simbólico como argumento de entrada.aplicar()es un método que está
disponible en cada capa y objeto de modelo en TensorFlow.js. Lo que hace el aplicar()método
hacer? Como sugiere su nombre, "aplica" el nuevo modelo de cabeza en una entrada y te da una
salida. Las cosas importantes a tener en cuenta son las siguientes:

- Tanto la entrada como la salida involucradas aquí son simbólicas: son marcadores de posición
para valores de tensor concretos.
- La Figura 5.10 muestra una ilustración gráfica de esto: la entrada simbólica (salida
base truncada)no es una entidad aislada; en cambio, es el resultado de la
penúltima capa densa del modelo base. La capa densa recibe entradas de otra
capa, que a su vez recibe entradas de su capa aguas arriba, y así sucesivamente.
Por lo tanto,base de salida truncadalleva consigo un subgráfico del modelo base: a
saber, el subgráfico entre la entrada del modelo base y la salida de la penúltima
capa densa. En otras palabras, es el gráfico completo del modelo base, menos la
parte posterior a la penúltima capa densa. Como resultado, la salida del aplicar()
call lleva un gráfico que consta de ese subgráfico más la nueva capa densa. La
salida y la entrada original se usan juntas en una llamada al tf.modelo()función,
que produce un nuevo modelo. Este nuevo modelo es igual al modelo base
excepto que su cabeza ha sido reemplazada con la nueva capa densa (ver la parte
inferior de la figura 5.10).
Tenga en cuenta que el enfoque aquí es diferente de cómo fusionamos modelos en la sección 5.1.2.
Allí, creamos una base truncada y un nuevo modelo de cabeza como dos instancias de modelo
separadas. Como resultado, ejecutar la inferencia en cada ejemplo de entrada implica dos predecir()
llamadas Aquí, las entradas esperadas por el nuevo modelo son idénticas a los tensores de
espectrograma de audio esperados por el modelo base. Al mismo tiempo, el nuevo modelo genera
directamente las puntuaciones de probabilidad de las nuevas palabras. Cada inferencia toma solo
unapredecir()llamada y por lo tanto es un proceso más ágil. Al encapsular todas las capas en un solo
modelo, nuestro nuevo enfoque tiene una ventaja adicional importante para nuestra aplicación:
nos permite realizar retropropagación a través de cualquiera de las capas involucradas en el
reconocimiento de las nuevas palabras. Esto nos permite realizar el truco de ajuste fino. Esto es lo
que exploraremos en la siguiente sección.
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 179

base de salida truncada transferirSalida


1
modelo base (este.modelo base) 2 cabeza de transferencia

conv2d aplanar denso 1 denso 2 denso 3

...

tf.modelo()
llamada
Entradas Salidas

Nuevo modelo (Este modelo)


conv2d aplanar denso 1 denso 3

...

Figura 5.10 Una ilustración esquemática de la forma en que se crea el nuevo modelo de extremo a extremo para el
aprendizaje por transferencia. Esta figura debe leerse junto con el listado 5.7. Algunas partes de la figura que
corresponden a las variables del listado 5.7 están rotuladas en fuente de ancho fijo. Paso 1: se obtiene el tensor
simbólico de salida de la penúltima capa densa del modelo original (indicado por la flecha gruesa). Posteriormente
se usará en el paso 3. Paso 2: se crea el nuevo modelo de cabeza, que consta de una sola capa densa de salida
(etiquetada como "3 densa"). Paso 3: elaplicar()El método del nuevo modelo de cabeza se invoca con el tensor
simbólico del paso 1 como argumento de entrada. La llamada conecta la entrada al nuevo modelo principal con el
modelo base truncado del paso 1. Paso 4: el valor de retorno del aplicar()call se usa junto con el tensor simbólico
de entrada del modelo original durante una llamada altf.modelo()función. Esta llamada devuelve un nuevo
modelo que contiene todas las capas del modelo original desde la primera capa hasta la penúltima capa densa,
además de la capa densa en la nueva cabeza. En efecto, esto cambia el cabezal antiguo del modelo original por el
cabezal nuevo, preparando el escenario para el entrenamiento posterior en los datos de transferencia. Tenga en
cuenta que algunas (siete) capas del modelo de comando de voz real se omiten en este diagrama en aras de la
simplicidad visual. En esta figura, las capas teñidas se pueden entrenar, mientras que las capas de color blanco no
se pueden entrenar.
180 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

Ajuste fino a través de la descongelación de capas

El ajuste fino es un paso opcional del aprendizaje de transferencia que sigue a una fase inicial de
entrenamiento del modelo. En la fase inicial, se congelaron todas las capas del modelo base (sus
entrenableatributo se estableció enfalso),y la actualización del peso ocurrió solo en las capas de la
cabeza. Hemos visto este tipo de capacitación inicial en los ejemplos mnist-transfer-cnn y webcam-
transfer-learning anteriormente en este capítulo. Durante el ajuste fino, algunas de las capas del
modelo base se descongelan (susentrenableatributo se establece en cierto),y luego el modelo se
vuelve a entrenar en los datos de transferencia. Este descongelamiento de capas se muestra
esquemáticamente en la figura 5.11. El código del listado 5.8 (de speech-commands/src/
browser_fft_recognizer.ts) muestra cómo se hace eso en TensorFlow.js para el ejemplo de
SpeechCommand.

A. Fase inicial B. Fase de puesta a punto

conv2d aplanar denso 1 denso 3 conv2d aplanar denso 1 denso 3

... ...

Capas congeladas Capa entrenable Capas congeladas Capas entrenables

Figura 5.11 Ilustración de capas congeladas y no congeladas (es decir, entrenables) durante las fases inicial (panel A) y ajuste fino (panel B)
del aprendizaje de transferencia como lo hace el código en el listado 5.8. Tenga en cuenta que la razón por la que dense1 es seguido
inmediatamente por dense3 es que dense2 (la salida original del modelo base) se ha truncado como el primer paso del aprendizaje de
transferencia (consulte la figura 5.10).

10

Listado 5.8 Aprendizaje de transferencia inicial, seguido de ajustes10

asíncrono entrenar(config?: TransferLearnConfig):


Promesa<tf.Historia|[tf.Historia, tf.Historia]> if (config == {
null) {
Se asegura de que todas las
configuración = {};
capas de la base truncada
}
modelo, incluido el que se
if (este.modelo == nulo) {
ajustará más adelante, se
this.createTransferModelFromBaseModel();
congelan para la fase inicial
}
del entrenamiento de
this.secondLastBaseDenseLayer.trainable = falso; transferencia
this.model.compile({
pérdida: 'categoricalCrossentropy', optimizador:
Compila el modelo para el entrenamiento
config.optimizer || 'sgd', métricas: ['acc'] de transferencia inicial

});

10 Se ha eliminado parte del código de verificación de errores para centrarse en las partes clave del algoritmo.
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 181

const {xs, ys} = this.collectTransferDataAsTensors(); dejar entrenarXs:


tf.Tensor;
vamos a entrenar: tf.Tensor;
let valData: [tf.Tensor, tf.Tensor]; tratar { Si se requiere validationSplit, divide los datos
de transferencia en un conjunto de
if (config.validationSplit != null) { entrenamiento y un conjunto de validación
divisiones const = divisiónValorEntrenEquilibrado( de forma equilibrada
x, sí, config.validationSplit);
entrenarXs = divisiones.trenXs;
entrenar = divide.trenYs;
valData = [splits.valXs, splits.valYs]; } demás {

entrenarXs = xs;
entrenar = sí;
}

const history = esperar este.modelo.fit(trainXs, trainYs, {


Llamadas épocas: config.epochs == nulo? 20: config.epochs, datos de validación:
Modelo.fit() datos de valor,
para inicial tamaño de lote: config.batchSize,
transferir
devoluciones de llamada: config.callback == nulo? nulo: [config.callback]});
capacitación

if (config.fineTuningEpochs != null && config.fineTuningEpochs > 0) {


this.secondLastBaseDenseLayer.entrenable =
cierto;
Para afinar,
descongela el const fineTuningOptimizer: string|tf.Optimizer =
penúltimo config.fineTuningOptimizer == nulo? 'sgd':
densa capa de config.fineTuningOptimizer;
el modelo base este.modelo.compilar({
(la última capa pérdida: 'categoricalCrossentropy', Vuelve a compilar el modelo después
del truncado optimizador: fineTuningOptimizer, de descongelar la capa (o la
modelo básico)
métricas: ['acc'] descongelación no surtirá efecto)

});
const fineTuningHistory = esperar este.modelo.fit(trenXs, trenYs, {
épocas: config.fineTuningEpochs,
ValidationData: valData,
tamaño de lote: config.batchSize, Llamadas Model.fit()

devoluciones de llamada: config.fineTuningCallback == nulo? para afinar


nulo :
[config.fineTuningCallback]
});
regreso [historia, fineTuningHistory];
} demás {
regreso historia;
}
} finalmente {
tf.dispose([xs, ys, trainXs, trainYs, valData]);
}
}

Hay varias cosas importantes para señalar sobre el código en el listado 5.8:
- Cada vez que congela o descongela cualquier capa cambiando suentrenable
atributo, necesita llamar alcompilar()método del modelo de nuevo con el fin de
182 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

que el cambio surta efecto. Ya hemos cubierto eso cuando hablamos del ejemplo de
transferencia de aprendizaje de MNIST en la sección 5.1.1.
- Reservamos una fracción de los datos de entrenamiento para validación. Esto garantiza que la
pérdida y la precisión que observamos reflejen qué tan bien funciona el modelo en las entradas
que no ha visto durante la retropropagación. Sin embargo, la forma en que dividimos una fracción
de los datos recopilados para la validación es diferente a la anterior y merece algo de atención.

En el ejemplo de convnet MNIST (listado 4.2 en el capítulo 4), usamos el validaciónDividir


parámetro para dejar que elModelo.fit()reserve el último 15-20% de los datos para la validación. El
mismo enfoque no funcionará muy bien aquí. ¿Por qué? Porque aquí tenemos un conjunto de
entrenamiento mucho más pequeño en comparación con el tamaño de los datos en los ejemplos
anteriores. Como resultado, dividir a ciegas los últimos ejemplos para la validación puede muy
bien dar lugar a escenarios en los que algunas palabras están subrepresentadas en el
subconjunto de validación. Por ejemplo, suponga que ha recopilado ocho ejemplos para cada una
de las cuatro palabras "sentir", "sello", "ternera" y "celo" y elige el último 25 % de las 32 muestras
(8 ejemplos) para la validación. Entonces, en promedio, habrá solo dos ejemplos para cada
palabra en el subconjunto de validación. Debido a la aleatoriedad, algunas de las palabras pueden
terminar teniendo solo un ejemplo en el subconjunto de validación, ¡y otras pueden no tener
ningún ejemplo allí! Obviamente, si el conjunto de validación carece de ciertas palabras, no será
un conjunto muy bueno para medir la precisión del modelo. Es por eso que usamos una función
personalizada (equilibradoTrainValSpliten el listado 5.8). Esta función tiene en cuenta la etiqueta
de palabra verdadera de los ejemplos y garantiza que todas las palabras diferentes obtengan una
representación justa en los subconjuntos de entrenamiento y validación. Si tiene una aplicación
de transferencia de aprendizaje que involucra un conjunto de datos pequeño similar, es una
buena idea hacer lo mismo.

Entonces, ¿qué hace el ajuste fino por nosotros? ¿Qué valor añadido aporta además de la fase inicial del
aprendizaje por transferencia? Para ilustrar eso, trazamos las curvas de pérdida y precisión de las fases
inicial y de ajuste fino concatenadas como curvas continuas en el panel A de la figura 5.12. El conjunto de
datos de transferencia involucrado aquí consta de las mismas cuatro palabras que vimos en la figura 5.9.
Las primeras 100 épocas de cada curva corresponden a la fase inicial, mientras que las últimas 300 épocas
corresponden al ajuste fino. Puede ver que hacia el final de las 100 épocas de entrenamiento inicial, las
curvas de pérdida y precisión comienzan a aplanarse y comienzan a entrar en regímenes de rendimientos
decrecientes. La precisión en el subconjunto de validación se nivela alrededor del 84 %. (Observe cuán
engañoso sería mirar solo la curva de precisión de lasubconjunto de entrenamiento, que fácilmente se
aproxima al 100%). Sin embargo, al descongelar la capa densa en el modelo base, volver a compilar el
modelo y comenzar la fase de ajuste fino del entrenamiento, la precisión de la validación se desacelera y
podría llegar hasta el 90–92 %, que es una ganancia muy decente de 6 a 8 puntos porcentuales en
precisión. Se puede ver un efecto similar en la curva de pérdida de validación.

Para ilustrar el valor del ajuste fino sobre el aprendizaje de transferencia sin ajuste fino, mostramos en
el panel B de la figura 5.12 lo que sucede si el modelo de transferencia se entrena para un
Introducción al aprendizaje por transferencia: reutilización de modelos preentrenados 183

Ajuste fino (FT) Ajuste fino (FT)


A
1
1.5 entrenar entrenar

valor valor

0.8

Exactitud
Pérdida 1 tren (FT) tren (FT)
valor (FT) valor (FT)
0.5 0.6

0 0.4
0 100 200 300 0 100 200 300
época # época #

1
entrenar entrenar

1.5 valor valor

0.8

Exactitud
Pérdida

1
0.6
0.5

0 0.4
0 100 200 300 0 100 200 300
época # época #

Figura 5.12 Panel A: ejemplo de curvas de pérdida y precisión del aprendizaje de transferencia y el ajuste fino posterior (FT
en las leyendas de la gráfica). Observe el punto de inflexión en la unión entre las partes inicial y de ajuste fino de las
curvas. El ajuste fino acelera la reducción de la pérdida y la ganancia en la precisión, que se debe a la descongelación de
las pocas capas superiores del modelo base y al aumento resultante en la capacidad del modelo, y su adaptación hacia las
características únicas en la transferencia. datos de aprendizaje. Panel B: las curvas de pérdida y precisión del
entrenamiento del modelo de transferencia un número igual de épocas (400 épocas) sin ajuste fino. Tenga en cuenta que
sin el ajuste fino, la pérdida de validación converge a un valor más alto y la precisión de validación a un valor más bajo en
comparación con el panel A. Tenga en cuenta que mientras que la precisión final alcanza aproximadamente0.9con ajuste
fino (panel A), se atasca aproximadamente0.85sin el ajuste fino pero con el mismo número de épocas totales (panel B).

igual número de (400) épocas sin ajustar las pocas capas superiores del modelo base. No hay
un "punto de inflexión" en las curvas de pérdida o precisión que ocurrieron en el panel A en
la época 100 cuando se inicia el ajuste fino. En cambio, las curvas de pérdida y precisión se
estabilizan y convergen a valores peores.
Entonces, ¿por qué ayuda el ajuste fino? Puede entenderse como un aumento en la capacidad
del modelo. Al descongelar algunas de las capas superiores del modelo base, permitimos que el
modelo de transferencia minimice la función de pérdida en un espacio de parámetros de mayor
dimensión que la fase inicial. Esto es similar a agregar capas ocultas a una red neuronal. Los
parámetros de peso de la capa densa descongelada se optimizaron para el conjunto de datos
original (el que consta de palabras como "uno", "dos", "sí" y "no"), que pueden no ser óptimos para
las palabras de transferencia. Esto se debe a que las representaciones internas que ayudan al
modelo a distinguir entre esas palabras originales pueden no ser las representaciones que hacen
que las palabras de transferencia sean más fáciles de distinguir entre sí. Al permitir
184 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

Para que esos parámetros se optimicen aún más (es decir, se ajusten con precisión) para las palabras de
transferencia, permitimos que la representación se optimice para las palabras de transferencia. Por lo tanto,
obtenemos un aumento en la precisión de la validación de las palabras de transferencia. Tenga en cuenta que
este impulso es más fácil de ver cuando la tarea de transferencia de aprendizaje es difícil (como con las cuatro
palabras confusas: "sentir", "sellar", "ternera" y "celo"). Con tareas más sencillas (palabras más distintas como
"rojo" y "verde"), la precisión de la validación puede llegar al 100 % con solo el aprendizaje de transferencia inicial.

Una pregunta que quizás desee hacer es, aquí descongelamos solo una capa en el modelo base,
pero ¿ayudará descongelar más capas? La respuesta corta es, depende, porque descongelar aún
más capas le da al modelo una capacidad aún mayor. Pero como mencionamos en el capítulo 4 y
discutiremos con mayor detalle en el capítulo 8, una mayor capacidad conduce a un mayor riesgo
de sobreajuste, especialmente cuando nos enfrentamos a un pequeño conjunto de datos como los
ejemplos de audio recopilados en el navegador aquí. Esto sin mencionar la carga de cómputo
adicional requerida para entrenar más capas. Le recomendamos que experimente con él usted
mismo como parte del ejercicio 4 al final de este capítulo.
Terminemos esta sección sobre el aprendizaje de transferencia en TensorFlow.js. Presentamos
tres formas diferentes de reutilizar un modelo previamente entrenado en nuevas tareas. Para
ayudarlo a decidir qué enfoque utilizar en sus futuros proyectos de transferencia de aprendizaje,
resumimos los tres enfoques y sus pros y contras relativos en la tabla 5.1.

Tabla 5.1 Un resumen de tres enfoques para transferir el aprendizaje en TensorFlow.js y sus ventajas y
desventajas relativas

Acercarse ventajas Contras

Use el modelo original y congele • Simple y conveniente • Funciona solo si la forma de salida y la
sus primeras capas (extracción de activación requeridas por el
características) aprendizaje de transferencia
(sección 5.1.1). coinciden con las del modelo base

Obtenga activaciones internas del • Aplicable a casos de transferencia • Necesidad de administrar dos instancias de
modelo original como de aprendizaje que requieren una modelo separadas
incrustaciones para el ejemplo de forma de salida diferente a la • Difícil de ajustar las capas del
entrada y cree un nuevo modelo original modelo original
que tome la incrustación como • Los tensores incrustados son directamente
entrada (sección 5.1.2). accesibles, lo que hace posibles métodos
como clasificadores de k-vecinos más
cercanos (kNN, consulte el cuadro de
información 5.2)

Cree un nuevo modelo que • Aplicable a casos de transferencia • Activaciones internas


contenga las capas de extracción de aprendizaje que requieren una (incrustaciones) que no son
de características del modelo forma de salida diferente a la directamente accesibles
original y las capas de la nueva original
cabeza (sección 5.1.3). • Solo una única instancia de modelo para
administrar
• Permite el ajuste fino de las capas de

extracción de características
Detección de objetos mediante transferencia de aprendizaje en una convnet 185

5.2 Detección de objetos mediante transferencia de aprendizaje en una convnet


Los ejemplos de transferencia de aprendizaje que ha visto en este capítulo hasta ahora tienen algo
en común: la naturaleza de la tarea de aprendizaje automático permanece igual después de la
transferencia. En particular, toman un modelo de visión por computadora entrenado en una tarea
de clasificación multiclase y lo aplican en otra tarea de clasificación multiclase. En esta sección,
mostraremos que esto no tiene por qué ser así. El modelo base se puede usar en una tarea muy
diferente de la original, por ejemplo, cuando desea usar un modelo base entrenado en una tarea
de clasificación para realizar una regresión (ajustar un número). Este tipo de transferencia entre
dominios es un buen ejemplo de la versatilidad y reutilización del aprendizaje profundo, que es una
de las principales razones detrás del éxito de este campo.
La nueva tarea que usaremos para ilustrar este punto esdetección de objetos, el primer tipo de
problema de visión artificial sin clasificación que encontrará en este libro. La detección de objetos
implica detectar ciertas clases de objetos en una imagen. ¿En qué se diferencia de la clasificación?
En la detección de objetos, el objeto detectado se informa en términos no solo de su clase (qué tipo
de objeto es), sino también de información adicional sobre la ubicación del objeto dentro de la
imagen (dónde está el objeto). Este último es un dato que un mero clasificador no proporciona. Por
ejemplo, en un sistema típico de detección de objetos utilizado por automóviles autónomos, se
analiza un marco de imagen de entrada para que el sistema emita no solo los tipos de objetos
interesantes que están presentes en la imagen (como vehículos y peatones), sino también el
ubicación, tamaño aparente y pose de tales objetos dentro del sistema de coordenadas de la
imagen.
El código de ejemplo está en el directorio de detección de objetos simples del repositorio tfjs-
examples. Tenga en cuenta que este ejemplo es diferente de los que ha visto hasta ahora en que combina
el entrenamiento de modelos en Node.js con la inferencia en el navegador. Específicamente, el
entrenamiento del modelo ocurre con tfjs-node (o tfjs-node-gpu) y el modelo entrenado se guarda en el
disco. A continuación, se utiliza un servidor de paquetes para servir los archivos de modelo guardados,
junto con index.html e index.js estáticos, a fin de mostrar la inferencia sobre el modelo en el navegador.

La secuencia de comandos que puede usar para ejecutar el ejemplo es la siguiente (con
algunas cadenas de comentarios que no necesita incluir al ingresar los comandos):

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


simple-object-detection
discos compactos

hilo
# Paso opcional para entrenar tu propio modelo usando Node.js: yarn train \

- - numEjemplos 20000 \
- - initialTransferEpochs 100 \
- - fineTuningEpochs 200
yarn watch # Ejecuta la inferencia de detección de objetos en el navegador.

lostren de hiloEl comando realiza el entrenamiento del modelo en su máquina y guarda el modelo dentro
de la carpeta ./dist cuando finaliza. Tenga en cuenta que este es un trabajo de entrenamiento de larga
duración y se maneja mejor si tiene una GPU habilitada para CUDA, lo que aumenta el entrenamiento.
186 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

velocidad por un factor de 3 a 4. Para hacer esto, solo necesita agregar el --GPUbandera a latren de
hilomando:

tren de hilo--GPU\
- - numEjemplos 20000 \
- - épocas de transferencia inicial 100 \
- - fineTuningEpochs 200

Sin embargo, si no tiene el tiempo o los recursos para entrenar el modelo en su propia máquina,
no se preocupe: puede omitir eltren de hilocomando y proceder directamente areloj de hiloLa
página de inferencia que se ejecuta en el navegador le permitirá cargar un modelo que ya hemos
entrenado para usted desde una ubicación centralizada a través de HTTP.

5.2.1 Un problema simple de detección de objetos basado en escenas sintetizadas


Las técnicas de detección de objetos de última generación implican muchos trucos que no son adecuados para un tutorial inicial sobre el tema.

Nuestro objetivo aquí es mostrar la esencia de cómo funciona la detección de objetos sin atascarnos con demasiados detalles técnicos. Con este

fin, diseñamos un problema simple de detección de objetos que involucra escenas de imágenes sintetizadas (ver figura 5.13). Estas imágenes

sintetizadas tienen una dimensión de 224 × 224 y una profundidad de color de 3 (canales RGB) y, por lo tanto, coinciden con la especificación de

entrada del modelo MobileNet que formará la base de nuestro modelo. Como muestra el ejemplo de la figura 5.13, cada escena tiene un fondo

blanco. El objeto a detectar es un triángulo equilátero o un rectángulo. Si el objeto es un triángulo, su tamaño y orientación son aleatorios; si el

objeto es un rectángulo, su alto y ancho varían aleatoriamente. Si la escena consistiera únicamente en el fondo blanco y el objeto de interés, la

tarea sería demasiado fácil para mostrar el poder de nuestra técnica. Para aumentar la dificultad de la tarea, una serie de "objetos de ruido" se

esparcen al azar en la escena. Estos incluyen 10 círculos y 10 segmentos de línea en cada imagen. Las ubicaciones y tamaños de los círculos se

generan aleatoriamente, al igual que las ubicaciones y longitudes de los segmentos de línea. Algunos de los objetos de ruido pueden estar

encima del objeto de destino, oscureciéndolo parcialmente. Todos los objetos de destino y de ruido tienen colores generados aleatoriamente.

Estos incluyen 10 círculos y 10 segmentos de línea en cada imagen. Las ubicaciones y tamaños de los círculos se generan aleatoriamente, al

igual que las ubicaciones y longitudes de los segmentos de línea. Algunos de los objetos de ruido pueden estar encima del objeto de destino,

oscureciéndolo parcialmente. Todos los objetos de destino y de ruido tienen colores generados aleatoriamente. Estos incluyen 10 círculos y 10

segmentos de línea en cada imagen. Las ubicaciones y tamaños de los círculos se generan aleatoriamente, al igual que las ubicaciones y

longitudes de los segmentos de línea. Algunos de los objetos de ruido pueden estar encima del objeto de destino, oscureciéndolo parcialmente.

Todos los objetos de destino y de ruido tienen colores generados aleatoriamente.

Con los datos de entrada completamente caracterizados, ahora podemos definir la tarea para el
modelo que estamos a punto de crear y entrenar. El modelo generará cinco números, que están
organizados en dos grupos:

-El primer grupo contiene un solo número, que indica si el objeto detectado es un
triángulo o un rectángulo (independientemente de su ubicación, tamaño, orientación
y color).
- Los cuatro números restantes forman el segundo grupo. Son las coordenadas del cuadro
delimitador alrededor del objeto detectado. Específicamente, son la coordenada x izquierda, la
coordenada x derecha, la coordenada y superior y la coordenada y inferior del cuadro
delimitador, respectivamente. Consulte la figura 5.13 para ver un ejemplo.

Lo bueno de usar datos sintetizados es 1) los verdaderos valores de las etiquetas se conocen
automáticamente y 2) podemos generar tantos datos como queramos. Cada vez que generamos
Detección de objetos mediante transferencia de aprendizaje en una convnet 187

A B

cierto

cierto

predicho predicho

Tiempo de inferencia (ms): 36.7 Clase de Tiempo de inferencia (ms): 26.0 Clase de
objeto verdadero:triángulo Clase de objeto verdadero:rectángulo Clase de
objeto prevista:triángulo objeto prevista:rectángulo

Figura 5.13 Un ejemplo de las escenas sintetizadas utilizadas por la detección de objetos simples. Panel A:
un triángulo equilátero girado como objeto de destino. Panel B: un rectángulo como objeto de destino.
Los cuadros etiquetados como "verdadero" son el cuadro delimitador verdadero para el objeto de interés.
Tenga en cuenta que el objeto de interés a veces puede quedar parcialmente oscurecido por algunos de
los objetos de ruido (segmentos de línea y círculos).

una imagen de escena, el tipo de objeto y su cuadro delimitador están automáticamente disponibles para
nosotros desde el proceso de generación. Por lo tanto, no hay necesidad de ningún etiquetado laborioso de las
imágenes de entrenamiento. Este proceso muy eficiente en el que las características de entrada y las etiquetas se
sintetizan juntas se usa en muchos entornos de prueba y creación de prototipos para modelos de aprendizaje
profundo y es una técnica con la que debe estar familiarizado. Sin embargo, entrenar modelos de detección de
objetos destinados a entradas de imágenes de la vida real requiere escenas reales etiquetadas manualmente.
Afortunadamente, existen tales conjuntos de datos etiquetados disponibles. El conjunto de datos Common
Object in Context (COCO) es uno de ellos (verhttp://cocodataset.org).
Una vez que se completa el entrenamiento, el modelo debería poder localizar y clasificar los objetos de
destino con una precisión razonablemente buena (como se muestra en los ejemplos de la figura 5.13). Para
comprender cómo aprende el modelo esta tarea de detección de objetos, sumérjase con nosotros en el código de
la siguiente sección.

5.2.2 Inmersión profunda en la detección de objetos simples

Ahora construyamos la red neuronal para resolver el problema de detección de objetos sintetizados.
Como antes, construimos nuestro modelo en el modelo MobileNet preentrenado para usar el poderoso
extractor de características visuales generales en las capas convolucionales del modelo. Esto es lo que
cargarBaseTruncada()el método del listado 5.9 lo hace. Sin embargo, un nuevo desafío que enfrenta
nuestro nuevo modelo es cómo predecir dos cosas al mismo tiempo: determinar qué forma tiene el
objeto objetivo y encontrar sus coordenadas en la imagen. No hemos visto este tipo de "predicción de
doble tarea" antes. El truco que usamos aquí es dejar que el modelo genere un tensor que encapsule
ambas predicciones, y diseñaremos una nueva función de pérdida que mida qué tan bien le está yendo al
modelo en ambas tareas a la vez. Nosotrospudoentrenar dos
188 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

modelos separados, uno para clasificar la forma y otro para predecir el cuadro delimitador.
Pero en comparación con el uso de un solo modelo para realizar ambas tareas, ejecutar dos
modelos implicará más cómputo y más uso de memoria y no aprovecha el hecho de que las
capas de extracción de características se pueden compartir entre las dos tareas. (El siguiente
código es de simple-object-detection/train.js).11

Listado 5.9 Definición de un modelo para el aprendizaje de objetos simples basado en MobileNet truncado11

const topLayerGroupNames = [ Establece qué capas descongelar


'conv_pw_9', 'conv_pw_10', const 'conv_pw_11']; para un ajuste fino
topLayerName =
`${topLayerGroupNames[topLayerGroupNames.longitud - 1]}_relu`;

función asíncrona loadTruncatedBase() {


const mobilenet = esperar tf.loadLayersModel(
'https://storage.googleapis.com/' +
'tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json');

const fineTuningLayers = [];


const capa = mobilenet.getLayer(topLayerName); const base
obtiene un intermedio
truncada =
capa: la última capa de
forma el tf.modelo({
extracción de características
truncado entradas: mobilenet.entradas,
MobileNet salidas: capa.salida
});
para (capa const de truncatedBase.layers) {
capa.entrenable = falso;
Congela todas las características-
for (const groupName de topLayerGroupNames) { capas de extracción para
if (layer.name.indexOf(groupName) === 0) { la fase inicial del aprendizaje
fineTuningLayers.push(capa); descanso; por transferencia

} Realiza un seguimiento de las


} capas que se descongelarán
} durante el ajuste fino
devuelve {base truncada, fineTuningLayers};
} La salida de longitud 5 consta de un
longitud-1indicador de forma y un
función construirNuevoCabeza(entradaForma) { cuadro delimitador de longitud 4 (consulte la figura 5.14).
construye lo nuevo const newHead = tf.secuencial();
modelo de cabeza
newHead.add(tf.layers.flatten({inputShape})); newHead.add(tf.layers.dense({unidades:
por lo sencillo 200, activación: 'relu'}));
detección de objetos
newHead.add(tf.layers.dense({unidades: 5})); volver
tarea
cabeza nueva;
}
función asíncrona buildObjectDetectionModel() {
const {truncatedBase, fineTuningLayers} = espera loadTruncatedBase();

const newHead = buildNewHead(truncatedBase.outputs[0].shape.slice(1)); const newOutput =


newHead.apply(truncatedBase.outputs[0]);
const modelo = tf.modelo({ Pone el nuevo modelo de cabeza encima del
MobileNet truncado para formar el modelo
completo para la detección de objetos

11Parte del código para verificar las condiciones de error se eliminó para mayor claridad.
Detección de objetos mediante transferencia de aprendizaje en una convnet 189

entradas: base truncada.entradas,


salidas: nueva salida Coloca el nuevo modelo de cabeza sobre la
}); MobileNet truncada para formar el modelo
completo para la detección de objetos
volver {modelo, fineTuningLayers};
}

La parte clave del modelo de “doble tarea” está construida por elconstruirNewHead()método en el
listado 5.9. Un dibujo esquemático del modelo se muestra en la parte izquierda de la figura 5.14. La
nueva cabeza consta de tres capas. Una capa plana da forma a la salida de la última capa
convolucional de la base de MobileNet truncada para que las capas densas se puedan agregar más
adelante. La primera capa densa es una capa oculta con una no linealidad relu. La segunda capa
densa es el resultado final del cabezal y, por lo tanto, el resultado final de todo el modelo de
detección de objetos. Esta capa tiene la activación lineal por defecto. Es la clave para comprender
cómo funciona este modelo y, por lo tanto, debe analizarse detenidamente.

Modelo de detección de objetos


Función de pérdida personalizada

cabeza nueva
Predicción

Trufln
acttaetned MdoebnilseeN1etDelawareFloridanorteasttmies3denso1 denso2

Aporte
...

224 error medio cuadrado Pérdida


Forma
Etiquetas verdaderas
indicador

Límite X
cajas

Figura 5.14 El modelo de detección de objetos y la función de pérdida personalizada en la que se basa. Consulte el listado 5.9 para ver
cómo se construye el modelo (la parte izquierda). Consulte el listado 5.10 para ver cómo se escribe la función de pérdida personalizada.

Como puede ver en el código, la capa densa final tiene un recuento de unidades de salida de 5.
¿Qué representan los cinco números? Combinan la predicción de forma y la predicción de cuadro
delimitador. Curiosamente, lo que determina su significado no es el modelo en sí, sino la función
de pérdida que se utilizará en él. Anteriormente, vio varios tipos de funciones de pérdida que
pueden ser nombres de cadena sencillos como "error medio cuadrado"y son adecuados para sus
respectivas tareas de aprendizaje automático (por ejemplo, consulte la tabla 3.6 en el capítulo 3).
Sin embargo, esta es solo una de las dos formas de especificar funciones de pérdida en
TensorFlow.js. La otra forma, que es la que estamos usando aquí,
190 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

implica definir una función de JavaScript personalizada que satisfaga una determinada firma. La
firma es la siguiente:

- Dos argumentos de entrada: 1) las etiquetas verdaderas de los ejemplos de entrada y 2) las
predicciones correspondientes del modelo. Cada uno de ellos se representa como un tensor
2D. La forma de los dos tensores debe ser idéntica, siendo la primera dimensión de cada
tensor el tamaño del lote.
- El valor devuelto es un tensor escalar (un tensor de forma []) cuyo valor es la
pérdida media de los ejemplos en el lote.

Nuestra función de pérdida personalizada, escrita de acuerdo con esta firma, se muestra en el
listado 5.10 y se ilustra gráficamente en la parte derecha de la figura 5.14. La primera entrada a
Custom-LossFunction (yTrue)es el verdadero tensor de etiquetas, que tiene una forma de [tamaño de
lote, 5]. La segunda entrada (yPred)es la predicción de salida del modelo, con exactamente la misma
forma queyVerdadero.De las cinco dimensiones a lo largo del segundo eje deyVerdadero (las cinco
columnas, si lo vemos como una matriz), la primera es un indicador 0-1 para la forma del objeto de
destino (0 para triángulo y 1 para rectángulo). Esto está determinado por cómo se sintetizan los
datos (ver simple-object-detection/synthetic_images.js). Las cuatro columnas restantes son el
cuadro delimitador del objeto de destino, es decir, sus valores izquierdo, derecho, superior e
inferior, cada uno de los cuales varía de 0 aTAMAÑO DEL LIENZO (224). El número 224 es la altura y
el ancho de las imágenes de entrada y proviene del tamaño de la imagen de entrada para
MobileNet, en el que se basa nuestro modelo.

Listado 5.10 Definición de la función de pérdida personalizada para la tarea de detección de objetos

const labelMultiplier = tf.tensor1d([CANVAS_SIZE, 1, 1, 1, 1]); function


customLossFunction(yTrue, yPred) {
volver tf.ordenado(() => {
devuelve tf.metrics.meanSquaredError(
yVerdadero.mul(etiquetaMultiplicador), yPred);
});
} La columna del indicador de forma de yTrue está escalada por CANVAS_SIZE
(224) para asegurar una contribución aproximadamente igual a la pérdida por
predicción de forma y predicción de cuadro delimitador.

La función de pérdida personalizada tomayVerdaderoy escala su primera columna (el indicador de


forma 0-1) porTAMAÑO DEL LIENZO,mientras deja las otras columnas sin cambios. Luego calcula el
MSE entreyPredy el escaladoyVerdadero.¿Por qué escalamos la etiqueta de forma 0–1 en ¿Verdad?
Queremos que el modelo genere un número que represente si predice que la forma será un
triángulo o un rectángulo. Específicamente, genera un número cercano a 0 para el triángulo y un
número cercano aTAMAÑO DEL LIENZO (224) para rectángulo. Entonces, durante el tiempo de
inferencia, podemos comparar el primer valor en la salida del modelo conCANVAS_SIZE/2
(112) para obtener la predicción del modelo de si la forma se parece más a un triángulo o a
un rectángulo. Entonces, la pregunta es cómo medir la precisión de esta predicción de forma
para generar una función de pérdida. Nuestra respuesta es calcular la diferencia entre este
número y el indicador 0-1, multiplicado porTAMAÑO DEL LIENZO.
Detección de objetos mediante transferencia de aprendizaje en una convnet 191

¿Por qué hacemos esto en lugar de usar la entropía cruzada binaria como hicimos para el
ejemplo de detección de phishing en el capítulo 3? Necesitamos combinar dos métricas de
precisión aquí: una para la predicción de la forma y otra para la predicción del cuadro delimitador.
La última tarea implica predecir valores continuos y puede verse como una tarea de regresión. Por
lo tanto, MSE es una métrica natural para cuadros delimitadores. Para combinar las métricas,
simplemente "pretendemos" que la predicción de la forma también es una tarea de regresión. Este
truco nos permite usar una sola función métrica (latf.metric.meanSquaredError()llamada en el listado
5.10) para encapsular la pérdida para ambas predicciones.
Pero, ¿por qué escalamos el indicador 0-1 por¿TAMAÑO DEL LIENZO?Bueno, si no hiciéramos
esta escala, nuestro modelo terminaría generando un número en la vecindad de 0-1 como
indicador de si predice que la forma será un triángulo (cerca de 0) o un rectángulo (cerca de 0). 1).
La diferencia entre los números alrededor de [0, 1]El intervalo sería claramente mucho más
pequeño en comparación con las diferencias que obtenemos al comparar el cuadro delimitador
verdadero y los predichos, que están en el rango de 0–224. Como resultado, la señal de error de la
predicción de la forma quedaría totalmente eclipsada por la señal de error de la predicción del
cuadro delimitador, lo que no nos ayudaría a obtener predicciones precisas de la forma. Al escalar
el indicador de forma de 0 a 1, nos aseguramos de que la predicción de forma y la predicción del
cuadro delimitador contribuyan por igual al valor de pérdida final (el valor de retorno de
customLossFunction()),de modo que cuando el modelo esté entrenado, optimizará ambos tipos de
predicciones a la vez. En el ejercicio 4 al final de este capítulo, se le anima a experimentar con esta
escala usted mismo.12
Con los datos preparados y el modelo y la función de pérdida definidos, ¡estamos listos para
entrenar nuestro modelo! Las partes clave del código de entrenamiento del modelo se muestran
en el listado 5.11 (de simple-object-detection/train.js). Al igual que el ajuste fino que hemos visto
antes (sección 5.1.3), el entrenamiento se desarrolla en dos fases: una fase inicial, durante la cual
solo se entrenan las nuevas capas de la cabeza, y una fase de ajuste fino, durante la cual el nuevo
jefe las capas se entrenan junto con las pocas capas superiores de la base truncada de MobileNet.
Cabe señalar que elcompilar()El método debe invocarse (nuevamente) justo antes del ajuste fino
encajar()llamar para los cambios en elentrenablepropiedad de las capas para tener efecto. Si ejecuta
el entrenamiento en su propia máquina, será fácil observar un salto significativo hacia abajo en los
valores de pérdida tan pronto como comience la fase de ajuste, lo que refleja un aumento en la
capacidad del modelo y la adaptación del descongelado. capas de extracción de características a las
características únicas en los datos de detección de objetos como resultado de su descongelación.
La lista de capas descongeladas durante el ajuste fino está determinada por elFineTuningLayers
matriz, que se completa cuando truncamos MobileNet

Una alternativa al escalado yerror medio cuadrado-enfoque basado aquí es tomar la primera columna de
12
yPredcomo la puntuación de probabilidad de forma y calcule la entropía cruzada binaria con la primera columna de
yVerdadero. Luego, el valor de la entropía cruzada binaria se puede sumar junto con el MSE calculado en las columnas
restantes deyVerdaderoyyPred.Pero en este enfoque alternativo, la entropía cruzada debe escalarse correctamente para
garantizar el equilibrio con la pérdida del cuadro delimitador, al igual que en nuestro enfoque actual. La escala implica un
parámetro libre cuyo valor debe seleccionarse cuidadosamente. En la práctica, se convierte en un hiperparámetro
adicional del modelo y requiere tiempo y recursos informáticos para ajustarse, lo cual es una desventaja del enfoque.
Optamos en contra del enfoque a favor de nuestro enfoque actual por simplicidad.
192 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

(ver elcargarBaseTruncada()función en el listado 5.9). Estas son las nueve capas


superiores de la MobileNet truncada. En el ejercicio 3 al final del capítulo, puede
experimentar descongelando menos o más capas superiores de la base y observar
cómo cambian la precisión del modelo producido por el proceso de entrenamiento.

Listado 5.11 Fase dos del entrenamiento del modelo de detección de objetos

const {modelo, fineTuningLayers} = espera buildObjectDetectionModel(); modelo.compilar({

pérdida: customLossFunction, optimizador: Utiliza una tasa de aprendizaje


tf.train.rmsprop(5e-3) }); relativamente alta para la fase inicial

esperar model.fit(imágenes, objetivos, {


épocas: args.initialTransferEpochs, tamaño de
Realiza la fase inicial del
lote: args.batchSize,
aprendizaje por transferencia
división de validación: args. división de
validación });

// Fase de puesta a punto del aprendizaje por transferencia.

para (capa constante de fineTuningLayers) { Comienza la fase de puesta a punto


capa.entrenable = verdadero; Descongela algunas capas para un ajuste fino
}
modelo.compilar({
pérdida: customLossFunction, optimizador: Utiliza una tasa de aprendizaje ligeramente
tf.train.rmsprop(2e-3) }); más baja para la fase de ajuste fino

esperar model.fit(imágenes, objetivos, {


épocas: args.fineTuningEpochs, batchSize: Durante la fase de ajuste fino, reducimos el
args.batchSize / 2, validationSplit: tamaño del lote para evitar problemas de falta
args.validationSplit }); de memoria causados por el hecho de que la
retropropagación implica más pesos y consume
Realiza la fase de ajuste fino
más memoria que la fase inicial.

Una vez que finaliza el ajuste fino, el modelo se guarda en el disco y luego se carga durante el paso
de inferencia en el navegador (iniciado por elreloj de hilomando). Si carga un modelo alojado, o si
ha invertido el tiempo y los recursos informáticos para entrenar un modelo razonablemente bueno
en su propia máquina, la forma y la predicción del cuadro delimitador que verá en la página de
inferencia deberían ser bastante buenas (pérdida de validación). a <100 después de 100 épocas de
entrenamiento inicial y 200 épocas de ajuste fino). Los resultados de la inferencia son buenos pero
no perfectos (ver los ejemplos en la figura 5.13). Cuando examine los resultados, tenga en cuenta
que la evaluación en el navegador es justa y refleja el verdadero poder de generalización del
modelo porque los ejemplos que el modelo entrenado debe resolver en el navegador son
diferentes de los ejemplos de entrenamiento y validación que tiene. visto durante el proceso de
transferencia-aprendizaje.
Para finalizar esta sección, mostramos cómo un modelo entrenado previamente en la clasificación de
imágenes se puede aplicar con éxito a una tarea diferente: la detección de objetos. Al hacer esto, demostramos
cómo definir una función de pérdida personalizada para adaptarse a la naturaleza de "doble tarea" (clasificación
de formas + regresión de cuadro delimitador) del problema de detección de objetos y cómo
Detección de objetos mediante transferencia de aprendizaje en una convnet 193

use la pérdida personalizada durante el entrenamiento del modelo. Este ejemplo no solo ilustra los principios
básicos detrás de la detección de objetos, sino que también destaca la flexibilidad del aprendizaje por
transferencia y la variedad de problemas en los que se puede utilizar. Los modelos de detección de objetos que
se usan en las aplicaciones de producción son, por supuesto, más complejos e involucran más trucos que el
ejemplo de juguete que construimos usando un conjunto de datos sintetizados aquí. El cuadro de información 5.3
presenta brevemente algunos datos interesantes sobre los modelos avanzados de detección de objetos y
describe en qué se diferencian del ejemplo simple que acaba de ver y cómo puede usar uno de ellos a través de
TensorFlow.js.

ICAJA NFO5.3 Modelos de detección de objetos de producción

Un ejemplo de resultado de detección de objetos de


la versión TensorFlow.js del modelo Single-Shot
Detection (SSD). Observe los múltiples cuadros
delimitadores y su clase de objeto asociada y
puntuaciones de confianza.

La detección de objetos es una tarea importante de interés para muchos tipos de aplicaciones, como la
comprensión de imágenes, la automatización industrial y los automóviles autónomos. Los modelos de
detección de objetos de última generación más conocidos incluyen la detección de disparo únicoa
(SSD, para el cual se muestra un resultado de inferencia de ejemplo en la figura) y Solo miras una
vez (YOLO).BEstos modelos son similares al modelo que vimos en nuestro ejemplo de detección de
objetos simples en los siguientes aspectos:

- Predicen tanto la clase como la ubicación de los objetos.


- Se basan en modelos de clasificación de imágenes preentrenados, como MobileNet y
VGG16.Cy se capacitan a través del aprendizaje por transferencia.

a
Wei Liu et al., "SSD: Detector MultiBox de disparo único",Apuntes de clase en informática
9905, 2016,http://mng.bz/G4qD.
B
Joseph Redmon et al., "Solo mira una vez: Detección unificada de objetos en tiempo real"Actas
de la Conferencia IEEE sobre visión artificial y reconocimiento de patrones(CVPR), 2016, págs.
779–788,http://mng.bz/zlp1.
c Karen Simonyan y Andrew Zisserman, “Redes convolucionales muy profundas para
Reconocimiento de imágenes”, presentado el 4 de septiembre de 2014,https://arxiv.org/abs/1409.1556.
194 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

(continuado)
Sin embargo, también son diferentes de nuestro modelo de juguete en muchos aspectos:

- Los modelos reales de detección de objetos predicen muchas más clases de objetos que nuestro
modelo simple (por ejemplo, el conjunto de datos COCO tiene 80 categorías de objetos; consulte
http://cocodataset.org/#home).
- Son capaces de detectar múltiples objetos en la misma imagen (ver la figura
de ejemplo).
-Las arquitecturas de sus modelos son más complejas que la de nuestro modelo simple. Por
ejemplo, el modelo SSD agrega varias cabezas nuevas encima de un modelo de imagen
previamente entrenado truncado para predecir la puntuación de confianza de la clase y los
cuadros delimitadores para varios objetos en la imagen de entrada.
- En lugar de usar un soloerror medio cuadradométrica como la función de pérdida, la función de
pérdida de un modelo real de detección de objetos es una suma ponderada de dos tipos de
pérdidas: 1) una pérdida similar a la entropía cruzada softmax para las puntuaciones de
probabilidad previstas para las clases de objetos y 2) unaerror medio cuadradooerror absoluto
medio-como pérdida para cuadros delimitadores. El peso relativo entre los dos tipos de valores
de pérdida se ajusta cuidadosamente para garantizar contribuciones equilibradas de ambas
fuentes de error.
- Los modelos de detección de objetos reales producen una gran cantidad de cuadros delimitadores
candidatos por imagen de entrada. Estos cuadros delimitadores se "recortan" para que los que tengan
las puntuaciones de probabilidad de clase de objeto más altas se conserven en el resultado final.
- Algunos modelos reales de detección de objetos incorporan "conocimiento previo" sobre la
ubicación de los cuadros delimitadores de objetos. Estas son conjeturas informadas sobre dónde
están los cuadros delimitadores en la imagen, basadas en el análisis de un mayor número de
imágenes reales etiquetadas. Los priores ayudan a acelerar el entrenamiento de los modelos al
comenzar desde un estado inicial razonable en lugar de conjeturas aleatorias completas (como en
nuestro ejemplo de detección de objetos simples).

Se han portado algunos modelos reales de detección de objetos a TensorFlow.js. Por ejemplo, uno
de los mejores con los que puedes jugar está en el directorio coco-ssd del repositorio tfjs-models.
Para verlo en acción, haga lo siguiente:

git clonar https://github.com/tensorflow/tfjs-models.git tfjs-models/coco-


ssd/demo
discos compactos

hilo y reloj de hilo

Si está interesado en obtener más información sobre los modelos reales de detección de objetos, puede
leer las siguientes publicaciones de blog. Son para el modelo SSD y el modelo YOLO, respectivamente, que
utilizan diferentes técnicas de posprocesamiento y arquitectura de modelos:

- "Comprender SSD MultiBox: detección de objetos en tiempo real en aprendizaje


profundo" por Eddie Forson:http://mng.bz/07dJ.
- “Detección de objetos en tiempo real con YOLO, YOLOv2 y ahora YOLOv3” por
Jonathan Hui:http://mng.bz/KEqX.
Ejercicios 195

Hasta ahora, en este libro, hemos abordado conjuntos de datos de aprendizaje automático que se nos
entregan y están listos para ser explorados. Están bien formateados, se han limpiado a través del trabajo
minucioso de los científicos de datos y los investigadores de aprendizaje automático antes que nosotros,
en la medida en que podemos centrarnos en el modelado sin preocuparnos demasiado sobre cómo
ingerir los datos y si los datos son correctos. . Esto es cierto para el MNIST y los conjuntos de datos de
audio utilizados en este capítulo; también es cierto para los conjuntos de datos de phishing-website y iris-
flower que usamos en el capítulo 3.
Podemos decir con seguridad que esto esNuncael caso de los problemas de aprendizaje automático
del mundo real que encontrará. De hecho, la mayor parte del tiempo de un profesional del aprendizaje
automático se dedica a adquirir, preprocesar, limpiar, verificar y formatear los datos.13En el próximo
capítulo, le mostraremos las herramientas disponibles en TensorFlow.js para facilitar estos flujos de
trabajo de ingestión y manipulación de datos.

Ejercicios
1 Cuando visitamos el ejemplo mnist-transfer-cnn en la sección 5.1.1, señalamos que
configurar elentrenablepropiedad de las capas de un modelo no tendrá efecto durante el
entrenamiento, a menos que el modelocompilar()se llama al método antes del
entrenamiento. Verifique que haciendo algunos cambios en elreentrenarModelo()en el
archivo index.js del ejemplo. Específicamente,
a Agrega uneste.modelo.resumen()llamar justo antes de la línea conEste modelo .
compilar(),y observar el número de parámetros entrenables y no entrenables. ¿Qué
muestran? ¿En qué se diferencian de los números que obtienes después de la
compilar()¿llamada?
B Independientemente del punto anterior, mover eleste.modelo.compilar()llamar a la parte
justo antes del ajuste de laentrenablepropiedad de las capas de características. En otras
palabras, establezca la propiedad de esas capas después de lacompilar() llamada. ¿Cómo
cambia eso la velocidad de entrenamiento? ¿La velocidad es consistente solo con las
últimas capas del modelo que se está actualizando? ¿Puede encontrar otras formas de
confirmar que, en este caso, los pesos de las primeras capas de los modelos se
actualizan durante el entrenamiento?
2 Durante el aprendizaje de transferencia en la sección 5.1.1 (listado 5.1), congelamos
las dos primeras capas conv2d configurando suentrenablepropiedades afalsoantes de
comenzar el encajar()llamada. ¿Puede agregar algún código a index.js en el ejemplo
mnist-transfer-cnn para verificar que los pesos de las capas conv2d no estén alterados
por elencajar()¿llamada? Otro enfoque con el que experimentamos en la misma sección
fue llamarencajar()sin congelar las capas. ¿Puede verificar que los valores de peso de
las capas están efectivamente alterados por elencajar()llamar en ese caso? (Pista:
recuerda que en la sección 2.4.2 del capítulo 2, usamos elcapasatributo de un objeto
modelo y suobtenerPesos()para acceder al valor de los pesos.)

13Gil Press, "Limpieza de Big Data: la tarea de ciencia de datos que consume más tiempo y es la menos agradable, según una encuesta"Forbes,
23 de marzo de 2016,http://mng.bz/9wqj.
196 CPASADO5Transferencia de aprendizaje: reutilización de redes neuronales preentrenadas

3 Convierta el Keras MobileNetV214(¡No MobileNetV1!, eso ya lo hicimos) en el formato


TensorFlow.js y cárguelo en TensorFlow.js en el navegador. Consulte el cuadro de
información 5.1 para conocer los pasos detallados. ¿Puedes usar elresumen() método
para examinar la topología de MobileNetV2 e identificar sus principales diferencias
con MobileNetV1?
4 Una de las cosas importantes sobre el código de ajuste fino del listado 5.8 es que el
compilar()El método del modelo se vuelve a llamar después de descongelar la capa densa en
el modelo base. ¿Puedes hacer lo siguiente?
a Use el mismo método del ejercicio 2 para verificar que los pesos (núcleo y sesgo) de la
capa densa no se vean alterados por la primera.encajar()convocatoria (la de la fase inicial
de aprendizaje por transferencia) y que efectivamente lo son para la segundaencajar()
llamada (la de la fase de puesta a punto).
B Intenta comentar elcompilar()llame después de la línea de descongelación (la línea que
cambia el valor del atributo entrenable) y vea cómo eso afecta los cambios de valor de
peso que acaba de observar. Convéncete de que el compilar()de hecho, la llamada es
necesaria para permitir que los cambios en los estados congelado/descongelado del
modelo surtan efecto.
C Cambie el código e intente descongelar más capas de peso del modelo base de
comando de voz (por ejemplo, la capa conv2d antes de la segunda última capa
densa) y vea cómo eso afecta el resultado del ajuste fino.
5 En la función de pérdida personalizada que definimos para la tarea simple de detección de
objetos, escalamos la etiqueta de forma 0–1 para que la señal de error de la predicción de forma
coincidiera con la señal de error de la predicción del cuadro delimitador (consulte el listado 5.10).
Experimente con lo que sucede si esta escala no se realiza eliminando elmulti() llame al código en
el listado 5.10. Convénzase de que esta escala es necesaria para garantizar predicciones de forma
razonablemente precisas. Esto también se puede hacer por
simplemente reemplazando las instancias decustomLossFunctionconerror medio cuadrado
durante elcompilar()llamar (ver listado 5.11). También tenga en cuenta que la eliminación de
la escala durante el entrenamiento debe ir acompañada de un cambio en el umbral durante
el tiempo de inferencia: cambie el umbral deCANVAS_SIZE/2para1/2en la lógica de inferencia
(en simple-object-detection/index.js).
6 La fase de ajuste fino en el ejemplo simple de detección de objetos involucró
descongelar las nueve capas superiores de la base truncada de MobileNet (vea cómo
fineTuning-Layersse completa en el listado 5.9). Una pregunta natural es, ¿por qué
nueve? En este ejercicio, cambie el número de capas descongeladas incluyendo menos
o más capas en elFineTuningLayersformación. ¿Qué espera ver en las siguientes
cantidades cuando descongela menos capas durante el ajuste fino: 1) el valor de
pérdida final y 2) el tiempo que tarda cada época en la fase de ajuste fino? ¿El

14 Mark Sandler et al., "MobileNetV2: Inverted Residuals and Linear Bottlenecks", revisado el 21 de marzo de 2019,
https://arxiv.org/abs/1801.04381.
Resumen 197

¿El resultado del experimento coincide con sus expectativas? ¿Qué hay de descongelar más capas
durante el ajuste fino?

Resumen
- El aprendizaje por transferencia es el proceso de reutilización de un modelo previamente entrenado o una parte
de él en una tarea de aprendizaje relacionada, pero diferente de aquella para la que se entrenó originalmente el
modelo. Esta reutilización acelera la nueva tarea de aprendizaje.

- En las aplicaciones prácticas del aprendizaje por transferencia, las personas a menudo reutilizan los
convennets que han sido entrenados en conjuntos de datos de clasificación muy grandes, como
MobileNet capacitado en el conjunto de datos de ImageNet. Debido al gran tamaño del conjunto de
datos original y la diversidad de los ejemplos que contiene, estos modelos preentrenados traen consigo
capas convolucionales que son poderosos extractores de características de propósito general para una
amplia variedad de problemas de visión computacional. Tales capas son difíciles, si no imposibles, de
entrenar con la pequeña cantidad de datos que están disponibles en los problemas típicos de
transferencia de aprendizaje.

- Discutimos varios enfoques generales del aprendizaje por transferencia en TensorFlow.js,


que difieren entre sí en términos de 1) si se crean nuevas capas como el "nuevo jefe" para el
aprendizaje por transferencia y 2) si el aprendizaje por transferencia se realiza con una
instancia de modelo o dos. Cada enfoque tiene sus pros y sus contras y es adecuado para
diferentes casos de uso (consulte la tabla 5.1).
- Al establecer elentrenableatributo de la capa de un modelo, podemos evitar que sus pesos se
actualicen durante el entrenamiento (Modelo.fit()llamadas). Esto se conoce como congelación y se
usa para "proteger" las capas de extracción de características del modelo base durante el
aprendizaje de transferencia.
-En algunos problemas de transferencia de aprendizaje, podemos aumentar el rendimiento del nuevo

modelo descongelando algunas capas superiores del modelo base después de una fase inicial de
entrenamiento. Esto refleja la adaptación de las capas descongeladas a las características únicas del
nuevo conjunto de datos.

- El aprendizaje por transferencia es una técnica versátil y flexible. El modelo base puede ayudarnos
a resolver problemas que son diferentes de aquellos en los que se entrenó originalmente.
Ilustramos este punto mostrando cómo entrenar un modelo de detección de objetos basado en
MobileNet.
- Las funciones de pérdida en TensorFlow.js se pueden definir como funciones de JavaScript
personalizadas que operan en entradas y salidas de tensor. Como mostramos en el ejemplo simple de
detección de objetos, a menudo se necesitan funciones de pérdida personalizadas para resolver
problemas prácticos de aprendizaje automático.
parte 3

Aprendizaje profundo avanzado


con TensorFlow.js

A Después de leer las partes 1 y 2, ahora debería estar familiarizado con la forma en que se
realiza el aprendizaje profundo básico en TensorFlow.js. La Parte 3 está destinada a usuarios que
desean desarrollar una comprensión más firme de las técnicas y obtener una comprensión más
amplia del aprendizaje profundo. El Capítulo 6 cubre las técnicas para ingerir, transformar y usar
datos para el aprendizaje automático. El Capítulo 7 presenta herramientas para visualizar datos y
modelos. El capítulo 8 se ocupa de los importantes fenómenos de infraadaptación y
sobreadaptación y cómo abordarlos de manera eficaz. Con base en esta discusión, presentamos el
flujo de trabajo universal del aprendizaje automático. Los capítulos 9 a 11 son recorridos prácticos
de tres áreas avanzadas: modelos orientados a secuencias, modelos generativos y aprendizaje por
refuerzo, respectivamente. Lo familiarizarán con algunas de las fronteras más emocionantes del
aprendizaje profundo.
Trabajar con datos

Este capítulo cubre


- Cómo usar eltf.datosAPI para entrenar modelos usando grandes
conjuntos de datos

- Explorar sus datos para encontrar y solucionar posibles problemas

- Cómo usar el aumento de datos para crear nuevos


"pseudoejemplos" para mejorar la calidad del modelo

La amplia disponibilidad de grandes volúmenes de datos es un factor importante que conduce a la revolución
actual del aprendizaje automático. Sin un fácil acceso a grandes cantidades de datos de alta calidad, el
espectacular aumento del aprendizaje automático no se habría producido. Los conjuntos de datos ahora
están disponibles en todo Internet, se comparten libremente en sitios como Kaggle y OpenML, entre otros, al
igual que los puntos de referencia para el rendimiento de última generación. Ramas enteras del aprendizaje
automático se han visto impulsadas por la disponibilidad de conjuntos de datos de "desafío", estableciendo
un estándar y un punto de referencia común para la comunidad.1
Si el aprendizaje automático es la carrera espacial de nuestra generación, entonces los datos son claramente nuestro cohete

1
Vea cómo ImageNet impulsó el campo del reconocimiento de objetos o qué hizo el desafío de Netflix para el filtrado
colaborativo.

201
202 CPASADO6Trabajar con datos

Gasolina;2es potente, es valioso, es volátil y es absolutamente crítico para un sistema de aprendizaje automático
que funcione. Sin mencionar que los datos contaminados, como el combustible contaminado, pueden conducir
rápidamente a una falla sistémica. Este capítulo trata sobre los datos. Cubriremos las mejores prácticas para
organizar datos, cómo detectar y limpiar problemas y cómo usarlos de manera eficiente.
“¿Pero no hemos estado trabajando con datos todo este tiempo?” podrías protestar. Es cierto: en capítulos anteriores
trabajamos con todo tipo de fuentes de datos. Hemos entrenado modelos de imágenes utilizando conjuntos de datos de
imágenes sintéticas y de cámaras web. Usamos el aprendizaje por transferencia para construir un reconocedor de
palabras habladas a partir de un conjunto de datos de muestras de audio, y accedimos a conjuntos de datos tabulares
para predecir precios. Entonces, ¿qué queda por discutir? ¿No somos ya competentes en el manejo de datos?

Recuerde en nuestros ejemplos anteriores los patrones de nuestro uso de datos. Por lo general, primero
necesitamos descargar nuestros datos desde una fuente remota. Luego (generalmente) aplicamos alguna
transformación para obtener nuestros datos en el formato correcto, por ejemplo, convirtiendo cadenas en
vectores de vocabulario de un solo uso o normalizando las medias y las varianzas de las fuentes tabulares.
Entonces siempre hemos necesitado agrupar nuestros datos y convertirlos en un bloque estándar de números
representados como un tensor antes de conectarlos a nuestro modelo. Todo esto antes incluso de ejecutar
nuestro primer paso de entrenamiento.
Este patrón de lote de transformación de descarga es muy común, y TensorFlow.js viene con
herramientas para hacer que este tipo de manipulaciones sea más fácil, más modular y menos propensa
a errores. En este capítulo se presentarán las herramientas en eltf.datosespacio de nombres: lo más
importante,tf.data.Conjunto de datos,que se puede utilizar para transmitir datos de forma perezosa. El
enfoque de lazystreaming permite descargar, transformar y acceder a los datos según sea necesario en
lugar de descargar la fuente de datos en su totalidad y mantenerla en la memoria a medida que se
accede a ella. La transmisión diferida hace que sea mucho más fácil trabajar con fuentes de datos que son
demasiado grandes para caber en una sola pestaña del navegador o incluso demasiado grandes dentro
de la memoria RAM de una sola máquina.
Primero presentaremos eltf.data.Conjunto de datosAPI y mostrar cómo configurarlo y conectarlo
a un modelo. Luego, presentaremos algo de teoría y herramientas para ayudarlo a revisar y
explorar sus datos y resolver los problemas que pueda descubrir. El capítulo concluye presentando
el aumento de datos, un método para expandir un conjunto de datos para mejorar la calidad del
modelo mediante la creación de pseudoejemplos sintéticos.

6.1 Uso de tf.data para administrar datos


¿Cómo entrenaría a un filtro de spam si su base de datos de correo electrónico tuviera cientos de gigabytes y
requiriera credenciales especiales para acceder? ¿Cómo puede construir un clasificador de imágenes si su base
de datos de imágenes de entrenamiento es demasiado grande para caber en una sola máquina?
El acceso y la manipulación de grandes volúmenes de datos es una habilidad clave para el ingeniero
de aprendizaje automático, pero hasta ahora, hemos estado tratando con aplicaciones en las que los
datos posiblemente podrían caber dentro de la memoria disponible para nuestra aplicación. Muchas
aplicaciones requieren trabajar con datos grandes, engorrosos y posiblemente sensibles a la privacidad.

2Crédito por la analogía con Edd Dumbill, "Big Data Is Rocket Fuel"Grandes datos, vol. 1, no. 2, págs. 71 y 72.
Uso de tf.data para administrar datos 203

fuentes para las que esta técnica no es adecuada. Las aplicaciones grandes requieren tecnología para
acceder a datos desde una fuente remota, pieza por pieza, bajo demanda.
TensorFlow.js viene empaquetado con una biblioteca integrada diseñada solo para este tipo de
administración de datos. Está diseñado para permitir a los usuarios ingerir, preprocesar y enrutar datos
de una manera concisa y legible, inspirado en eltf.datosAPI en la versión Python de TensorFlow.
Suponiendo que su código importe TensorFlow.js usando una declaración de importación como

importar * como tf desde '@tensorflow/tfjs';

esta funcionalidad estará disponible bajo eltf.datosespacio de nombres

6.1.1 El objeto tf.data.Dataset


La mayor interacción contfjs-datosviene a través de un solo tipo de objeto llamadoConjunto de datos. los
tf.data.Conjunto de datosobject proporciona una manera simple, configurable y eficiente de iterar y
procesar listas grandes (posiblemente ilimitadas) de elementos de datos.3En la abstracción más tosca,
puede imaginar un conjunto de datos como una colección iterable de elementos arbitrarios, no muy
diferente de laArroyoen Node.js. Siempre que se solicite el siguiente elemento del conjunto de datos, la
implementación interna lo descargará, accederá a él o ejecutará una función para crearlo, según sea
necesario. Esta abstracción facilita que el modelo se entrene con más datos de los que posiblemente se
pueden almacenar en la memoria a la vez. También hace que sea conveniente compartir y organizar
conjuntos de datos como objetos de primera clase cuando hay más de un conjunto de datos para realizar
un seguimiento.conjunto de datosproporciona un beneficio de memoria al transmitir solo los bits de datos
necesarios, en lugar de acceder a todo de forma monolítica. losconjunto de datosLa API también
proporciona optimizaciones de rendimiento sobre la implementación ingenua mediante la obtención
previa de valores que están a punto de ser necesarios.

6.1.2 Creando un tf.data.Dataset


A partir de la versión 1.2.7 de TensorFlow.js, hay tres formas de conectarsetf.data .Conjunto de datos a
algún proveedor de datos. Examinaremos cada uno con cierto detalle, pero la tabla 6.1 contiene un breve
resumen.

CRELACIONANDO UN TF.DATOS.DATASET DE UNA MATRIZ


La forma más sencilla de crear un nuevotf.data.Conjunto de datoses construir uno a partir de una matriz de
elementos de JavaScript. Dada una matriz que ya está en la memoria, puede crear un conjunto de datos
respaldado por la matriz usando eltf.datos.array()función. Por supuesto, no traerá ningún beneficio de velocidad
de entrenamiento o uso de memoria en comparación con el uso directo de la matriz, pero acceder a una matriz a
través de un conjunto de datos ofrece otros beneficios importantes. Por ejemplo, el uso de conjuntos de datos
facilita la configuración del preprocesamiento y facilita nuestra capacitación y evaluación a través de
lo simplemodelo.fitDataset()ymodelo.evaluateDataset()APIs, como veremos en

3
En este capítulo utilizaremos el términoelementoscon frecuencia para referirse a los elementos en elConjunto de datos.En la mayoría de los
casos, elementoes sinónimo deejemploopunto de datos—es decir, en el conjunto de datos de entrenamiento, cada elemento es un (x, y) par.
Al leer de una fuente CSV, cada elemento es una fila del archivo.conjunto de datoses lo suficientemente flexible para manejar tipos
heterogéneos de elementos, pero esto no se recomienda.
204 CPASADO6Trabajar con datos

Tabla 6.1 Creando untf.data.Conjunto de datosobjeto de una fuente de datos

Cómo obtener un nuevo


tf.data.Conjunto de datos API Cómo usarlo para construir un conjunto de datos

A partir de una matriz de elementos de tf.data.array(elementos) conjunto de datos const =


JavaScript; también funciona para tf.data.array([1,2,3,4,5]);
arreglos escritos como Consulte el listado 6.1 para obtener más información.

Matriz flotante32

Desde un archivo CSV tf.datos.csv( conjunto de datos const =


(posiblemente remoto), donde fuente, tf.data.csv("https://ruta/a/mi.csv");
cada fila es un elemento csvConfig)
Consulte el listado 6.2 para obtener más información.

El único parámetro obligatorio es la URL desde la que


leer los datos. Adicionalmente,csvConfigacepta un
objeto con claves para ayudar a guiar el análisis del
archivo CSV. Por ejemplo,
• nombres de columna-Acuerda[]se puede proporcionar para
establecer los nombres de las columnas manualmente si no
existen en un encabezado o deben anularse.
• delimitador: se puede usar una cadena de un solo carácter
para anular el delimitador de coma predeterminado.
• ColumnConfigs—Un mapa de cuerdasnombre de columna
paracolumnConfigse pueden proporcionar objetos para
guiar el análisis y el tipo de devolución del conjunto de datos.
loscolumnConfiginformará al analizador del tipo de
elemento (cadena o int), o si la columna debe considerarse
como la etiqueta del conjunto de datos.
• configuradoColumnsOnly—Si devolver datos
para cada columna en el CSV o solo aquellas
columnas incluidas en elColumnConfigsobjeto.
Hay más detalles disponibles en los documentos de la
API en js.tensorflow.org.

A partir de una función tf.datos.generador( función* cuenta atrás desde 10() {


generadora genérica que produce generadorFunción) para (sea i=10; i>0; i--) {
elementos rendimiento (yo);

}
}

conjunto de datos const =


tf.data.generator(countDownFrom10);
Consulte el listado 6.3 para obtener más información.

Tenga en cuenta que el argumento pasado a


tf.datos.generador()cuando se llama sin argumentos
devuelve unGeneradorobjeto.

sección 6.2. En contraste conmodelo.fit(x, y), modelo.fitDataset(myDataset)no


mueva inmediatamente todos los datos a la memoria de la GPU, lo que significa que es posible trabajar con
conjuntos de datos más grandes de los que puede contener la GPU. Tenga en cuenta que el límite de memoria
del motor de JavaScript V8 (1,4 GB en sistemas de 64 bits) suele ser mayor que el de TensorFlow.js
Uso de tf.data para administrar datos 205

puede contener en la memoria WebGL a la vez. Utilizando eltf.datosLa API también es una buena
práctica de ingeniería de software, ya que facilita el intercambio de otro tipo de datos de forma
modular sin cambiar mucho el código. Sin la abstracción del conjunto de datos, es fácil dejar que
los detalles de la implementación de la fuente del conjunto de datos se filtren en su uso en el
entrenamiento del modelo, un enredo que deberá resolverse tan pronto como se use una
implementación diferente.
Para construir un conjunto de datos a partir de una matriz existente, usetf.data.array(itemsAsArray),como se
muestra en el siguiente listado.

Listado 6.1 Construyendo untf.data.Conjunto de datosde una matriz

const miArray = [{xs: [1, 0, 9], ys: 10}, Crea el conjunto de datos tfjs-data
{xs: [5, 1, 3], ys: 11}, {xs: [1, 1, 9], respaldado por una matriz. Tenga en

ys: 12}]; cuenta que esto no clona la matriz o

const miPrimerConjuntoDeDatos =tf.data.array(miArray); esperar sus elementos.

miPrimerConjuntoDeDatos.para cada asíncrono(


e => consola.log(e));

// Produce una salida como // {xs: Utiliza el método forEachAsync() para iterar en todos los valores
proporcionados por el conjunto de datos. Tenga en cuenta que forEachAsync()
Array(3), ys: 10} // {xs: Array(3), ys:
es una función asíncrona y, por lo tanto, debe usar await con ella.
11} // {xs: Array(3), ys: 12}

Iteramos sobre los elementos del conjunto de datos usando elpara cada asíncrono ()función, que
produce cada elemento a su vez. Ver más detalles sobre elConjunto de datos.forEachAsyncfunción en
la sección 6.1.3.
Los elementos de los conjuntos de datos pueden contener primitivas de JavaScript4(como números y
cadenas), así como tuplas, matrices y objetos anidados de tales estructuras, además de tensores. En este
pequeño ejemplo, los tres elementos del conjunto de datos tienen la misma estructura. Son todos objetos
con las mismas claves y el mismo tipo de valores en esas claves. tf.data.Conjunto de datosEn general,
puede admitir una combinación de tipos de elementos, pero el caso de uso común es que los elementos
del conjunto de datos son unidades semánticas significativas del mismo tipo. Por lo general, deberían
representar ejemplos del mismo tipo de cosas. Por lo tanto, excepto en casos de uso muy inusuales, cada
elemento debe tener el mismo tipo y estructura.

CRELACIONANDO UN TF.DATOS.DATASET DE UNCSVEXPEDIENTE


Un tipo muy común de elemento de conjunto de datos es un objeto clave-valor que representa una fila de una
tabla, como una fila de un archivo CSV. La siguiente lista muestra un programa muy simple que se conectará y
enumerará el conjunto de datos de viviendas de Boston, el que usamos por primera vez en el capítulo 2.

4
Si está familiarizado con la implementación de Python TensorFlow detf.datos,te sorprenderá que tf.data.Conjunto
de datospuede contener primitivas de JavaScript además de tensores.
206 CPASADO6Trabajar con datos

Listado 6.2 Construyendo untf.data.Conjunto de datosdesde un archivo CSV

const myURL = Crea el tfjs-


"https://storage.googleapis.com/tfjs-examples/" + conjunto de datos
"multivariante-regresión lineal/datos/tren-datos.csv"; respaldado por un

const miCSVDataset =tf.data.csv(miURL); archivo CSV remoto

esperar myCSVDataset.para cada asíncrono(e => consola.log(e));

// Produce una salida de 333 filas como


// {crim: 0.327, zn: 0, indus: 2.18, chas: 0, nox: 0.458, rm: 6.998, // age: 45.8, tax: 222}

Utiliza el método forEachAsync() para iterar en


// ... todos los valores proporcionados por el conjunto de datos. Tenga en cuenta que

forEachAsync() es una función asíncrona.


En lugar detf.datos.array(),aquí usamostf.datos.csv()y apunte a una URL de un archivo CSV. Esto


creará un conjunto de datos respaldado por el archivo CSV y, al iterar sobre el conjunto de datos,
se iterará sobre las filas de CSV. En Node.js, podemos conectarnos a un archivo CSV local usando
un identificador de URL con el prefijo file://, como el siguiente:

> datos constantes = tf.datos.csv( 'file://./relative/fs/path/to/boston-housing-


train.csv');

Al iterar, vemos que cada fila CSV se transforma en un objeto JavaScript. Los elementos
devueltos del conjunto de datos son objetos con una propiedad para cada columna del CSV y
las propiedades se nombran de acuerdo con los nombres de las columnas en el archivo CSV.
Esto es conveniente para interactuar con los elementos en el sentido de que ya no es
necesario recordar el orden de los campos. La Sección 6.3.1 entrará en más detalles
describiendo cómo trabajar con CSV y presentará un ejemplo.

CRELACIONANDO UN TF.DATOS.DATASET DE UNA FUNCIÓN DE GENERADOR


La tercera y más flexible forma de crear untf.data.Conjunto de datoses construir uno a partir de una
función generadora. Esto se hace usando eltf.datos.generador()método. tf.datos.generador()toma un
JavaScriptfunción de generador(ofunción*)5como su argumento. Si no está familiarizado con las
funciones del generador, que son relativamente nuevas en JavaScript, puede tomarse un momento
para leer su documentación. El propósito de una función generadora es "producir" una secuencia
de valores a medida que se necesitan, ya sea para siempre o hasta que se agote la secuencia. Los
valores que se generan a partir de la función del generador fluyen para convertirse en los valores
del conjunto de datos. Una función de generador muy simple podría, por ejemplo, producir
números aleatorios o extraer instantáneas de datos de una pieza de hardware adjunta. Un
generador sofisticado puede integrarse con un videojuego, produciendo capturas de pantalla,
puntajes y control de entrada y salida. En la siguiente lista, la función de generador muy simple
produce muestras de tiradas de dados.

5Obtenga más información sobre las funciones del generador de ECMAscript enhttp://mng.bz/Q0rj.
Uso de tf.data para administrar datos 207

Listado 6.3 Construyendo untf.data.Conjunto de datospara tiradas de dados al azar

let numPlaysSoFar = 0; numPlaysSoFar está cerrado por rollTwoDice(),


function tirarDosDados() { lo que nos permite calcular cuántas veces el
numPlaysSoFar++; conjunto de datos ejecuta la función.
return [Math.ceil(Math.random() * 6), Math.ceil(Math.random() * 6)];
}

function* generarDosDadosGeneradorFn() { Define una función generadora (usando


mientras (verdadero) { la sintaxis de función*) que producirá el
rendimiento tiraDosDados(); resultado de llamar a rollTwoDice() un
} número ilimitado de veces
}

const miGeneradorDataset =tf.datos.generador(


El conjunto de datos se crea aquí.
tiradaDosDadosGeneradorFn);
espera myGeneratorDataset.toma(1).forEachAsync(
e => consola.log(e));

// Imprime en la consola un valor como // [4, 2] Toma una muestra de exactamente un elemento de
el conjunto de datos El método take() será
descrito en la sección 6.1.4.

Un par de notas interesantes sobre el conjunto de datos de simulación de juego creado en el listado 6.3.
Primero, tenga en cuenta que el conjunto de datos creado aquí,miGeneradorDataset,es infinito. Dado que
la función del generador nunca regresa, es posible que podamos tomar muestras del conjunto de datos
para siempre. Si tuviéramos que ejecutarpara cada asíncrono ()oaArray() (consulte la sección 6.1.3) sobre
este conjunto de datos, nunca terminaría y probablemente colapsaría nuestro servidor o navegador, así
que tenga cuidado con eso. Para trabajar con tales objetos, necesitamos crear algún otro conjunto de
datos que sea una muestra limitada del ilimitado usandotomado).Más sobre esto en un momento.

En segundo lugar, tenga en cuenta que el conjunto de datos se cierra sobre una variable local. Esto es
útil para el registro y la depuración para determinar cuántas veces se ha ejecutado la función del
generador.
Tercero, tenga en cuenta que los datos no existen hasta que se solicitan. En este caso, solo
accedemos exactamente a una muestra del conjunto de datos, y esto se reflejaría en el valor
denumPlaysSoFar.
Los conjuntos de datos del generador son poderosos y tremendamente flexibles y permiten a los
desarrolladores conectar modelos a todo tipo de API que proporcionan datos, como datos de una
consulta de base de datos, de datos descargados por partes a través de la red o de una pieza de hardware
conectado. Más detalles sobre eltf.datos.generador()API se proporcionan en el cuadro de información 6.1.
208 CPASADO6Trabajar con datos

ICAJA NFO6.1 Especificación del argumento tf.data.generator()


lostf.datos.generador()La API es flexible y potente, lo que permite al usuario conectar el
modelo a muchos tipos de proveedores de datos. El argumento pasó atf.datos.generador()
debe cumplir con las siguientes especificaciones:

- Debe ser invocable con cero argumentos.


- Cuando se llama con cero argumentos, debe devolver un objeto que se ajuste al
iterador y al protocolo iterable. Esto significa que el objeto devuelto debe
tener un métodopróximo().Cuándopróximo()se llama sin argumentos, debe
devolver un objeto JavaScript {valor: ELEMENTO, hecho: falso}para pasar el
valorELEMENTO.Cuando no hay más valores para devolver, debería
devolver {valor: indefinido, hecho: verdadero}.
Vuelven las funciones del generador de JavaScriptGeneradorobjetos, que cumplen con esta
especificación y, por lo tanto, son la forma más fácil de usartf.datos.generador().La función puede
cerrarse sobre variables locales, acceder al hardware local, conectarse a recursos de red, etc.

La tabla 6.1 contiene el siguiente código que ilustra cómo usartf.datos.generador():


función* cuenta atrás desde 10() {
para (sea i = 10; i > 0; i--) {
rendimiento (yo);
}
}

conjunto de datos const = tf.data.generator(countDownFrom10);

Si desea evitar el uso de funciones de generador por alguna razón y prefiere implementar el
protocolo iterable directamente, también puede escribir el código anterior de la siguiente
manera equivalente:

función cuenta atrás desde 10 funciones () {


sea i = 10;
regreso {
siguiente: () => {
si (yo > 0) {
return {valor: i--, hecho: falso}; } demás {

regreso {hecho: verdadero};


}
}
}
}

conjunto de datos const = tf.data.generator(countDownFrom10Func);


Uso de tf.data para administrar datos 209

6.1.3 Acceder a los datos de su conjunto de datos

Una vez que tenga sus datos como un conjunto de datos, inevitablemente querrá acceder a los datos que
contiene. Las estructuras de datos que puede crear pero nunca leer no son realmente útiles. Hay dos API
para acceder a los datos de un conjunto de datos, perotf.datoslos usuarios solo deberían necesitar usarlos
con poca frecuencia. Por lo general, las API de nivel superior accederán a los datos dentro de un conjunto
de datos por usted. Por ejemplo, cuando entrenamos un modelo, usamos elmodelo.fit-Dataset()API,
descrita en la sección 6.2, que accede a los datos en el conjunto de datos por nosotros, y nosotros, los
usuarios, nunca necesitamos acceder a los datos directamente. Sin embargo, al depurar, probar y llegar a
comprender cómo funciona elconjunto de datosobjeto funciona, es importante saber cómo echar un
vistazo a los contenidos.
La primera forma de acceder a los datos de un conjunto de datos es transmitirlos todos a una matriz
usando Conjunto de datos.toArray().Esta función hace exactamente lo que parece. Recorre en iteración todo
el conjunto de datos, insertando todos los elementos en una matriz y devolviéndola al usuario. El usuario
debe tener cuidado al ejecutar esta función para no producir inadvertidamente una matriz que sea
demasiado grande para el tiempo de ejecución de JavaScript. Este error es fácil de cometer si, por
ejemplo, el conjunto de datos está conectado a una gran fuente de datos remota o es un conjunto de
datos ilimitado que lee de un sensor.
La segunda forma de acceder a los datos de un conjunto de datos es ejecutar una función en
cada ejemplo del conjunto de datos usandoconjunto de datos.forEachAsync(f).El argumento
proporcionado a para cada asíncrono ()se aplicará a cada elemento a su vez de una manera similar a
lapara cada() construir en matrices y conjuntos de JavaScript, es decir, el nativoMatriz.paraCada()y
Establecer.paraCada().
Es importante tener en cuenta queConjunto de datos.forEachAsync()yConjunto de datos.toArray()están
ambas funciones asíncronas. Esto es en contraste conMatriz.paraCada(),que es síncrono, por lo que
podría ser fácil cometer un error aquí.Conjunto de datos.toArray()devuelve una promesa y en
general requeriráesperaro .luego()si se requiere un comportamiento síncrono. cuidado que si
esperarse olvida, es posible que la promesa no se resuelva en el orden esperado y que surjan
errores. Un error típico es que el conjunto de datos aparezca vacío porque los contenidos se
repiten antes de que se resuelva la promesa.
La razón por la cualConjunto de datos.forEachAsync()es asíncrono mientrasMatriz.paraCada()
no es que los datos a los que accede el conjunto de datos podrían, en general, necesitar ser
creados, calculados o extraídos de una fuente remota. La asincronía aquí nos permite hacer
un uso eficiente de la computación disponible mientras esperamos. Estos métodos se
resumen en la tabla 6.2.
210 CPASADO6Trabajar con datos

Cuadro 6.2 Métodos que iteran sobre un conjunto de datos

Método de instancia de
el tf.data.Conjunto de datos
objeto Que hace Ejemplo

. aArray() Iteración asíncrona const a = tf.data.array([1, 2, 3, 4, 5, 6]); const arr =


Coma sobre todo el conjunto esperar a.toArray();
de datos y empuja consola.log(arr);
cada elemento en una
matriz, que es // 1,2,3,4,5,6
devuelto

. para cada asíncrono (f) Iteración asíncrona const a = tf.data.array([1, 2, 3]);


Come sobre todos los await a.forEachAsync(e => console.log("hola" + e));
elementos del conjunto de
datos y ejecutaFen cada // hola 1
// hola 2
// hola 3

6.1.4 Manipulación de conjuntos de datos tfjs-data

Sin duda, es muy agradable cuando podemos usar los datos directamente tal como se han
proporcionado, sin ninguna limpieza o procesamiento. Pero en la experiencia de los autores, estocasi
nuncaocurre fuera de los ejemplos construidos con fines educativos o de evaluación comparativa. En el
caso más común, los datos deben transformarse de alguna manera antes de que puedan analizarse o
usarse en una tarea de aprendizaje automático. Por ejemplo, a menudo la fuente contiene elementos
adicionales que deben filtrarse; o los datos en ciertas claves deben analizarse, deserializarse o
renombrarse; o los datos se almacenaron ordenados y, por lo tanto, deben barajarse aleatoriamente
antes de usarlos para entrenar o evaluar un modelo. Quizás el conjunto de datos deba dividirse en
conjuntos que no se superpongan para el entrenamiento y la prueba. El preprocesamiento es casi
inevitable. Si se encuentra con un conjunto de datos que está limpio y listo para usar, lo más probable es
que alguien ya haya hecho la limpieza y el preprocesamiento por usted.
tf.data.Conjunto de datosproporciona una API encadenable de métodos para realizar este tipo de
operaciones, descritas en la tabla 6.3. Cada uno de estos métodos devuelve un nuevoconjunto de
datos objeto, pero no se deje engañar pensando que todos los elementos del conjunto de datos se
copian o que todos los elementos se repiten para cada llamada de método. lostf.datos . conjunto de
datosLa API solo carga y transforma elementos de forma perezosa. Un conjunto de datos que se
creó al encadenar varios de estos métodos se puede considerar como un pequeño programa que
se ejecutará solo una vez que se soliciten los elementos desde el final de la cadena. Es sólo en ese
momento que elconjunto de datosla instancia retrocede en la cadena de operaciones, posiblemente
hasta solicitar datos de la fuente remota.
Uso de tf.data para administrar datos 211

Tabla 6.3 Métodos encadenables en eltf.data.Conjunto de datosobjeto

Método de instancia del


objeto tf.data.Dataset Que hace Ejemplo

. filtro (predicado) Devuelve un conjunto de datos que miConjuntoDeDatos.filter(x => x < 10);
contiene solo elementos para los Devuelve un conjunto de datos que contiene solo valores de
que el predicado se evalúa como mi conjunto de datosque son menos de 10.
cierto

. mapa (transformar) Aplica la función proporcionada miConjuntoDeDatos.map(x => x * x);


a cada elemento del conjunto Devuelve un conjunto de datos de los valores cuadrados del conjunto de datos
de datos y devuelve un nuevo original.
conjunto de datos del
elementos mapeados

. mapAsync( Me gustamapa, pero la función myDataset.mapAsync(fetchAsync);


asyncTransform) proporcionada debe ser AsumiendofetchAsynces una función asíncrona que genera los
asíncrona datos obtenidos de una URL proporcionada, devolverá un nuevo
conjunto de datos que contiene los datos en cada URL.

. lote( Agrupa tramos secuenciales const a = tf.data.array(


tamaño del lote, de elementos en grupos de [1, 2, 3, 4, 5, 6, 7, 8]) . lote(4);
¿pequeño último lote?) un solo elemento y convierte
elementos primitivos en esperar a.forEach(e => e.print());
tensores
// Huellas dactilares:

// Tensor [1, 2, 3, 4] Tensor


// [5, 6, 7, 8]

. concatenar( Concatena los elementos myDataset1.concatenate(myDataset2)


conjunto de datos) mentos de dos conjuntos de datos Devuelve un conjunto de datos que iterará sobre todos los valores de
juntos para formar un nuevo myDataset1primero, y luego sobre todos los valores de
conjunto de datos miConjuntoDeDatos2.

. repetir (contar) Devuelve un conjunto de datos myDataset.repeat(NUM_EPOCHS)


que iterará sobre el conjunto de Devuelve un conjunto de datos que iterará sobre todos los valores de
datos original varias veces miconjunto de datos NUM_EPOCHSveces. SiNUM_ÉPOCASes negativo
(posiblemente ilimitadas) o indefinido, el resultado se repetirá un número ilimitado de veces.

. tomar (contar) Devuelve un conjunto de datos miConjuntoDeDatos.take(10);


que contiene solo el primero Devuelve un conjunto de datos que contiene solo los primeros 10
contarejemplos elementos demi conjunto de datos. Simi conjunto de datoscontiene menos
de 10 elementos, entonces no hay cambio.

. saltar (contar) Devuelve un conjunto de datos miConjuntoDeDatos.skip(10);


que se salta el primero.contar Devuelve un conjunto de datos que contiene todos los elementos de mi
ejemplos conjunto de datosexcepto los primeros 10. Simi conjunto de datoscontiene
10 o menos elementos, esto devuelve un conjunto de datos vacío.
212 CPASADO6Trabajar con datos

Cuadro 6.3 Métodos encadenables en eltf.data.Conjunto de datosobjeto(continuado)

Método de instancia del


objeto tf.data.Dataset Que hace Ejemplo

. barajar( Produce un conjunto de datos const a = tf.data.array(


tamaño del búfer, que mezcla los elementos del [1, 2, 3, 4, 5, 6]).shuffle(3); esperar
¿semilla? conjunto de datos original a.forEach(e => console.log(e)); // imprime, por
) Tenga en cuenta: este barajado ejemplo, 2, 4, 1, 3, 6, 5
se realiza seleccionando al azar Imprime los valores del 1 al 6 en un orden aleatorio. La
dentro de una ventana de reproducción aleatoria es parcial, ya que no todos los pedidos
tamañotamaño del búfer; por lo son posibles ya que la ventana es más pequeña que el tamaño
tanto, se conserva el orden más total de los datos. Por ejemplo, no es posible que el último
allá del tamaño de la ventana. elemento, 6, sea ahora el primero en el nuevo orden, ya que el 6
necesitaría retroceder más detamaño del búfer(3) espacios.

Estas operaciones se pueden encadenar para crear canalizaciones de procesamiento simples pero
potentes. Por ejemplo, para dividir un conjunto de datos aleatoriamente en conjuntos de datos de
entrenamiento y prueba, puede seguir la receta en la siguiente lista (consulte tfjs-examples/iris-fitDataset/
data.js).

Listado 6.4 Creando una división de tren/prueba usandotf.data.Conjunto de datos

const semilla = Matemáticas.piso(


Matemáticas.aleatoria() * 10000);
Usamos la misma semilla
const trainData = tf.data.array(IRIS_RAW_DATA) aleatoria para los datos de
. barajar (IRIS_RAW_DATA.longitud, semilla); entrenamiento y prueba; de lo
. llevar(NORTE); contrario, se barajarán de forma
toma el primero
. mapa (preprocesoFn); independiente y algunas
N muestras
const testData = tf.data.array(IRIS_RAW_DATA) muestras estarán tanto en
Para el
datos de entrenamiento
. barajar(IRIS_RAW_DATA.longitud, semilla); entrenamiento como en prueba.
. saltar(NORTE);
Omite las primeras N muestras para los
. mapa (preprocesoFn);
datos de prueba

Hay algunas consideraciones importantes a tener en cuenta en esta lista. Nos gustaría asignar aleatoriamente
muestras en las divisiones de entrenamiento y prueba y, por lo tanto, primero barajamos los datos. Tomamos el
primeronortemuestras para los datos de entrenamiento. Para los datos de prueba, omitimos esas muestras y
tomamos el resto. Es muy importante que se barajen los datosde la misma manera cuando estamos tomando las
muestras, para que no terminemos con el mismo ejemplo en ambos conjuntos; por lo tanto, usamos la misma
semilla aleatoria para ambos al muestrear ambas tuberías.
También es importante notar que aplicamos elmapa()funcióndespuésla operación de salto.
También sería posible llamar.map(preprocesoFn)antes deel salto, pero luego el preprocesarFn
se ejecutaría incluso para los ejemplos que descartamos: un desperdicio de cálculo. Este
comportamiento se puede verificar con el siguiente listado.
Uso de tf.data para administrar datos 213

Listado 6.5 IlustrandoConjunto de datos.para cada salto ()ymapa()interacciones

dejar contar = 0;

// Función de identidad que también incrementa la cuenta. función


identidadFn(x) {
contar + = 1;
regreso X;
}
console.log('saltar antes del mapa'); esperar
tf.data.array([1, 2, 3, 4, 5, 6])
. saltar(6) Salta luego mapas
. mapa(identidadFn)
. forEachAsync(x => indefinido);
console.log(`contar es ${contar} )̀;

console.log('mapa antes saltar');


esperar tf.data.array([1, 2, 3, 4, 5, 6])
. mapa(identidadFn) Mapas luego salta
. saltar(6)
. forEachAsync(x => indefinido);
console.log(`contar es ${contar} )̀;

// Imprime:
// saltar antes del mapa //
el recuento es 0
// mapa antes de saltar //
el recuento es 6

Otro uso común paraconjunto de datos.mapa()es normalizar nuestros datos de entrada. Podemos
imaginar un escenario en el que deseamos normalizar nuestra entrada para que sea media cero,
pero tenemos un número ilimitado de muestras de entrada. Para restar la media, primero
tendríamos que calcular la media de la distribución, pero no es posible calcular la media de un
conjunto ilimitado. También podríamos considerar tomar una muestra representativa y calcular la
media de esa muestra, pero podríamos estar cometiendo un error si no sabemos cuál es el tamaño
de muestra correcto. Considere una distribución en la que casi todos los valores son 0, pero cada
diezmillonésimo ejemplo tiene un valor de 1e9. Esta distribución tiene un valor medio de 100, pero
si calcula la media en los primeros 1 millón de ejemplos, estará bastante equivocado. Podemos
realizar una normalización de transmisión utilizando la API del conjunto de datos de la siguiente
manera (listado 6.6). En este listado, llevaremos un registro continuo de cuántas muestras hemos
visto y cuál ha sido la suma de esas muestras. De esta forma, podemos realizar una normalización
del streaming. Este listado opera en escalares (no tensores), pero una versión diseñada para
tensores tendría una estructura similar.

Listado 6.6 Normalización de streaming usandotf.datos.mapa()

función newStreamingZeroMeanFn() {
Devuelve una función unaria, que
dejar muestrasSoFar = 0; sea
devolverá su entrada menos la media de
sumaSoFar = 0;
todas sus entradas hasta el momento

devolver (x) => {


214 CPASADO6Trabajar con datos

muestras hasta ahora + = 1;


sumahastaahora += X;
const estimadaMean = sumSoFar / samplesSoFar; return x -
media estimada;
}
}
const normalizedDataset1 =
unNormalizedDataset1.map(newStreamingZeroMeanFn()); const
normalizedDataset2 =
unNormalizedDataset2.map(newStreamingZeroMeanFn());

Tenga en cuenta que generamos una nueva función de mapeo, que se cierra sobre su propia copia del contador
de muestras y el acumulador. Esto es para permitir que varios conjuntos de datos se normalicen de forma
independiente. De lo contrario, ambos conjuntos de datos usarían las mismas variables para contar invocaciones
y sumas. Esta solución no está exenta de limitaciones, especialmente con la posibilidad de desbordamiento
numérico ensumaSoFaromuestras hasta ahora,por lo que se justifica un poco de cuidado.

6.2 Modelos de entrenamiento con model.fitDataset


La API del conjunto de datos de transmisión es agradable, y hemos visto que nos permite realizar una
manipulación de datos elegante, pero el objetivo principal de latf.datosAPI es para simplificar la conexión
de datos a nuestro modelo para entrenamiento y evaluación. Como estf.datos¿Vas a ayudarnos aquí?
Desde el capítulo 2, cada vez que hemos querido entrenar un modelo, hemos usado el
modelo.fit()API. Recordar quemodelo.fit()toma al menos dos argumentos obligatorios: xsysí.Como
recordatorio, elxsvariable debe ser un tensor que represente una colección de ejemplos de
entrada. lossíLa variable debe estar vinculada a un tensor que represente una colección
correspondiente de objetivos de salida. Por ejemplo, en el listado 5.11 del capítulo anterior,
entrenamos y ajustamos nuestro modelo sintético de detección de objetos con llamadas como

model.fit(imágenes, objetivos, modelFitArgs)

dondeimágenesera, por defecto, un tensor de forma de rango 4 [2000, 224, 224, 3],que
representa una colección de 2.000 imágenes. losmodelFitArgsEl objeto de configuración
especificó el tamaño del lote para el optimizador, que de forma predeterminada era 128.
Retrocediendo, vemos que a TensorFlow.js se le dio una memoria6colección de 2,000
ejemplos, que representan la totalidad de los datos, y luego recorrió esos datos 128 ejemplos
a la vez para completar cada época.
¿Qué pasaría si estos datos no fueran suficientes y quisiéramos entrenar con un conjunto de
datos mucho más grande? En esta situación, nos enfrentamos a un par de opciones menos que
ideales. La opción 1 es cargar una matriz mucho más grande y ver si funciona. Sin embargo, en
algún momento, TensorFlow.js se quedará sin memoria y emitirá un error útil que indica que no
pudo asignar el almacenamiento para los datos de entrenamiento. La opción 2 es que subamos
nuestros datos a la GPU en partes separadas y llamemosmodelo.fit()en cada trozo. Tendríamos que
realizar nuestra propia orquestación demodelo.fit(),entrenar nuestro modelo en piezas de nuestros
datos de entrenamiento de forma iterativa cuando esté listo. Si quisiéramos realizar más de una
época, tendríamos que volver atrás y volver a descargar nuestros fragmentos en algunos

6EnGPUmemoria, que suele ser más limitada que la RAM del sistema.
Modelos de entrenamiento con model.fitDataset 215

(presumiblemente barajado) orden. Esta orquestación no solo es engorrosa y propensa a


errores, sino que también interfiere con los propios informes de TensorFlow sobre el
contador de épocas y las métricas informadas, que nos veremos obligados a unir de nuevo.
Tensorflow.js nos brinda una herramienta mucho más conveniente para esta tarea usando el
modelo.fitDataset()API:
modelo.fitDataset(conjunto de datos, modelFitDatasetArgs)

modelo.fitDataset()acepta un conjunto de datos como su primer argumento, pero el conjunto de datos


debe cumplir con un cierto patrón para funcionar. Específicamente, el conjunto de datos debe producir
objetos con dos propiedades. La primera propiedad se llamaxsy tiene un valor de tipoTensor,representar
las características de un lote de ejemplos; esto es parecido alxsargumento amodelo.fit(), pero el conjunto
de datos produce elementos de un lote a la vez en lugar de toda la matriz a la vez. La segunda propiedad
requerida se llamasíy contiene el tensor objetivo correspondiente.7Comparado conmodelo.fit(),
modelo.fitDataset()proporciona una serie de ventajas. Principalmente, no necesitamos escribir código para
administrar y orquestar la descarga de partes de nuestro conjunto de datos; esto se maneja de manera
eficiente y según sea necesario. Las estructuras de almacenamiento en caché integradas en el conjunto
de datos permiten la obtención previa de datos que se anticipa que se necesitarán, haciendo un uso
eficiente de nuestros recursos computacionales. Esta llamada API también es más poderosa, lo que nos
permite entrenar en conjuntos de datos mucho más grandes de los que caben en nuestra GPU. De hecho,
el tamaño del conjunto de datos en el que podemos entrenar ahora está limitado solo por el tiempo que
tenemos porque podemos continuar entrenando mientras podamos obtener nuevos ejemplos de
entrenamiento. Este comportamiento se ilustra en el ejemplo del generador de datos en el repositorio
tfjs-examples.
En este ejemplo, entrenaremos un modelo para que aprenda a estimar la probabilidad de ganar
un simple juego de azar. Como de costumbre, puede usar los siguientes comandos para verificar y
ejecutar la demostración:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-ejemplos/


generador de datos
discos compactos

hilo
hilo reloj

El juego utilizado aquí es un juego de cartas simplificado, parecido al póquer. Ambos jugadores reciben
nortetarjetas, dondenortees un número entero positivo, y cada carta está representada por un número
entero aleatorio entre 1 y 13. Las reglas del juego son las siguientes:

- El jugador con el grupo más grande de cartas del mismo valor gana. Por ejemplo, si el
jugador 1 tiene un trío y el jugador 2 solo tiene un par, el jugador 1 gana.
- Si ambos jugadores tienen el grupo máximo del mismo tamaño, entonces gana el jugador con el
grupo con el valor nominal más grande. Por ejemplo, un par de 5 supera a un par de 4. Si ninguno
- de los jugadores tiene un par, gana el jugador con la carta individual más alta. Los empates se
- resuelven al azar, 50/50.

7
Para modelos con múltiples entradas, se espera una matriz de tensores en lugar de los tensores de características individuales. El patrón es
similar para los modelos que se ajustan a varios objetivos.
216 CPASADO6Trabajar con datos

Debería ser fácil convencerse de que cada jugador tiene las mismas posibilidades
de ganar. Así, si no sabemos nada de nuestras cartas, solo deberíamos poder
adivinar si ganaremos o no la mitad de las veces. Construiremos y entrenaremos un
modelo que toma como entrada las cartas del jugador 1 y predice si ese jugador
ganará. En la captura de pantalla de la figura 6.1, debería ver que pudimos lograr
aproximadamente un 75 % de precisión en este problema después de entrenar en
aproximadamente 250 000 ejemplos (50 épocas * 50 lotes por época * 100
muestras por lote). En esta simulación se usaron cinco cartas por mano, pero se
logran precisiones similares para otros conteos. Se pueden lograr precisiones más
altas ejecutando lotes más grandes y durante más épocas, pero incluso al 75 %,

Si tuviéramos que realizar esta operación usandomodelo.fit(),necesitaríamos crear y almacenar


un tensor de 250 000 ejemplos solo para representar las características de entrada. Los datos

Figura 6.1 La interfaz de usuario del ejemplo del generador de datos. Una descripción de las reglas del juego y un botón para ejecutar
simulaciones se encuentran en la parte superior izquierda. Debajo de eso están las características generadas y la canalización de datos. El
botón Dataset-to-Array ejecuta las operaciones de conjuntos de datos encadenados que simularán el juego, generarán características,
agruparán muestras por lotes, tomaránnortedichos lotes, conviértalos en una matriz e imprima la matriz. En la parte superior derecha, hay
opciones para entrenar un modelo usando esta canalización de datos. Cuando el usuario hace clic en el botón Train-Model-Using-Fit-Dataset, el
modelo.fitDataset()toma el control y extrae muestras de la tubería. Las curvas de pérdida y precisión se imprimen debajo de esto. En la parte
inferior derecha, el usuario puede ingresar valores para la mano del jugador 1 y presionar un botón para hacer predicciones a partir del
modelo. Las predicciones más grandes indican que el modelo cree que es más probable que la mano gane. Los valores se dibujan con
reemplazo, por lo que pueden ocurrir cinco de una clase.
Modelos de entrenamiento con model.fitDataset 217

en este ejemplo son bastante pequeños, solo decenas de flotantes por instancia, pero para nuestra tarea de
detección de objetos en el capítulo anterior, 250,000 ejemplos habrían requerido 150 GB de memoria GPU,8
mucho más allá de lo que está disponible en la mayoría de los navegadores en 2019.
Profundicemos en las partes relevantes de este ejemplo. Primero, veamos cómo generamos
nuestro conjunto de datos. El código en la siguiente lista (simplificado de tfjs-examples/data-
generator/index.js) es similar al conjunto de datos del generador de dados en la lista 6.3, con un
poco más de complejidad ya que estamos almacenando más información.

Listado 6.7 Construyendo untf.data.Conjunto de datospara nuestro juego de cartas

importar * como juego desde './juego';


La biblioteca de juegos proporciona funciones

let numSimulacioneshastaahora = 0; randomHand() y compareHands() para generar


una mano a partir de un juego de cartas
function ejecutarUnJuegoJugar() { simplificado similar al póquer y para comparar
const jugador1Mano = juego.randomHand(); const dos de esas manos para saber qué jugador ha
jugador2Mano = juego.randomHand(); const ganado.
jugador1Gana = juego.compararManos(
Simula dos jugadores en un juego de
jugador1mano, jugador2mano);
cartas simple similar al póquer
Calcula numSimulacioneshastaahora++;
el ganador return {jugador1Mano, jugador2Mano, jugador1Ganar};
del juego devuelve los dos
}
manos y quien gano
función* juegoGeneradorFunción() {
mientras (verdadero) {
rendimiento ejecutarUnJuegoJugar();
}
}

exportar constante GAME_GENERATOR_DATASET =


tf.data.generator(gameGeneratorFunction);

espera GAME_GENERATOR_DATASET.take(1).forEach(
e => consola.log(e));

// Huellas dactilares

// {jugador1Mano: [11, 9, 7, 8], //


jugador2Mano: [10, 9, 5, 1], //
jugador1Ganador: 1}

Una vez que tenemos nuestro conjunto de datos generador básico conectado a la lógica del juego,
queremos formatear los datos de una manera que tenga sentido para nuestra tarea de aprendizaje.
Específicamente, nuestra tarea es intentar predecir lajugador1ganarpoco de lajugador1mano.Para
hacerlo, necesitaremos que nuestro conjunto de datos devuelva elementos del formulario [lote de
características, lote de objetivos],donde las características se calculan a partir de la mano del jugador 1. El
siguiente código está simplificado de tfjs-examples/data-generator/index.js.

8numExamples × ancho × alto × colorDepth × sizeOfInt32 = 250 000 × 224 × 224 × 3 × 4 bytes.


218 CPASADO6Trabajar con datos

Listado 6.8 Construyendo un conjunto de datos de características del jugador

función juegoParaCaracterísticasYEtiqueta(juegoEstado) {
volver tf.ordenado(() => {
const jugador1Mano = tf.tensor1d(juegoEstado.jugador1Mano, 'int32'); const
manoUnoCaliente = tf.unoCaliente(
Toma el estado de uno completo
tf.sub(player1Hand, tf.scalar(1, 'int32')),
juego y devuelve una representación
game.GAME_STATE.max_card_value); característica del jugador1's

const características = tf.sum(handOneHot, 0); mano y estado de victoria


const label = tf.tensor1d([gameState.player1Win]); return {xs: handOneHot tiene la forma
características, ys: etiqueta}; [numCards,
}); max_value_card]. Esta
} operación suma el
número de cada tipo de
sea BATCH_SIZE = 50; tarjeta, resultando un
exportar const TRAINING_DATASET = tensor con la forma
[tarjeta_de_valor_máximo].
GAME_GENERATOR_DATASET.map(gameToFeaturesAndLabel)
. lote(BATCH_SIZE);

espera TRAINING_DATASET.take(1).forEach( Agrupa BATCH_SIZE


e => console.log([e.shape, e.shape])); elementos consecutivos en un
solo elemento Esto también
// Imprime la forma de los tensores: // [[50, 13], convertiría los datos de las
[50, 1]] matrices de JavaScript en
tensores si aún no lo eran.
Convierte cada elemento del formato de objeto de salida
del juego en una matriz de dos tensores: uno para las
características y otro para el objetivo

Ahora que tenemos un conjunto de datos en la forma adecuada, podemos conectarlo a nuestro
modelo usandomodelo.fitDataset(),como se muestra en la siguiente lista (simplificado de
tfjsexamples/data-generator/index.js).

Listado 6.9 Construyendo y entrenando un modelo en el conjunto de datos

Esta convocatoria inicia la formación.


Cuántos lotes constituye una época. Dado que nuestro conjunto de datos
// Construir modelo. es ilimitado, esto debe definirse para decirle a TensorFlow.js
modelo = tf.secuencial(); cuándo ejecutar la devolución de llamada de fin de época.
modelo.add(tf.layers.dense({
forma de entrada: [juego.GAME_STATE.max_card_value],
unidades: 20,
activación: 'relú'
}));
model.add(tf.layers.dense({unidades: 20, activación: 'relu'}));
model.add(tf.layers.dense({unidades: 1, activación: 'sigmoide'}));

// modelo de tren
esperarmodelo.fitDataset(ENTRENAMIENTO_CONJUNTO DE DATOS, {
lotesPerEpoch: ui.getBatchesPerEpoch(), épocas:
ui.getEpochsToTrain(),
datos de validación: TRAINING_DATASET,

Estamos utilizando los datos de entrenamiento como datos de validación. Normalmente esto es malo, ya que

obtendremos una impresión sesgada de lo bien que lo estamos haciendo. En este caso lo es
no es un problema, ya que se garantiza que los datos utilizados para el entrenamiento y
los datos utilizados para la validación son independientes en virtud del generador.
Modelos de entrenamiento con model.fitDataset 219

lotes de validación: 10,


Necesitamos decirle a TensorFlow.js cuántas
devoluciones de llamada: {
muestras tomar del conjunto de datos de
onEpochEnd: asíncrono (época, registros) => {
validación para constituir una evaluación.
tfvis.show.history(
ui.lossContainerElement, trainLogs, ['loss', 'val_loss'])
tfvis.show.history(
ui.accuracyContainerElement, trainLogs, ['acc', 'val_acc'], {zoomToFitAccuracy:
true})
},
model.fitDataset() crea un
}
historial que es compatible con
}
tfvis, al igual que model.fit().

Como vemos en la lista anterior, ajustar un modelo a un conjunto de datos es tan simple como ajustar un
modelo a un par de tensores x, y. Siempre que nuestro conjunto de datos produzca valores de tensor en
el formato correcto, todo funciona, obtenemos el beneficio de transmitir datos desde una fuente
posiblemente remota y no necesitamos administrar la orquestación por nuestra cuenta. Más allá de pasar
un conjunto de datos en lugar de un par de tensores, hay algunas diferencias en el objeto de
configuración que merecen discusión:

- lotesPerEpoch—Como vimos en el listado 6.9, la configuración paramodelo.fit-Dataset()


toma un campo opcional para especificar el número de lotes que constituyen una
época. Cuando entregamos la totalidad de los datos amodelo.fit(), fue fácil calcular
cuántos ejemplos hay en todo el conjunto de datos. es solodatos.forma[0]!Cuando usas
ajusteDataset(),podemos decirle a TensorFlow.js cuándo termina una época de una de
dos maneras. La primera forma es utilizar este campo de configuración,
yajusteDataset()ejecutaráenEpochEndyonEpochStartdevoluciones de llamada después
tantos lotes. La segunda forma es hacer que el conjunto de datos finalice como una señal de que el
conjunto de datos está agotado. En el listado 6.7, podríamos cambiar

mientras (verdadero) { ... }

para
for (sea i = 0; i<ui.getBatchesPerEpoch(); i++) { ... }

para imitar este comportamiento.

-datos de validación—Cuando usasajusteDataset(),losValidación de datostal vez un


conjunto de datos también. Pero no tiene que ser así. Puedes seguir usando tensores para
Validación de datossi quieres. El conjunto de datos de validación debe cumplir con la misma
especificación con respecto al formato de los elementos devueltos que el conjunto de datos de
entrenamiento.
- lotes de validación—Si sus datos de validación provienen de un conjunto de datos, debe decirle a
TensorFlow.js cuántas muestras debe tomar del conjunto de datos para constituir una evaluación
completa. Si no se especifica ningún valor, TensorFlow.js seguirá extrayendo datos del conjunto
de datos hasta que devuelva una señal de finalización. Debido a que el código del listado 6.7 usa
un generador interminable para generar el conjunto de datos, esto nunca sucedería y el
programa se bloquearía.

El resto de la configuración es idéntica a la delmodelo.fit()API, por lo que no es necesario


realizar cambios.
220 CPASADO6Trabajar con datos

6.3 Patrones comunes para acceder a los datos


Todos los desarrolladores necesitan algunas soluciones para conectar sus datos a su modelo. Estas
conexiones van desde conexiones de acciones comunes hasta conjuntos de datos experimentales bien
conocidos como MNIST, conexiones completamente personalizadas y formatos de datos propietarios
dentro de una empresa. En esta sección, revisaremos cómotf.datospuede ayudar a que estas conexiones
sean simples y fáciles de mantener.

6.3.1 Trabajar con datos en formato CSV


Más allá de trabajar con conjuntos de datos de acciones comunes, la forma más común de acceder a los
datos implica cargar datos preparados almacenados en algún formato de archivo. Los archivos de datos a
menudo se almacenan en formato CSV (valores separados por comas)9debido a su simplicidad, legibilidad
humana y amplio soporte. Otros formatos tienen otras ventajas en cuanto a eficiencia de almacenamiento
y velocidad de acceso, pero CSV podría considerarse ellingua francade conjuntos de datos. En la
comunidad de JavaScript, normalmente queremos poder transmitir datos de manera conveniente desde
algún punto final HTTP. Es por eso que TensorFlow.js brinda soporte nativo para transmitir y manipular
datos de archivos CSV. En la sección 6.1.2, describimos brevemente cómo construir un tf.data.Conjunto de
datosrespaldado por un archivo CSV. En esta sección, profundizaremos en la API de CSV para mostrar
cómotf.datoshace que trabajar con estas fuentes de datos sea muy fácil. Describiremos una aplicación de
ejemplo que se conecta a conjuntos de datos CSV remotos, imprime su esquema, cuenta los elementos
del conjunto de datos y ofrece al usuario la posibilidad de seleccionar e imprimir los ejemplos
individuales. Mira el ejemplo usando los comandos familiares:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


data-csv
discos compactos

hilo y reloj de hilo

Esto debería abrir un sitio que nos indique ingresar la URL de un archivo CSV alojado o usar una de las
cuatro URL sugeridas haciendo clic, por ejemplo, Boston Housing CSV. Consulte la figura 6.2 para ver una
ilustración. Debajo del cuadro de entrada de entrada de URL, se proporcionan botones para realizar tres
acciones: 1) contar las filas en el conjunto de datos, 2) recuperar los nombres de las columnas del CSV, si
existen, y 3) acceder e imprimir una fila de muestra específica del conjunto de datos. conjunto de datos
Veamos cómo funcionan y cómotf.datosLa API los hace muy fáciles.
Vimos anteriormente que crear un conjunto de datos tfjs-data desde un CSV remoto es muy simple
usando un comando como

const myData = tf.data.csv(url);

dondeURLes un identificador de cadena que usa el protocolo http://, https:// o file://, o un


Solicitar información.Esta llamada hacenoen realidad emita solicitudes a la URL para verificar
si, por ejemplo, el archivo existe o es accesible, debido a la iteración diferida. En el listado
6.10, el CSV se obtiene primero en el asincrónicomisDatos.paraCada()llamada. los

9
A partir de enero de 2019, el sitio de desafíos de ciencia de datos y aprendizaje automáticokaggle.com/conjuntos de datoscuenta con 13.971 conjuntos de
datos públicos, de los cuales más de dos tercios están alojados en formato CSV.
Patrones comunes para acceder a los datos 221

Figura 6.2 Interfaz de usuario web para nuestro ejemplo CSV de tfjs-data. Haga clic en uno de los botones CSV preestablecidos
en la parte superior o ingrese una ruta a su propio CSV alojado, si tiene uno. Asegúrese de habilitar el acceso CORS para su CSV
si va con su propio archivo alojado.

función que llamamos en elpara cada()simplemente encadenará e imprimirá elementos en el conjunto de datos,
pero podríamos imaginarnos haciendo otras cosas con este iterador, como generar elementos de interfaz de
usuario para cada elemento en el conjunto o calcular estadísticas para un informe.

Listado 6.10 Imprimiendo los primeros 10 registros en un archivo CSV remoto

const url = document.getElementById('queryURL').value; const myData =


tf.data.csv(url);
Crea el conjunto de datos tfjs-data
esperar myData.take(10).forEach(
proporcionando la URL a tf.data.csv()
x => consola.log(JSON.stringify(x))));

// La salida es como //
{"crim":0.26169,"zn":0,"indus":9.9,"chas":0,"nox":0.544,"rm":6.023, "medv":19.4} ...
//
// {"crim":5.70818,"zn":0,"indus":18.1,"chas":0,"nox":0.532,"rm":6.75, "medv":23.7} ...
//
// ...

Crea un conjunto de datos que consta de la primera10 filas del conjunto de datos CSV. Luego, usa el método forEach() para iterar sobre todos los
valores proporcionados por el conjunto de datos. Tenga en cuenta que forEach() es una función asíncrona.

Los conjuntos de datos CSV a menudo usan la primera fila como un encabezado de metadatos que
contiene los nombres asociados con cada columna. Por defecto,tf.datos.csv()asume que este es el caso,
pero se puede controlar usando elcsvConfigobjeto pasado como el segundo argumento. Si el archivo CSV
no proporciona los nombres de las columnas, se pueden proporcionar manualmente en el constructor de
la siguiente manera:

const myData = tf.data.csv(url, {


hasHeader: falso,
columnNames: ["nombre", "apellido", "id"] });
222 CPASADO6Trabajar con datos

Si proporciona un manualnombres de columnaconfiguración al conjunto de datos CSV, tendrá prioridad sobre la


lectura de la fila del encabezado del archivo de datos. De forma predeterminada, el conjunto de datos asumirá
que la primera línea es una fila de encabezado. Si la primera fila no es un encabezado, la ausencia debe
configurarse ynombres de columnaproporcionada manualmente.
Una vez elCSVConjunto de datosexiste el objeto, es posible consultarlo para los nombres de las columnas
usandoconjunto de datos.columnNames(),que devuelve una lista de cadenas ordenadas de los nombres de las
columnas. losnombres de columna()El método es específico para elCSVConjunto de datossubclase y generalmente
no está disponible en conjuntos de datos creados a partir de otras fuentes. El botón Obtener nombres de
columnas en el ejemplo está conectado a un controlador que usa esta API. Solicitar los nombres de las columnas
da como resultado elconjunto de datosobjeto que realiza una llamada de búsqueda a la URL proporcionada para
acceder y analizar la primera fila; por lo tanto, la llamada asíncrona en la siguiente lista (condensada de tfjs-
examples/csv-data/index.js).

Listado 6.11 Accediendo a nombres de columnas desde un CSV

const url = document.getElementById('queryURL').value; const myData =


tf.data.csv(url);
const columnNames = await myData.columnNames();
Contacta con el control remoto
consola.log(nombresdecolumna);
CSV para recopilar y
// Da como resultado algo como [ //
analizar la columna
"crim", "zn", "indus", ..., "tax", "ptratio", "lstat"] para encabezados
// Boston Housing

Ahora que tenemos los nombres de las columnas, obtengamos una fila de nuestro conjunto de datos. En
el listado 6.12, mostramos cómo la aplicación web imprime una sola fila seleccionada del archivo CSV,
donde el usuario selecciona qué fila a través de un elemento de entrada. Para cumplir con esta solicitud,
primero utilizaremos elConjunto de datos.skip()método para crear un nuevo conjunto de datos igual que el
original, pero omitiendo el primeron - 1elementos. Luego usaremos elConjunto de datos.tomar()método
para crear un conjunto de datos que termina después de un elemento. Finalmente, utilizaremosConjunto
de datos.toArray()para extraer los datos en una matriz de JavaScript estándar. Si todo va bien, nuestra
solicitud producirá una matriz que contiene exactamente un elemento en la posición especificada. Esta
secuencia se reúne en la siguiente lista (condensada de tfjs-examples/csvdata/index.js).

Listado 6.12 Accediendo a una fila seleccionada desde un CSV remoto

sampleIndex es un número devuelto por Crea el conjunto de datos myData, configurado para leer
un elemento de la interfaz de usuario. de url, pero en realidad no se conecta todavía

const url = document.getElementById('queryURL').value; const


sampleIndex = document.getElementById( Crea un nuevo
'cuálEntradaDeMuestra').valorComoNúmero; conjunto de datos pero se salta

const myData = tf.data.csv(url); const sobre el primero


muestra = aguardar myData índice de muestra

. saltar (índice de muestra) valores

Esta es la llamada que realmente hace que el objeto del conjunto . toma 1)
Crea un nuevo conjunto de datos, pero
de datos se comunique con la URL y realice la búsqueda. . aArray();
solo conserva el primero1elemento
Tenga en cuenta que el tipo de retorno es una matriz de objetos, en
este caso, que contiene exactamente un objeto, con
teclas correspondientes a los nombres de cabecera y
valores asociados con esas columnas.
Patrones comunes para acceder a los datos 223

consola.log(muestra);
// Da como resultado algo como: [{crim: 0.3237, zn: 0, indus: 2.18, ..., tax: // 222, pratio: 18.7, lstat:
2.94}]
// para Vivienda de Boston.

Ahora podemos tomar la salida de la fila, que, como puede ver en la salida de la consola.logen
el listado 6.12 (repetido en un comentario)—viene en forma de un objeto que asigna el
nombre de la columna al valor y le da estilo para insertarlo en nuestro documento. Algo a
tener en cuenta: si solicitamos una fila que no existe, tal vez el elemento 400 de un conjunto
de datos de 300 elementos, terminaremos con una matriz vacía.
Cuando se conecta a conjuntos de datos remotos, es bastante común cometer un error y usar una URL
incorrecta o credenciales incorrectas. En estas circunstancias, es mejor detectar el error y proporcionar al
usuario un mensaje de error razonable. Desde elconjunto de datosel objeto no se pone en contacto con el
recurso remoto hasta que se necesitan los datos, es importante tener cuidado de escribir el manejo de
errores en el lugar correcto. La siguiente lista muestra un breve fragmento de cómo se realiza el manejo
de errores en nuestra aplicación web de ejemplo CSV (resumido de tfjs-examples/csv-data/index.js). Para
obtener más detalles sobre cómo conectarse a archivos CSV protegidos por autenticación, consulte el
cuadro de información 6.2.

Listado 6.13 Manejo de errores derivados de conexiones fallidas

const url = 'http://alguna.mala.url';


Envolver esta línea en un bloque de
const sampleIndex = document.getElementById(
prueba no ayudaría porque la URL
'cuálEntradaDeMuestra').valorComoNúmero;
incorrecta no se obtiene aquí.
const myData = tf.data.csv(url); let nombres de
columna;
tratar { El error de una mala conexión
columnNames = esperar myData.columnNames(); } aparecerá en este paso.
atrapar (e) {
ui.updateColumnNamesMessage(No se pudo conectar a ${url}`);
}

En la sección 6.2, aprendimos cómo usarmodelo.fitDataset().Vimos que el método requiere un conjunto de datos
que produce elementos en una forma muy particular. Recuerde que la forma es un objeto con dos propiedades {
xs, ys},dondexses un tensor que representa un lote de la entrada, ysíes un tensor que representa un lote del
objetivo asociado. De forma predeterminada, el conjunto de datos CSV devolverá elementos como objetos de
JavaScript, pero podemos configurar el conjunto de datos para que, en su lugar, devuelva elementos más
cercanos a lo que necesitamos para el entrenamiento. Para esto, haremos
necesita usar elcsvConfig.columnConfigscampo detf.datos.csv().Considera un CSV
archivo sobre golf con tres columnas: "club", "fuerza" y "distancia". Si deseáramos predecir la distancia del
palo y la fuerza, podríamos aplicar una función de mapa en la salida sin procesar para organizar los
campos enxsysí;o, más fácilmente, podríamos configurar el lector de CSV para que lo haga por nosotros.
La Tabla 6.4 muestra cómo configurar el conjunto de datos CSV para separar las propiedades de las
características y las etiquetas, y realizar el procesamiento por lotes para que la salida sea adecuada.
capaz de entrar enmodelo.fitDataset().
224 CPASADO6Trabajar con datos

Cuadro 6.4 Configurar un conjunto de datos CSV para trabajar conmodelo.fitDataset()

Resultado de dataset.take(1).toArray()[0]
Cómo se construye el conjunto de datos (el primer elemento devuelto
y configurado Código para construir el conjunto de datos. del conjunto de datos)

Valor predeterminado de CSV sin procesar conjunto de datos = tf.data.csv(csvURL) {trébol: 1, fuerza: 45,
distancia: 200}

CSV con etiqueta configurada ColumnConfigs = {x: {trébol: 1, fuerza: 45},


enColumnConfigs {distancia: {esLabel: verdadero}}; ys: {distancia: 200}}
conjunto de datos = tf.data.csv(csvURL,
{configuraciones de columna});

CSV con ColumnConfigs = [xs: {club: Tensor,


ColumnConfigsy {distancia: {esLabel: cierto}}; fuerza: Tensor},
luego dosificado conjunto de datos = tf.datos ys: {distancia:Tensor}]
. csv(csvURL,
{configuraciones de columna}) Cada uno de estos tres tensores
. lote(128); tiene forma =[128].

CSV con ColumnConfigs = {distancia: {xs: tensor, ys: tensor}


ColumnConfigsy {esLabel: cierto}}; Tenga en cuenta que la función de mapeo
luego agrupado y mapeado conjunto de datos = tf.datos
devolvió elementos de la forma{xs:
de objeto a matriz . csv(csvURL, [número, número], ys: [número]}. La
{configuraciones de columna})
operación por lotes convierte
. mapa(({xs, ys}) => automáticamente matrices numéricas en
{ tensores. Así, el primer tensor (xs)tiene
regreso forma= [128,2]. El segundo tensor(sí)tiene
{xs:Objeto.valores(xs), forma= [128, 1].

ys:Objeto.valores(ys)};
})
. lote(128);

ICAJA NFO6.2 Obtener datos CSV protegidos por autenticación


En los ejemplos anteriores, nos hemos conectado a datos disponibles de archivos remotos
simplemente proporcionando una URL. Esto funciona bien tanto en Node.js como desde el
navegador y es muy fácil, pero a veces nuestros datos están protegidos y debemos proporcionar
Solicitud parámetros lostf.datos.csv()API nos permite proporcionarSolicitar informaciónen lugar de
una URL de cadena sin procesar, como se muestra en el siguiente código. Aparte del parámetro de
autorización adicional, no hay cambios en el conjunto de datos:

> const url = 'http://ruta/a/su/privado.csv'


> const requestInfo = nueva solicitud (url);
> constante API_KEY = 'abcdef123456789'
> requestInfo.headers.append('Autorización', API_KEY);
> const myDataset = tf.data.csv(requestInfo);
Traducido del inglés al español - www.onlinedoctranslator.com

Patrones comunes para acceder a los datos 225

6.3.2 Acceder a datos de video usando tf.data.webcam()


Una de las aplicaciones más emocionantes para los proyectos de TensorFlow.js es entrenar y
aplicar modelos de aprendizaje automático a los sensores disponibles directamente en los
dispositivos móviles. ¿Reconocimiento de movimiento usando el acelerómetro a bordo del móvil?
¿Entendimiento del sonido o del habla usando el micrófono integrado? ¿Asistencia visual usando la
cámara a bordo? Hay tantas buenas ideas por ahí, y acabamos de empezar.
En el capítulo 5, exploramos el trabajo con la cámara web y el micrófono en el contexto del
aprendizaje por transferencia. Vimos cómo usar la cámara para controlar un juego de Pac-Man y
usamos el micrófono para afinar un sistema de comprensión del habla. Si bien no todas las
modalidades están disponibles como una conveniente llamada a la API,tf.datostiene una API simple
y fácil para trabajar con la cámara web. Exploremos cómo funciona y cómo usarlo para predecir a
partir de modelos entrenados.
Con eltf.datosAPI, es muy simple crear un iterador de conjunto de datos que produzca un
flujo de imágenes desde la cámara web. El listado 6.14 muestra un ejemplo básico de la
documentación. Lo primero que notamos es la llamada altf.datos.webcam().Este constructor
devuelve un iterador de cámara web tomando un elemento HTML opcional como argumento
de entrada. El constructor solo funciona en el entorno del navegador. Si se llama a la API en
el entorno Node.js, o si no hay una cámara web disponible, el constructor generará una
excepción que indica el origen del error. Además, el navegador solicitará permiso al usuario
antes de abrir la cámara web. El constructor lanzará una excepción si se deniega el permiso.
El desarrollo responsable debe cubrir estos casos con mensajes fáciles de usar.

Listado 6.14 Creando un conjunto de datos usandotf.datos.webcam()y un elemento HTML

Constructor para el objeto del conjunto de datos de video. El


El elemento muestra el video de la cámara elemento mostrará contenido de la cámara web. El elemento también
web y determina el tamaño del tensor configura el tamaño de los tensores creados.

const videoElement = document.createElement('video');


videoElement.width = 100;
videoElement.altura = 100;

onst webcam = esperar tf.data.webcam(videoElement); const img =


esperar webcam.capture();
img.imprimir();
cámara web.stop();
Detiene la transmisión de video y pausa el
iterador de la cámara web
Toma un cuadro de la transmisión de video
y ofrece el valor como un tensor

Al crear un iterador de cámara web, es importante que el iterador conozca la forma de los tensores que
se van a producir. Hay dos formas de controlar esto. La primera forma, que se muestra en el listado 6.14,
utiliza la forma del elemento HTML provisto. Si la forma debe ser diferente, o quizás el video no se
muestre en absoluto, la forma deseada se puede proporcionar a través de un objeto de configuración,
como se muestra en el listado 6.15. Tenga en cuenta que lo proporcionado
226 CPASADO6Trabajar con datos

El argumento del elemento HTML no está definido, lo que significa que la API creará un elemento oculto
en el DOM para que actúe como identificador del video.

Listado 6.15 Creando un conjunto de datos de cámara web básico usando un objeto de configuración

const videoElement = indefinido; const Creación de un iterador de conjunto de datos de cámara


webcamConfig = { web utilizando un objeto de configuración en lugar de un
modo de orientación: 'usuario', elemento HTML. Aquí, también especificamos qué cámara
ancho de cambio de tamaño: 100, usar en un dispositivo con múltiples cámaras. 'usuario' se
redimensionarAltura: 100}; refiere a la cámara frente al usuario; como alternativa a

const webcam = esperar tf.data.webcam( 'usuario', 'entorno' se refiere a la cámara trasera.

elemento de video, webcamConfig);

También es posible utilizar el objeto de configuración para recortar y cambiar el tamaño de partes de la
transmisión de video. Utilizando el elemento HTML y el objeto de configuración en tándem, la API permite a la
persona que llama especificar una ubicación desde la que recortar y el tamaño de salida deseado. El tensor de
salida se interpolará al tamaño deseado. Consulte la siguiente lista para ver un ejemplo de cómo seleccionar una
parte rectangular de un video cuadrado y luego reducir el tamaño para que se ajuste a un modelo pequeño.

Listado 6.16 Recortar y cambiar el tamaño de los datos de una cámara web

const videoElement = document.createElement('video');


videoElement.ancho = 300;
videoElement.altura = 300;
Sin la configuración explícita,
const webcamConfig = { videoElement controlaría el tamaño
ancho de cambio de tamaño: 150,
de salida, 300 × 300 aquí.
redimensionar Altura: 100,
El usuario solicita un150 ×100
cultivo central: verdadero
extracción del video.
};
Los datos extraídos serán del
const webcam = esperar tf.data.webcam( centro del video original.
elemento de video, webcamConfig);
Los datos capturados de este iterador de
cámara web dependen tanto del
elemento HTML como de webcamConfig.

Es importante señalar algunas diferencias obvias entre este tipo de conjunto de datos y los conjuntos de
datos con los que hemos estado trabajando hasta ahora. Por ejemplo, los valores producidos por la
cámara web dependen de cuándo los extraiga. Compare esto con el conjunto de datos CSV, que producirá
las filas en orden sin importar qué tan rápido o lento se dibujen. Además, se pueden extraer muestras de
la cámara web durante el tiempo que el usuario desee más. Las personas que llaman a la API deben
decirle explícitamente a la secuencia que finalice cuando hayan terminado.
Se accede a los datos desde el iterador de la cámara web usando elcapturar()que devuelve un tensor
que representa el fotograma más reciente. Los usuarios de API deben usar este tensor para su trabajo de
aprendizaje automático, pero deben recordar desecharlo para evitar una pérdida de memoria. Debido a
las complejidades involucradas en el procesamiento asíncrono de los datos de la cámara web, es mejor
aplicar las funciones de preprocesamiento necesarias directamente al cuadro capturado en lugar de
utilizar el diferido.mapa()funcionalidad proporcionada portf.datos.
Patrones comunes para acceder a los datos 227

Es decir, en lugar de procesar datos usandodatos.mapa(),


// No:
let webcam = await tfd.webcam(myElement) webcam =
webcam.map(myProcessingFunction); const imgTensor =
webcam.capture();
// usa imgTensor aquí.
tf.dispose(imgTensor)

aplicar la función directamente a la imagen:

// Sí:
let webcam = esperar tfd.webcam(myElement);
const imgTensor = myPreprocessingFunction(webcam.capture()); // usa
imgTensor aquí.
tf.dispose(imgTensor)

lospara cada()yaArray()Los métodos no deben usarse en un iterador de cámara web. Para procesar
largas secuencias de fotogramas del dispositivo, los usuarios deltf.datos.webcam() La API debe
definir su propio bucle utilizando, por ejemplo,tf.fotograma siguiente()y llama capturar()a una
velocidad de fotogramas razonable. La razón es que si llamaraspara cada() en su cámara web,
entonces el marco dibujaría marcos tan rápido como el motor Java-Script del navegador pueda
solicitarlos desde el dispositivo. Por lo general, esto creará tensores más rápido que la velocidad de
fotogramas del dispositivo, lo que dará como resultado fotogramas duplicados y cálculos
desperdiciados. Por razones similares, un iterador de cámara web deberíanopasar como
argumento a lamodelo.fit()método.
El listado 6.17 muestra el ciclo de predicción abreviado del ejemplo de aprendizaje de transferencia de
cámara web (Pac-Man) que vimos en el capítulo 5. Tenga en cuenta que el ciclo externo continuará mientras
esPredecires verdadero, que está controlado por un elemento de la interfaz de usuario. Internamente, la
velocidad del bucle se modera mediante una llamada atf.fotograma siguiente(),que está anclado a la frecuencia de
actualización de la interfaz de usuario. El siguiente código es de tfjs-examples/webcam-transfer-learning/index.js.

Listado 6.17 Usandotf.datos.webcam()en un bucle de predicción

función asíncrona getImage() {


Captura un cuadro de la cámara web y lo
volver (esperar webcam.capture())
normaliza entre:1y1.Devuelve una
. expandirDims(0) imagen por lotes (1-lote de elementos) de
. flotar() forma [1,w, h, c].
. div(tf.escalar(127))
. sub(tf. escalar(1)); webcam aquí se refiere a un iterador
devuelto por tfd.webcam; ver init() en el
tiempo (está prediciendo) { listado 6.18.
const img = esperar obtenerImagen();
Dibuja el siguiente cuadro del
const clasepredicha = tf.tidy(() => {
iterador de la cámara web.
// Captura el marco de la cámara web.

// Procesar la imagen y hacer predicciones... Espera hasta el siguiente cuadro de


... animación antes de realizar otra
predicción
esperar tf.nextFrame();
}
228 CPASADO6Trabajar con datos

Una nota final: al usar la cámara web, a menudo es una buena idea dibujar, procesar y descartar una
imagen antes de hacer predicciones en el feed. Hay dos buenas razones para esto. En primer lugar, pasar
la imagen a través del modelo garantiza que los pesos del modelo relevantes se hayan cargado en la GPU,
lo que evita cualquier lentitud entrecortada en el inicio. En segundo lugar, esto le da tiempo al hardware
de la cámara web para calentarse y comenzar a enviar fotogramas reales. Según el hardware, a veces las
cámaras web envían cuadros en blanco mientras el dispositivo se enciende. Consulte la siguiente lista
para ver un fragmento que muestra cómo se hace esto en el ejemplo de aprendizaje de transferencia de
cámara web (de webcam-transfer-learning/index.js).

Listado 6.18 Crear un conjunto de datos de video a partir detf.datos.webcam()

función asíncrona init() {


tratar { Constructor para el objeto del conjunto de datos

webcam = esperar tfd.webcam( de video. El elemento 'webcam' es un elemento

documento.getElementById('cámara web')); } de video en el documento HTML.

atrapar (e) {
consola.log(e);
document.getElementById('sin cámara web').style.display = 'bloquear';
}
truncatedMobileNet = aguardar loadTruncatedMobileNet();

ui.init();

// Calentar el modelo. Esto sube pesos a la GPU y compila los programas // WebGL, por lo que la
primera vez que recopilamos datos de la cámara web // será rápido.

const screenShot = esperar webcam.capture();


truncadoMobileNet.predict(screenShot.expandDims(0)); captura de
pantalla.dispose();
} El valor devuelto por
Hace una predicción en el primer
cuadro devuelto por la cámara web
webcam.capture() es un tensor. Debería
para asegurarse de que el modelo es
desecharse para evitar fugas.
completamente cargado en el hardware

6.3.3 Acceder a datos de audio usando tf.data.microphone()


Junto con los datos de la imagen,tf.datostambién incluye manejo especializado para recopilar datos de audio del
micrófono del dispositivo. Similar a la API de la cámara web, la API del micrófono crea un iterador perezoso que
permite a la persona que llama solicitar fotogramas según sea necesario, empaquetados cuidadosamente como
tensores adecuados para el consumo directamente en un modelo. El caso de uso típico aquí es recopilar marcos
que se utilizarán para la predicción. Si bien es técnicamente posible producir un flujo de capacitación con esta
API, sería un desafío comprimirlo junto con las etiquetas.
El Listado 6.19 muestra un ejemplo de cómo recolectar un segundo de datos de audio usando el
tf.datos.micrófono()API. Tenga en cuenta que la ejecución de este código activará el navegador para
solicitar que el usuario conceda acceso al micrófono.

Listado 6.19 Recolectando un segundo de datos de audio usando eltf.datos.micrófono()API

const mic = esperar tf.data.microphone({


La configuración del micrófono permite al
tamaño fft: 1024,
usuario controlar algunos parámetros de
columnaTruncateLength: 232,
audio comunes. Explicamos algunos de
ellos en el texto principal.
Patrones comunes para acceder a los datos 229

numFramesPerSpectrogram: 43,
sampleRateHz: 44100,
smoothingTimeConstant: 0, Ejecuta la captura
includeSpectrogram: verdadero, de audio de la Los datos del espectro de audio
includeWaveform: verdadero micrófono se devuelven como un tensor de

}); forma [43, 232,1].

const audioData = await mic.capture();


const spectrogramTensor = audioData.spectrogram; const
waveformTensor = audioData.waveform; mic.stop();
Además de los datos del espectrograma, también
Los usuarios deben llamar a stop() es posible recuperar los datos de forma de onda
para finalizar la transmisión de audio directamente. La forma de estos datos será [fftSize
y apagar el micrófono. * numFramesPerSpectrogram,1] = [44032,1].

El micrófono incluye una serie de parámetros configurables para brindar a los usuarios un control preciso
sobre cómo se aplica la transformada rápida de Fourier (FFT) a los datos de audio. Los usuarios pueden
querer más o menos fotogramas de datos de audio en el dominio de la frecuencia por espectrograma, o
pueden estar interesados solo en un determinado rango de frecuencia del espectro de audio, como las
frecuencias necesarias para el habla audible. Los campos del listado 6.19 tienen el siguiente significado:

- muestraRateHz: 44100
– La frecuencia de muestreo de la forma de onda del micrófono. Debe ser exactamente
44.100 o 48.000 y debe coincidir con la tasa especificada por el propio dispositivo.
Lanzará un error si el valor especificado no coincide con el valor disponible por el
dispositivo.
- fftTamaño: 1024
– Controla el número de muestras utilizadas para calcular cada “fotograma” de audio que no se
superpone. Cada cuadro se somete a una FFT, y los cuadros más grandes brindan más sensibilidad
de frecuencia pero tienen menos resolución de tiempo, ya que la información de tiempo dentro del
marcoestá perdido.
– Debe ser una potencia de 2 entre 16 y 8.192, ambos inclusive. Aquí,1024significa que la energía
dentro de una banda de frecuencia se calcula en un lapso de aproximadamente 1024
muestras.
– Tenga en cuenta que la frecuencia medible más alta es igual a la mitad de la frecuencia de muestreo, o
aproximadamente 22 kHz.
- columnTruncateLength: 232
– Controla cuánta información de frecuencia se retiene. Por defecto, cada cuadro de audio
contienefftTamañopuntos, o 1.024 en nuestro caso, cubriendo todo el espectro desde 0 hasta
el máximo (22 kHz). Sin embargo, normalmente estamos interesados solo en las frecuencias
más bajas. El habla humana generalmente es solo de hasta 5 kHz y, por lo tanto, solo
mantenemos la parte de los datos que representa de cero a 5 kHz.
– Aquí, 232 = (5 kHz/22 kHz) * 1024.
230 CPASADO6Trabajar con datos

- numFramesPerSpectrogram: 43
– La FFT se calcula en una serie de ventanas (o cuadros) que no se superponen de
la muestra de audio para crear un espectrograma. Este parámetro controla
cuántos se incluyen en cada espectrograma devuelto. El espectro devuelto
el gramo tendrá la forma [numFramesPerSpectrogram, fftSize, 1],o [43,
232, 1]en nuestro caso.
– La duración de cada fotograma es igual a latasa de muestra/tamaño fft.En nuestro
caso, 44 kHz * 1024 son unos 0,023 segundos.
– No hay demora entre fotogramas, por lo que la duración total del espectrograma es de
aproximadamente 43 * 0,023 = 0,98, o aproximadamente 1 segundo.
- constante de tiempo de suavizado: 0

– Cuánto combinar los datos del cuadro anterior con este cuadro. Debe estar
entre 0 y 1.
- includeSpectogram: Verdadero
– Si es verdadero, el espectrograma se calculará y estará disponible como un tensor.
Establézcalo en falso si la aplicación no necesita calcular el espectrograma. Esto
puede suceder solo si se necesita la forma de onda.
- incluir forma de onda: verdadero

– Si es verdadero, la forma de onda se mantiene y está disponible como tensor. Esto se puede establecer en falso

si la persona que llama no necesitará la forma de onda. Tenga en cuenta que al menos uno de
incluirespectrogramayincluir forma de ondadebe ser cierto Es un error si ellos
ambos son falsos. Aquí los hemos configurado como verdaderos para mostrar que esta es una
opción válida, pero en una aplicación típica, solo será necesario uno de los dos.

Al igual que la transmisión de video, la transmisión de audio a veces tarda un poco en comenzar y,
para empezar, los datos del dispositivo pueden no tener sentido. Los ceros y los infinitos se
encuentran comúnmente, pero los valores reales y las duraciones dependen de la plataforma. La
mejor solución es “calentar” el micrófono durante un breve período de tiempo desechando las
primeras muestras hasta que los datos ya no estén dañados. Por lo general, 200 ms de datos son
suficientes para comenzar a obtener muestras limpias.

6.4 Es probable que sus datos tengan fallas: Cómo lidiar con problemas en sus datos
Es casi una garantía de que hay problemas con sus datos sin procesar. Si está utilizando su
propia fuente de datos y no ha pasado varias horas con un experto analizando las
características individuales, sus distribuciones y sus correlaciones, entonces existe una gran
posibilidad de que haya fallas que se debiliten o rompan. su modelo de aprendizaje
automático. Nosotros, los autores de este libro, podemos decir esto con confianza debido a
nuestra experiencia como mentores de la construcción de muchos sistemas de aprendizaje
automático en muchos dominios y construyendo algunos nosotros mismos. El síntoma más
común es que algún modelo no converge, o lo hace con una precisión muy por debajo de lo
esperado. Otro patrón relacionado pero aún más nefasto y difícil de depurar es cuando el
Es probable que sus datos tengan fallas: Cómo lidiar con problemas en sus datos 231

El modelo converge y funciona bien en los datos de validación y prueba, pero luego no cumple con
las expectativas en producción. A veces hay un problema de modelado genuino, un
hiperparámetro incorrecto o simplemente mala suerte, pero, con mucho, la causa raíz más común
de estos errores es que hay una falla en los datos.
Detrás de escena, todos los conjuntos de datos que hemos usado (como MNIST, flores de iris y
comandos de voz) pasaron por inspección manual, eliminación de ejemplos incorrectos, formateo en un
formato estándar y adecuado, y otras operaciones de ciencia de datos que realizamos. no habló. Los
problemas de datos pueden surgir de muchas formas, incluidos campos faltantes, muestras
correlacionadas y distribuciones sesgadas. Existe tal riqueza y diversidad de complejidad en el trabajo con
datos, que alguien podría escribir un libro al respecto. De hecho, por favor vea Gestión de datos con
JavaScriptpor Ashley Davis para una exposición más completa!10
Los científicos de datos y los administradores de datos se han convertido en roles profesionales de tiempo
completo en muchas empresas. Las herramientas que utilizan estos profesionales y las mejores prácticas que
siguen son diversas y, a menudo, dependen del dominio específico bajo escrutinio. En esta sección, tocaremos los
conceptos básicos y señalaremos algunas herramientas para ayudarlo a evitar la angustia de las largas sesiones
de depuración del modelo solo para descubrir que eran los datos en sí los que tenían fallas. Para un tratamiento
más completo de la ciencia de datos, ofreceremos referencias donde puede obtener más información.

6.4.1 Teoría de datos


Para saber cómo detectar y corregirmalodatos, primero debemos saber québienlos datos parecen. Gran
parte de la teoría que sustenta el campo del aprendizaje automático se basa en la premisa de que
nuestros datos provienen de unDistribución de probabilidad. En esta formulación, nuestros datos de
entrenamiento consisten en una colección de datos independientesmuestras. Cada muestra se describe
como un (X,y) par, dondeyes la parte de la muestra que deseamos predecir a partir de laXparte.
Continuando con esta premisa, nuestros datos de inferencia consisten en una colección de muestrasde la
misma distribución exacta que nuestros datos de entrenamiento. La única diferencia importante entre los
datos de entrenamiento y los datos de inferencia es que, en el momento de la inferencia, no podemos ver
y. Se supone que debemos estimar elyparte de la muestra de laXparte usando las relaciones estadísticas
aprendidas de los datos de entrenamiento.
Hay varias formas en que nuestros datos de la vida real pueden no estar a la altura de este ideal platónico. Si,
por ejemplo, nuestros datos de entrenamiento y datos de inferencia son muestras dediferente distribuciones,
decimos que hay un conjunto de datossesgar. Como un ejemplo simple, si está estimando el tráfico vial en
función de características como el clima y la hora del día, y todos sus datos de entrenamiento provienen de los
lunes y martes, mientras que los datos de prueba provienen de los sábados y domingos, puede esperar que la
precisión del modelo aumente. ser menos que óptimo. La distribución del tráfico de automóviles entre semana
no es la misma que la distribución del tráfico los fines de semana. Como otro ejemplo, imagina que estamos
construyendo un sistema de reconocimiento facial y entrenamos el sistema para que reconozca rostros en
función de una colección de datos etiquetados de nuestro país de origen. No deberíamos sorprendernos al
encontrar que el sistema se esfuerza y falla cuando se usa en

10 Disponible en Publicaciones de Manning,www.manning.com/books/data-wrangling-with-javascript.


232 CPASADO6Trabajar con datos

lugares con diferentes datos demográficos. La mayoría de los problemas de sesgo de datos que encontrará en
entornos reales de aprendizaje automático serán más sutiles que estos dos ejemplos.
Otra forma en que el sesgo puede infiltrarse en un conjunto de datos es si hubo algún cambio durante la recopilación
de datos. Si, por ejemplo, estamos tomando muestras de audio para aprender las señales del habla, y luego, a la mitad de
la construcción de nuestro conjunto de entrenamiento, nuestro micrófono se rompe, por lo que compramos una
actualización, podemos esperar que la segunda mitad de nuestro conjunto de entrenamiento tenga un diferente ruido y
distribución de audio que nuestra primera mitad. Presumiblemente, en el momento de la inferencia, estaremos probando
usando solo el nuevo micrófono, por lo que también existe un sesgo entre el conjunto de entrenamiento y el de prueba.

En algún nivel, el sesgo del conjunto de datos es inevitable. Para muchas aplicaciones, nuestros datos de
entrenamiento provienen necesariamente del pasado, y los datos que pasamos a nuestra aplicación
necesariamente provienen del presente. La distribución subyacente que produce estas muestras está destinada a
cambiar a medida que las culturas, los intereses, las modas y otros factores de confusión cambian con los
tiempos. En tal situación, todo lo que podemos hacer es comprender el sesgo y minimizar el impacto. Por esta
razón, muchos modelos de aprendizaje automático en entornos de producción se vuelven a entrenar
constantemente utilizando los datos de entrenamiento más recientes disponibles en un intento de mantenerse al
día con las distribuciones en constante cambio.
Otra forma en que nuestras muestras de datos pueden no estar a la altura del ideal es al no ser
independientes. Nuestro ideal establece que las muestras sonindependientes e idénticamente distribuidas (IID).
Pero en algunos conjuntos de datos, una muestra da pistas sobre el valor probable de la siguiente. Las muestras
de estos conjuntos de datos no son independientes. La forma más común en que la dependencia de muestra a
muestra se cuela en un conjunto de datos es mediante el fenómeno de clasificación. Por la velocidad de acceso y
todo tipo de otras buenas razones, hemos sido capacitados como científicos informáticos para organizar
nuestros datos. De hecho, los sistemas de bases de datos a menudo organizan nuestros datos sin que lo
intentemos. Como resultado, cuando transmite sus datos desde alguna fuente, debe tener mucho cuidado de
que los resultados no tengan algún patrón en su orden.
Considere la siguiente hipótesis. Deseamos construir una estimación del costo de la vivienda en
California para una aplicación en bienes raíces. Obtenemos un conjunto de datos CSV de precios de la
vivienda11de todo el estado, junto con características relevantes, como la cantidad de habitaciones, la
antigüedad del desarrollo, etc. Podríamos sentirnos tentados a simplemente comenzar a entrenar una
función desde las características hasta el precio de inmediato, ya que tenemos los datos y sabemos cómo
hacerlo. Pero sabiendo que los datos a menudo tienen fallas, decidimos echar un vistazo primero.
Comenzamos trazando algunas características frente a su índice en la matriz, utilizando conjuntos de
datos y Plotly.js. Vea las gráficas en la figura 6.3 para una ilustración12y el siguiente listado (resumido de
https://codepen.io/tfjs-book/pen/MLQOem) sobre cómo se hicieron las ilustraciones.

11Una descripción del conjunto de datos de viviendas de California utilizado aquí está disponible en el Curso acelerado de aprendizaje automático
enhttp://mng.bz/Xpm6.
12Los gráficos de la figura 6.3 se realizaron con CodePen enhttps://codepen.io/tfjs-book/pen/MLQOem.
Es probable que sus datos tengan fallas: Cómo lidiar con problemas en sus datos 233

Listado 6.20 Construyendo un gráfico de una característica vs. índice usando tfjs-data

const plottingData = {
X: [],
y: [],
modo: 'marcadores',
escribe: 'dispersión',
marcador: {símbolo: 'círculo', tamaño: 8}
};
const nombre de archivo = 'https://storage.googleapis.com/learnjs-data/csv-
conjuntos de datos/california_housing_train.csv'; conjunto
de datos const = tf.data.csv(nombre de archivo); esperar
dataset.take(1000).forEachAsync(fila => {
toma el primero1,000
plottingData.x.push(i++);
muestras y recoge sus
plottingData.y.push(fila['longitud']); });
valores y sus índices. No
olvides esperar, o tu trama
(probablemente) estar vacío!
Plotly.newPlot('plot', [plottingData], {
ancho: 700,
title: 'Característica de longitud frente a índice de muestra',
xaxis: {título: 'índice de muestra'},
eje y: {título: 'longitud'} });

Imagine que tuviéramos que construir una división de prueba de tren con este conjunto de
datos donde tomamos las primeras 500 muestras para entrenamiento y el resto para prueba.
¿Qué pasaría? A partir de este análisis, parece que estaríamos entrenando con datos de un
área geográfica y probando con datos de otra. El panel Longitud en la figura 6.3 muestra el
quid del problema: las primeras muestras son de una longitud más alta (más al oeste) que
cualquiera de las otras. Probablemente todavía hay mucha señal en las características, y el
modelo "funcionaría" un poco, pero no sería tan preciso o de alta calidad como si nuestros
datos fueran realmente IID. Si no lo supiéramos mejor, podríamos pasar días o semanas
jugando con diferentes modelos e hiperparámetros antes de descubrir qué estaba mal y
mirar nuestros datos.
¿Qué podemos hacer para limpiar esto? Solucionar este problema en particular es bastante simple.
Para eliminar la relación entre los datos y el índice, podemos mezclar nuestros datos en un orden
aleatorio. Sin embargo, hay algo que debemos tener en cuenta aquí. Los conjuntos de datos de
TensorFlow.js tienen una rutina de reproducción aleatoria incorporada, pero es unventana de transmisión
rutina aleatoria. Esto significa que las muestras se barajan aleatoriamente dentro de una ventana de
tamaño fijo, pero no más allá. Esto es necesario porque los conjuntos de datos de TensorFlow.js
transmiten datos y pueden transmitir una cantidad ilimitada de muestras. Para mezclar completamente
una fuente de datos interminable, primero debe esperar hasta que termine.
Entonces, ¿podemos arreglárnoslas con esta ventana de transmisión aleatoria para nuestra función de
longitud? Ciertamente, si conocemos el tamaño de los conjuntos de datos (17 000 en este caso), podemos
especificar que la ventana sea más grande que el conjunto de datos completo, y estamos listos. En el límite de
tamaños de ventana muy grandes, el barajado en ventana y nuestro barajado exhaustivo normal son idénticos. Si
no sabemos qué tan grande es nuestro conjunto de datos, o si el tamaño es prohibitivamente grande (es decir,
no podemos almacenar todo de una vez en un caché de memoria), es posible que tengamos que arreglárnoslas
con menos.
234 CPASADO6Trabajar con datos

Figura 6.3 Gráficos de cuatro características del conjunto de datos frente al índice de muestra. Idealmente, en un conjunto de datos IID limpio,
esperaríamos que el índice de muestra no nos proporcione información sobre el valor de la característica. Vemos que para algunas características,
la distribución de los valores de y depende claramente de x. Lo más notorio es que la característica de "longitud" parece estar ordenada por el
índice de muestra.

Figura 6.4, creada conhttps://codepen.io/tfjs-book/pen/JxpMrj, ilustra lo que sucede cuando


mezclamos nuestros datos con cuatro tamaños de ventana diferentes usandotf.datos
. Conjunto de datos'sbarajar()método:

for (let windowSize of [10, 50, 250, 6000]) {


shuffledDataset =dataset.shuffle(ventanaTamaño);
myPlot(shuffledDataset, windowSize)
}

Vemos que la relación estructural entre el índice y el valor de la característica permanece clara incluso
para tamaños de ventana relativamente grandes. No es hasta que el tamaño de la ventana es de 6000
que parece a simple vista que los datos ahora se pueden tratar como IID. Entonces, ¿6000 es el tamaño de
ventana correcto? ¿Había algún número entre 250 y 6000 que hubiera funcionado? ¿Sigue siendo 6000
insuficiente para detectar los problemas de distribución que no vemos en estas ilustraciones? El enfoque
correcto aquí es barajar todo el conjunto de datos usando un tamaño de ventana >=el número de muestras
en el conjunto de datos. Para conjuntos de datos donde esto no es posible debido a limitaciones de
memoria, restricciones de tiempo o conjuntos de datos posiblemente ilimitados, debe ponerse su
sombrero de científico de datos y examinar la distribución para determinar un tamaño de ventana
apropiado.
Es probable que sus datos tengan fallas: Cómo lidiar con problemas en sus datos 235

Figura 6.4 Cuatro gráficos de longitud frente al índice de muestra para cuatro conjuntos de datos barajados. El tamaño de la ventana de
reproducción aleatoria es diferente para cada uno, aumentando de 10 a 6000 muestras. Vemos que incluso con un tamaño de ventana de
250, todavía existe una fuerte relación entre el índice y el valor de la característica. Hay más valores grandes cerca del principio. No es hasta
que usamos un tamaño de ventana aleatoria casi tan grande como el conjunto de datos que la naturaleza IID de los datos casi se restaura.

6.4.2 Detección y limpieza de problemas con datos


En la sección anterior, analizamos cómo detectar y solucionar un tipo de problema de datos:
dependencia de muestra a muestra. Por supuesto, este es solo uno de los muchos tipos de
problemas que pueden surgir en los datos. Un tratamiento completo de todos los tipos de cosas
que pueden salir mal está mucho más allá del alcance de este libro, porque hay tantas cosas que
pueden salir mal con los datos como cosas que pueden salir mal con el código. Sin embargo,
repasemos algunos aquí, para que reconozca los problemas cuando los vea y sepa qué términos
buscar para encontrar más información.

OÚLTIMO
Los valores atípicos son muestras en nuestro conjunto de datos que son muy inusuales y de alguna manera no
pertenecen a la distribución subyacente. Por ejemplo, si trabajáramos con un conjunto de datos de estadísticas
de salud, podríamos esperar que el peso típico de un adulto estuviera entre 40 y 130 kilogramos
aproximadamente. Si, en nuestro conjunto de datos, el 99,9 % de nuestras muestras estuvieran en este rango,
pero de vez en cuando encontramos un informe de muestra sin sentido de 145 000 kg, o 0 kg, o peor, NaN,13
consideraríamos estas muestras como valores atípicos. Una búsqueda rápida en línea revela que hay muchas
opiniones sobre la forma correcta de tratar con los valores atípicos. Idealmente, tendríamos muy pocos valores
atípicos en nuestros datos de entrenamiento y sabríamos cómo encontrar

13 Ingerir un valor de NaN en nuestras características de entrada propagaría ese NaN a lo largo de nuestro modelo.
236 CPASADO6Trabajar con datos

ellos. Si pudiéramos escribir un programa para rechazar los valores atípicos, podríamos eliminarlos de
nuestro conjunto de datos y seguir entrenando sin ellos. Por supuesto, también nos gustaría activar esa
misma lógica en el momento de la inferencia; de lo contrario introduciríamos sesgo. En este caso,
podríamos usar la misma lógica para informar al usuario que su muestra constituye un valor atípico para
el sistema y que debe probar algo diferente.
Otra forma común de lidiar con los valores atípicos en el nivel de características es fijar los
valores al proporcionar un mínimo y un máximo razonables. En nuestro caso, podríamos
reemplazar el peso con

peso = Math.min(MAX_WEIGHT, Math.max(weight, MIN_WEIGHT));

En tales circunstancias, también es una buena idea agregar una nueva característica que
indique que el valor atípico ha sido reemplazado. De esta forma, se puede distinguir un valor
original de 40 kg de un valor de –5 kg que se fijó en 40 kg, lo que brinda a la red la
oportunidad de conocer la relación entre el estado de valor atípico y el objetivo, si existe tal
relación:

isOutlierWeight = peso > MAX_WEIGHT | peso < PESO_MIN;

METRODATOS EMITIDOS

Con frecuencia, nos enfrentamos a situaciones en las que algunas muestras carecen de algunas
funciones. Esto puede suceder por varias razones. A veces, los datos provienen de formularios ingresados
a mano y algunos campos simplemente se omiten. A veces, los sensores se rompieron o fallaron en el
momento de la recopilación de datos. Para algunas muestras, quizás algunas características simplemente
no tengan sentido. Por ejemplo, ¿cuál es el precio de venta más reciente de una casa que nunca se ha
vendido? ¿O cuál es el número de teléfono de una persona sin teléfono?
Al igual que con los valores atípicos, hay muchas formas de abordar el problema de los
datos faltantes, y los científicos de datos tienen diferentes opiniones sobre qué técnicas son
apropiadas en qué situaciones. La mejor técnica depende de algunas consideraciones, entre
ellas, si la probabilidad de que falte la función depende del valor de la función en sí o si la
"ausencia" se puede predecir a partir de otras funciones de la muestra. El cuadro de
información 6.3 describe un glosario de categorías de datos faltantes.

ICAJA NFO6.3 Categorías de datos faltantes


Falta al azar (MAR):

- La probabilidad de que falte la característica no depende del valor perdido


oculto, pero puede depender de algún otro valor observado.
- Ejemplo: si tuviéramos un sistema visual automatizado que registrara el tráfico de
automóviles, podría registrar, entre otras cosas, los números de matrícula y la hora
del día. A veces, si está oscuro, no podemos leer la matrícula. La presencia de la placa
no depende del valor de la placa, pero puede depender de la característica de la hora
del día (observada).
Es probable que sus datos tengan fallas: Cómo lidiar con problemas en sus datos 237

Falta completamente al azar (MCAR)

- La probabilidad de que falte la característica no depende del valor perdido


oculto ni de ninguno de los valores observados.
- Ejemplo: los rayos cósmicos interfieren con nuestro equipo y, a veces, corrompen los
valores de nuestro conjunto de datos. La probabilidad de corrupción no depende del valor
almacenado ni de otros valores en el conjunto de datos.

Falta no al azar (MNAR)


- La probabilidad de que falte la característica depende del valor oculto, dados los
datos observados.
- Ejemplo: una estación meteorológica personal realiza un seguimiento de todo tipo de
estadísticas, como la presión del aire, la lluvia y la radiación solar. Sin embargo, cuando nieva, el
medidor de radiación solar no toma señal.

Cuando faltan datos en nuestro conjunto de entrenamiento, tenemos que aplicar algunas
correcciones para poder convertir los datos en un tensor de forma fija, que requiere un valor en
cada celda. Hay cuatro técnicas importantes para tratar con los datos que faltan.
La técnica más simple, si los datos de entrenamiento son abundantes y los campos que faltan
son raros, es descartar las muestras de entrenamiento que tienen datos que faltan. Sin embargo,
tenga en cuenta que esto puede introducir un sesgo en su modelo entrenado. Para ver esto
claramente, imagine un problema en el que faltan datos mucho más comúnmente de la clase
positiva que de la clase negativa. Terminarías aprendiendo una probabilidad incorrecta de las
clases. Solo si sus datos faltantes son MCAR, puede descartar muestras con total seguridad.

Listado 6.21 Manejo de características faltantes al eliminar los datos

const filteredDataset = Mantiene solo aquellos elementos cuyo valor de


tf.data.csv(csvNombre de archivo) 'featureName' es verdadero: es decir, no 0, nulo,
. filter(e => e['featureName']); indefinido, NaN o una cadena vacía

Otra técnica para manejar los datos que faltan es completar los datos que faltan con algún valor,
también conocido comoimputación. Las técnicas de imputación comunes incluyen reemplazar los
valores de características numéricas que faltan con el valor medio, mediano o de moda de esa
característica. Las características categóricas faltantes se pueden reemplazar con el valor más
común para esa característica (también modo). Las técnicas más sofisticadas involucran la
construcción de predictores para las características que faltan a partir de las características
disponibles y su uso. De hecho, el uso de redes neuronales es una de las “técnicas sofisticadas”
para la imputación de datos faltantes. La desventaja de usar la imputación es que el alumno no es
consciente de que faltaba la función. Si hay información en la falta sobre la variable objetivo, se
perderá en la imputación.
238 CPASADO6Trabajar con datos

Listado 6.22 Manejo de características faltantes con imputación

Función para calcular el valor a utilizar


para la imputación. Recuerde incluir solo
valores válidos al calcular la media.

función asíncrona calcularMeanOfNonMissing(


dataset, featureName) { let
samplesSoFar = 0; sea Aquí se considera que faltan valores
sumaSoFar = 0; indefinidos y nulos. Algunos conjuntos de
esperar dataset.forEachAsync(fila => { datos pueden usar valores centinela como
const x = fila[nombre de la función]; – 1o 0 para indicar ausencia.
si (x != nulo) { ¡Asegúrate de mirar tus datos!
muestras hasta ahora + = 1;
sumahastaahora += X;
}
Tenga en cuenta que esto devolverá
});
NaN cuando falten todos los datos.
return sumSoFar / samplesSoFar;
}
función reemplazarMissingWithImputed(
fila, nombre de característica, valor imputado)) Función para actualizar
condicionalmente una fila si
{ const x = fila [nombre de característica];
falta el valor en featureName
si (x == nulo) {
return {...row, [featureName]: valorimputado}; demás {
}
fila de retorno;
Utiliza el método tf.data.Dataset
}
map() para mapear el reemplazo
} sobre todos los elementos

const rawDataset tf.data.csv(csvNombre de archivo);


const imputadoValor = esperar calcularMeanOfNonMissing(
rawDataset, 'miCaracterística'); const
imputadoDataset = rawDataset.map(
fila => reemplazarMissingWithImputed(row,
'myFeature', imputatedValue));

A veces, los valores que faltan se reemplazan con unvalor centinela. Por ejemplo, un valor de peso
corporal faltante podría reemplazarse con un -1, lo que indica que no se tomó ningún peso. Si este
parece ser el caso con sus datos, tenga cuidado de manejar el valor centinelaantes de sujetándolo
como un valor atípico (por ejemplo, en base a nuestro ejemplo anterior, reemplazando este -1 con
40 kg).
Posiblemente, si existe una relación entre la falta de la característica y el objetivo que
se va a predecir, el modelo puede utilizar el valor centinela. En la práctica, el modelo
gastará algunos de sus recursos computacionales aprendiendo a distinguir cuándo la
característica se usa como valor y cuándo se usa como indicador.
Quizás la forma más sólida de administrar los datos faltantes es usar la imputación para
completar un valor y agregar una segunda función de indicador para comunicar al modelo cuándo
falta esa función. En este caso, reemplazaríamos el peso corporal faltante con una conjetura y
también agregaríamos una nueva característicapeso_perdido,que es 1 cuando faltaba el peso y 0
cuando se proporcionó. Esto permite que el modelo aproveche la falta, si es valiosa, y también que
no la confunda con el valor real del peso.
Es probable que sus datos tengan fallas: Cómo lidiar con problemas en sus datos 239

Listado 6.23 Agregando una función para indicar ausencia

función añadirMissingness(fila, nombre de característica)) {


Función para agregar una
const x = fila[nombre de la función];
nueva característica a cada
const falta = (x == nulo) ? 1 : 0;
fila, que es1si falta la función
return {...row, [featureName + '_isMissing']: isMissing};
y 0 en caso contrario
}

const rawDataset tf.data.csv(csvFilename); const Utiliza el método tf.data.Dataset


datasetWithIndicator = rawDataset.map( map() para asignar la característica
(fila) => addMissingness(fila, nombre de característica); adicional a cada fila

SKEW
Anteriormente en este capítulo, describimos el concepto de sesgo, una diferencia en la distribución de un
conjunto de datos a otro. Es uno de los principales problemas que enfrentan los profesionales del aprendizaje
automático cuando implementan modelos entrenados en producción. Detectar sesgos implica modelar las
distribuciones de los conjuntos de datos y compararlos para ver si coinciden. Una forma sencilla de ver
rápidamente las estadísticas de su conjunto de datos es usar una herramienta como Facetas (https://
paircode.github.io/facets/). Consulte la figura 6.5 para ver una captura de pantalla. Las facetas analizarán y
resumirán sus conjuntos de datos para permitirle ver las distribuciones por característica, lo que lo ayudará a
detectar rápidamente problemas con diferentes distribuciones entre sus conjuntos de datos.

Figura 6.5 Una captura de pantalla de Facetas que muestra las distribuciones de valores por función para la división de
entrenamiento y prueba de los conjuntos de datos de ingresos del censo de UC Irvine (verhttp://archive.ics.uci.edu/ml/datasets/
Census+ Ingresos). Este conjunto de datos es el predeterminado cargado enhttps://pair-code.github.io/facets/, pero puede
navegar al sitio y cargar sus propios CSV para comparar. Esta vista se conoce como Vista general de facetas.
240 CPASADO6Trabajar con datos

Un algoritmo de detección de sesgo simple y rudimentario puede calcular la media, la mediana y la


varianza de cada característica y verificar si las diferencias entre los conjuntos de datos están
dentro de los límites aceptables. Los métodos más sofisticados pueden intentar predecir, dadas las
muestras, de qué conjunto de datos provienen. Idealmente, esto no debería ser posible ya que son
de la misma distribución. Si es posible predecir si un punto de datos es de entrenamiento o prueba,
esto es un signo de sesgo.

BCADENAS DE ANUNCIOS

Muy comúnmente, los datos categóricos se proporcionan como características con valores de cadena. Por
ejemplo, cuando los usuarios acceden a su página web, puede mantener registros de qué navegador se
utilizó con valores comoFIREFOX, SAFARI,yCROMO.Por lo general, antes de incorporar estos valores en un
modelo de aprendizaje profundo, los valores se convierten en números enteros (ya sea a través de un
vocabulario conocido o mediante hash), que luego se asignan a unnorte-espacio vectorial dimensional
(Ver sección 9.2.3 sobre incrustaciones de palabras). Un problema común es cuando las cadenas de un
conjunto de datos tienen un formato diferente al de las cadenas en un conjunto de datos diferente. Por
ejemplo, los datos de entrenamiento pueden tenerFIREFOX,mientras que en el momento del servicio, el
modelo recibeFIREFOX\n,con el carácter de nueva línea incluido, o "FIREFOX",con comillas Esta es una
forma particularmente insidiosa de sesgo y debe manejarse como tal.

OOTRAS COSAS A TENER EN CUENTA EN SUS DATOS


Además de los problemas mencionados en las secciones anteriores, aquí hay algunas cosas más que
debe tener en cuenta al alimentar sus datos a un sistema de aprendizaje automático:

- Datos demasiado desequilibrados—Si hay algunas características que toman el mismo valor para
casi todas las muestras en su conjunto de datos, puede considerar deshacerse de ellas. Es muy
fácil sobreajustarse con este tipo de señal, y los métodos de aprendizaje profundo no manejan
bien los datos muy dispersos.
- Distinción numérica/categórica—Algunos conjuntos de datos usarán números enteros para
representar elementos de un conjunto enumerado, y esto puede causar problemas cuando el
orden de clasificación de estos números enteros no tiene sentido. Por ejemplo, si tenemos un
conjunto enumerado de géneros musicales, comoROCA CLÁSICA,y así sucesivamente, y un
vocabulario que asignó estos valores a números enteros, es importante que manejemos los
valores como valores enumerados cuando los pasemos al modelo. Esto significa codificar los
valores utilizando one-hot o incrustación (consulte el capítulo 9). De lo contrario, estos números se
interpretarán como valores de punto flotante, lo que sugiere relaciones espurias entre términos
en función de la distancia numérica entre sus codificaciones.
- Grandes diferencias de escala—Esto se mencionó anteriormente, pero vale la pena repetirlo en
esta sección sobre lo que puede salir mal con los datos. Tenga cuidado con las características
numéricas que tienen diferencias a gran escala. Pueden conducir a la inestabilidad en el
entrenamiento. En general, es mejor normalizar z (normalizar la media y la desviación estándar
de) sus datos antes del entrenamiento. Solo asegúrese de usar el mismo preprocesamiento en el
momento del servicio que usó durante el entrenamiento. Puede ver un ejemplo de esto en el
ejemplo de iris de tensorflow/tfjs-examples, como exploramos en el capítulo 3.
- Sesgo, seguridad y privacidad—Obviamente, hay mucho más en el desarrollo del aprendizaje
automático responsable de lo que se puede cubrir en un capítulo de libro. Es crítico, si Ud.
Es probable que sus datos tengan fallas: Cómo lidiar con problemas en sus datos 241

están desarrollando soluciones de aprendizaje automático, que dedique tiempo a familiarizarse


con al menos los conceptos básicos de las mejores prácticas para administrar el sesgo, la
seguridad y la privacidad. Un buen lugar para comenzar es la página sobre prácticas responsables
de IA en https://ai.google/education/responsible-ai-practices. Seguir estas prácticas es lo correcto
para ser una buena persona y un ingeniero responsable, objetivos obviamente importantes en sí
mismos. Además, prestar mucha atención a estos problemas es una buena elección desde una
perspectiva puramente egoísta, ya que incluso las pequeñas fallas de sesgo, seguridad o
privacidad pueden conducir a fallas sistémicas vergonzosas que rápidamente llevan a los clientes
a buscar soluciones más confiables en otros lugares.

En general, debe dedicar tiempo a convencerse de que sus datos son como espera que sean. Hay muchas
herramientas para ayudarlo a hacer esto, desde cuadernos como Observable, Jupyter, Kaggle Kernel y
Colab, hasta herramientas gráficas de interfaz de usuario como Facets. Ver figura 6.6

Figura 6.6 Otra captura de pantalla de Facets, esta vez explorando el conjunto de datos de campus del estado de Nueva
York del ejemplo data-csv. Aquí, vemos la vista Inmersión de facetas que le permite explorar las relaciones entre las
diferentes características de un conjunto de datos. Cada punto que se muestra es un punto de datos del conjunto de
datos, y aquí lo tenemos configurado para que la posición x del punto se establezca en la función Latitude1, la posición y
es la función Longitude1, el color está relacionado con la función de inscripción de pregrado, y las palabras en el anverso
están configuradas en la función Ciudad, que contiene, para cada punto de datos, el nombre de la ciudad en la que se
encuentra el campus universitario. Podemos ver en esta visualización un contorno aproximado del estado de Nueva York,
con Buffalo en el oeste y Nueva York en el sureste. Aparentemente,
242 CPASADO6Trabajar con datos

para conocer otra forma de explorar sus datos en Facetas. Aquí, usamos la función de trazado de
facetas, conocida como Facets Dive, para ver puntos del conjunto de datos de las Universidades
Estatales de Nueva York (SUNY). Facets Dive permite al usuario seleccionar columnas de los datos y
expresar visualmente cada campo de forma personalizada. Aquí, hemos usado los menús
desplegables para usar el campo Longitude1 como la posición x del punto, el campo Latitude1
como la posición y del punto, el campo de cadena Ciudad como el nombre del punto y el campo
Inscripción de pregrado como el color del punto. Esperamos que la latitud y la longitud, trazadas
en el plano 2D, revelen un mapa del estado de Nueva York y, de hecho, eso es lo que vemos. La
corrección del mapa se puede verificar comparándolo con la página web de SUNY enwww.suny.
edu/attend/visit-us/campus-map/.

6.5 Aumento de datos


Hemos recopilado nuestros datos, los hemos conectado a untf.data.Conjunto de datospara una fácil
manipulación, y lo hemos examinado y limpiado de problemas. ¿Qué más podemos hacer para ayudar a
que nuestro modelo tenga éxito?
A veces, los datos que tiene no son suficientes y desea expandir el conjunto de datos mediante programación,
creando nuevos ejemplos haciendo pequeños cambios en los datos existentes. Por ejemplo, recuerde el
problema de clasificación de dígitos escritos a mano del MNIST del capítulo 4. El MNIST contiene 60 000 imágenes
de entrenamiento de 10 dígitos escritos a mano, o 6000 por dígito. ¿Es eso suficiente para aprender todos los
tipos de flexibilidad que queremos para nuestro clasificador de dígitos? ¿Qué sucede si alguien dibuja un dígito
demasiado grande o pequeño? O girado ligeramente? ¿O sesgada? ¿O con un bolígrafo más grueso o más
delgado? ¿Seguirá entendiendo nuestro modelo?
Si tomamos un dígito de muestra del MNIST y alteramos la imagen moviendo el dígito un píxel a la
izquierda, la etiqueta semántica del dígito no cambia. El 9 desplazado a la izquierda sigue siendo un 9,
pero tenemos un nuevo ejemplo de entrenamiento. Este tipo de ejemplo generado mediante
programación, creado a partir de la mutación de un ejemplo real, se conoce comopseudo-ejemplo, y el
proceso de agregar pseudo-ejemplos a los datos se conoce comoaumento de datos.
El aumento de datos adopta el enfoque de generar más datos de entrenamiento a partir de muestras
de entrenamiento existentes. En el caso de los datos de imagen, varias transformaciones, como rotar,
recortar y escalar, a menudo producen imágenes de aspecto creíble. El propósito es aumentar la
diversidad de los datos de entrenamiento para beneficiar el poder de generalización del modelo
entrenado (en otras palabras, para mitigar el sobreajuste), lo cual es especialmente útil cuando el tamaño
del conjunto de datos de entrenamiento es pequeño.
La Figura 6.7 muestra el aumento de datos aplicado a un ejemplo de entrada que consiste en una
imagen de un gato, de un conjunto de datos de imágenes etiquetadas. Los datos se aumentan aplicando
rotaciones y sesgos de tal manera que la etiqueta del ejemplo, es decir, "CAT", no cambia, pero el ejemplo
de entrada cambia significativamente.
Si entrena una nueva red con esta configuración de aumento de datos, la red nunca verá la misma
entrada dos veces. Pero las entradas que ve todavía están fuertemente interrelacionadas porque
provienen de una pequeña cantidad de imágenes originales: no puede producir información nueva, solo
puede mezclar la información existente. Como tal, esto puede no ser suficiente
Aumento de datos 243

Figura 6.7 Generación de imágenes de gatos mediante aumento de datos aleatorios. Un solo ejemplo
etiquetado puede generar toda una familia de muestras de entrenamiento al proporcionar rotaciones,
reflejos, traslaciones y sesgos aleatorios. Maullar.

para deshacerse por completo del sobreajuste. Otro riesgo de usar el aumento de datos es que
ahora es menos probable que los datos de entrenamiento coincidan con la distribución de los
datos de inferencia, lo que introduce sesgo. Si los beneficios de los pseudoejemplos de
entrenamiento adicionales superan los costos del sesgo depende de la aplicación, y es algo que tal
vez necesite probar y experimentar.
El listado 6.24 muestra cómo puede incluir el aumento de datos como unconjunto de
datos.mapa() función, inyectando transformaciones permitidas en su conjunto de datos. Tenga en
cuenta que el aumento debe aplicarse por ejemplo. También es importante ver que el aumento
debenoaplicarse al conjunto de validación o prueba. Si probamos con datos aumentados,
tendremos una medida sesgada del poder de nuestro modelo porque los aumentos no se
aplicarán en el momento de la inferencia.
244 CPASADO6Trabajar con datos

Listado 6.24 Entrenamiento de un modelo en un conjunto de datos con aumento de datos

La función de aumento toma una muestra en formato Suponga que randomRotate, randomSkew
{image, label} y devuelve una nueva muestra perturbada y randomMirror están definidos en otra
en el mismo formato. parte por alguna biblioteca. La cantidad
para rotar, sesgar, etc. se genera
funciónaumentarFn(muestra) { aleatoriamente para cada llamada. El

const img = muestra.imagen; const aumento debe depender solo de las


características, no de la etiqueta de la
imgaumentada = rotaciónaleatoria(
muestra.
randomSkew(randomMirror(img))));
return {imagen: img.aumentada, etiqueta: muestra.etiqueta};
} Esta función devuelve dos
tf.data.Datasets, cada uno con el
const (conjunto de datos de entrenamiento, conjunto de datos de validación} =
tipo de elemento {image, label}.
getDatsetsFromSource();
conjunto de datos aumentado = conjunto de datos de entrenamiento

. repetir().mapa (aumentoFn).lote(TAMAÑO_LOTE); el aumento es


aplicado a los elementos
// modelo de tren
individuales antes de la dosificación.
esperar model.fitDataset(augmentedDataset, {
Nos ajustamos a lotesPerEpoch: ui.getBatchesPerEpoch(), épocas:
modelo en el ui.getEpochsToTrain(),
aumentado datos de validación: conjunto de datos de
conjunto de datos
validación.repetir (), lotes de validación: 10,
¡IMPORTANTE! No aplique aumento a
devoluciones de llamada: { ... }, }
el conjunto de validación. Se llama a la repetición en la
validación de datos aquí ya que los datos no se repetirán
} automáticamente. Solamente1Se toman 0 lotes por
medición de validación, según lo configurado.

Con suerte, este capítulo lo convenció de la importancia de comprender sus datos antes de
lanzarles modelos de aprendizaje automático. Hablamos sobre herramientas listas para usar, como
Facetas, que puede usar para examinar sus conjuntos de datos y, por lo tanto, profundizar su
comprensión de ellos. Sin embargo, cuando necesita una visualización más flexible y personalizada
de sus datos, se vuelve necesario escribir código para hacer ese trabajo. En el próximo capítulo, le
enseñaremos los conceptos básicos de tfjs-vis, un módulo de visualización mantenido por los
autores de TensorFlow.js que puede admitir tales casos de uso de visualización de datos.

Ejercicios
1 Extienda el ejemplo de detección de objetos simples del capítulo 5 para usartf.datos .
generador()ymodelo.fitDataset()en lugar de generar el conjunto de datos completo por
adelantado. ¿Qué ventajas tiene esta estructura? ¿Mejora significativamente el rendimiento
si se proporciona al modelo un conjunto de datos de imágenes mucho más grande para
entrenar?
2 Agregue aumento de datos al ejemplo MNIST agregando pequeños cambios,
escalas y rotaciones a los ejemplos. ¿Esto ayuda en el rendimiento? ¿Tiene sentido
validar y probar el flujo de datos con aumento, o es más adecuado probar solo en
ejemplos naturales "reales"?
Resumen 245

3 Trate de trazar algunas de las características de algunos de los conjuntos de datos que hemos
usado en otros capítulos usando las técnicas de la sección 6.4.1. ¿Cumplen los datos las
expectativas de independencia? ¿Hay valores atípicos? ¿Qué pasa con los valores perdidos?
4 Cargue algunos de los conjuntos de datos CSV que hemos discutido aquí en la herramienta Facetas.
¿Qué características parecen que podrían causar problemas? ¿Alguna sorpresa?

5 Considere algunos de los conjuntos de datos que hemos usado en capítulos anteriores. ¿Qué tipo de
técnicas de aumento de datos funcionarían para ellos?

Resumen
- Los datos son una fuerza crítica que impulsa la revolución del aprendizaje profundo. Sin acceso a grandes
conjuntos de datos bien organizados, la mayoría de las aplicaciones de aprendizaje profundo no podrían
realizarse.

- TensorFlow.js viene empaquetado con eltf.datosAPI para facilitar la transmisión de grandes


conjuntos de datos, transformar datos de varias maneras y conectarlos a modelos para
entrenamiento y predicción.
- Hay varias formas de construir untf.data.Conjunto de datosobjeto: de una matriz de JavaScript, de un
archivo CSV o de una función de generación de datos. La creación de un conjunto de datos que se
transmite desde un archivo CSV remoto se puede realizar en una línea de JavaScript.

- tf.data.Conjunto de datosLos objetos tienen una API encadenable que hace que sea fácil y
conveniente mezclar, filtrar, procesar por lotes, mapear y realizar otras operaciones comúnmente
necesarias en una aplicación de aprendizaje automático.
- tf.data.Conjunto de datosaccede a los datos de forma perezosa. Esto hace que trabajar con
grandes conjuntos de datos remotos sea simple y eficiente, pero tiene el costo de trabajar
con operaciones asincrónicas.
- modelo tfLos objetos pueden ser entrenados directamente desde untf.data.Conjunto de datosusando su

ajusteDataset()método.
- La auditoría y la limpieza de datos requiere tiempo y cuidado, pero es un paso obligatorio para
cualquier sistema de aprendizaje automático que pretenda poner en práctica. Detectar y
gestionar problemas como sesgo, datos faltantes y valores atípicos en la etapa de procesamiento
de datos terminará ahorrando tiempo de depuración durante la etapa de modelado.
- El aumento de datos se puede utilizar para expandir el conjunto de datos e incluir
pseudoejemplos generados mediante programación. Esto puede ayudar al modelo a cubrir las
invariancias conocidas que estaban subrepresentadas en el conjunto de datos original.
Visualización de datos y modelos

Este capítulo cubre


- Cómo usar tfjs-vis para realizar una visualización de datos personalizada

- Cómo echar un vistazo al funcionamiento interno de los modelos después


de entrenarlos y obtener información útil

La visualización es una habilidad importante para los profesionales del aprendizaje automático porque está
involucrada en cada fase del flujo de trabajo del aprendizaje automático. Antes de construir modelos,
examinamos nuestros datos visualizándolos; durante la ingeniería y el entrenamiento del modelo,
monitoreamos el proceso de entrenamiento a través de la visualización; Después de entrenar el modelo,
usamos la visualización para tener una idea de cómo funciona.
En el capítulo 6, aprendió los beneficios de visualizar y comprender los datos antes de aplicarles el
aprendizaje automático. Describimos cómo usar Facets, una herramienta basada en navegador que lo
ayuda a obtener una vista rápida e interactiva de sus datos. En este capítulo, presentaremos una
nueva herramienta, tfjs-vis, que lo ayuda a visualizar sus datos de manera programática y
personalizada. El beneficio de hacerlo, en lugar de solo mirar los datos en su formato sin procesar o
usar herramientas listas para usar como Facets, es el paradigma de visualización más flexible y versátil
y la comprensión más profunda de los datos a la que conduce.
Además de la visualización de datos, mostraremos cómo se puede usar la visualización en
modelos de aprendizaje profundodespuésestán entrenados. Usaremos los ejemplos fascinantes
de mirar a escondidas las "cajas negras" de las redes neuronales al visualizar su

246
Visualización de datos 247

activaciones internas y el cálculo de los patrones que "excitan" al máximo las capas de una
convnet. Esto completará la historia de cómo la visualización va de la mano con el aprendizaje
profundo en todas y cada una de sus etapas.
Al final de este capítulo, sabrá por qué la visualización es una parte indispensable de cualquier
flujo de trabajo de aprendizaje automático. También debe estar familiarizado con las formas
estándar en que se visualizan los datos y los modelos en el marco de TensorFlow.js y poder
aplicarlos a sus propios problemas de aprendizaje automático.

7.1 Visualización de datos


Empecemos por la visualización de datos, porque eso es lo primero que hace un profesional del
aprendizaje automático cuando se enfrenta a un nuevo problema. Suponemos que la tarea de
visualización es más avanzada de lo que puede cubrir Facets (por ejemplo, los datos no están en un
archivo CSV pequeño). Para eso, primero presentaremos una API de gráficos básicos que lo ayudará a
crear tipos de gráficos simples y ampliamente utilizados, incluidos gráficos de líneas, gráficos de
dispersión, gráficos de barras e histogramas, en el navegador. Una vez que hayamos cubierto los
ejemplos básicos usando datos codificados a mano, uniremos las cosas usando un ejemplo que involucre
la visualización de un conjunto de datos real interesante.

7.1.1 Visualización de datos usando tfjs-vis

tfjs-vis es una biblioteca de visualización estrechamente integrada con TensorFlow.js. Entre las
muchas funciones que se tratarán en este capítulo se encuentra una API de gráficos ligera bajo su
tfvis.render.*espacio de nombres1Esta API sencilla e intuitiva le permite crear gráficos en el
navegador, centrándose en los tipos de gráficos que se utilizan con más frecuencia en el
aprendizaje automático. Para ayudarle a empezar contfvis.render,le daremos un recorrido por el
CodePen en https://codepen.io/tfjs-book/pen/BvzMZr, que muestra cómo usartfvis.renderpara crear
varios tipos de gráficos de datos básicos.

BASICS DE TFJS-VIS
Primero, tenga en cuenta que tfjs-vis está separado de la biblioteca principal de TensorFlow.js. Puede ver
esto por cómo CodePen importa tfjs-vis con un <guion>etiqueta:

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@latest " > </script>

Esto es diferente de cómo se importa la biblioteca principal de TensorFlow.js:

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/ tfjs@latest "> </script>

La misma distinción se aplica a los paquetes npm de tfjs-vis y TensorFlow.js (@


tensorflow/tfjs-visy @tensorflow/tfjs,respectivamente). En una página web o programa de
JavaScript que depende tanto de TensorFlow.js como de tfjs-vis, las dos dependencias
deben importarse.

1Esta API de gráficos se basa en la biblioteca de visualización de Vega:https://vega.github.io/vega/.


248 CPASADO7Visualización de datos y modelos

gráficos de líneas

El tipo de gráfico más utilizado es quizás elGráfico de linea(una curva que traza una cantidad
contra una cantidad pedida). Un gráfico de líneas tiene un eje horizontal y un eje vertical, que
a menudo se denominaneje xyeje y, respectivamente. Este tipo de visualización se ve en
todas partes en la vida. Por ejemplo, podemos trazar cómo cambia la temperatura a lo largo
del día con un gráfico de líneas en el que el eje horizontal es la hora del día y el eje vertical es
la lectura de un termómetro. El eje horizontal de un gráfico de líneas también puede ser algo
distinto del tiempo. Por ejemplo, podemos usar un gráfico de líneas para mostrar la relación
entre el efecto terapéutico de un medicamento para la presión arterial alta (cuánto reduce la
presión arterial) y la dosis (cuánto medicamento se usa por día). Tal trama se conoce como
curva dosis-respuesta. Otro buen ejemplo de un gráfico de líneas no temporal es la curva
ROC que discutimos en el capítulo 3. Allí, ni el eje x ni el eje y tienen que ver con el tiempo
(son las tasas positivas falsas y verdaderas de un clasificador binario).

Para crear un gráfico de líneas contfvis.render,utilizar elGráfico de linea()función. Como muestra


el primer ejemplo en CodePen (también listado 7.1), la función toma tres argumentos:

1 El primer argumento es el elemento HTML en el que se dibujará el gráfico. un vacío <


div>el elemento es suficiente.
2 El segundo argumento son los valores de los puntos de datos en el gráfico. Este es un
objeto JavaScript simple y antiguo (POJO) con elvalorcampo que apunta a una matriz. La
matriz consta de varios pares de valores xy, cada uno de los cuales está representado por
un POJO con campos denominadosXyy.losXyylos valores son, por supuesto, las coordenadas
x e y de los puntos de datos, respectivamente.
3 El tercer argumento, que es opcional, contiene campos de configuración adicionales
para el gráfico de líneas. En este ejemplo, usamos elanchocampo para especificar el
ancho del gráfico resultante (en píxeles). Verá más campos de configuración en los
próximos ejemplos.2

Listado 7.1 Hacer un gráfico de líneas simple usandotfvis.render.linechart()

sean valores = [{x: 1, y: 20}, {x: 2, y: 30},


{x: 3, y: 5}, {x: 4, y: 12}];
tfvis.render.linechart(document.getElementById('plot1'),
{valores},
El segundo argumento es un
{ancho: 400});
El primer argumento es un objeto que contiene la clave
elemento HTML en el que se "valor".
dibujará el gráfico. Aquí, 'parcela1'
es el ID de un div vacío.
La configuración personalizada se pasa
como el tercer argumento. En este caso,
La serie de datos es una
configuramos solo el ancho de la trama.
matriz de pares xy.

2
https://js.tensorflow.org/api_vis/latest/contiene la documentación completa de la API tfjs-vis, donde puede encontrar
información sobre otros campos de configuración de esta función.
Visualización de datos 249

El gráfico de líneas creado por el código en el listado 7.1 se muestra en el panel izquierdo de la figura 7.1.
Esta es una curva simple con solo cuatro puntos de datos. Pero elGráfico de linea()La función puede
admitir curvas con muchos más puntos de datos (por ejemplo, miles). Sin embargo, eventualmente se
encontrará con las restricciones de recursos del navegador si intenta trazar demasiados puntos de datos
a la vez. El límite depende del navegador y la plataforma y debe descubrirse empíricamente. En general,
es una buena práctica limitar el tamaño de los datos que se representarán en las visualizaciones
interactivas en aras de una interfaz de usuario fluida y receptiva.

30 Serie 1 50 mi serie 1
mi serie 2
40

Mi etiqueta del eje y


20 30
y

20
10
10

0
0
0.0 1.0 2.0 3.0 0.0 1.0 2.0 3.0
X Mi etiqueta del eje x

Figura 7.1 Gráficos de líneas creados usandotfvis.render.linechart(). Izquierda: una sola serie, realizada con el código
del listado 7.1. Derecha: dos series en los mismos ejes, realizadas con el código del listado 7.2.

A veces, desea trazar dos curvas en el mismo gráfico para mostrar la relación entre
ellas (por ejemplo, para contrastarlas entre sí). Puedes hacer este tipo de tramas
contfvis.render.linechart().Un ejemplo se muestra en el panel derecho de la figura
7.1 y el código en el listado 7.2.
Estos son conocidos comomultiseriesgráficos, y cada línea se llamaserie. Para crear un
gráfico multiseries, debe incluir un campo adicional,serie,en el primer argumento de Gráfico
de linea().El valor del campo es una matriz de cadenas. Las cadenas son los nombres dados a
la serie y se representarán como una leyenda en el gráfico resultante. En el código de
ejemplo, llamamos a nuestra serie 'Mi serie 1'y 'Mi serie 2'.
losvalorEl campo del primer argumento también debe especificarse correctamente para un gráfico de varias
series. Para nuestro primer ejemplo, proporcionamos una matriz de puntos, pero para gráficas de series
múltiples, debemos proporcionar una matriz de matrices. Cada elemento de la matriz anidada son los puntos de
datos de una serie y tiene el mismo formato que la matriz de valores que vimos en el listado 7.1 cuando trazamos
un gráfico de una sola serie. Por lo tanto, la longitud de la matriz anidada debe coincidir con la longitud de laserie
matriz, o se producirá un error.
El gráfico creado por el listado 7.2 se muestra en el panel derecho de la figura 7.1. Como puede ver en
el gráfico de las versiones electrónicas de este libro, tfjs-vis ha elegido dos colores diferentes (azul y
naranja) para representar las dos curvas. Este esquema de colores predeterminado funciona bien en
general porque el azul y el naranja son fáciles de diferenciar. aparte. Si hay más series para renderizar, se
seleccionarán automáticamente otros colores nuevos.
250 CPASADO7Visualización de datos y modelos

Las dos series de este gráfico de ejemplo son un poco especiales en el sentido de que tienen
exactamente el mismo conjunto de valores de coordenadas x (1, 2, 3 y 4). Sin embargo, en general,
los valores de coordenadas x de diferentes series en un gráfico de varias series no tienen que ser
idénticos. Le animamos a intentarlo en el ejercicio 1 al final de este capítulo. Pero tenga en cuenta
que no siempre es una buena idea trazar dos curvas en el mismo gráfico. Por ejemplo, si las dos
curvas tienen rangos de valores Y muy diferentes y que no se superponen, trazarlas en el mismo
gráfico de líneas hará que la variación en cada curva sea más difícil de ver. En tales casos, es mejor
trazarlos en gráficos de líneas separados.
Otra cosa que vale la pena señalar en el listado 7.2 son las etiquetas personalizadas para los
ejes. usamos elxEtiquetayyEtiquetacampos en el objeto de configuración (el tercer argumento
pasado aGráfico de linea())para etiquetar los ejes x e y como cadenas personalizadas de nuestra
elección. En general, es una buena práctica etiquetar siempre los ejes, ya que hace que los gráficos
se expliquen por sí mismos. tfjs-vis siempre etiquetará sus ejes comoXyysi no especificasxEtiquetay
yetiqueta,que es lo que sucedió en el listado 7.1 y el panel izquierdo de la figura 7.1.

Listado 7.2 Hacer un gráfico de líneas con dos series usandotfvis.render.linechart()

valores = [
[{x: 1, y: 20}, {x: 2, y: 30}, {x: 3, y: 5}, {x: 4, y: 12}], [{x: 1, y: 40}, {x: 2, y: 0}, {x: 3, y:
50}, {x: 4, y: -5}]
];
let serie = ['Mi serie 1', 'Mi serie 2']; tfvis.render.linechart(

document.getElementById('plot2'), {valores, serie}, { 400,


ancho:
xLabel: 'Mi etiqueta del eje x',
Los nombres de las series
yLabel: 'Mi etiqueta del eje y' }); deben proporcionarse al trazar
serie múltiple.
Para mostrar varias series en los mismos ejes, Anula el valor predeterminado

convierta los valores en una matriz que consta de etiquetas de los ejes x e y
varias matrices de pares xy.

Gráfico de dispersión

Gráfico de dispersiónson otro tipo de gráfico que puede crear contfvis.render.La diferencia más
destacada entre un gráfico de dispersión y un gráfico de líneas es el hecho de que un gráfico de
dispersión no conecta los puntos de datos con segmentos de línea. Esto hace que los diagramas de
dispersión sean adecuados para los casos en los que el orden entre los puntos de datos no es importante.
Por ejemplo, un gráfico de dispersión puede trazar la población de unos pocos países frente a su PIB per
cápita. En una gráfica de este tipo, la información principal es la relación entre los valores de x e y, no un
orden entre los puntos de datos.
Entfvis.render,la función que te permite crear diagramas de dispersión esgráfico de dispersión(). Como
muestra el ejemplo del listado 7.3,gráfico de dispersión()puede renderizar varias series, al igual queGráfico
de linea().De hecho, las API degráfico de dispersión()yGráfico de linea()son prácticamente idénticos, como
puedes ver comparando el listado 7.2 con el listado 7.3. El diagrama de dispersión creado por el listado
7.3 se muestra en la figura 7.2.
Visualización de datos 251

Listado 7.3 Haciendo un diagrama de dispersión usandotfvis.render.scatterplot()

valores = [
[{x: 20, y: 40}, {x: 32, y: 0}, {x: 5, y: 52}, {x: 12, y: -6}], [{x: 15, y : 35}, {x: 0, y: 9}, {x: 7, y:
28}, {x: 16, y: 8}]
];
serie = ['Mi serie de dispersión 1', 'Mi serie de dispersión 2'];
tfvis.render.scatterplot(
Como en linechart(), usa una
document.getElementById('plot4'), {valores,
matriz de matrices de pares xy para
serie},
mostrar múltiples series en un
{
gráfico de dispersión
ancho: 400,
xEtiqueta: 'Mis valores x', recuerda siempre
yLabel: 'Mis valores de y' para etiquetar sus ejes.
});

Serie
50 Mi serie de dispersión 1 Mi
serie de dispersión 2
40

30
Mis valores de y

20

10
Figura 7.2 Diagrama de dispersión
0 que contiene dos series, hecho con
– 10 el código del listado 7.3.
0 5 10 15 20 25 30 35
Mis valores de x

Gráfica de barras

Como su nombre lo indica, ungráfico de barrasusa barras para mostrar la magnitud de las cantidades.
Tales barras generalmente comienzan desde cero en la parte inferior para que las proporciones entre las
cantidades se puedan leer desde las alturas relativas de las barras. Por lo tanto, los gráficos de barras son
una buena opción cuando la relación entre cantidades es importante. Por ejemplo, es natural utilizar un
gráfico de barras para mostrar los ingresos anuales de una empresa durante varios años. En este caso,
las alturas relativas de las barras dan al espectador una idea intuitiva de cómo cambian los ingresos de un
trimestre a otro en términos de la relación entre ellos. Esto hace que los gráficos de barras sean distintos
de los gráficos de líneas y los diagramas de dispersión, en los que los valores no están necesariamente
"anclados" en cero.
Para crear un gráfico de barras contfvis.render,utilizargráfico de barras().Puede encontrar un ejemplo en
el listado 7.4. El gráfico de barras creado por el código se muestra en la figura 7.3. la API de
gráfico de barras()es similar a las deGráfico de linea()ygráfico de dispersión().Sin embargo, un
se debe notar una diferencia importante. El primer argumento pasó agráfico de barras()no es un objeto
que consta de unvalorcampo. En cambio, es una matriz simple de pares de valores de índice. Los valores
horizontales no se especifican con un campo llamadoX,pero en su lugar se especifican con un campo
llamadoíndice.De manera similar, los valores verticales no se especifican con un campo llamadoy,pero en
cambio están asociados con un campo llamadovalor.¿Por qué esta diferencia? Está
252 CPASADO7Visualización de datos y modelos

porque los valores horizontales de una barra en un gráfico de barras no tienen que tener un número. En
su lugar, pueden ser cadenas o números, como se muestra en nuestro ejemplo en la figura 7.3.

Listado 7.4 Creando un gráfico de barras usandotfvis.render.barchart()

datos constantes = [
{índice: 'foo', valor: 1},{índice: 'barra', valor: 7}, {índice: 3, valor: 3},

{índice: 5, valor: 6}]; tfvis.render.barchart(document.getElementById('plot5'),


datos, {
yLabel: 'Mi valor',
Observe cómo el índice de un gráfico de barras puede
ancho: 400
ser numérico o una cadena. Tenga en cuenta que el
});
importa el orden de los elementos.

5
mi valor

2
Figura 7.3 Un gráfico de barras compuesto por

1 barras con nombre de cadena y numérico,


hechas con el código en el listado 7.4
0
Foo
bar

Histogramas

Los tres tipos de diagramas descritos anteriormente le permiten representar los valores de una
cierta cantidad. A veces, los valores cuantitativos detallados no son tan importantes como los
distribuciónde los valores Por ejemplo, considere a un economista que observa los datos de
ingresos familiares anuales del resultado de un censo nacional. Para el economista, los valores
detallados de los ingresos no son la información más interesante. Contienen demasiada
información (sí, ¡a veces demasiada información puede ser algo malo!). En cambio, el economista
quiere un resumen más sucinto de los valores de los ingresos. Están interesados en cómo se
distribuyen esos valores, es decir, cuántos de ellos están por debajo de los 20 000 dólares, cuántos
están entre 20 000 y 40 000 dólares, o entre 40 000 y 60 000 dólares, etc. Histogramasson un tipo
de gráfico adecuado para tal tarea de visualización.
Un histograma asigna los valores encontenedores. Cada contenedor es simplemente un rango
continuo para el valor, con un límite inferior y un límite superior. Los contenedores se eligen para
que sean adyacentes entre sí para cubrir todos los valores posibles. En el ejemplo anterior, el
economista puede usar intervalos como 0 ~ 20k, 20k ~ 40k, 40k ~ 60k, etc. Una vez que tal conjunto
denortebins, puede escribir un programa para contar el número de puntos de datos individuales
que caen en cada uno de los bins. Ejecutar este programa le daránortenúmeros (uno para cada
contenedor). Luego puede trazar los números usando barras verticales. Esto le da un histograma.
Visualización de datos 253

tfvis.render.histograma()hace todos estos pasos por ti. Esto le ahorra el esfuerzo de determinar
los límites de los contenedores y contar los ejemplos por contenedores. Invocar histograma(),
simplemente pase una matriz de números, como se muestra en la siguiente lista. Estos números
no necesitan ordenarse en ningún orden.

Listado 7.5 Visualizando una distribución de valor usandotfvis.render.histograma()

datos constantes = [1, 5, 5, 5, 5, 10, -3, -3];


tfvis.render.histogram(document.getElementById('plot6'), datos, {
ancho: 400
});
Utiliza contenedores generados automáticamente
// Histograma: con número personalizado de contenedores. // Tenga en cuenta que los
datos son los mismos que los anteriores.
tfvis.render.histogram(document.getElementById('plot7'), datos, {
contenedores máximos: 3,
Especifica el número de contenedores explícitamente
ancho: 400
});

En el listado 7.5, hay dos ligeramente diferenteshistograma()llamadas La primera llamada no especifica


ninguna opción personalizada más allá del ancho de la trama. En este caso,histograma() utiliza su
heurística integrada para calcular los contenedores. Esto da como resultado siete contenedores –4 ~ –2, –
2 ~ 0, 0 ~ 2, . . ., 8 ~ 10, como se muestra en el panel izquierdo de la figura 7.4. Cuando se divide entre
estos siete contenedores, el histograma muestra el valor más alto en el contenedor 4 ~ 6, que contiene un
recuento de 4 porque cuatro de los valores en la matriz de datos son 5. Tres contenedores del histograma
(–2 ~ 0, 2 ~ 4 y 6 ~ 8) tienen valor cero porque ninguno de los elementos de los puntos de datos cae en
ninguno de estos tres contenedores.
Por lo tanto, podemos argumentar que las heurísticas predeterminadas terminan con demasiados
contenedores para nuestros puntos de datos particulares. Si hay menos contenedores, entonces será menos
probable que alguno de ellos termine vacío. Puedes usar el campo de configuraciónmaxBinspara anular la
heurística de agrupamiento predeterminada y limitar el número de agrupaciones. Esto es lo que se hace por el
segundo histograma()call en el listado 7.5, cuyo resultado se muestra a la derecha en la figura 7.4. Puede ver que
al limitar el número de contenedores a tres, todos los contenedores se vuelven no vacíos.

Num vals mínimo máx. # ceros # NaN # Infinito Num vals mínimo máx. # ceros # NaN # Infinito

8 –3 10 0 0 0 8 –3 10 0 0 0

4 5

4
Número de registros

Número de registros

3
3
2
2
1
1

0 0
–4 –2 0 2 4 6 8 10 –5 0 5 10
Valor (agrupado) Valor (agrupado)

Figura 7.4 Histogramas de los mismos datos, trazados con los intervalos calculados automáticamente (izquierda) y un número de intervalos
explícitamente especificado (derecha). El código que genera estos histogramas se encuentra en el listado 7.5.
254 CPASADO7Visualización de datos y modelos

mapas de calor

Amapa de calormuestra una matriz 2D de números como una cuadrícula de celdas de colores. El color de
cada celda refleja la magnitud relativa de los elementos de la matriz 2D. Tradicionalmente, los colores
"más fríos" como el azul y el verde se usan para representar valores más bajos, mientras que los colores
"más cálidos" como el naranja y el rojo se usan para mostrar valores más altos. Es por eso que estos
gráficos se llaman mapas de calor. Quizás los ejemplos más frecuentes de mapas de calor en el
aprendizaje profundo son las matrices de confusión (consulte el ejemplo de la flor del iris en el capítulo 3)
y las matrices de atención (consulte el ejemplo de conversión de fechas en el capítulo 9). tfjsvis
proporciona la funcióntfvis.render.heatmap()para apoyar la representación de este tipo de visualización.

El listado 7.6 muestra cómo hacer un mapa de calor para visualizar una matriz de confusión
inventada que involucra tres clases. El valor de la matriz de confusión se especifica en elvalores
campo del segundo argumento de entrada. Los nombres de las clases, que se utilizan para
etiquetar las columnas y filas del mapa de calor, se especifican comoEtiquetas xTickyyTickLabels. No
confunda estas etiquetas de marca conxEtiquetayyEtiquetaen el tercer argumento, que son para
etiquetar los ejes x e y completos. La figura 7.5 muestra el diagrama de mapa de calor resultante.

Listado 7.6 Visualizando tensores 2D usandotfvis.render.heatmap()

tfvis.render.heatmap(document.getElementById('plot8'), {
valores: [[1, 0, 0], [0, 0.3, 0.7], [0, 0.7, 0.3]], xTickLabels: ['Apple',
'Orange', 'Tangerine'], yTickLabels: ['Apple' , 'Naranja', 'Mandarina']
Los valores
pasó a
}, { mapa de calor () puede
ancho: 500, xTickLabels se utiliza para etiquetar el
ser un anidado
altura: 300, columnas individuales a lo largo
matriz de JavaScript
xLabel: 'Fruta real', yLabel: 'Fruta eje x. No lo confunda con
(como se muestra aquí)
reconocida', colorMap: 'blues' xLabel. Asimismo, yTickLabels es
o un tf.Tensor 2D.
se utiliza para etiquetar al individuo
filas a lo largo del eje y.
});

xLabel e yLabel se utilizan para Además del mapa de colores 'azules' que
etiquetar los ejes completos, a se muestra aquí, también hay 'escala de
diferencia de xTickLabel e yTickLabel. grises' y 'viridian'.

Valor
1.0
manzana

0.8
Fruto reconocido

0.6
naranja

0.4

0.2
Mandarina
Figura 7.5 El mapa de calor
0.0 representado por el código en
el listado 7.6. Muestra una
manzana

naranja

Mandarina

confusión imaginaria.
matriz que involucra a tres
fruta real clases
Visualización de datos 255

Esto concluye nuestro recorrido rápido por los cuatro tipos principales de gráficos compatibles con
tfvis.render. Si su trabajo futuro involucra la visualización de datos usando tfjs-vis, lo más probable es que
use mucho estos gráficos. La Tabla 7.1 proporciona un breve resumen de los tipos de gráficos para
ayudarlo a decidir cuál usar para una tarea de visualización determinada.

Tabla 7.1 Un resumen de los cinco tipos principales de gráficos soportados por tfjs-vis bajo eltfvis.render espacio de
nombres

Tareas de visualización adecuadas y


Nombre del gráfico Función correspondiente en tfjs-vis ejemplos de aprendizaje automático

Gráfico de linea tfvis.render.linechart() Un escalar (valor de y) que varía con otro escalar (valor de x) que
tiene un orden intrínseco (tiempo, dosis, etc.). Se pueden trazar
varias series en los mismos ejes: por ejemplo, métricas de los
conjuntos de entrenamiento y validación, cada uno de los cuales se
traza contra el número de época de entrenamiento.

Gráfico de dispersión tfvis.render.scatterplot() pares de valores escalares xy que no tienen un orden


intrínseco, como la relación entre dos columnas numéricas de
un conjunto de datos CSV. Se pueden trazar varias series en
los mismos ejes.

Gráfico de barras tfvis.render.barchart() Un conjunto de valores pertenecientes a un pequeño número de


categorías, como las precisiones (como números porcentuales)
logradas por varios modelos en el mismo problema de clasificación.

Histograma tfvis.render.histograma() Un conjunto de valores cuya distribución es de interés


principal, como la distribución de valores de parámetros en
el núcleo de una capa densa.

Mapa de calor tfvis.render.heathmap() Una matriz 2D de números que se visualizará como una cuadrícula
2D de celdas, cada elemento del cual está codificado por colores
para reflejar la magnitud del valor correspondiente: por ejemplo,
matriz de confusión de un clasificador multiclase (sección 3.3);
matriz de atención de un modelo de secuencia a secuencia (sección
9.3).

7.1.2 Un estudio de caso integrador: visualización de datos meteorológicos con tfjs-vis

Los ejemplos de CodePen en la sección anterior usaban datos pequeños codificados a mano.
En esta sección, mostraremos cómo usar las funciones de gráficos de tfjs-vis en un conjunto
de datos reales mucho más grande e interesante. Esto demostrará el verdadero poder de la
API y demostrará el valor de dicha visualización de datos en el navegador. Este ejemplo
también resaltará algunos de los matices y errores que puede encontrar al usar la API de
gráficos en problemas reales.
Los datos que utilizaremos son el conjunto de datos de Jena-weather-archive. Incluye las
mediciones recopiladas con una variedad de instrumentos meteorológicos en un lugar en Jena,
Alemania, durante un curso de ocho años (entre 2009 y 2017). El conjunto de datos, que se puede
descargar desde la página de Kaggle (verwww.kaggle.com/pankrzysiu/weatherarchive-jena), viene
en un archivo CSV de 42 MB. Consta de 15 columnas. La primera columna es
256 CPASADO7Visualización de datos y modelos

una marca de tiempo, mientras que las columnas restantes son datos meteorológicos como la
temperatura (T grados (C)),presión del aire (p (mbar)),humedad relativa (derecha (%s)),velocidad del
viento (wv (m/s)),y así. Si examina las marcas de tiempo, puede ver que tienen un espacio de 10
minutos, lo que refleja el hecho de que las mediciones se realizaron cada 10 minutos. Este es un
rico conjunto de datos para visualizar, explorar y probar el aprendizaje automático. En las
siguientes secciones, intentaremos hacer pronósticos meteorológicos utilizando varios modelos de
aprendizaje automático. En particular, pronosticaremos la temperatura del día siguiente utilizando
los datos meteorológicos de los 10 días anteriores. Pero antes de embarcarnos en esta
emocionante tarea de pronóstico del tiempo, sigamos el principio de "siempre mire sus datos antes
de probar el aprendizaje automático" y veamos cómo se puede usar tfjs-vis para trazar los datos de
una manera clara e intuitiva.
Para descargar y ejecutar el ejemplo del clima de Jena, use los siguientes comandos:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


jena-weather
discos compactos

hilo
hilo reloj

LIMITACIÓN DE LA CANTIDAD DE DATOS PARA UNA VISUALIZACIÓN EFICIENTE Y EFICAZ


El conjunto de datos meteorológicos de Jena es bastante grande. Con un tamaño de archivo de 42 MB, es más grande que

todos los conjuntos de datos CSV o tabulares que ha visto en este libro hasta ahora. Esto lleva a dos desafíos:

- El primer desafío es para la computadora: si traza todos los datos de los ocho años a la vez, la
pestaña del navegador se quedará sin recursos, dejará de responder y probablemente se
bloquee. Incluso si se limita a solo 1 de las 14 columnas, todavía hay alrededor de 420 000 puntos
de datos para mostrar. Esto es más de lo que tfjs-vis (o cualquier biblioteca de trazado de
JavaScript, para el caso) puede representar de forma segura a la vez.
- El segundo desafío es para el usuario: es difícil para un ser humano mirar una gran cantidad
de datos a la vez y encontrarles sentido. Por ejemplo, ¿cómo se supone que alguien mire los
420 000 puntos de datos y extraiga información útil de ellos? Al igual que la computadora, el
cerebro humano tiene un ancho de banda de procesamiento de información limitado. El
trabajo de un diseñador de visualización es presentar los aspectos más relevantes e
informativos de los datos de manera eficiente.

Usamos tres trucos para abordar estos desafíos:


- En lugar de trazar los datos de los ocho años completos a la vez, permitimos que el usuario elija qué
intervalo de tiempo trazar mediante una interfaz de usuario interactiva. Este es el propósito del menú
desplegable Intervalo de tiempo en la interfaz de usuario (vea las capturas de pantalla en las figuras 7.6 y
7.7). Las opciones de intervalo de tiempo incluyen Día, Semana, 10 días, Mes, Año y Completo. El último
corresponde a los ocho años completos. Para cualquiera de los otros lapsos de tiempo, la interfaz de
usuario permite al usuario avanzar y retroceder en el tiempo. Para esto están los botones de flecha
izquierda y flecha derecha.
- Para cualquier lapso de tiempo superior a una semana,reducir la muestrala serie de tiempo antes
de trazarlas en la pantalla. Por ejemplo, considere el período de tiempo Mes (30 días). Los datos
completos para este lapso de tiempo contienen aproximadamente 30 * 24 * 6 = 4.32k datos
Visualización de datos 257

puntos. En el código del listado 7.7, puede ver que solo trazamos cada sexto punto de datos
cuando mostramos los datos de un mes. Esto reduce el número de puntos de datos
trazados a 0,72k, una reducción significativa en el costo de representación. Pero a los ojos
humanos, esta reducción de seis veces en el recuento de puntos de datos apenas marca la
diferencia.
- Similar a lo que hicimos con el menú desplegable Intervalo de tiempo, incluimos un menú
desplegable en la interfaz de usuario para que el usuario pueda elegir qué datos meteorológicos
trazar en un momento dado. Observe los menús desplegables denominados Serie de datos 1 y
Serie de datos 2. Al usarlos, el usuario puede trazar 1 o 2 de las 14 columnas como gráficos de
líneas en la pantalla, en los mismos ejes.

El listado 7.7 muestra el código responsable de hacer los gráficos como los de la figura 7.6. A pesar
de que el código llamatfvis.render.linechart()Al igual que el ejemplo de Code-Pen en la sección
anterior, es mucho más abstracto en comparación con el código de los listados anteriores. Esto se
debe a que en nuestra página web necesitamos diferir la decisión de qué cantidades trazar de
acuerdo con el estado de la interfaz de usuario.

Listado 7.7 Datos meteorológicos como un gráfico de líneas multiseries (en jena-weather/index.js)

jenaWeatherData es un objeto que nos ayuda a


organizar y recuperar los datos meteorológicos del
archivo CSV. Consulte jena-weather/data.js.

función hacerTimeSerieChart(
series1, series2, timeSpan, normalize, chartContainer) { valores constantes =
[];
serie const = []; const
incluirTiempo = verdadero; if
(serie1 !== 'Ninguno') { elige el
valores.push(jenaWeatherData.getColumnData( apropiado
series1, includeTime, normalize, currBeginIndex, paso
TIME_SPAN_RANGE_MAP[timeSpan], (reducción de muestreo
especifica
TIME_SPAN_STRIDE_MAP[intervalo de tiempo])); series.push(normalizar ? `$ factor)
el tiempo
{serie1} (normalizado)` : serie1);
lapso para
visualización
}
if (serie2 !== 'Ninguno') { Aprovecha el hecho de que
valores.push(jenaWeatherData.getColumnData( el gráfico de líneas de tfjs-
series2, includeTime, normalize, currBeginIndex, vis admite múltiples series
TIME_SPAN_RANGE_MAP[timeSpan],
TIME_SPAN_STRIDE_MAP[intervalo de tiempo])); serie.push(normalizar ? `$
{serie2} (normalizado)` : serie2);
}
tfvis.render.linechart({valores, serie: serie}, chartContainer, {
ancho: chartContainer.offsetWidth * 0.95, altura:
chartContainer.offsetWidth * 0.3, xLabel: 'Tiempo',
Siempre etiquete los ejes.
yEtiqueta: serie.longitud === 1 ? serie[0] : '' });

}
258 CPASADO7Visualización de datos y modelos

Le animamos a explorar la interfaz de usuario de visualización de datos. Contiene muchos patrones


interesantes que puedes descubrir sobre el clima. Por ejemplo, el panel superior de la figura 7.6 muestra
cómo la temperatura normalizada (T (grados C))y presión de aire normalizada (p (mbar))varían en un
período de tiempo de 10 días. En la curva de temperatura, puede ver un ciclo diario claro: la temperatura
tiende a alcanzar su punto máximo alrededor de la mitad del día y tocar fondo poco después de la
medianoche. Además del ciclo diario, también puede ver una tendencia más global (un aumento gradual)
durante el período de 10 días. Por el contrario, la curva de presión de aire no muestra un patrón claro. El
panel inferior de la misma figura muestra las mismas medidas durante el lapso de tiempo de un año. Allí,
puedes ver el ciclo anual de temperatura: alcanza su punto máximo alrededor de agosto y llega a su
punto más bajo alrededor de enero. La presión del aire nuevamente muestra un patrón menos claro que
la temperatura en esta escala de tiempo.

Figura 7.6 Gráficos de líneas de temperatura (T (grados C)) y presión de aire (p (mbar)) del conjunto de datos del
archivo Jenaweather, trazado en dos escalas de tiempo diferentes. Arriba: período de tiempo de 10 días. Observe
el ciclo diario en la curva de temperatura. Abajo: período de tiempo de 1 año. Observe el ciclo anual en la curva de
temperatura y la ligera tendencia de la presión del aire a ser más estable durante la primavera y el verano que
durante otras estaciones.
Visualización de datos 259

La presión puede variar de forma algo caótica durante todo el año, aunque parece haber una
tendencia a que sea menos variable en verano que en invierno. Al observar las mismas
medidas en diferentes escalas de tiempo, podemos notar varios patrones interesantes. Todos
estos patrones son casi imposibles de notar si observamos solo los datos sin procesar en el
formato CSV numérico.
Una cosa que quizás haya notado en los gráficos de la figura 7.6 es que muestran valores normalizados de temperatura y

presión del aire en lugar de sus valores absolutos, lo cual se debe al hecho de que la casilla de verificación Normalizar datos en la

interfaz de usuario estaba marcada cuando hicimos estos parcelas Mencionamos brevemente la normalización cuando discutimos el

modelo de vivienda de Boston en el capítulo 2. La normalización implicaba restar la media y dividir el resultado por la desviación

estándar. Hicimos esto para mejorar el entrenamiento del modelo. La normalización que realizamos aquí es exactamente la misma.

Sin embargo, no es solo por la precisión de nuestro modelo de aprendizaje automático (que se tratará en la siguiente sección), sino

también por la visualización. ¿Por qué? Si intenta desmarcar la casilla de verificación Normalizar datos cuando el gráfico muestra la

temperatura y la presión del aire, verá inmediatamente el motivo. La medición de la temperatura varía en el rango entre -10 y 40 (en

la escala Celsius), mientras que la presión del aire reside en el rango entre 980 y 1000. Cuando se trazan en los mismos ejes sin

normalización, las dos cantidades con rangos muy diferentes obligan al eje y a expandirse a un rango muy grande, lo que hace que

ambas curvas se vean básicamente como líneas planas con pequeñas variaciones. La normalización evita este problema asignando

todas las medidas a una distribución de media cero y desviación estándar unitaria. haciendo que ambas curvas parezcan

básicamente líneas planas con pequeñas variaciones. La normalización evita este problema asignando todas las medidas a una

distribución de media cero y desviación estándar unitaria. haciendo que ambas curvas parezcan básicamente líneas planas con

pequeñas variaciones. La normalización evita este problema asignando todas las medidas a una distribución de media cero y

desviación estándar unitaria.

La Figura 7.7 muestra un ejemplo de cómo graficar dos mediciones climáticas una contra la otra
como un gráfico de dispersión, un modo que puede activar marcando la casilla de verificación
Trazar una contra la otra y asegurándose de que ninguno de los menús desplegables de la Serie de
datos esté configurado en Ninguno. El código para hacer tales diagramas de dispersión es similar
alhacer-TimeSerieChart()función en el listado 7.7 y, por lo tanto, se omite aquí por razones de
concisión. Puede estudiarlo en el mismo archivo (jena-weather/index.js) si está interesado en los
detalles.
El diagrama de dispersión de ejemplo muestra la relación entre la densidad del aire normalizada (eje y)
y la temperatura normalizada (eje x). Aquí, puede detectar una correlación negativa bastante fuerte entre
las dos cantidades: la densidad del aire disminuye a medida que aumenta la temperatura. Esta gráfica de
ejemplo utiliza el lapso de tiempo de 10 días, pero puede verificar que la tendencia también se mantiene
en gran medida en otros lapsos de tiempo. Este tipo de correlación entre variables es fácil de visualizar
con diagramas de dispersión, pero es mucho más difícil de descubrir simplemente mirando los datos en
formato de texto. Este es otro ejemplo del valor que ofrece la visualización de datos.
260 CPASADO7Visualización de datos y modelos

Figura 7.7 Un diagrama de dispersión de ejemplo de la demostración del clima de Jena. El gráfico
muestra la relación entre la densidad del aire (rho, eje vertical) y la temperatura (T, horizontal) durante
un período de tiempo de 10 días, donde se puede observar una correlación negativa.

7.2 Visualización de modelos después del entrenamiento

En las secciones anteriores, mostramos cómo la visualización puede ser útil para los datos. En esta
sección, le mostraremos cómo visualizar varios aspectos de los modelos después de entrenarlos
para obtener información útil. Con este fin, nos centraremos principalmente en las redes que
toman imágenes como entradas, ya que son muy utilizadas y producen resultados de visualización
interesantes.
Es posible que haya escuchado el comentario de que las redes neuronales profundas son "cajas
negras". No permita que este comentario lo engañe y le haga pensar que es difícil obtener información
del interior de una red neuronal durante su inferencia o entrenamiento. Por el contrario, es bastante fácil
echar un vistazo a lo que hace cada capa dentro de un modelo escrito en TensorFlow.js.3

3
Lo que realmente significa ese comentario es que la gran cantidad de operaciones matemáticas que ocurren en una red neuronal
profunda, incluso si se puede acceder a ellas, son más difíciles de describir en términos sencillos en comparación con otros tipos
de algoritmos de aprendizaje automático, como la decisión. árboles y regresión logística. Por ejemplo, con un árbol de decisión,
puede recorrer los puntos de ramificación uno por uno y explicar por qué se elige una determinada rama verbalizando el motivo
con una oración simple como "porque el factor X es mayor que 0,35". Ese problema se conoce comointerpretabilidad del modeloy
es un asunto diferente de lo que estamos cubriendo en esta sección.
Visualización de modelos después del entrenamiento 261

Además, en lo que respecta a las convenciones, las representaciones internas que aprenden
son muy susceptibles de visualización, en gran parte porque son representaciones de
conceptos visuales. Desde 2013, se ha desarrollado una amplia gama de técnicas para
visualizar e interpretar estas representaciones. Dado que no es práctico cubrir todas las
técnicas interesantes, cubriremos tres de las más básicas y útiles:
- Visualización de las salidas de capas intermedias (activaciones intermedias) de un convnet— Esto
es útil para comprender cómo las sucesivas capas de convnet transforman sus entradas y para
tener una primera idea de las características visuales aprendidas por los filtros de convnet
individuales.
- Visualización de filtros de convnet al encontrar imágenes de entrada que los activen al
máximo—Esto es útil para comprender a qué patrón visual o concepto es sensible cada
filtro.
- Visualización de mapas de calor de activación de clases en una imagen de entrada—Esto
ayuda a comprender qué partes de una imagen de entrada juegan el papel más importante
en hacer que el convnet genere el resultado de clasificación final, lo que también puede ser
útil para interpretar cómo un convnet llega a su salida y para "depurar" salidas incorrectas.

El código que usaremos para mostrar estas técnicas se encuentra en el ejemplo de visualización de
convnet del repositorio tfjs-examples. Para ejecutar el ejemplo, use estos comandos:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


visualizar-convnet
discos compactos

hilo && hilo visualizar

loshilo visualizarEl comando es diferente delhilo relojcomando que has


visto en los ejemplos anteriores. Además de crear e iniciar la página web, realiza algunos pasos
adicionales fuera del navegador. Primero, instala algunas bibliotecas de Python requeridas, luego
descarga y convierte el modelo VGG16 (un deep convnet muy conocido y ampliamente utilizado) al
formato TensorFlow.js. El modelo VGG16 se entrenó previamente en el conjunto de datos de
ImageNet a gran escala y está disponible como una aplicación de Keras. Una vez completada la
conversión del modelo,hilo visualizarrealiza una serie de análisis sobre el modelo convertido en
tfjs-node. ¿Por qué estos pasos se realizan en Node.js en lugar del navegador? Porque VGG16 es
una convnet relativamente grande.4Como resultado, varios de los pasos son computacionalmente
pesados y se ejecutan mucho más rápido en el entorno con menos recursos restringidos en
Node.js. El cálculo se puede acelerar aún más si usa tfjs-node-gpu en lugar del tfjs-node
predeterminado (esto requiere una GPU habilitada para CUDA con el controlador y las bibliotecas
necesarios instalados; consulte el apéndice A):

hilo visualizar --gpu

Una vez que se completen los pasos computacionalmente pesados en Node.js, generarán un
conjunto de archivos de imagen en la carpeta dist/. Como último paso,hilo visualizarcompilará y

4
Para tener una idea de qué tan grande es VGG16, tenga en cuenta que su tamaño de peso total es de 528 MB, en comparación con el tamaño
de peso <10 MB de MobileNet.
262 CPASADO7Visualización de datos y modelos

inicie un servidor web para un conjunto de archivos web estáticos, incluidas esas imágenes, además de
abrir la página de índice en su navegador.
loshilo visualizarEl comando contiene algunas banderas configurables adicionales. Por ejemplo,
de forma predeterminada, realiza cálculos y visualizaciones en ocho filtros por capa convolucional
de interés. Puede cambiar la cantidad de filtros usando el --filtros bandera: por ejemplo,hilo
visualizar --filtros 32.Además, la imagen de entrada predeterminada utilizada por hilo visualizares la
imagen cat.jpg que viene con el código fuente. Puede usar otros archivos de imagen usando --
imagenbandera.5Ahora veamos los resultados de visualización basados en la imagen cat.jpg y 32
filtros.

7.2.1 Visualización de las activaciones internas de un convnet


Aquí, calculamos y mostramos el mapa de características generado por varias capas
convolucionales del modelo VGG16 dada una imagen de entrada. Estos mapas de características se
llamaninternoactivación porque no son el resultado final del modelo (el resultado final del modelo
es un vector de longitud 1000 que representa las puntuaciones de probabilidad para las 1000
clases de ImageNet). En cambio, son los pasos intermedios del cálculo del modelo. Estas
activaciones internas nos dan una idea de cómo la entrada se descompone en diferentes funciones
aprendidas por la red.
Recuerde del capítulo 4 que la salida de una capa convolucional tiene la forma NHWC
[numEjemplos, alto, ancho, canales].Aquí, estamos tratando con una sola entrada.
imagen, entoncesnumEjemploses 1. Queremos visualizar la salida de cada capa convolucional a lo
largo de las tres dimensiones restantes: alto, ancho y canales. La altura y el ancho de la salida de
una capa convolucional están determinados por el tamaño del filtro, el relleno y los pasos, así como
por la altura y el ancho de la entrada de la capa. En general, se vuelven más y más pequeños a
medida que se profundiza en una convnet. Por otra parte, el valor decanales generalmente se
vuelve más grande a medida que se profundiza, ya que la convnet extrae una cantidad cada vez
mayor de características a través de capas sucesivas de transformación de representación. Estos
canales de capas convolucionales no pueden interpretarse como diferentes componentes de color.
En su lugar, son las dimensiones de características aprendidas. Es por eso que nuestra visualización
los divide en paneles separados y los dibuja en escala de grises. La Figura 7.8 muestra las
activaciones de cinco capas convolucionales de VGG16 dada la imagen de entrada cat.jpg.
Lo primero que puede notar en las activaciones internas es que se ven cada vez más diferentes
de la entrada original a medida que profundiza en la red. Las capas anteriores (comobloque1_conv1)
parecen codificar características visuales relativamente simples, como bordes y colores. Por
ejemplo, la flecha etiquetada como "A" apunta a una activación interna que parece responder a los
colores amarillo y rosa. La flecha etiquetada como "B" apunta a una activación interna que parece
estar sobre los bordes a lo largo de ciertas orientaciones en la imagen de entrada.

Pero las capas posteriores (comobloque4_conv2ybloque5_conv3)mostrar patrones de activación


que se eliminan cada vez más de las funciones simples a nivel de píxel en la entrada

5Se admiten los formatos de imagen más comunes, incluidos JPEG y PNG.
Visualización de modelos después del entrenamiento 263

A B

Figura 7.8 Activación interna de varias capas convolucionales de VGG16 realizando inferencia en la imagen cat.jpg. La imagen de
entrada original se muestra a la izquierda, junto con las tres clases principales generadas por el modelo y sus puntajes de
probabilidad asociados. Las cinco capas visualizadas son las capas nombradasbloque1_conv1, bloque2_conv1,bloque3_conv2,
bloque4_conv2, ybloque5_conv3. Están ordenados por su profundidad en el modelo VGG16 de arriba a abajo. Es decir,
bloque1_conv1es la más cercana a la capa de entrada, mientras quebloque5_conv1 es la más cercana a la capa de salida. Tenga
en cuenta que todas las imágenes de activación interna se escalan al mismo tamaño para fines de visualización, aunque las
activaciones tienen tamaños más pequeños (resolución más baja) en las capas posteriores debido a la sucesiva convolución y
agrupación. Esto se puede ver en los patrones de píxeles gruesos en las últimas capas.

imagen. Por ejemplo, la flecha etiquetada como "C" en la figura 7.8 apunta a un filtro enblock4_conv2que
parece codificar las características faciales del gato, incluidas las orejas, los ojos y la nariz. Este es un
ejemplo concreto de la extracción incremental de funciones que mostramos esquemáticamente en la
figura 4.6 del capítulo 4. Sin embargo, tenga en cuenta que no todos los filtros en las capas posteriores se
pueden explicar verbalmente de manera sencilla. Otra observación interesante es que la “escasez” de los
mapas de activación también aumenta con la profundidad de la capa: en la primera capa que se muestra
en la figura 7.8, todos los filtros son activados (muestran un patrón de píxeles no constante) por la
imagen de entrada; sin embargo, en la última capa, algunas de las capas quedan en blanco (patrón de
píxeles constante; por ejemplo, vea la última fila en el panel derecho de la figura 7.8). Esto significa que
las características codificadas por esos filtros en blanco están ausentes de esta imagen de entrada en
particular.
Acabas de presenciar una importante característica universal de las representaciones aprendidas por
convnets profundos: las características extraídas por una capa se vuelven cada vez más abstractas con la
profundidad de la capa. Las activaciones de capas más profundas llevan cada vez menos
264 CPASADO7Visualización de datos y modelos

información sobre los detalles en la entrada y más y más información sobre el objetivo (en este
caso, a cuál de las 1000 clases de ImageNet pertenece la imagen). Entonces, una red neuronal
profunda actúa efectivamente como untubería de destilación de información, con datos sin
procesar que ingresan y se transforman repetidamente para que los aspectos de la entrada que
son irrelevantes para la tarea se filtren y los aspectos que son útiles para la tarea se amplíen y
perfeccionen gradualmente. Aunque mostramos esto a través de un ejemplo de convnet, esta
característica también se aplica a otras redes neuronales profundas (como las MLP).
Los aspectos de las imágenes de entrada que un convnet encuentra útiles pueden ser diferentes de
los que el sistema visual humano encuentra útiles. El entrenamiento de convnet está impulsado por datos
y, por lo tanto, es propenso a sesgos en los datos de entrenamiento. Por ejemplo, el artículo de Marco
Ribeiro y sus colegas enumerado en la sección "Materiales para lectura y exploración adicionales" al final
del capítulo señala un caso en el que la imagen de un perro se clasificó erróneamente como un lobo
debido a la presencia de nieve. en el fondo, presumiblemente porque las imágenes de entrenamiento
contenían instancias de lobos contra fondos nevados pero no perros contra fondos similares.

Estos son los conocimientos útiles que obtuvimos al visualizar los patrones de activación
internos de una convnet profunda. La siguiente subsección describe cómo escribir código en
Tensor-Flow.js para extraer estas activaciones internas.

DEEP SUMÉRGETE EN CÓMO SE EXTRAEN LAS ACTIVACIONES INTERNAS


Los pasos para extraer las activaciones internas están encapsulados en elescribir activación
interna y obtener salida ()función (listado 7.8). Toma como entrada un objeto modelo
TensorFlow.js que ya se ha construido o cargado y los nombres de las capas en cuestión (
nombres de capa).El paso clave es crear un nuevo objeto modelo (modelo compuesto)con
múltiples salidas, incluida la salida de las capas especificadas y la salida del modelo original.
modelo compuestose construye con latf.modelo()API, como vio en los ejemplos de detección
de objetos simples y Pac-Man en el capítulo 5. Lo bueno demodelo compuestoes que es
predecir()El método devuelve todas las activaciones de las capas, junto con la predicción final
del modelo (consulte laconstantellamadosalidas).El resto del código del listado 7.8 (de
visualize-convnet/main.js) se trata de la tarea más mundana de dividir los resultados de las
capas en filtros individuales y escribirlos en archivos en el disco.

Listado 7.8 Calculando la activación interna de una convnet en Node.js

función asíncrona writeInternalActivationAndGetOutput(


modelo, nombres de capa, imagen de entrada, filtros numéricos, directorio de salida)
{ const nombre de capa2 rutas de archivo = {};
salidas de capa const =
layerNames.map(layerName => model.getLayer(layerName).output); const modelo
compuesto = tf.modelo(
Construye un modelo que
{
devuelve todas las activaciones
entradas: modelo.entrada,
internas deseadas, además del
salidas: layerOutputs.concat(model.outputs[0]) }); resultado final del modelo
original
Visualización de modelos después del entrenamiento 265

const salidas = compositeModel.predict(inputImage);


outputs es una matriz de
for (sea i = 0; i < salidas.longitud - 1; ++i) { tf.Tensor, que incluye
const nombreCapa = nombresCapa[i]; const las activaciones internas
activacionTensores = y la salida final.
divide el tf.split(salidas[i],
activación salidas[i].forma[salidas[i].forma.longitud – 1],
de El - 1);
convolucional const actualNumFilters = filtros <= activaciónTensores.longitud ?
capa por filtro numFiltros:
activaciónTensores.longitud; const
filePaths = [];
for (sea j = 0; j <actualNumFilters; ++j) {
const imageTensor = tf.tidy( Activación de formatos
() => deprocessImage(tf.tile(activationTensors[j], tensores y escrituras
[1, 1, 1, 3]))); ellos al disco
const rutaArchivoSalida = ruta.join(
salidaDir, `${layerName}_${j + 1}.png`);
filePaths.push(outputFilePath);
esperar utils.writeImageTensorToFile(imageTensor, ruta del archivo de salida);
}
layerName2FilePaths[layerName] = filePaths;
tf.dispose(tensores de activación);
}
tf.dispose(outputs.slice(0, outputs.length - 1));
return {modelOutput: outputs[outputs.length - 1], layerName2FilePaths};
}

7.2.2 Visualizar a qué capas convolucionales son sensibles: activar


al máximo las imágenes
Otra forma de ilustrar lo que aprende una convnet es encontrar las imágenes de entrada a las que son
sensibles sus diversas capas internas. Lo que queremos decir con que un filtro sea sensible a una
determinada imagen de entrada es una activación máxima en la salida del filtro (promediada a través de
sus dimensiones de alto y ancho de salida) debajo de la imagen de entrada. Al observar tales entradas de
activación máxima para varias capas de la convnet, podemos inferir para qué está capacitada cada capa
para responder.
La forma en que encontramos las imágenes de activación máxima es a través de un truco que invierte
el proceso de entrenamiento de la red neuronal "normal". El panel A de la figura 7.9 muestra
esquemáticamente lo que sucede cuando entrenamos una red neuronal con tf.Modelo.ajuste().
Congelamos los datos de entrada y permitimos que los pesos del modelo (como los núcleos y los sesgos
de todas las capas entrenables) se actualicen desde la función de pérdida.6vía retropropagación. Sin
embargo, no hay ninguna razón por la que no podamos intercambiar los roles de la entrada y los pesos:
podemos congelar los pesos y permitir que elaportepara ser actualizado a través de la retropropagación.
Mientras tanto, modificamos la función de pérdida para que provoque que la retropropagación empuje la
entrada de una manera que maximice la salida de un determinado filtro convolucional cuando se
promedia en sus dimensiones de altura y anchura.

6
Este diagrama se puede ver como una versión simplificada de la figura 2.9, que usamos para presentar la retropropagación en el
capítulo 2.
266 CPASADO7Visualización de datos y modelos

UN.Descenso de gradiente en el espacio de peso B.Ascenso de gradiente en el espacio de entrada

Actualizaciones a través
retropropagación
Aporte Aporte

Personalizado

pérdida

peso1 Modelo pérdida


peso1 Modelo que
maximiza
capa
peso2 peso2 activación
... ...

pesonorte pesonorte

Actualizaciones a través
retropropagación

Figura 7.9 Un diagrama esquemático que muestra la idea básica detrás de cómo se encuentra la imagen de activación máxima para
un filtro convolucional a través del ascenso de gradiente en el espacio de entrada (panel B) y cómo difiere del proceso normal de
entrenamiento de redes neuronales basado en el descenso de gradiente en el espacio de peso ( panel A). Tenga en cuenta que esta
figura difiere de algunos de los diagramas modelo mostrados anteriormente en que separa los pesos del modelo. Esto es para
resaltar los dos conjuntos de cantidades que se pueden actualizar a través de la retropropagación: los pesos y la entrada.

Este proceso se muestra esquemáticamente en el panel B de la figura 7.9 y se llamaascenso de gradiente en el


espacio de entrada, a diferencia de ladescenso de gradiente en el espacio de pesoque subyace en el
entrenamiento modelo típico. El código que implementa el descenso de gradiente en el espacio de entrada se
muestra en la siguiente subsección y los lectores interesados pueden estudiarlo.
La figura 7.10 muestra el resultado de realizar el proceso de ascenso de gradiente en el espacio de
entrada en cuatro capas convolucionales del modelo VGG16 (el mismo modelo que usamos para mostrar
las activaciones internas). Como en la ilustración anterior, la profundidad de las capas aumenta desde la
parte superior a la inferior de la figura. Se pueden obtener algunos patrones interesantes de estas
imágenes de entrada de activación máxima:

- Primero, estas son imágenes a color en lugar de las activaciones internas en escala de
grises como las de la sección anterior. Esto se debe a que tienen el formato de la entrada
real de la convnet: una imagen que consta de tres canales (RGB). Por lo tanto, se pueden
mostrar en color.
-La capa más superficial (bloque1_conv1)es sensible a patrones simples como valores
de color globales y bordes con ciertas orientaciones.
- Las capas de profundidad intermedia (comobloque2_conv1)responde al máximo a las texturas
simples hechas de la combinación de diferentes patrones de borde.
- Los filtros en las capas más profundas comienzan a responder a patrones más complejos que muestran
cierto parecido con las características visuales de las imágenes naturales (desde los datos de
entrenamiento de Image-Net, por supuesto), como granos, agujeros, rayas de colores, plumas, ondas,
etc. adelante.
Visualización de modelos después del entrenamiento 267

Figura 7.10 Imágenes de entrada de activación máxima para cuatro capas de la convnet profunda VGG16. Estas imágenes se calculan a
través de 80 iteraciones de ascenso de gradiente en el espacio de entrada.

En general, a medida que aumenta la profundidad de la capa, los patrones se eliminan cada
vez más del nivel de píxeles y se vuelven cada vez más grandes y complejos. Esto refleja la
destilación capa por capa de características por la convnet profunda, componiendo patrones
de patrones. Mirando los filtros de la misma capa, aunque comparten niveles similares de
abstracción, existe una variabilidad considerable en los patrones de detalle. Esto destaca el
hecho de que cada capa presenta múltiples representaciones de la misma entrada de
manera mutuamente complementaria para capturar la mayor cantidad posible de
información útil para resolver la tarea que la red está entrenada para resolver.

DEEP INMERSIÓN EN EL ASCENSO DE GRADIENTE EN EL ESPACIO DE ENTRADA

En el ejemplo de visualización-convnet, la lógica central para el ascenso de gradiente en el espacio de entrada


está en elentradaGradienteAscenso()funciona en main.js y se muestra en el listado 7.9. El código se ejecuta en
Node.js debido a que consume mucho tiempo y memoria.7Tenga en cuenta que aunque la idea básica detrás del
ascenso de gradiente en el espacio de entrada es análoga al entrenamiento modelo basado en el descenso de
gradiente en el espacio de peso (consulte la figura 7.10), no podemos reutilizar

7
Para convnets más pequeños que VGG16 (como MobileNet y MobileNetV2), es posible ejecutar este algoritmo dentro de
un período de tiempo razonable en el navegador web.
268 CPASADO7Visualización de datos y modelos

tf.Modelo.ajuste()directamente porque esa función está especializada en congelar la entrada y


actualizar los pesos. En su lugar, necesitamos definir una función personalizada que calcule una
"pérdida" dada una imagen de entrada. Esta es la función definida por la línea

const lossFunction = (entrada) =>


auxModel.apply(input, {entrenamiento: verdadero}).reunir([filterIndex], 3);

Aquí,modelo auxiliares un objeto de modelo auxiliar creado con el familiartf.modelo() función.


Tiene la misma entrada que el modelo original pero genera la activación de una capa convolucional
dada. invocamos elaplicar()método del modelo auxiliar para obtener el valor de activación de la
capa.aplicar()es parecido apredecir()en el sentido de que ejecuta la ruta de avance de un modelo.
Sin embargo,aplicar()proporciona un control más detallado, como configurar elcapacitaciónopción
acierto,como se hace en la línea de código anterior. sin ajustecapacitaciónparacierto,la
retropropagación no sería posible porque el paso hacia adelante dispone de forma
predeterminada de activaciones de capa intermedia para la eficiencia de la memoria. losciertovalor
en elcapacitaciónla bandera permite queaplicar()llame a preservar esas activaciones internas y, por
lo tanto, habilite la retropropagación. losrecolectar()call extrae la activación de un filtro específico.
Esto es necesario porque la entrada de activación máxima se calcula filtro por filtro y los resultados
difieren entre filtros incluso de la misma capa (consulte los resultados de ejemplo en la figura 7.10).

Una vez que tenemos la función custom loss, se la pasamos atf.grado()para obtener una
función que nos dé el gradiente de pérdida con respecto a la entrada:

const gradFunction = tf.grad(lossFunction);

Lo importante a tener en cuenta aquí es quetf.grado()no nos da los valores de gradiente


directamente; en cambio, nos da una función (gradFunciónen la línea anterior) que devolverá
los valores de gradiente cuando se invoque.
Una vez que tenemos esta función de gradiente, la invocamos en un ciclo. En cada iteración, usamos el
valor de gradiente que devuelve para actualizar la imagen de entrada. Un truco no obvio importante aquí
es normalizar los valores de gradiente antes de agregarlos a la imagen de entrada, lo que garantiza que la
actualización en cada iteración tenga una magnitud constante:

norma const = tf.sqrt(tf.mean(tf.square(grads))).add(EPSILON); volver


grads.div(norma);

Esta actualización iterativa de la imagen de entrada se realiza 80 veces, dándonos los resultados que se muestran
en la figura 7.10.

Listado 7.9 Ascenso de gradiente en el espacio de entrada (en Node.js, de visualize-convnet/main.js)

función entradaGradienteAscenso(
modelo, nombre de capa, índice de filtro, iteraciones = 80) { return
tf.tidy(() => {
const imagenH = modelo.entradas[0].forma[1]; const
imagenW = modelo.entradas[0].forma[2]; const imageDepth
= model.inputs[0].shape[3];

const capaSalida = modelo.getLayer(nombreCapa).salida;


Visualización de modelos después del entrenamiento 269

const auxModel = tf.model({ Crea un modelo auxiliar para el cual la entrada es


entradas: modelo.entradas, la misma que el modelo original, pero la salida es
salidas: salida de capa la capa convolucional de interés
});
const lossFunction = (entrada) =>
auxModel.apply(input, {entrenamiento: verdadero}).reunir([filterIndex], 3);

const gradFunction = tf.grad(lossFunction);

let image = tf.randomUniform([1, imageH, imageW, imageDepth], 0, 1)


. mul(20).add(128);

for (sea i = 0; i < iteraciones; ++i) {


const ScaledGrads = tf.tidy(() => {
const grads = gradFunction(imagen);
norma const = tf.sqrt(tf.mean(tf.square(grads))).add(EPSILON);
regreso graduados.div(norma);
});
imagen = tf.clipPorValor(
imagen.add(graduaciones escaladas), 0, 255);
} Realiza un paso de ascenso de
return deprocesarImagen(imagen); }); gradiente: actualiza la imagen a lo largo
la dirección del gradiente
}

Esta función calcula el gradiente de la Truco importante: escala el


salida del filtro convolucional con gradiente con la magnitud
respecto a la imagen de entrada. (norma) del gradiente

Esta función calcula el valor de la salida de Genera una imagen aleatoria


la capa convolucional en el índice de filtro como punto de partida de la
designado. ascenso de gradiente

7.2.3 Interpretación visual del resultado de clasificación de un convnet


La última técnica de visualización de convnet posterior al entrenamiento que presentaremos es lamapa
de activacion de clases(algoritmo CAM). La pregunta que CAM pretende responder es "¿qué partes de la
imagen de entrada juegan los roles más importantes para hacer que el convnet emita su decisión de
clasificación superior?" Por ejemplo, cuando la imagen cat.jpg se pasó a la red VGG16, obtuvimos una
clase superior de "gato egipcio" con una puntuación de probabilidad de 0,89. Pero al observar solo la
entrada de la imagen y la salida de la clasificación, no podemos decir qué partes de la imagen son
importantes para esta decisión. Seguramente algunas partes de la imagen (como la cabeza del gato)
deben haber jugado un papel más importante que otras partes (por ejemplo, el fondo blanco). Pero, ¿hay
alguna forma objetiva de cuantificar esto para cualquier imagen de entrada?
¡La respuesta es sí! Hay múltiples formas de hacer esto, y CAM es una de ellas.8
Dada una imagen de entrada y un resultado de clasificación de un convnet, CAM le brinda un mapa de
calor que asigna puntajes de importancia a diferentes partes de la imagen. La Figura 7.11 muestra estos
mapas de calor generados por CAM superpuestos sobre tres imágenes de entrada: un gato, un búho,

8
El algoritmo CAM se describió por primera vez en Bolei Zhou et al., "Learning Deep Features for Discriminative
Localization", 2016,http://cnnlocalization.csail.mit.edu/. Otro método bien conocido es el modelo local
interpretable-explicaciones agnósticas (LIME). Verhttp://mng.bz/yzpq.
270 CPASADO7Visualización de datos y modelos

y dos elefantes. En el resultado del gato, vemos que el contorno de la cabeza del
gato tiene los valores más altos en el mapa de calor. Podemos hacer la observación
post hoc de que esto se debe a que el contorno revela la forma de la cabeza del
animal, que es un rasgo distintivo de un gato. El mapa de calor de la imagen del
búho también cumple con nuestras expectativas porque resalta la cabeza y el ala
del animal. El resultado de la imagen con dos elefantes es interesante porque la
imagen difiere de las otras dos imágenes en que contiene dos animales
individuales en lugar de uno. El mapa de calor generado por CAM asigna
puntuaciones de alta importancia a las regiones de la cabeza de ambos elefantes
en la imagen. Hay una clara tendencia a que el mapa de calor se centre en las
trompas y las orejas de los animales,

A B C

• Gato egipcio (p = 0,8856) • gran búho gris (p = 0,9850) • Elefante africano (p = 0,6495)
• atigrado, gato atigrado (p = 0,0425) • tití (p = 0,0042) • colmillo (p = 0,2529)
• lince, catamount (p = 0,0125) • codorniz (p = 0,0040) • Elefante indio (p = 0,0971)

Figura 7.11 Mapas de activación de clases (CAM) para tres imágenes de entrada a la convnet profunda VGG16.
Los mapas de calor CAM se superponen a las imágenes de entrada originales.

TLADO TÉCNICO DELLEVAALGORITMO


A pesar de lo poderoso que es el algoritmo CAM, la idea detrás de él en realidad no es complicada. En
pocas palabras, cada píxel en un mapa CAM muestra cuánto cambiará el puntaje de probabilidad de la
clase ganadora si el valor del píxel aumenta en una cantidad unitaria. Para entrar en detalles un poco
más, los siguientes pasos están involucrados en CAM:

1 Encuentre la última capa convolucional (es decir, la más profunda) de la convnet. En VGG16,
esta capa se llamabloque5_conv3.
2 Calcule el gradiente de la probabilidad de salida de la red para la clase ganadora
con respecto a la salida de la capa convolucional.
3El degradado tiene una forma de [1, h, w, númFiltros],dondeh, w,ynúmero-
filtrosson la altura, el ancho y el filtro de salida de la capa, respectivamente. Luego
promediamos el gradiente en las dimensiones de ejemplo, alto y ancho, lo que nos da
un tensor de forma [númFiltros].Esta es una matriz de puntuaciones de importancia,
una para cada filtro de la capa convolucional.
Materiales para lectura y exploración adicionales 271

4 Tome el tensor de puntuación de importancia (de forma [númFiltros]),y


multiplíquelo con el valor de salida real de la capa convolucional (de forma [1, h,
w, númFiltros]),con radiodifusión (ver apéndice B, apartado B.2.2). Esto nos da un
nuevo tensor de forma [1, h, w, númFiltros]y es una versión "a escala de
importancia" de la salida de la capa.
5 Finalmente, promedie la salida de la capa con escala de importancia en la última dimensión
(filtro) y elimine la primera dimensión (ejemplo), lo que produce una imagen en escala de
grises de la forma [h, w].Los valores de esta imagen son una medida de la importancia de
cada parte de la imagen para el resultado de la clasificación ganadora. Sin embargo, esta
imagen contiene valores negativos y tiene dimensiones más pequeñas que la imagen de
entrada original (es decir, 14 × 14 frente a 224 × 224 en nuestro ejemplo VGG16). Entonces,
ponemos a cero los valores negativos y muestreamos la imagen antes de superponerla en
la imagen de entrada.

El código detallado está en la función llamadagradClassActivationMap()en visualizeconvnet/main.js.


Aunque esta función se ejecuta en Node.js de forma predeterminada, la cantidad de cálculo que
implica es significativamente menor que el algoritmo de gradiente de ascenso en el espacio de
entrada que vimos en la sección anterior. Por lo tanto, debería poder ejecutar el algoritmo CAM
usando el mismo código en el navegador con una velocidad aceptable.
Hablamos de dos cosas en este capítulo: cómo visualizar datos antes de que entren en el
entrenamiento de un modelo de aprendizaje automático y cómo visualizar un modelo después de
entrenarlo. Omitimos intencionalmente el importante paso intermedio, es decir, la visualización del
modelo.tiempoestá siendo entrenado. Este será el tema central del próximo capítulo. La razón por
la que destacamos el proceso de formación es que está relacionado con los conceptos y fenómenos
de subadaptación y sobreadaptación, que son absolutamente críticos para cualquier tarea de
aprendizaje supervisado y, por lo tanto, merecen un tratamiento especial. La visualización facilita
significativamente la detección y corrección de ajustes insuficientes y excesivos. En el próximo
capítulo, revisaremos la biblioteca tfjs-vis que presentamos en la primera parte del capítulo y
veremos que puede ser útil para mostrar cómo progresa un proceso de entrenamiento de
modelos, además de su poder de visualización de datos discutido en este capítulo.

Materiales para lectura y exploración adicionales


- Marco Tulio Ribeiro, Sameer Singh y Carlos Guestrin, “¿Por qué debería confiar en ti?
Explicando las predicciones de cualquier clasificador”, 2016,https://arxiv.org/pdf/
1602.04938.pdf.
-TensorSpace (tensorspace.org) utiliza gráficos animados en 3D para visualizar la
topología y las activaciones internas de convnets en el navegador. Está construido
sobre TensorFlow.js, three.js y tween.js.
-La biblioteca TensorFlow.js tSNE (github.com/tensorflow/tfjs-tsne) es una
implementación eficiente del algoritmo Stochastic Neighbor Embedding (tSNE)
distribuido en t basado en WebGL. Puede ayudarlo a visualizar conjuntos de datos de
alta dimensión al proyectarlos en un espacio 2D mientras conserva las estructuras
importantes en los datos.
272 CPASADO7Visualización de datos y modelos

Ejercicios
1 Experimente con las siguientes características detfjs.vis.linechart():
a Modifique el código del listado 7.2 y vea qué sucede cuando las dos series que se
grafican tienen diferentes conjuntos de valores de coordenadas x. Por ejemplo,
intente hacer que los valores de la coordenada x sean 1, 3, 5 y 7 para la primera
serie y 2, 4, 6 y 8 para la segunda serie. Puede bifurcar y modificar CodePen desde
https://codepen.io/tfjs-book/pen/BvzMZr.
B Todos los gráficos de líneas en el CodePen de ejemplo están hechos con series de datos sin
valores de coordenadas x duplicados. Explora cómo elGráfico de linea()La función maneja
puntos de datos con valores de coordenadas x idénticos. Por ejemplo, en una serie de datos,
incluya dos puntos de datos que tengan un valor de x 0 pero que tengan valores de y
diferentes (como –5 y 5).
2 En el ejemplo de visualización-convnet, use --imagenbandera a lahilo visualizar comando
para especificar su propia imagen de entrada. Dado que solo usamos imágenes de
animales en la sección 7.2, explore otros tipos de contenido de imágenes, como personas,
vehículos, artículos para el hogar y paisajes naturales. Vea qué información útil puede
obtener de las activaciones internas y las CAM.
3 En el ejemplo en el que calculamos la CAM de VGG16, calculamos los gradientes del
puntaje de probabilidad para elvictoriosoclase con respecto a la salida de la última
capa convolucional. ¿Qué sucede si, en cambio, calculamos los gradientes para un no
ganadorclase (como la de menor probabilidad)? Deberíamos esperar que la imagen
CAM resultantenoresalte las partes clave que pertenecen al tema real de la imagen.
Confirme esto modificando el código del ejemplo de visualización-convnet y
volviéndolo a ejecutar. Específicamente, el índice de clase para el cual se calcularán los
gradientes se especifica como argumento de la funcióngradClassActivation-Map()en
visualizar-convnet/cam.js. La función se llama en visualize-convnet/main.js.

Resumen
- Estudiamos el uso básico de tfjs-vis, una biblioteca de visualización estrechamente integrada con
TensorFlow.js. Se puede utilizar para representar tipos básicos de gráficos en el navegador.
-La visualización de datos es una parte indispensable del aprendizaje automático. La presentación
eficiente y eficaz de los datos puede revelar patrones y proporcionar información que, de otro
modo, sería difícil de obtener, como demostramos al utilizar los datos del archivo meteorológico
- de Jena. Se pueden extraer patrones e información detallados de redes neuronales entrenadas.
Mostramos los pasos y resultados de
– Visualizar las activaciones de la capa interna de una convnet profunda.
– Calcular a qué responden al máximo las capas.
– Determinar qué partes de una imagen de entrada son más relevantes para la
decisión de clasificación del convnet. Estos nos ayudan a comprender lo que
aprende la convnet y cómo opera durante la inferencia.
Underfitting, overfitting y
el flujo de trabajo universal
de aprendizaje automático

Este capítulo cubre


- Por qué es importante visualizar el proceso de formación de modelos y
cuáles son las cosas importantes que hay que buscar

- Cómo visualizar y comprender el ajuste insuficiente y el sobreajuste


- La forma principal de lidiar con el sobreajuste: la
regularización y cómo visualizar su efecto
- Cuál es el flujo de trabajo universal del aprendizaje automático, qué
pasos incluye y por qué es una receta importante que guía todas las
tareas supervisadas de aprendizaje automático

En el capítulo anterior, aprendió a usar tfjs-vis para visualizar datos antes de comenzar a diseñar
y entrenar modelos de aprendizaje automático para ellos. Este capítulo comenzará donde lo
dejó y describirá cómo se puede usar tfjs-vis para visualizar la estructura y las métricas de los
modelos durante su entrenamiento. El objetivo más importante al hacerlo es detectar los
fenómenos más importantes dedesajustarysobreajuste. Una vez que podamos detectarlos,
profundizaremos en cómo remediarlos y cómo verificar que nuestros enfoques de remediación
funcionan mediante la visualización.

273
274 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

8.1 Formulación del problema de predicción de la temperatura


Para demostrar el ajuste insuficiente y el sobreajuste, necesitamos un problema concreto de aprendizaje
automático. El problema que usaremos es predecir la temperatura con base en el conjunto de datos
meteorológicos de Jena que acaba de ver en el capítulo anterior. La Sección 7.1 mostró el poder de
visualizar datos en el navegador y los beneficios de hacerlo utilizando el conjunto de datos
meteorológicos de Jena. Con suerte, ha formado una intuición del conjunto de datos al jugar con la
interfaz de usuario de visualización en la sección anterior. Ahora estamos listos para comenzar a aplicar
algo de aprendizaje automático al conjunto de datos. Pero primero, necesitamos definir el problema.
La tarea de predicción se puede considerar como un problema de pronóstico del tiempo de juguete.
Lo que estamos tratando de predecir es la temperatura 24 horas después de un cierto momento en el
tiempo. Intentamos hacer esta predicción utilizando los 14 tipos de mediciones meteorológicas tomadas
en el período de 10 días previo a ese momento.
Aunque la definición del problema es sencilla, la forma en que generamos los datos de entrenamiento
a partir del archivo CSV requiere una explicación cuidadosa porque es diferente de los procedimientos de
generación de datos en los problemas vistos hasta ahora en este libro. En esos problemas, cada fila en el
archivo de datos sin procesar correspondía a un ejemplo de entrenamiento. Así funcionaron los ejemplos
de irisflower, Boston-housing y phishing-detection (véanse los capítulos 2 y 3). Sin embargo, en este
problema, cada ejemplo se forma muestreando y combinando varias filas del archivo CSV. Esto se debe a
que una predicción de la temperatura no se realiza simplemente observando un momento en el tiempo,
sino observando los datos durante un período de tiempo. Consulte la figura 8.1 para ver una ilustración
esquemática del proceso de generación de ejemplos.
Para generar las características de un ejemplo de entrenamiento, muestreamos un conjunto de filas
durante un período de 10 días. En lugar de usar todas las filas de datos de los 10 días, muestreamos cada
sexta fila. ¿Por qué? Por dos razones. Primero, muestrear todas las filas nos daría seis veces más datos y
conduciría a un tamaño de modelo más grande y un tiempo de entrenamiento más largo. En segundo
lugar, los datos en una escala de tiempo de 1 hora tienen mucha redundancia (la presión del aire de hace
6 horas suele ser cercana a la de hace 6 horas y 10 minutos). Al descartar cinco sextos de los datos,
obtenemos un modelo más liviano y de mayor rendimiento sin sacrificar mucho poder predictivo. Las filas
muestreadas se combinan en un tensor de forma de características 2D [pasos de tiempo, número de
características]para nuestro ejemplo de entrenamiento (ver figura 8.1). Por defecto, pasos de tiempotiene
un valor de 240, que corresponde a los 240 tiempos de muestreo distribuidos uniformemente a lo largo
del período de 10 días.númeroCaracterísticases 14, que corresponde a las 14 lecturas de instrumentos
meteorológicos disponibles en el conjunto de datos CSV.
Obtener el objetivo para el ejemplo de entrenamiento es más fácil: solo avanzamos un cierto retraso
de tiempo desde la última fila que entra en el tensor de características y extraemos el valor de la columna
de temperatura. La Figura 8.1 muestra cómo se genera un solo ejemplo de entrenamiento. Para generar
múltiples ejemplos de entrenamiento, simplemente comenzamos desde diferentes filas del archivo CSV.

Es posible que haya notado algo peculiar sobre el tensor de características para nuestro problema de
predicción de temperatura (consulte la figura 8.1): en todos los problemas anteriores, el tensor de
características de un solo ejemplo era 1D, lo que llevó a un tensor 2D cuando se agruparon varios
ejemplos. Sin embargo, en este problema, el tensor de características de un solo ejemplo ya es
Traducido del inglés al español - www.onlinedoctranslator.com

Formulación del problema de predicción de la temperatura 275

jena_climate_2009_2016.csv

pags T Tpot ... wd


tensor de características

de un solo ejemplo
Forma: [pasos de tiempo,
Paso = 6 númeroCaracterísticas]

pasos de tiempo

...
Paso = 6

númeroCaracterísticas

Solía hacerlo

...
... predecir

tensor objetivo
de un solo ejemplo
Forma: [1]
Demora
...

= 144

Figura 8.1 Diagrama esquemático que muestra cómo se genera un solo ejemplo de entrenamiento a partir de los
datos tabulares. Para generar el tensor de características del ejemplo, el archivo CSV se muestrea cadapasofilas (por
ejemplo,paso = 6) hastapasos de tiempodichas filas (por ejemplo,pasos de tiempo = 240). Esto forma un tensor de
forma[pasos de tiempo, número de características], dondenúmeroCaracterísticas(predeterminado: 14) es el
número de columnas de características en el archivo CSV. Para generar el objetivo, muestree la temperatura (T) valor
en el paso de retraso de fila (por ejemplo, 144) después de la última fila que entró en el tensor de características. Se
pueden generar otros ejemplos comenzando desde una fila diferente en el archivo CSV, pero siguen la misma regla.
Esto forma el problema de predicción de la temperatura: dadas las 14 mediciones meteorológicas durante un cierto
período de tiempo (como 10 días) hasta ahora, prediga la temperatura con cierto retraso (como 24 horas) a partir de
ahora. El código que hace lo que se muestra en este diagrama está en elgetNextBatchFunction()función en jena-
weather/data.js.

2D, lo que significa que obtendremos un tensor 3D (de forma [tamaño de lote, pasos de tiempo, número de
características])cuando combinamos múltiples ejemplos en un lote. ¡Esta es una observación astuta! La
forma del tensor de características 2D se origina en el hecho de que las características provienen de un
secuenciade eventos. En concreto, son las medidas meteorológicas tomadas en 240 puntos en el tiempo.
Esto distingue este problema de todos los demás problemas que ha visto hasta ahora, en los que las
características de entrada para un ejemplo dado no abarcan varios momentos en el tiempo, ya sean las
medidas del tamaño de la flor en el problema de la flor del iris o el 28 × 28 valores de píxeles de una
imagen MNIST.1

1
El problema de reconocimiento de comandos de voz del capítulo 4, de hecho, involucró una secuencia de eventos: a
saber, los cuadros sucesivos de espectros de audio que formaron el espectrograma. Sin embargo, nuestra metodología
trató todo el espectrograma como una imagen, ignorando así la dimensión temporal del problema al tratarlo como una
dimensión espacial.
276 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

Esta es la primera vez que encuentra datos de entrada secuenciales en este libro. En el próximo
capítulo, profundizaremos en cómo crear modelos especializados y más potentes (RNN) para datos
secuenciales en TensorFlow.js. Pero aquí abordaremos el problema utilizando dos tipos de modelos
que ya conocemos: regresores lineales y MLP. Esto forma una acumulación de nuestro estudio de
RNN y nos brinda una línea de base que se puede comparar con los modelos más avanzados.

El código real que realiza el proceso de generación de datos ilustrado en la figura 8.1 está en
jena-weather/data.js, bajo la funcióngetNextBatchFunction().Esta es una función interesante
porque en lugar de devolver un valor concreto, devuelve un objeto con una función llamada
próximo().lospróximo()La función devuelve valores de datos reales cuando se llama. El objeto con el
próximo()función se conoce como unaiterador. ¿Por qué usamos esta indirección en lugar de
escribir un iterador directamente? Primero, esto se ajusta a la especificación del generador/
iterador de JavaScript.2Pronto se lo pasaremos altf.datos . generador()API para crear un objeto de
conjunto de datos para el entrenamiento del modelo. La API requiere esta firma de función. En
segundo lugar, nuestro iterador debe ser configurable; una función que devuelve el iterador es una
buena manera de habilitar la configuración.
Puede ver las posibles opciones de configuración desde la firma deobtenerSiguiente-
Función por lotes ():

getNextBatchFunction(
barajar, retroceder, retrasar, tamaño de lote, paso, índice mínimo, índice máximo,
normalizar,
incluir fecha y hora)

Hay bastantes parámetros configurables. Por ejemplo, puede utilizar elmirar atrásargumento
para especificar cuánto tiempo debe mirar hacia atrás al hacer una predicción de
temperatura. También puede utilizar eldemoraargumento para especificar qué tan lejos en el
futuro se hará la predicción de la temperatura. Los argumentosíndice mínimoymaxÍndice le
permite especificar el rango de filas de las que extraer datos, etc.
Convertimos elgetNextBatchFunction()funcionar en untf.data.Conjunto de datosobjeto
al pasarlo altf.datos.generador()función. Como describimos en el capítulo 6, un tf.data.Conjunto de
datosobjeto, cuando se utiliza junto con elajusteDataset()método de unmodelo tfobjeto, nos permite
entrenar el modelo incluso si los datos son demasiado grandes para caber en la memoria WebGL (o
cualquier tipo de memoria de respaldo aplicable) como un todo. losconjunto de datos El objeto
creará un lote de datos de entrenamiento en la GPU solo cuando esté a punto de entrar en el
entrenamiento. Esto es exactamente lo que hacemos aquí para el problema de predicción de la
temperatura. De hecho, no seríamos capaces de entrenar el modelo utilizando el modelo ordinario
encajar() debido al gran número y tamaño de los ejemplos. losajusteDataset()La llamada se puede
encontrar en jena-weather/models.js y se parece a la siguiente lista.

2 Consulte "Iteradores y generadores", documentos web de MDN,http://mng.bz/RPWK.


Formulación del problema de predicción de la temperatura 277

Listado 8.1 Visualizando elajusteDatasetentrenamiento de modelos basado en tfjs-vis

El primer objeto Dataset generará


const trainShuffle = verdadero;
los datos de entrenamiento.
const trenDataset = tf.data.generator(
() => jenaWeatherData.getNextBatchFunction(
trainShuffle, lookBack, delay, batchSize, step, TRAIN_MIN_ROW, TRAIN_MAX_ROW,
normalize, includeDateTime)).prefetch(8); const evalShuffle = falso;

const valDataset = tf.data.generator(


() => jenaWeatherData.getNextBatchFunction(
evalShuffle, lookBack, delay, batchSize, step, VAL_MIN_ROW, VAL_MAX_ROW,
normalize, includeDateTime));

esperar model.fitDataset(trainDataset, { El segundo conjunto de datos

el objeto generará
lotesPerEpoch: 500,
los datos de validación.
épocas,
devoluciones de llamada: devolución de llamada
personalizada, datos de validación: valDataset });
La configuración de validationData para fitDataset()
acepta un objeto Dataset o un conjunto de tensores.
Aquí se utiliza la primera opción.

Los dos primeros campos del objeto de configuración paraajusteDataset()especifique para cuántas
épocas entrenar el modelo y cuántos lotes dibujar para cada época. Como aprendió en el capítulo 6, son
los campos de configuración estándar para unajusteDataset() llamada. Sin embargo, el tercer campo (
devoluciones de llamada: devolución de llamada personalizada)es algo nuevo Es cómo visualizamos el
proceso de formación. NuestroDevolución de llamada personalizadatoma diferentes valores dependiendo
de si el entrenamiento del modelo ocurre en el navegador o, como veremos en el próximo capítulo, en
Node.js.
En el navegador, la funcióntfvis.show.fitCallbacks()proporciona el valor de devolución de llamada
personalizada.La función nos ayuda a visualizar el entrenamiento del modelo en la página web con solo
una línea de código JavaScript. No solo nos ahorra todo el trabajo de acceder y realizar un seguimiento de
los valores de métricas y pérdidas lote por lote y época por época, sino que también elimina la necesidad
de crear y mantener manualmente los elementos HTML en los que se incluirán los gráficos. prestado:

const superficieentrenamiento =
tfvis.visor().surface({pestaña: modelType, nombre: 'Model Training'}); const
customCallback = tfvis.show.fitCallbacks(trainingSurface,
['pérdida', 'val_loss'], {
devoluciones de llamada: ['enBatchEnd', 'enEpochEnd']

}));

El primer argumento aencajar devoluciones de llamada ()especifica un área de representación creada con el
tfvis.visor().superficie()método. se llama unsuperficie de la viseraen la terminología de tfjs-vis. Un visor es
un contenedor que lo ayuda a organizar convenientemente toda la visualización relacionada con sus
tareas de aprendizaje automático en el navegador. Estructuralmente, una visera se organiza en dos
niveles de jerarquía. En el nivel superior, puede haber una o más pestañas en las que el usuario puede
navegar mediante clics. En el nivel inferior, cada pestaña contiene uno o mássuperficies.
278 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

lostfvis.visor().superficie()método, con supestañaynombrecampos de configuración, le permite crear una


superficie en una pestaña de visor designada con un nombre designado. La superficie de una visera no se
limita a la pérdida de representación y las curvas métricas. De hecho, todos los gráficos básicos que
mostramos con el ejemplo de CodePen en la sección 7.1 se pueden representar en superficies de visor.
Dejamos esto como un ejercicio para usted al final de este capítulo.
El segundo argumento a favorencajar devoluciones de llamada ()especifica qué pérdidas y métricas se
representarán en la superficie del visor. En este caso, trazamos la pérdida de los conjuntos de datos de
entrenamiento y validación. El tercer argumento contiene un campo que controla la frecuencia con la que
se actualizan los gráficos. Al usar ambosenBatchEndyenEpochEnd,obtendremos actualizaciones al final de
cada lote y cada época. En la siguiente sección, examinaremos las curvas de pérdida creadas porencajar
devoluciones de llamada ()y utilícelos para detectar desajustes y sobreajustes.

8.2 Ajuste insuficiente, sobreajuste y contramedidas


Durante el entrenamiento de un modelo de aprendizaje automático, queremos monitorear qué tan bien
nuestro modelo captura los patrones en los datos de entrenamiento. Se dice que un modelo que no capta
muy bien los patrones esdesequilibrado;un modelo que captura los patronestambiénbien, en la medida
en que lo que aprende se generaliza mal a nuevos datos, se dice que essobreajustado. Un modelo
sobreajustado puede volver a encarrilarse mediante contramedidas como la regularización. En esta
sección, mostraremos cómo la visualización puede ayudarnos a detectar estos comportamientos modelo
y los efectos de las contramedidas.

8.2.1 Infraequipamiento

Para resolver el problema de la predicción de la temperatura, primero probemos el modelo de


aprendizaje automático más simple posible: un regresor lineal. El código del listado 8.2 (de
jenaweather/index.js) crea dicho modelo. Utiliza una capa densa con una sola unidad y la activación
lineal predeterminada para generar la predicción. Sin embargo, en comparación con el regresor
lineal que construimos para el problema de predicción del tiempo de descarga en el capítulo 2, este
modelo tiene una capa extra plana. Esto se debe a que la forma del tensor de características en
este problema es 2D, que debe aplanarse en 1D para cumplir con el requisito de la capa densa
utilizada para la regresión lineal. Este proceso de aplanamiento se ilustra en la figura 8.2. Es
importante tener en cuenta que esta operación de aplanamiento descarta la información sobre el
ordenamiento secuencial (temporal) de los datos.

Listado 8.2 Creando un modelo de regresión lineal para el problema de predicción de temperatura

función construirModeloRegresiónLineal(formaDeEntrada) { Aplana la forma de entrada


const modelo = tf.secuencial(); [batchSize, timeSteps,
modelo.add(tf.layers.flatten({inputShape})); numFeatures] a [batchSize,
modelo.add(tf.layers.dense({unidades: 1})); modelo de timeSteps * numFeatures] para
retorno; aplicar la capa densa
Una capa densa de una sola unidad con la

} activación predeterminada (lineal)


es un regresor lineal.
Underfitting, overfitting y contramedidas 279

tensor de características

de un solo ejemplo
Forma: [pasos de tiempo,
númeroCaracterísticas]

pasos de tiempo númeroCaracterísticas


aplanar Para
pasos de tiempo ... regresor lineal
o MLP
...

númeroCaracterísticas

Figura 8.2 Aplanamiento del tensor de forma de características 2D[pasos de tiempo, número de características]en un tensor de forma 1D
[pasos de tiempo × número de características], como lo hizo el regresor lineal en el listado 8.2 y el modelo MLP en el listado 8.3

Una vez construido el modelo, lo compilamos para entrenarlo con

model.compile({pérdida: 'meanAbsoluteError', optimizador: 'rmsprop'});

Aquí, usamos la función de pérdidaerror absoluto medioporque nuestro problema es predecir un valor
continuo (la temperatura normalizada). A diferencia de algunos de los problemas anteriores, no se define
ninguna métrica separada,
porque la función de pérdida MAE en sí
sirve como la métrica interpretable por
humanos. Sin embargo, tenga en cuenta
que dado que estamos prediciendo el
normalizado temperatura, la pérdida
MAE debe multiplicarse por la desviación
estándar de la columna de temperatura
(8,476 grados Celsius) para convertirla en
un error de predicción en términos
absolutos. Por ejemplo, si obtenemos un
MAE de 0,5, se traduce en 8,476 * 0,5 =
4,238 grados Celsius de error de
predicción.
En la interfaz de usuario de demostración,
elija Regresión lineal en el menú desplegable
Tipo de modelo y haga clic en Entrenar modelo
para iniciar el entrenamiento del regresor
lineal. Inmediatamente después de que
comience el entrenamiento, verá un resumen
tabular del modelo en una "tarjeta" que Figura 8.3 El visor tfjs-vis que visualiza el entrenamiento de un modelo
de regresión lineal. Arriba: una tabla de resumen para el modelo.
aparece en el lado derecho de la página (vea la
Abajo: las curvas de pérdida durante 20 épocas de entrenamiento. Este
captura de pantalla en la figura 8.3). Esta tabla gráfico se crea contfvis.show . encajar devoluciones de llamada
de resumen del modelo es ()(ver jena-weather/index.js).
280 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

algo similar a la salida de texto de unResumen Modelo()call pero se representa


gráficamente en HTML. El código que crea la tabla es el siguiente:
const superficie = tfvis.visor().surface({nombre: 'Resumen del modelo', pestaña});
tfvis.show.modelSummary(superficie, modelo);

Con la superficie creada, dibujamos una tabla de resumen del modelo en ella pasando la superficie
atfvis.show.modelSummary(),como en la segunda línea del fragmento de código anterior.
En la parte Resumen del modelo de la pestaña de regresión lineal hay un gráfico que muestra las
curvas de pérdida del entrenamiento del modelo (figura 8.3). Es creado por elencajar devoluciones de
llamada ()llamada que describimos en el apartado anterior. A partir de la gráfica, podemos ver qué tan
bien funciona el regresor lineal en el problema de predicción de la temperatura. Tanto las pérdidas de
entrenamiento como las de validación acaban oscilando en torno a 0,9, lo que corresponde a 8,476 * 0,9 =
7,6 grados centígrados en términos absolutos (recordemos que 8,476 es la desviación estándar de la
columna de temperatura en el archivo CSV). Esto significa que después del entrenamiento, nuestro
regresor lineal comete un error de predicción de 7,6 grados Celsius (o 13,7 grados Fahrenheit) en
promedio. Estas predicciones son bastante malas. ¡Nadie querría confiar en el pronóstico del tiempo
basado en este modelo! Esto es un ejemplo dedesajustar.
El ajuste insuficiente suele ser el resultado del uso de una capacidad de representación (poder)
insuficiente para modelar la relación característica-objetivo. En este ejemplo, nuestro regresor
lineal es estructuralmente demasiado simple y, por lo tanto, no tiene suficiente potencia para
capturar la relación entre los datos meteorológicos de los 10 días anteriores y la temperatura del
día siguiente. Para superar el desajuste, normalmente aumentamos la potencia del modelo
haciéndolo más grande. Los enfoques típicos incluyen agregar más capas (con activaciones no
lineales) al modelo y aumentar el tamaño de las capas (como el número de unidades en una capa
densa). Entonces, agreguemos una capa oculta al regresor lineal y veamos cuánta mejora podemos
obtener del MLP resultante.

8.2.2 Reequipamiento

La función que crea modelos MLP está en el listado 8.3 (de jena-weather/index.js). El MLP que
crea incluye dos capas densas, una como capa oculta y otra como capa de salida, además de
una capa plana que tiene el mismo propósito que en el modelo de regresión lineal. Puede ver
que la función tiene dos argumentos más en comparación con
construirModeloRegresiónLineal()en el listado 8.2. En particular, elkernelRegularizer
ytasa de deserción escolarLos parámetros son las formas en que combatiremos el sobreajuste más
adelante. Por ahora, veamos qué precisión de predicción tiene un MLP que no usakernelRegularizer otasa
de deserción escolares capaz de lograr.

Listado 8.3 Creando un MLP para el problema de predicción de temperatura

function buildMLPModel(inputShape, kernelRegularizer, dropoutRate) {


const modelo = tf.secuencial();
modelo.add(tf.layers.flatten({inputShape}));
modelo.add(tf.layers.dense({
unidades: 32,
kernelRegularizer
Si lo especifica la persona que llama,
activación: 'relu',
agregue la regularización al kernel de
})); la capa densa oculta.
Underfitting, overfitting y contramedidas 281

if (tasa de abandono > 0) {


model.add(tf.layers.dropout({tasa: tasa de deserción escolar}));

}
modelo.add(tf.layers.dense({unidades: 1})); modelo
Si lo especifica la persona que llama, agregue
de retorno;
una capa de omisión entre la capa densa
} oculta y la capa densa de salida.

El panel A de la figura 8.4 muestra las curvas de pérdida del MLP. En comparación con las curvas de
pérdida del regresor lineal, podemos ver algunas diferencias importantes:

- Las curvas de pérdida de entrenamiento y validación muestran un patrón divergente. Esto es diferente
del patrón de la figura 8.3, donde dos curvas de pérdida muestran tendencias en gran medida
consistentes.

- La pérdida de entrenamiento converge hacia un error mucho menor que antes. Después de 20
épocas de entrenamiento, la pérdida de entrenamiento tiene un valor de alrededor de 0,2, lo que
corresponde a un error de 8,476 * 0,2 = 1,7 grados centígrados, mucho mejor que el resultado de
la regresión lineal.
- Sin embargo, la pérdida de validación disminuye brevemente en las dos primeras épocas y luego
comienza a subir lentamente. Al final de la época 20, tiene un valor significativamente más alto que la
pérdida de entrenamiento (0,35 o alrededor de 3 grados centígrados).

A B
Curvas de pérdida Curvas de pérdida

Serie Serie
0.8 entrenar 1.0 entrenar

valor valor

0.8
0.6
0.6
Pérdida

Pérdida

0.4
0.4
0.2 0.2

0.0 0.0
0 2 4 6 8 10 12 14 16 18 20 Época# 0 2 4 6 8 10 12 14 16 18 20 Época#

Figura 8.4 Las curvas de pérdida al aplicar dos modelos MLP diferentes en el problema de predicción de temperatura. Panel A: de
un modelo MLP sin ninguna regularización. Panel B: a partir de un modelo MLP del mismo tamaño de capa y cuenta que el modelo
del panel A, pero con regularización L2 de los núcleos de las capas densas. Observe que los rangos del eje y difieren ligeramente
entre los dos paneles.

La disminución de más de cuatro veces en la pérdida de entrenamiento en relación con el resultado


anterior se debe al hecho de que nuestro MLP tiene una potencia mayor que el modelo de regresión
lineal gracias a una capa más y varias veces más parámetros de peso entrenables. Sin embargo, la mayor
potencia del modelo tiene un efecto secundario: hace que el modelo se ajuste a los datos de
entrenamiento significativamente mejor que los datos de validación (datos que el modelo no puede ver
durante el entrenamiento). Esto es un ejemplo desobreajuste. Es un caso en el que un modelo “presta
demasiada atención” a los detalles irrelevantes en los datos de entrenamiento, hasta el punto de que las
predicciones del modelo comienzan a generalizarse mal a datos no vistos.
282 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

8.2.3 Reducir el sobreajuste con la regularización de peso y visualizar su funcionamiento


En el capítulo 4, redujimos el sobreajuste en una convnet agregando capas de abandono al
modelo. Aquí, veamos otro enfoque de reducción de sobreajuste que se usa con frecuencia:
agregar regularización a los pesos. En la interfaz de usuario de demostración de Jena-weather, si
selecciona el tipo de modelo MLP con regularización L2, el código subyacente creará un MLP
llamando construirMLPModel() (listado 8.3) de la siguiente manera:

modelo = buildMLPModel(inputShape, tf.regularizers.l2());

El segundo argumento, el valor de retorno detf.regularizadores.l2()—es unregularizador L2.


Introduciendo el código anterior en elconstruirMLPModel()función en el listado 8.3, puede ver
que el regularizador L2 entra en elkernelRegularizerde la configuración de la capa densa
oculta. Esto une el regularizador L2 al núcleo de la capa densa. Cuando un peso (como el
núcleo de una capa densa) tiene un regularizador adjunto, decimos que el peso es
regularizado. De manera similar, cuando se regularizan algunos o todos los pesos de un
modelo, decimos que el modelo está regularizado.
¿Qué le hace el regularizador al kernel de capa densa y al MLP al que pertenece? Añade un
término extra a la función de pérdida. Considere cómo se calcula la pérdida del MLP no
regularizado: se define simplemente como el MAE entre los objetivos y las predicciones del
modelo. En pseudocódigo, se puede expresar como

pérdida = error absoluto medio (objetivos, predicciones)

Con un peso regularizado, la pérdida del modelo incluye un término extra. En pseudocódigo,

pérdida = error absoluto medio (objetivos, predicción) + 12 Tasa * 12 (núcleo)

Aquí,l2Rate * l2(núcleo)es el término extra de regularización L2 de la función de pérdida.


A diferencia del MAE, este término nonodependen de las predicciones del modelo. En
cambio, depende solo del núcleo (un peso de la capa) que se regulariza. Dado el valor
del kernel, genera un número asociado solo con los valores del kernel. Puede pensar en
el número como una medida de cuán indeseable es el valor actual del kernel.
Ahora veamos la definición detallada de la función de regularización L2:l2 (núcleo).Calcula los
cuadrados sumados de todos los valores de peso. Por ejemplo, imagina que nuestro núcleo tiene
una forma pequeña de [2, 2]en aras de la simplicidad, y supongamos sus valores
están [[0,1, 0,2], [-0,3, -0,4]];luego,
l2(núcleo) = 0,1^2 + 0,2^2 + (-0,3)^2 + (-0,4)^2 = 0,3

Por lo tanto,l2 (núcleo)siempre devuelve un número positivo que penaliza los valores de gran
peso ennúcleo.Con el término incluido en la pérdida total, fomenta todos los elementos de
núcleoser menor en valor absoluto, siendo todo lo demás igual.
Ahora, la pérdida total incluye dos términos diferentes: el desajuste de la predicción del objetivo y un
término relacionado connúcleo's magnitudes. Como resultado, el proceso de entrenamiento intentará no
solo minimizar el desajuste de la predicción objetivo, sino también reducir la suma de los cuadrados de
los elementos del kernel. A menudo, los dos objetivos entrarán en conflicto entre sí. Para
Underfitting, overfitting y contramedidas 283

Por ejemplo, una reducción en la magnitud de los elementos del núcleo puede reducir el segundo
término pero aumentar el primero (la pérdida de MSE). ¿Cómo equilibra la pérdida total la
importancia relativa de los dos términos en conflicto? Ahí es donde elTasa l2El multiplicador entra
en juego. Cuantifica la importancia del término L2 en relación con el término de error de predicción
objetivo. Cuanto mayor sea el valor detasa l2,más tenderá el proceso de entrenamiento a reducir el
término de regularización de L2 a costa de un mayor error de predicción de objetivos. Este término,
que por defecto es1e-3,es un hiperparámetro cuyo valor se puede ajustar a través de la
optimización de hiperparámetros.
Entonces, ¿cómo nos ayuda el regularizador L2? El panel B de la figura 8.4 muestra las curvas de
pérdida del MLP regularizado. Al compararlo con las curvas del MLP no regularizado (panel A de la misma
figura), puede ver que el modelo regularizado produce curvas de pérdida de entrenamiento y validación
menos divergentes. Esto significa que el modelo ya no "presta atención indebida" a los patrones
idiosincrásicos en el conjunto de datos de entrenamiento. En cambio, el patrón que aprende del conjunto
de entrenamiento se generaliza bien a ejemplos no vistos en el conjunto de validación. En nuestro MLP
regularizado, solo la primera capa densa incorporó un regularizador, mientras que la segunda capa densa
no lo hizo. Pero eso resultó ser suficiente para superar el sobreajuste en este caso. En la siguiente sección,
profundizaremos en por qué los valores de kernel más pequeños conducen a un menor sobreajuste.

VESTIMACIÓN DEL EFECTO DE LA REGULARIZACIÓN EN LOS VALORES DE PESO


Dado que el regularizador L2 funciona fomentando que el kernel de la capa densa oculta
tenga valores más pequeños, deberíamos poder ver que los valores del kernel posteriores al
entrenamiento son más pequeños en el MLP regularizado que en el no regularizado. ¿Cómo
podemos hacer eso en TensorFlow.js? lostfvis.mostrar.capa()La función de tfjs-vis permite
visualizar los pesos de un modelo TensorFlow.js con una línea de código. El listado 8.4 es un
extracto de código que muestra cómo se hace esto. El código se ejecuta cuando finaliza el
entrenamiento de un modelo MLP. lostfvis.mostrar.capa()call toma dos argumentos: la
superficie de la visera en la que ocurrirá el renderizado y la capa que se renderiza.

Listado 8.4 Visualizando la distribución del peso de las capas (de jena-weather/index.js)

función visualizarModelLayers (pestaña, capas, nombres de capa) {


capas.forEach((capa, i) => {
const superficie = tfvis.visor().superficie({nombre: nombres de capa[i], pestaña});
tfvis.show.layer(superficie, capa);
});
}

La visualización que hace este código se muestra en la figura 8.5. Los paneles A y B muestran los
resultados de los MLP no regularizados y regularizados, respectivamente. En cada panel,
tfvis.mostrar.capa()muestra una tabla de los pesos de la capa, con detalles sobre los nombres de los
pesos, su forma y recuento de parámetros, mínimo/máximo de los valores de los parámetros y
recuentos de los valores de los parámetros cero y NaN (el último de los cuales puede ser útil para
diagnosticar carreras de entrenamiento). La visualización de capas también contiene Mostrar
284 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

A B
Capa densa 1 Capa densa 1

Figura 8.5 Distribución de los valores en el kernel con (panel A) y sin (panel B) regularización L2. La visualización se
crea contfvis.mostrar.capa(). Tenga en cuenta que los ejes x de los dos histogramas tienen escalas diferentes.

Botones de distribución de valores para cada uno de los pesos de la capa, que, al hacer clic, crearán
un histograma de los valores en el peso.
Al comparar las gráficas de los dos sabores de MLP, puede ver una clara diferencia: los valores
del kernel se distribuyen en un rango considerablemente más estrecho con la regularización L2
que sin ella. Esto se refleja tanto en los valores mínimos/máximos (la primera fila) como en el
histograma de valores. ¡Esto es regularización en el trabajo!
Pero, ¿por qué los valores kernel más pequeños dan como resultado un sobreajuste reducido y una
generalización mejorada? Una forma intuitiva de entender esto es que la regularización de L2 aplica el
principio de la navaja de Occam. En términos generales, una magnitud mayor en un parámetro de
ponderación tiende a hacer que el modelo se ajuste a los detalles de grano fino en las características de
entrenamiento que ve, y una magnitud menor tiende a permitir que el modelo ignore esos detalles. En el
caso extremo, un valor de kernel de cero significa que el modelo no atiende en absoluto a su
característica de entrada correspondiente. La regularización L2 fomenta que el modelo sea más
"económico" al evitar valores de ponderación de gran magnitud y retenerlos solo cuando vale la pena el
costo (cuando la reducción en el término de desajuste de predicción objetivo supera la pérdida del
regularizador).
La regularización de L2 es solo una de las armas contra el sobreajuste en el arsenal del practicante del
aprendizaje automático. En el capítulo 4, demostramos el poder de las capas de abandono. La deserción
es una poderosa contramedida para el sobreajuste en general. También nos ayuda a reducir el
sobreajuste en este problema de predicción de temperatura. Puede verlo usted mismo eligiendo el tipo
de modelo MLP con abandono en la interfaz de usuario de demostración. La calidad de la capacitación
que obtiene del MLP habilitado para abandono es comparable a la que obtiene del MLP regularizado con
L2. Discutimos cómo y por qué funciona el abandono en la sección 4.3.2 cuando lo aplicamos a una
convnet MNIST, por lo que no lo repetiremos aquí. Sin embargo,
Underfitting, overfitting y contramedidas 285

La tabla 8.1 proporciona una descripción general rápida de las contramedidas más utilizadas para
el sobreajuste. Incluye una descripción intuitiva de cómo funciona cada uno de ellos y la API
correspondiente en TensorFlow.js. La pregunta sobre qué contramedida usar para un problema en
particular generalmente se responde a través de 1) siguiendo modelos bien establecidos que
resuelven problemas similares y 2) tratando la contramedida como un hiperparámetro y
buscándola a través de la optimización de hiperparámetros (sección 3.1.2). Además, cada método
de reducción de sobreajuste en sí mismo contiene parámetros ajustables que también se pueden
determinar a través de la optimización de hiperparámetros (consulte la última columna de la tabla
8.1).

Tabla 8.1 Una descripción general de los métodos comúnmente utilizados para reducir el sobreajuste en TensorFlow.js

Nombre de API correspondiente en


método Cómo funciona el método TensorFlow.js Principales parámetros libres

regularizador L2 Asigna una pérdida tf.regularizadores.l2() Tasa de regularización L2


positiva (penalización) al
peso calculando la suma Consulte la sección "Reducción del
cuadrados de los valores de los sobreajuste con la regularización del
parámetros del peso. Eso peso", por ejemplo.
fomenta que el peso tenga un
parámetro más pequeño
valores.

regularizador L1 Como regularizadores L2, tf.regularizadores.l1() Tasa de regularización L1


fomenta el peso
los parámetros sean más pequeños.
Sin embargo, la pérdida

asigna a un peso se
basa en la suma valores
absolutosde los
parámetros, en lugar de
cuadrados sumados. Esta
definición de pérdida de

regularización hace que se evalúen

más parámetros de ponderación.

convertirse en cero (es decir,


"pesos más escasos").

Combinado L1-L2 Una suma ponderada de las tf.regularizadores.l1l2() Tasa de regularización L1


regularizador pérdidas de regularización L1 y L2. Tasa de regularización L2

Abandonar Establece aleatoriamente una tf.layers.dropout() Tasa de deserción escolar

fracción de las entradas en cero


durante el entrenamiento (pero no Véase la sección 4.3.2, por ejemplo.
durante la inferencia) para romper
las correlaciones espurias (o
"conspiración" en palabras de
Geoff Hinton)
entre parámetros de peso
ters que emergen durante el
entrenamiento.
286 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

Tabla 8.1 Una descripción general de los métodos comúnmente utilizados para reducir el sobreajuste en TensorFlow.js(continuado)

Nombre de API correspondiente en


método Cómo funciona el método TensorFlow.js Principales parámetros libres

Lote Aprende la media y la desviación tf.capas Varios (verhttps://js


normalización estándar de sus valores de entrada . normalización por lotes () . tensorflow.org/api/latest/
durante el entrenamiento y usa las #
estadísticas aprendidas para capas.batchNormalización)
normalizar las entradas a la media

cero y la desviación estándar de la

unidad como su salida.

Parada temprana de Detiene el entrenamiento del modelo tf.devoluciones de llamada minDelta: El umbral por
entrenamiento basado en tan pronto como se detiene el valor de . detención anticipada() debajo del cual se ignorarán
pérdida del conjunto de validación pérdida de fin de época en el conjunto los cambios
de validación paciencia: ¿Cuántas épocas
decreciente. consecutivas sin mejoría se
toleran como máximo?

Para concluir esta sección sobre la visualización de subajustes y sobreajustes, proporcionamos un diagrama
esquemático como una regla general rápida para detectar esos estados (figura 8.6). Como muestra el panel A, el
ajuste insuficiente se produce cuando el modelo alcanza un valor de pérdida subóptimo (alto),
independientemente de si se encuentra en el conjunto de entrenamiento o de validación. En el panel B, vemos un
patrón típico de sobreajuste, donde la pérdida de entrenamiento parece bastante satisfactoria (baja), pero la
pérdida de validación es peor (mayor) en comparación. La pérdida de validación puede estabilizarse e incluso
comenzar a subir, incluso cuando la pérdida del conjunto de entrenamiento continúa disminuyendo. El panel C es
el estado en el que queremos estar, es decir, un estado en el que el valor de pérdida no difiera demasiado entre
los conjuntos de entrenamiento y validación para que la pérdida de validación final sea baja. Tenga en cuenta que
la frase "suficientemente bajo" puede ser relativa, especialmente para problemas que ningún modelo existente
puede resolver a la perfección. Es posible que surjan nuevos modelos en el futuro y reduzcan la pérdida
alcanzable en relación con lo que tenemos en el panel C. En ese punto, el patrón en el panel C se convertiría en
un caso de ajuste insuficiente y necesitaríamos adoptar el nuevo tipo de modelo para poder para arreglarlo,
posiblemente pasando nuevamente por el ciclo de sobreajuste y regularización.

C.Frontera entre
UN.subequipamiento B.sobreajuste sub y sobreajuste

Capacitación

Validación
Pérdida

Pérdida

Pérdida

Época de entrenamiento Época de entrenamiento Época de entrenamiento

Figura 8.6 Diagrama esquemático que muestra las curvas de pérdida de casos simplificados de ajuste insuficiente (panel A),
sobreajuste (panel B) y ajuste perfecto (panel C) en el entrenamiento del modelo.
El flujo de trabajo universal del aprendizaje automático 287

Finalmente, tenga en cuenta que la visualización del entrenamiento no se limita a las pérdidas. A menudo,
también se visualizan otras métricas para ayudar a monitorear el proceso de capacitación. Ejemplos de
esto están salpicados a lo largo del libro. Por ejemplo, en el capítulo 3, trazamos las curvas ROC al
entrenar un clasificador binario para sitios web de phishing. También renderizamos la matriz de
confusión al entrenar el clasificador de flor de iris. En el capítulo 9, mostraremos un ejemplo de
visualización de texto generado por máquina durante el entrenamiento de un generador de texto. Ese
ejemplo no implicará una GUI pero, no obstante, proporcionará información útil e intuitiva en tiempo real
sobre el estado del entrenamiento del modelo. Específicamente, al observar el texto generado por el
modelo, puede obtener una idea intuitiva de qué tan bueno es el texto generado por el modelo
actualmente.

8.3 El flujo de trabajo universal del aprendizaje automático


Hasta este punto, ha visto todos los pasos importantes para diseñar y entrenar un modelo de aprendizaje
automático, incluida la adquisición, el formato, la visualización y la ingesta de datos; elegir la topología del
modelo y la función de pérdida apropiados para el conjunto de datos; y entrenar al modelo. También ha
visto algunos de los modos de falla más importantes que pueden aparecer durante el proceso de
entrenamiento: subajuste y sobreajuste. Por lo tanto, este es un buen lugar para que revisemos lo que
hemos aprendido hasta ahora y reflexionemos sobre lo que es común entre los procesos del modelo de
aprendizaje automático para diferentes conjuntos de datos. La abstracción resultante es lo que llamamos
el flujo de trabajo universal del aprendizaje automático. Enumeraremos el flujo de trabajo paso a paso y
ampliaremos las consideraciones clave en cada paso:

1 Determine si el aprendizaje automático es el enfoque correcto. Primero, considere si el


aprendizaje automático es el enfoque correcto para su problema y continúe con los siguientes
pasos solo si la respuesta es sí. En algunos casos, un enfoque que no sea de aprendizaje
automático funcionará igual de bien o incluso mejor, a un costo menor. Por ejemplo, con
suficientes esfuerzos de ajuste del modelo, puede entrenar una red neuronal para "predecir" la
suma de dos números enteros tomando los números enteros como datos de entrada de texto
(por ejemplo, el ejemplo de adición en el repositorio tfjs-examples). Pero esto está lejos de ser la
solución más eficiente o confiable para este problema: la buena y antigua operación de adición en
la CPU es suficiente en este caso.
2 Defina el problema de aprendizaje automático y lo que está tratando de predecir usando los datos
. En este paso, debe responder dos preguntas:
– ¿Qué tipo de datos están disponibles?En el aprendizaje supervisado, solo puede aprender a predecir
algo si tiene datos de entrenamiento etiquetados disponibles. Por ejemplo, el modelo de predicción
meteorológica que vimos anteriormente en este capítulo solo es posible porque el conjunto de datos
meteorológicos de Jena está disponible. La disponibilidad de datos suele ser un factor limitante en
esta etapa. Si los datos disponibles son insuficientes, es posible que deba recopilar más datos o
contratar personas para etiquetar manualmente un conjunto de datos sin etiquetar.
– ¿A qué tipo de problema te enfrentas?¿Es clasificación binaria, clasificación
multiclase, regresión u otra cosa? La identificación del tipo de problema guiará
la elección de la arquitectura del modelo, la función de pérdida, etc.
288 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

No puede pasar al siguiente paso hasta que sepa cuáles son las entradas y salidas y
qué datos usará. Tenga en cuenta las hipótesis que ha hecho implícitamente en esta
etapa:
– Tiene la hipótesis de que las salidas se pueden predecir dadas las entradas (la entrada
sola contiene suficiente información para que un modelo prediga la salida para todos los
ejemplos posibles en este problema).
– Plantea la hipótesis de que los datos disponibles son suficientes para que un modelo aprenda
esta relación de entrada-salida.
Hasta que tenga un modelo de trabajo, estas son solo hipótesis que esperan ser validadas o
invalidadas. No todos los problemas tienen solución: el hecho de que haya reunido un gran
conjunto de datos etiquetados que mapea de X a Y no significa que X contenga suficiente
información para el valor de Y. Por ejemplo, si está tratando de predecir el precio futuro de
una acción basada en el historial del precio de la acción, es probable que falle porque el
historial de precios no contiene suficiente información predictiva sobre el precio futuro.

Una clase de problemas irresolubles que debe tener en cuenta esno estacionario problemas
en los que la relación entrada-salida cambia con el tiempo. Suponga que está tratando de crear
un motor de recomendaciones para ropa (dado el historial de compras de ropa de un usuario) y
está entrenando su modelo solo con los datos de un año. El gran problema aquí es que los gustos
de las personas por la ropa cambian con el tiempo. No se garantiza que un modelo que funciona
con precisión en los datos de validación del año pasado funcione con la misma precisión este año.
Tenga en cuenta que el aprendizaje automático solo se puede usar para aprender patrones que
están presentes en los datos de entrenamiento. En este caso, actualizar los datos y entrenar
continuamente nuevos modelos será una solución viable.

3 Identifique una forma de medir de forma fiable el éxito de un modelo entrenado en su


objetivo. Para tareas simples, esto puede ser solo precisión de predicción, precisión y
recuperación, o la curva ROC y el valor AUC (consulte el capítulo 3). Pero en muchos casos,
requerirá métricas específicas de dominio más sofisticadas, como la tasa de retención de
clientes y las ventas, que están mejor alineadas con objetivos de mayor nivel, como el éxito
del negocio.
4 Preparar el proceso de evaluación. Diseñe el proceso de validación que utilizará para evaluar sus
modelos. En particular, debe dividir sus datos en tres conjuntos homogéneos pero que no se
superpongan: un conjunto de entrenamiento, un conjunto de validación y un conjunto de prueba. Las
etiquetas de los conjuntos de validación y prueba no deben filtrarse en los datos de entrenamiento. Por
ejemplo, con la predicción temporal, los datos de validación y prueba deben provenir de intervalos de
tiempodespuéslos datos de entrenamiento. Su código de preprocesamiento de datos debe estar cubierto
por pruebas para protegerse contra errores.

5 Vectorizar los datos. Convierte los datos en tensores, también conocidos comonorte-matrices
dimensionales, la lengua franca de los modelos de aprendizaje automático en marcos como
Tensor-Flow.js y TensorFlow. Tenga en cuenta las siguientes pautas para la vectorización de datos:
El flujo de trabajo universal del aprendizaje automático 289

– Los valores numéricos que toman los tensores normalmente deben escalarse a
valores pequeños y centrados: por ejemplo, dentro del [-1, 1]o [0, 1]intervalo.
– Si diferentes características (como la temperatura y la velocidad del viento) toman valores
en diferentes rangos (datos heterogéneos), entonces los datos deben normalizarse,
generalmente normalizados en z a media cero y desviación estándar unitaria para cada
característica.
Una vez que sus tensores de datos de entrada y datos de destino (salida) estén listos, puede
comenzar a desarrollar modelos.
6 Desarrolle un modelo que supere una línea base de sentido común. Desarrolle un modelo que supere
una línea de base que no sea de aprendizaje automático (como predecir el promedio de la población
para un problema de regresión o predecir el último punto de datos en un problema de predicción de
series temporales), demostrando así que el aprendizaje automático realmente puede agregar valor a su
solución . Este puede no ser siempre el caso (vea el paso 1).
Suponiendo que las cosas van bien, debe tomar tres decisiones clave para construir su primer
modelo de aprendizaje automático que supere la línea de base:

– Activación de última capa: esto establece restricciones útiles para la salida del modelo.
Esta activación debe adaptarse al tipo de problema que está resolviendo. Por ejemplo, el
clasificador de sitios web de phishing en el capítulo 3 usó la activación sigmoidea para su
última capa (de salida) debido a la naturaleza de clasificación binaria del problema, y los
modelos de predicción de temperatura en este capítulo usaron la activación lineal para
la capa debido a a la naturaleza regresiva del problema.
– Función de pérdida—De manera similar a la activación de la última capa, la función de
pérdida debe coincidir con el problema que está resolviendo. Por ejemplo, use
binariaCrosentropíapara problemas de clasificación binaria,Cruzentropía categóricapara
problemas de clasificación múltiple, yerror medio cuadradopara problemas de regresión.
– Configuración del optimizador—El optimizador es lo que impulsa las actualizaciones de los pesos de
la red neuronal. ¿Qué tipo de optimizador se debe utilizar? ¿Cuál debería ser su tasa de aprendizaje?
Por lo general, estas son preguntas respondidas mediante el ajuste de hiperparámetros. Pero en la
mayoría de los casos, puede comenzar con seguridad con elrmspropoptimizador y su tasa de
aprendizaje predeterminada.
7 Desarrollar un modelo con capacidad suficiente y sobreajustar los datos de entrenamiento.
Aumente gradualmente la arquitectura de su modelo cambiando manualmente los
hiperparámetros. Desea llegar a un modelo que se adapte demasiado al conjunto de
entrenamiento. Recuerde que la tensión central y universal en el aprendizaje automático
supervisado es entremejoramiento(ajustando los datos vistos durante el entrenamiento) y
generalización(ser capaz de hacer predicciones precisas para datos no vistos). El modelo ideal es
aquel que se encuentra justo en el límite entre la insuficiencia y la sobrecapacidad: es decir, entre
la capacidad insuficiente y la capacidad excesiva. Para averiguar dónde está este borde, primero
debes cruzarlo.
Para cruzarlo, debe desarrollar un modelo que sobreajuste. Esto suele ser
bastante fácil. Puedes
290 CPASADO8Underfitting, overfitting y el flujo de trabajo universal del aprendizaje automático

– Añadir más capas


– Hacer cada capa más grande
– Entrena el modelo para más épocas
Utilice siempre la visualización para monitorear las pérdidas de entrenamiento y validación, así
como cualquier métrica adicional que le interese (como AUC) en los conjuntos de entrenamiento y
validación. Cuando vea que la precisión del modelo en el conjunto de validación comienza a
degradarse (figura 8.6, panel B), habrá logrado el sobreajuste.
8 Agregue regularización a su modelo y ajuste los hiperparámetros. El siguiente paso es agregar
regularización a su modelo y ajustar aún más sus hiperparámetros (generalmente de forma
automatizada) para acercarse lo más posible al modelo ideal que no se ajusta ni se ajusta
demasiado. Este paso llevará más tiempo, aunque se puede automatizar. Modificará
repetidamente su modelo, lo entrenará, lo evaluará en el conjunto de validación (no en el
conjunto de prueba en este punto), lo modificará nuevamente y repetirá hasta que el modelo sea
tan bueno como sea posible. Estas son las cosas que debe probar en términos de regularización:

– Agregue capas de abandono con diferentes tasas de abandono.

– Pruebe la regularización L1 y/o L2.


– Pruebe diferentes arquitecturas: agregue o elimine una pequeña cantidad de capas.
– Cambiar otros hiperparámetros (por ejemplo, el número de unidades de una capa densa).

Tenga cuidado con el sobreajuste del conjunto de validación al ajustar los hiperparámetros. Debido a
que los hiperparámetros se determinan en función del rendimiento en el conjunto de validación, sus
valores estarán demasiado especializados para el conjunto de validación y, por lo tanto, es posible que
no se generalicen bien a otros datos. El propósito del conjunto de prueba es obtener una estimación
imparcial de la precisión del modelo después del ajuste de hiperparámetros. Por lo tanto, no debe usar el
conjunto de prueba al ajustar los hiperparámetros.

¡Este es el flujo de trabajo universal del aprendizaje automático! En el capítulo 12, le agregaremos
dos pasos prácticos más (un paso de evaluación y un paso de implementación). Pero por ahora,
esta es una receta para pasar de una idea de aprendizaje automático vagamente definida a un
modelo que está entrenado y listo para hacer algunas predicciones útiles.
Con este conocimiento fundamental, comenzaremos a explorar tipos más avanzados de redes
neuronales en la próxima parte del libro. Comenzaremos con modelos diseñados para datos
secuenciales en el capítulo 9.

Ejercicios
1 En el problema de predicción de la temperatura, encontramos que el regresor lineal no se
ajustaba significativamente a los datos y producía resultados de predicción deficientes
tanto en el conjunto de entrenamiento como en el de validación. ¿Agregar la regularización
L2 al regresor lineal ayudaría a mejorar la precisión de dicho modelo de ajuste insuficiente?
Debería ser fácil probarlo usted mismo modificando elconstruirModeloRegresiónLineal() en el
archivo jena-weather/models.js.
Resumen 291

2 Al predecir la temperatura del día siguiente en el ejemplo del clima de Jena, usamos un período
retrospectivo de 10 días para producir las características de entrada. Una pregunta natural es,
¿qué pasa si usamos un período retrospectivo más largo? ¿La inclusión de más datos nos ayudará
a obtener predicciones más precisas? Puede averiguarlo modificando elmirada constanteAtrásen
jena-weather/index.js y ejecutando el entrenamiento en el navegador (por ejemplo, usando el
MLP con regularización L2). Por supuesto, un período retrospectivo más largo aumentará el
tamaño de las características de entrada y conducirá a un tiempo de entrenamiento más largo.
Entonces, la otra cara de la pregunta es, ¿podemos usar un período retrospectivo más corto sin
sacrificar significativamente la precisión de la predicción? Prueba esto también.

Resumen
- tfjs-vis puede ayudar a visualizar el proceso de entrenamiento de un modelo de aprendizaje automático
en el navegador. Específicamente, mostramos cómo se puede usar tfjs-vis para
– Visualizar la topología de los modelos de TensorFlow.js.
– Trazar curvas de pérdidas y métricas durante el entrenamiento.

– Resumir las distribuciones de peso después del entrenamiento.

Mostramos ejemplos concretos de estos flujos de trabajo de visualización.


-El ajuste insuficiente y el sobreajuste son comportamientos fundamentales de los modelos de

aprendizaje automático y deben monitorearse y comprenderse en cada problema de aprendizaje


automático. Ambos pueden verse comparando las curvas de pérdida de los conjuntos de
entrenamiento y validación durante el entrenamiento. el incorporadotfvis.show.fitCallbacks() El
método le ayuda a visualizar estas curvas en el navegador con facilidad.
- El flujo de trabajo universal del aprendizaje automático es una lista de pasos comunes y
mejores prácticas de diferentes tipos de tareas de aprendizaje supervisado. Va desde
decidir la naturaleza del problema y los requisitos de los datos hasta encontrar un modelo
que se ajuste bien en el límite entre el ajuste insuficiente y el ajuste excesivo.
Aprendizaje profundo para

secuencias y texto

Este capítulo cubre


- En qué se diferencian los datos secuenciales de los datos no secuenciales

- Qué técnicas de aprendizaje profundo son adecuadas para problemas que


involucran datos secuenciales

- Cómo representar datos de texto en el aprendizaje profundo, incluso con


codificación one-hot, codificación multi-hot e incrustación de palabras

- Qué son las RNN y por qué son adecuadas para problemas
secuenciales
- Qué es la convolución 1D y por qué es una alternativa atractiva a las
RNN
- Las propiedades únicas de las tareas de secuencia a secuencia y
cómo usar el mecanismo de atención para resolverlas

Este capítulo se enfoca en problemas que involucran datos secuenciales. La esencia de los datos
secuenciales es el orden de sus elementos. Como te habrás dado cuenta, hemos tratado con datos
secuenciales antes. Específicamente, los datos meteorológicos de Jena que presentamos en el capítulo
7 son secuenciales. Los datos se pueden representar como una matriz de matrices de números.

292
293

El orden ciertamente importa para la matriz externa porque las medidas se obtienen con el tiempo. Si
invierte el orden de la matriz externa, por ejemplo, una tendencia ascendente de la presión del aire se
convierte en una descendente, tiene implicaciones completamente diferentes si está tratando de predecir
el clima futuro. Los datos secuenciales están en todas partes en la vida: precios de acciones, lecturas de
electrocardiogramas (ECG), cadenas de caracteres en código de software, fotogramas consecutivos de un
video y secuencias de acciones realizadas por un robot. Compare aquellos con datos no secuenciales
como las flores de iris en el capítulo 3: no importa si altera el orden de las cuatro características
numéricas (longitud y anchura de sépalos y pétalos).1
La primera parte del capítulo presentará un tipo fascinante de modelo que mencionamos en el
capítulo 1: RNN, o redes neuronales recurrentes, que están diseñadas específicamente para
aprender de datos secuenciales. Construiremos la intuición de qué características especiales de las
RNN hacen que estos modelos sean sensibles al orden de los elementos y la información que
contienen.
La segunda parte del capítulo hablará sobre un tipo especial de datos secuenciales: el texto, que
es quizás el dato secuencial más ubicuo (¡especialmente en el entorno web!). Comenzaremos
examinando cómo se representa el texto en el aprendizaje profundo y cómo aplicar RNN en tales
representaciones. Luego pasaremos a las convnets 1D y hablaremos sobre por qué también son
potentes en el procesamiento de texto y cómo pueden ser alternativas atractivas a las RNN para
ciertos tipos de problemas.
En la última parte del capítulo, daremos un paso más y exploraremos tareas basadas en
secuencias que son un poco más complejas que predecir un número o una clase. En particular, nos
aventuraremos en tareas secuencia a secuencia, que implican predecir una secuencia de salida a
partir de una de entrada. Usaremos un ejemplo para ilustrar cómo resolver tareas básicas de
secuencia a secuencia con un nuevo modelo de arquitectura llamadomecanismo de atención, que
se está volviendo cada vez más importante en el campo del procesamiento del lenguaje natural
basado en el aprendizaje profundo.
Al final de este capítulo, debería estar familiarizado con los tipos comunes de datos secuenciales
en el aprendizaje profundo, cómo se presentan como tensores y cómo usar Tensor-Flow.js para
escribir RNN básicos, convnets 1D y redes de atención para resolver tareas de aprendizaje
automático que involucran datos secuenciales.
Las capas y los modelos que verá en este capítulo se encuentran entre los más complejos
de este libro. Este es el costo que viene con su capacidad mejorada para tareas de
aprendizaje secuencial. Puede que le resulte difícil comprender algunos de ellos la primera
vez que los lea, aunque nos esforzamos por presentarlos de la forma más intuitiva posible,
con la ayuda de diagramas y pseudocódigo. Si ese es el caso, intente jugar con el código de
ejemplo y realice los ejercicios proporcionados al final del capítulo. En nuestra experiencia, la
experiencia práctica hace que sea mucho más fácil internalizar conceptos y arquitecturas
complejas como las que aparecen en este capítulo.

1Convénzase usted mismo de que este es el caso en el ejercicio 1 al final del capítulo.
294 CPASADO9Deep learning para secuencias y texto

9.1 Segundo intento de predicción meteorológica: introducción de RNN


Los modelos que construimos para el problema del clima de Jena en el capítulo 8 descartaron la
información del pedido. En esta sección, le diremos por qué ese es el caso y cómo podemos recuperar la
información del pedido mediante el uso de RNN. Esto nos permitirá lograr precisiones de predicción
superiores en la tarea de predicción de temperatura.

9.1.1 Por qué las capas densas no logran modelar el orden secuencial

Dado que describimos el conjunto de datos meteorológicos de Jena en detalle en el capítulo anterior,
revisaremos el conjunto de datos y la tarea de aprendizaje automático relacionada solo brevemente aquí.
La tarea implica predecir la temperatura 24 horas a partir de un momento determinado mediante el uso
de lecturas de 14 instrumentos meteorológicos (como temperatura, presión atmosférica y velocidad del
viento) durante un período de 10 días previo al momento. Las lecturas del instrumento se toman a
intervalos regulares de 10 minutos, pero las muestreamos en un factor de 6 a una vez por hora para
poder manejar el tamaño del modelo y el tiempo de entrenamiento. Entonces, cada ejemplo de
entrenamiento viene con un tensor de características de forma [240, 14],donde 240 es el número de pasos
de tiempo durante el período de 10 días y 14 es el número de lecturas diferentes de instrumentos
meteorológicos.
Cuando probamos un modelo de regresión lineal y un MLP en la tarea del capítulo anterior,
aplanamos las características de entrada 2D a 1D usando untf.layers.flattencapa (ver listado 8.2 y
figura 8.2). El paso de aplanamiento era necesario porque tanto el regresor lineal como el MLP
usaban capas densas para manejar los datos de entrada, y las capas densas requieren que los
datos de entrada sean 1D para cada ejemplo de entrada. Esto significa que la información de todos
los pasos de tiempo se mezcla de una manera que borra el significado de qué paso viene primero y
cuál sigue, qué paso de tiempo sigue a cuál otro, qué tan separados están dos pasos de tiempo,
etc. En otras palabras, no importa cómo ordenemos los 240 pasos de tiempo cuando aplanamos el
tensor de forma 2D [240, 14]en el tensor de forma 1D [3360]siempre y cuando seamos consistentes
entre entrenamiento e inferencia. Puede confirmar este punto experimentalmente en el ejercicio 1
al final de este capítulo. Pero desde un punto de vista teórico, esta falta de sensibilidad al orden de
los elementos de datos se puede entender de la siguiente manera. En el núcleo de una capa densa
hay un conjunto de ecuaciones lineales, cada una de las cuales multiplica cada valor de
característica de entrada [X1,X2, …, Xnorte] con un coeficiente sintonizable del kernel [k1,k2, …,knorte]:

y=F-k1-X1+k2-X2+...+knorte-Xnorte- (Ecuación 9.1)

La figura 9.1 proporciona una representación visual de cómo funciona una capa densa: las
rutas que van desde los elementos de entrada hasta la salida de la capa son gráficamente
simétricas entre sí, lo que refleja la simetría matemática de la ecuación 9.1. la simetria es
indeseablecuando tratamos con datos secuenciales porque hace que el modelo sea ciego al
orden entre los elementos.
Segundo intento de predicción meteorológica: introducción de RNN 295

capa densa
Figura 9.1 La arquitectura interna de una
capa densa. La multiplicación y suma
X1 k1·X realizada por una capa densa es simétrica
1
con respecto a sus entradas. Compare esto
con una capa RNN simple (figura 9.2), que
rompe la simetría al introducir el cálculo
X2 k2·X 2
paso a paso. Tenga en cuenta que asumimos
que la entrada tiene solo cuatro elementos y
Σ F()
omitimos los términos de sesgo por
X3 k3·X 3 simplicidad. Además, mostramos las
operaciones para una sola unidad de salida
de la capa densa. Las unidades restantes se
X4 representan como la pila de cajas ocultas en
k3·X3
el fondo.

De hecho, hay una manera fácil de demostrar que nuestro enfoque basado en capas densas (las
MLP, incluso con regularización) no proporcionó una muy buena solución al problema de
predicción de temperatura: comparar su precisión con la precisión que podemos obtener de un
sentido común. , enfoque sin aprendizaje automático.
¿Cuál es el enfoque de sentido común del que estamos hablando? Prediga la temperatura como
la última lectura de temperatura en las funciones de entrada. En pocas palabras, ¡simplemente
imagina que la temperatura dentro de 24 horas será la misma que la temperatura ahora mismo!
Este enfoque tiene “sentido visceral” porque sabemos por experiencia cotidiana que la temperatura
de mañana tiende a estar cerca de la temperatura de hoy (es decir, exactamente a la misma hora
del día). Es un algoritmo muy simple y da una suposición razonable que debería superar a todos los
demás algoritmos igualmente simples (como predecir la temperatura como la temperatura de hace
48 horas).
El directorio jena-weather de tfjs-examples que usamos en el capítulo 8 proporciona un
comando para evaluar la precisión de este enfoque de sentido común:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


jena-weather
discos compactos

hilo
hilo tren-rnn --modelType línea de base

lostren de hilo-rnnEl comando llama al script train-rnn.js y realiza el cálculo en el


entorno de back-end basado en Node.js.2Volveremos a este modo de operación
cuando exploremos las RNN en breve. El comando debería darle la siguiente salida
de pantalla:
Error absoluto medio de referencia de sentido común: 0,290331

Por lo tanto, el enfoque simple que no es de aprendizaje automático produce un error de predicción absoluto medio de

aproximadamente 0,29 (en términos normalizados), que es aproximadamente igual (si no un poco mejor)

2
El código que implementa este enfoque de sentido común, sin aprendizaje automático, se encuentra en la función
denominada getBaselineMeanAbsoluteError()en jena-weather/models.js. utiliza elpara cada asíncrono ()metodo de laconjunto
de datosobjeto iterar a través de todos los lotes del subconjunto de validación, calcular la pérdida MAE para cada lote y
acumular todas las pérdidas para obtener la pérdida final.
296 CPASADO9Deep learning para secuencias y texto

que) el mejor error de validación que obtuvimos del MLP en el capítulo 8 (ver figura 8.4). En otras
palabras, el MLP, con o sin regularización, ¡no fue capaz de superar la precisión del método de línea
base de sentido común de manera confiable!
Tales observaciones no son infrecuentes en el aprendizaje automático: no siempre es fácil para el
aprendizaje automático superar un enfoque de sentido común. Para superarlo, el modelo de aprendizaje
automático a veces debe diseñarse o ajustarse cuidadosamente a través de la optimización de
hiperparámetros. Nuestra observación también subraya lo importante que es crear una línea de base que
no sea de aprendizaje automático para comparar cuando se trabaja en un problema de aprendizaje
automático. ¡Ciertamente queremos evitar desperdiciar todo el esfuerzo en construir un algoritmo de
aprendizaje automático que ni siquiera pueda superar una línea de base mucho más simple y
computacionalmente más barata! ¿Podemos superar la línea de base en el problema de predicción de la
temperatura? La respuesta es sí, y confiaremos en las RNN para hacerlo. Ahora echemos un vistazo a
cómo los RNN capturan y procesan el orden secuencial.

9.1.2 Cómo modelan las RNN el orden secuencial

El panel A de la figura 9.2 muestra la estructura interna de una capa RNN mediante el uso de
una secuencia corta de cuatro elementos. Existen varias variantes de capas RNN, y el
diagrama muestra la variante más simple, que se conoce como RNN simple y está disponible
en TensorFlow.js como latf.capas.simpleRNN()función de fábrica. Hablaremos de variantes
RNN más complicadas más adelante en este capítulo, pero por ahora nos centraremos en
RNN simple.

UN.Capa RNN simple:


representación desenrollada

y0

X1 F(W-X1+tu-y0) y1 B.Capa RNN simple:


representación enrollada

X2 F(W-X2+tu-y1) y2 X F(W-X+tu-y) y

Hora

X3 F(W-X3+tu-y2) y3

X4 F(W-X4+tu-y3) y4

Figura 9.2 Las representaciones “desenrolladas” (panel A) y “enrolladas” (panel B) de la estructura interna de simpleRNN. La vista
enrollada (panel B) representa el mismo algoritmo que la vista desenrollada, aunque de una forma más sucinta. Ilustra el
procesamiento secuencial de datos de entrada de simpleRNN de una manera más concisa. En la representación enrollada en el panel
B, la conexión que vuelve desde la salida (y) en el propio modelo es la razón por la que tales capas se denominanrecurrente. Como
en la figura 9.1, mostramos solo cuatro elementos de entrada y omitimos los términos de sesgo por simplicidad.
Segundo intento de predicción meteorológica: introducción de RNN 297

El diagrama muestra cómo los intervalos de tiempo de la entrada (X1,X2,X3, …) se procesan


paso a paso. En cada paso,XIes procesado por una función (F()), representado como el cuadro
rectangular en el centro del diagrama. Esto produce una salida (yI) que se combina con el
siguiente segmento de entrada (XI+1) como entrada alF() en el siguiente paso. Es importante
tener en cuenta que, aunque el diagrama muestra cuatro cuadros separados con definiciones
de funciones, en realidad representan la misma función. Esta función (F()) se llamacélulade la
capa RNN. Se utiliza de forma iterativa durante la invocación de la capa RNN. Por lo tanto,
una capa RNN se puede ver como "una celda RNN envuelta en unporlazo."3
Comparando la estructura de la RNN simple y la de la capa densa (figura 9.1), podemos
ver dos grandes diferencias:
- SimpleRNN procesa los elementos de entrada (pasos de tiempo) paso a paso. Esto refleja la
naturaleza secuencial de las entradas, algo que una capa densa no puede hacer. En
- simpleRNN, el procesamiento en cada paso de tiempo de entrada genera una salida (yI). La
salida de un paso de tiempo anterior (por ejemplo,y1) es utilizado por la capa cuando
procesa el siguiente paso de tiempo (comoX2). Esta es la razón detrás de la parte
"recurrente" del nombre RNN: la salida de pasos de tiempo anteriores fluye hacia atrás y se
convierte en una entrada para pasos de tiempo posteriores. La recurrencia no ocurre en
tipos de capas como densa, conv2d y maxPooling2d. Esas capas no implican que la
información de salida fluya hacia atrás y, por lo tanto, se denominanretroalimentación
capas.

Debido a estas características únicas, simpleRNN rompe la simetría entre los elementos de
entrada. Es sensible al orden de los elementos de entrada. Si reordena los elementos de una
entrada secuencial, la salida se alterará como resultado. Esto distingue simpleRNN de una
capa densa.
El panel B de la figura 9.2 es una representación más abstracta de RNN simple. Se le conoce
como unarrolladoDiagrama RNN, versus eldesenrolladodiagrama en el panel A, porque "enrolla"
todos los pasos de tiempo en un solo ciclo. El diagrama enrollado corresponde muy bien a unpor
bucle en los lenguajes de programación, que es realmente cómo se implementan RNN simple y
otros tipos de RNN bajo el capó en TensorFlow.js. Pero en lugar de mostrar el código real, veamos
el pseudocódigo mucho más corto para RNN simple en la siguiente lista, que puede ver como la
implementación de la arquitectura RNN simple que se muestra en la figura 9.2. Esto lo ayudará a
concentrarse en la esencia de cómo funciona la capa RNN.

Listado 9.1 Pseudocódigo para el cómputo interno de simpleRNN

y=0
para x en input_sequence: x corresponde a la x de la figura 9.2. El
y = f(punto(W, x) + punto(U, y)) ciclo for itera sobre todos los pasos de
tiempo de la secuencia de entrada.
y corresponde a la y de la figura 9.2. El
estado se inicializa a ceros al principio. W y U son las matrices de peso para la entrada y el estado
(es decir, la salida que regresa y se convierte en la entrada
recurrente), respectivamente. Aquí también es donde la
salida para el paso de tiempo i se convierte en el estado
3
Cita atribuida a Eugene Brevdo. (entrada recurrente) para el paso de tiempo i +1.
298 CPASADO9Deep learning para secuencias y texto

En el listado 9.1, puede ver que la salida en el paso de tiempoIse convierte en el "estado" para el
siguiente paso de tiempo (siguiente iteración).Estadoes un concepto importante para las RNN. Es
como un RNN “recuerda” lo sucedido en los pasos de la secuencia de entrada que ya ha visto. En el
porbucle, este estado de memoria se combina con pasos de entrada futuros y se convierte en el
nuevo estado de memoria. Esto le da al RNN simple la capacidad de reaccionar al mismo elemento
de entrada de manera diferente según los elementos que hayan aparecido antes en la secuencia.
Este tipo de sensibilidad basada en la memoria está en el corazón del procesamiento secuencial.
Como un ejemplo simple, si está tratando de decodificar el código Morse (hecho de puntos y
guiones), el significado de un guión depende de la secuencia de puntos y guiones que van antes (y
después). Como otro ejemplo, en inglés, la palabraúltimopuede tener significados completamente
diferentes dependiendo de las palabras que vayan antes.
SimpleRNN recibe un nombre apropiado porque su salida y estado son lo mismo. Más
adelante, exploraremos arquitecturas RNN más complejas y poderosas. Algunos de estos
tienen salida y estado como dos cosas separadas; otros incluso tienen múltiples estados.
Otra cosa que vale la pena señalar acerca de los RNN es que elporloop les permite procesar
secuencias de entrada hechas de un número arbitrario de pasos de entrada. Esto es algo que no se
puede hacer aplanando una entrada secuencial y pasándola a una capa densa porque una capa
densa solo puede tomar una forma de entrada fija.
Además, elporloop refleja otra propiedad importante de los RNN:intercambio de parámetros. Lo
que queremos decir con esto es el hecho de que los mismos parámetros de peso (Wytu) se utilizan
en todos los pasos de tiempo. La alternativa es tener un valor único deW (ytu)por cada paso de
tiempo. Eso sería indeseable porque 1) limita la cantidad de pasos de tiempo que puede procesar la
RNN, y 2) conduce a un aumento dramático en la cantidad de parámetros ajustables, lo que
aumentará la cantidad de cómputo y la probabilidad de sobreajuste. durante el entrenamiento. Por
lo tanto, las capas RNN son similares a las capas conv2d en convnets en el sentido de que utilizan el
uso compartido de parámetros para lograr un cálculo eficiente y proteger contra el sobreajuste,
aunque las capas recurrente y conv2d logran compartir parámetros de diferentes maneras.
Mientras que las capas conv2d explotan la invariancia traslacional a lo largo de las dimensiones
espaciales, las capas RNN explotan la invariancia traslacional a lo largo de las dimensiones
espaciales.horadimensión.
La Figura 9.2 muestra lo que sucede en una RNN simple durante el tiempo de inferencia (el paso
hacia adelante). No muestra cómo los parámetros de peso (Wytu)se actualizan durante el
entrenamiento (el pase hacia atrás). Sin embargo, el entrenamiento de las RNN sigue las mismas
reglas de retropropagación que introdujimos en la sección 2.2.2 (figura 2.8), es decir, a partir de la
pérdida, retrocediendo en la lista de operaciones, tomando sus derivados y acumulando valores de
gradiente a través de ellos. Matemáticamente, el paso hacia atrás en una red recurrente es
básicamente el mismo que en una red de avance. La única diferencia es que el paso hacia atrás de
una capa RNN va hacia atrás en el tiempo, en un gráfico desenrollado como el del panel A de la
figura 9.2. Esta es la razón por la cual el proceso de entrenamiento de RNN a veces se denomina
retropropagación a través del tiempo(BPTT).
Segundo intento de predicción meteorológica: introducción de RNN 299

SIMPLEMENTARRNNEN ACCIÓN
Eso es suficiente reflexión abstracta sobre RNN simple y RNN en general. Veamos
ahora cómo crear una capa RNN simple e incluirla en un objeto modelo, de modo
que podamos usarla para predecir temperaturas con mayor precisión que antes. El
código en el listado 9.2 (extraído de jena-weather/train-rnn.js) es cómo se hace
esto. A pesar de toda la complejidad interna de la capa simpleRNN, el modelo en sí
es bastante simple. Tiene solo dos capas. El primero es simpleRNN, configurado
para tener 32 unidades. La segunda es una capa densa que usa la activación lineal
predeterminada para generar predicciones numéricas continuas para la
temperatura. Tenga en cuenta que debido a que el modelo comienza con un RNN,
ya no es necesario aplanar la entrada secuencial (compare esto con el listado 8.3 en
el capítulo anterior, cuando creamos MLP para el mismo problema). De hecho,

Listado 9.2 Creando un modelo simple basado en RNN para el problema de predicción de temperatura

El recuento de unidades codificadas de forma


función construirSimpleRNNModel(entradaForma) { rígida de la capa simpleRNN es un valor que

const modelo = tf.secuencial(); funciona bien, determinado mediante el ajuste

const rnnUnidades = 32; manual del hiperparámetro.

modelo.añadir(tf.capas.simpleRNN({
La primera capa del modelo es una capa
unidades: rnnUnidades,
RNN simple. No hay necesidad de aplanar
forma de entrada
la entrada secuencial, que tiene una forma
})); de [null, 240,14].
modelo.add(tf.layers.dense({unidades: 1})); modelo
de retorno; Terminamos el modelo con una capa densa con
} una sola unidad y la activación lineal
predeterminada para el problema de regresión.

Para ver el modelo simpleRNN en acción, use el siguiente comando:


hilo tren-rnn --modelType simpleRNN --logDir /tmp/
Jean-tiempo-simpleRNN-registros

El modelo RNN se entrena en el entorno de back-end utilizando tfjs-node. Debido a la cantidad de


cómputo involucrado en el entrenamiento de RNN basado en BPTT, sería mucho más difícil y lento,
si no imposible, entrenar el mismo modelo en el entorno de navegador con recursos restringidos.
Si tiene un entorno CUDA configurado correctamente, puede agregar el --GPUbandera al comando
para obtener un impulso adicional en la velocidad de entrenamiento.
Los --logDirflag en el comando anterior hace que el proceso de entrenamiento del modelo registre los
valores de pérdida en el directorio especificado. Puede cargar y trazar las curvas de pérdida en el
navegador utilizando una herramienta llamada TensorBoard. La figura 9.3 es una captura de pantalla de
Tensor-Board. A nivel de código JavaScript, esto se logra configurando eltf.Layers-Model.fit()llamar con una
devolución de llamada especial que apunta al directorio de registro. El cuadro de información 9.1 contiene
más información sobre cómo se hace esto.
300 CPASADO9Deep learning para secuencias y texto

ICAJA NFO9.1 Usar las devoluciones de llamada de TensorBoard para monitorear el entrenamiento de modelos de
ejecución prolongada en Node.js
En el capítulo 8, presentamos devoluciones de llamada de la biblioteca tfjs-vis que lo ayudan a monitorear
tf.LayersModel.fit()llamadas en el navegador. Sin embargo, tfjs-vis es una biblioteca solo para navegador y
no es aplicable a Node.js. Por defecto,tf.LayersModel.fit()en tfjsnode (o tfjs-node-gpu) representa barras
de progreso y muestra métricas de pérdida y tiempo en la terminal. Si bien esto es liviano e informativo, el
texto y los números a menudo son una forma menos intuitiva y visualmente menos atractiva de
monitorear el entrenamiento de modelos de ejecución prolongada que una GUI. Por ejemplo, los
pequeños cambios en el valor de la pérdida durante un extenso período de tiempo, que a menudo es lo
que buscamos durante las últimas etapas del entrenamiento del modelo, son mucho más fáciles de
detectar en un gráfico (con escalas y cuadrículas correctamente configuradas) que en un gráfico. cuerpo
de texto

Por suerte, una herramienta llamadaTensorTableropuede ayudarnos en el entorno backend.


Tensor-Board se diseñó originalmente para TensorFlow (Python), pero tfjs-node y tfjs-node-
gpu pueden escribir datos en un formato compatible que TensorBoard puede ingerir. Para
registrar pérdidas y valores métricos en TensorBoard desde untf.LayersModel.fit()otf.Layers-
Model.fitDataset()llama, sigue este patrón:

importar * como tf desde '@tensorflow/tfjs-node'; // O


'@tensorflow/tfjs-node-gpu'

// ...
espera model.fit(xs, ys, {
épocas,
devoluciones de llamada: tf.node.tensorBoard('/path/to/my/logdir') });

// O para fitDataset(): espera


model.fitDataset(dataset, {
épocas,
lotes por época,
devoluciones de llamada: tf.node.tensorBoard('/path/to/my/logdir') });

Estas llamadas escribirán los valores de pérdida, junto con cualquier métrica configurada durante
el compilar()call, al directorio /path/to/my/logdir. Para ver los registros en el navegador,

1 Abra una terminal separada.


2 Instale TensorBoard con el siguiente comando (a menos que ya esté instalado):
pip instalar tensorboard
3 Inicie el servidor back-end de TensorBoard y diríjalo al directorio de registro especificado durante
la creación de la devolución de llamada:
tensorboard --logdir /ruta/a/mi/logdir
4 En el navegador web, vaya a la URL http:// que muestra el proceso de TensorBoard. Luego,
los gráficos de pérdidas y métricas, como los que se muestran en las figuras 9.3 y 9.5,
aparecerán en la hermosa interfaz de usuario web de TensorBoard.
Segundo intento de predicción meteorológica: introducción de RNN 301

0.325 entrenar

valor
0.315

0.305
Pérdida

0.295

0.285

0.275

0.265

1 2 3 4 5 6 7 8 9 10 11

época #

Figura 9.3 Curvas de pérdida de MAE del modelo RNN simple construido para el
problema de predicción de temperatura de Jena. Este gráfico es una captura de
pantalla de TensorBoard que muestra los registros del entrenamiento basado en
Node.js del modelo simpleRNN.

El resumen de texto del modelo RNN simple creado por el listado 9.2 tiene el siguiente
aspecto:
Capa (escribe) Forma de salida Parámetro #
================================================== ===============
simple_rnn_SimpleRNN1 (Simple [nulo, 32] 1504
________________________________________________________________ denso_Dense1 (Denso)
[nulo, 1] 33
================================================== ===============
Total parámetros: 1537
entrenable parámetros: 1537
no entrenable parámetros: 0
_________________________________________________________________

Tiene significativamente menos parámetros de peso que el MLP que usamos antes (1537 versus 107 585,
o una reducción por un factor de 70), pero logra una pérdida de MAE de validación más baja (es decir,
predicciones más precisas) que el MLP durante el entrenamiento (0.271 frente a 0,289). Esta pequeña
pero sólida reducción en el error de predicción de la temperatura destaca el poder de compartir
parámetros en función de la invariancia temporal y las ventajas de las RNN en el aprendizaje de datos de
secuencia como los datos meteorológicos con los que estamos tratando.
Es posible que haya notado que, aunque simpleRNN involucra una cantidad relativamente pequeña de
parámetros de peso, su entrenamiento e inferencia toman mucho más tiempo en comparación con los
modelos feedforward como MLP. Esta es una deficiencia importante de las RNN, en la que es imposible
paralelizar las operaciones a lo largo de los pasos de tiempo. Tal paralelización no se puede lograr porque
los pasos subsiguientes dependen de los valores de estado calculados en los pasos anteriores (consulte la
figura 9.2 y el pseudocódigo en el listado 9.1). Si usamos la notación Big-O, el pase hacia adelante en un
RNN toma un O(norte) cantidad de tiempo, donde
302 CPASADO9Deep learning para secuencias y texto

nortees el número de pasos de tiempo de entrada. El pase hacia atrás (BPTT) requiere otro O(norte)
cantidad de tiempo. El futuro de entrada del problema del clima de Jena consta de una gran cantidad de
(240) pasos de tiempo, lo que lleva al tiempo de entrenamiento lento visto anteriormente. Esta es la razón
principal por la que entrenamos el modelo en tfjs-node en lugar de hacerlo en el navegador.
Esta situación de RNN contrasta con las capas de retroalimentación como densa y conv2d. En
esas capas, el cálculo se puede paralelizar entre los elementos de entrada porque la operación en
un elemento no depende del resultado de otro elemento de entrada. Esto permite que tales capas
de avance tomen menos de O(norte) tiempo (en algunos casos cercano a O(1)) para ejecutar sus
pases hacia adelante y hacia atrás con la ayuda de la aceleración de la GPU. En la sección 9.2,
exploraremos algunos enfoques de modelado secuencial más paralelizables, como la convolución
1D. Sin embargo, aún es importante estar familiarizado con los RNN porque son sensibles a las
posiciones secuenciales de una manera que no lo es la convolución 1D (más sobre esto más
adelante).

GRAMOUNIDAD RECURRENTE ATED: ATIPO MÁS SOFISTICADO DERNN


SimpleRNN no es la única capa recurrente disponible en TensorFlow.js. Hay otras dos: Unidad
Recurrente Cerrada (GRU4) y LSTM (que recordará son las siglas de Long Short-Term Memory5). En
la mayoría de los casos de uso práctico, probablemente querrá usar uno de estos dos. SimpleRNN
es demasiado simplista para la mayoría de los problemas reales, a pesar de que es
computacionalmente mucho más barato y tiene un mecanismo interno más fácil de entender que
GRU y LSTM. Hay un problema importante con simpleRNN: aunque teóricamente es capaz de
retener en el tiempotinformación sobre entradas vistas muchos pasos de tiempo antes, tales
dependencias a largo plazo son difíciles de aprender en la práctica.
Esto se debe a laproblema del gradiente de desaparición, un efecto similar al que se observa en las
redes feedforward que tienen muchas capas de profundidad: a medida que agrega capas a una red, el
tamaño de los gradientes retropropagados desde la función de pérdida hasta las primeras capas se
vuelve cada vez más pequeño. De ahora en adelante, las actualizaciones de los pesos se vuelven cada vez
más pequeñas, hasta el punto en que la red finalmente se vuelve imposible de entrenar. Para las RNN, la
gran cantidad de pasos de tiempo desempeña el papel de muchas capas en este problema. GRU y LSTM
son RNN diseñados para resolver el problema del gradiente de fuga, y GRU es el más simple de los dos.
Veamos cómo GRU hace eso.
En comparación con simpleRNN, GRU tiene una estructura interna más compleja. La figura 9.4
muestra una representación enrollada de la estructura interna de una GRU. En comparación con la
misma representación enrollada de simpleRNN (panel B de la figura 9.2), contiene más tuercas y
tornillos. La entrada (X) y la salida/estado (referido comohpor la convención en la literatura RNN)
pasan porcuatroecuaciones para dar lugar a la nueva salida/estado. Compare esto con simpleRNN,
que involucra solounaecuación. Esta complejidad también se refleja en el pseudocódigo del listado
9.3, que puede verse como una implementación de los mecanismos de la figura 9.4. Omitimos los
términos de sesgo en el pseudocódigo por simplicidad.

4Kyunghyun Cho et al., “Aprendizaje de representaciones de frases usando RNN Codificador-Decodificador para estadística
Traducción automática”, 2014,https://arxiv.org/abs/1406.1078.
5
Sepp Hochreiter y Jürgen Schmidhuber, “Memoria a largo plazo a corto plazo”,Computación neuronal, vol. 9, núm. 8,
1997, págs. 1735–1780.
Segundo intento de predicción meteorológica: introducción de RNN 303

capa GRU:
representación enrollada

puerta de actualización

σ(WZ·X+Uz·h) z (1 -z) ·h+z·h'


h
X

tanh(W·X+r·tu·h) h'
Restablecer puerta

σ(Wr·X+tu·h) r

Figura 9.4 Una representación enrollada de la celda GRU, un tipo de capa RNN más compleja y poderosa que la RNN simple. Esta es una
representación enrollada, comparable al panel B de la figura 9.2. Tenga en cuenta que omitimos los términos de sesgo en las ecuaciones por
simplicidad. Las líneas discontinuas indican conexiones de retroalimentación desde la salida de la celda GRU (h) a la misma celda en pasos de
tiempo posteriores.

Listado 9.3 Pseudocódigo para una capa GRU

h=0
para x_i en input_sequence:
z = sigmoide(punto(W_z, x) + punto(U_z, h)) r = z se llama puerta de actualización.
sigmoide(punto(W_r, x) + punto(W_r, h)) h_prime = r se llama puerta de reinicio.
tanh(punto(W, x) + punto(r , punto(U, h))) h = punto(1 - z, h) +
punto(z, h_prime) h_prime es el estado temporal
del estado actual.
Este bucle for itera sobre todos los pasos de
tiempo de la secuencia de entrada.
h_prime (estado temporal actual) y h
(estado anterior) se combinan de forma
Esta es la h en la figura 9.4. Como en ponderada (siendo z el peso) para formar
simpleRNN, el estado se inicializa a el nuevo estado.
cero al principio.

De todos los detalles internos de GRU, destacamos los dos más importantes:
1 GRU facilita el transporte de información a través de muchos pasos de tiempo. Esto se logra
mediante la cantidad intermediaz, al que se hace referencia como elpuerta de actualización.
Debido a la puerta de actualización, GRU puede aprender a llevar el mismo estado a lo largo
de muchos pasos de tiempo con cambios mínimos. En particular, en la ecuación (1-z)⋅h+z⋅h',
si el valor dezes 0, entonces el estadohsimplemente se copiará del paso de tiempo actual al
siguiente. La capacidad de realizar un transporte al por mayor como este es una parte
importante de cómo GRU combate el problema del gradiente de fuga. La puerta de reinicioz
se calcula como una combinación lineal de la entradaXy el estado actualh, seguido de una
no linealidad sigmoidea.
2 Además de la puerta de actualizaciónz, otra “puerta” en GRU es la llamadapuerta de reinicio, r.
Como la puerta de actualizaciónz,rse calcula como una no linealidad sigmoidea que opera en un
304 CPASADO9Deep learning para secuencias y texto

combinación lineal de la entrada y el estado actualH.La puerta de reinicio controla


cuánto del estado actual se "olvida". En particular, en la ecuación tanh(W
⋅ X+r⋅tu⋅h), si el valor derse convierte en 0, entonces el efecto del estado actualh se borra; y
si (1-z)en la ecuación aguas abajo también es cercana a cero, entonces la influencia del
estado actualhen el siguiente estado se minimizará. Entonces, ryztrabajar juntos para que el
GRU aprenda a olvidar la historia, o una parte de ella, en las condiciones apropiadas. Por
ejemplo, supongamos que estamos tratando de clasificar la crítica de una película como
positiva o negativa. La reseña puede comenzar diciendo "esta película es bastante
agradable", pero a la mitad de la reseña dice "sin embargo, la película no es tan buena
como otras películas basadas en ideas similares". En este punto, el recuerdo con respecto al
elogio inicial debe olvidarse en gran medida, porque es la última parte de la revisión la que
debe pesar más para determinar el resultado final del análisis de sentimiento de esta
revisión.

Entonces, ese es un esquema muy aproximado y de alto nivel de cómo funciona GRU. Lo
importante a recordar es que la estructura interna de GRU permite que RNN aprenda cuándo
transferir el estado anterior y cuándo actualizar el estado con información de las entradas. Este
aprendizaje se materializa mediante actualizaciones de los pesos ajustables,Wz,tuz,Wr,Wr,W, ytu
(además de los términos de sesgo omitidos).
No se preocupe si no sigue todos los detalles de inmediato. Al final del día, la explicación
intuitiva de GRU que escribimos en los últimos párrafos no importa tanto. No es el trabajo del
ingeniero humano entender cómo una GRU procesa los datos secuenciales a un nivel muy
detallado, al igual que no es el trabajo del ingeniero humano comprender los detalles
detallados de cómo un convnet convierte una entrada de imagen en probabilidades de clase
de salida. La red neuronal encuentra los detalles en el espacio de hipótesis delineado por los
datos de estructura de la RNN a través del proceso de entrenamiento basado en datos.
Para aplicar GRU en nuestro problema de predicción de temperatura, construimos un
modelo Tensor-Flow.js que contiene una capa GRU. El código que usamos para hacer esto
(extraído de jena-weather/train-rnn.js.) parece casi idéntico al que usamos para el modelo
simpleRNN (listado 9.2). La única diferencia es el tipo de la primera capa del modelo (GRU
versus simpleRNN).

Listado 9.4 Creando un modelo GRU para el problema de predicción de temperatura de Jena

function construirGRUModelo(entradaForma) { El recuento de unidades codificadas es un número


const modelo = tf.secuencial(); const que funciona bien, descubierto mediante el ajuste
rnnUnidades = 32; manual del hiperparámetro.
modelo.add(tf.layers.gru({
unidades: rnnUnidades,
forma de entrada La primera capa del modelo es una capa GRU.

}));
modelo.add(tf.layers.dense({unidades: 1})); modelo
El modelo termina con una capa densa con
de retorno;
una sola unidad y la activación lineal por
} defecto para el problema de regresión.
Creación de modelos de aprendizaje profundo para texto 305

Para comenzar a entrenar el modelo GRU en el conjunto de datos meteorológicos de Jena, use

hilo tren-rnn --modelType gru

La Figura 9.5 muestra las curvas de pérdida de entrenamiento y validación obtenidas con el modelo
GRU. Obtiene un mejor error de validación de aproximadamente 0,266, que supera al que
obtuvimos del modelo simpleRNN en la sección anterior (0,271). Esto refleja la mayor capacidad de
GRU en el aprendizaje de patrones secuenciales en comparación con simpleRNN. De hecho, hay
patrones secuenciales ocultos en las lecturas de los instrumentos meteorológicos que pueden
ayudar a mejorar la precisión de la predicción de la temperatura; esta información la recoge GRU
pero no simpleRNN. Esto tiene el costo de un mayor tiempo de entrenamiento. Por ejemplo, en una
de nuestras máquinas, el modelo GRU entrena a una velocidad de 3.000 ms/lote, frente a los 950
ms/lote de la RNN simple.6Pero si el objetivo es predecir la temperatura con la mayor precisión
posible, este costo probablemente valdrá la pena.

entrenar

valor
0.31

0.29
Pérdida

0.27

0.25

1 2 3 4 5 6 7 8

época #

Figura 9.5 Las curvas de pérdida del entrenamiento de un modelo GRU en el


problema de predicción de temperatura. Compare esto con las curvas de
pérdida del modelo RNN simple (figura 9.3), y observe la reducción pequeña
pero real en la mejor pérdida de validación lograda por el modelo GRU.

9.2 Creación de modelos de aprendizaje profundo para texto


El problema de predicción del tiempo que acabamos de estudiar se ocupaba de datos numéricos
secuenciales. Pero los tipos de datos secuenciales más ubicuos son probablemente texto en lugar
de números. En idiomas alfabéticos como el inglés, el texto se puede ver como una secuencia de
caracteres o como una secuencia de palabras. Los dos enfoques son adecuados para diferentes
problemas y los usaremos para tareas diferentes en esta sección.

6
Estos números de rendimiento se obtienen de tfjs-node ejecutándose en el backend de la CPU. Si usa tfjs-nodegpu y el
backend GPU CUDA, obtendrá aceleraciones aproximadamente proporcionales para ambos tipos de modelos.
306 CPASADO9Deep learning para secuencias y texto

Los modelos de aprendizaje profundo para datos de texto que presentaremos en las siguientes secciones pueden realizar
tareas relacionadas con el texto, como

- Asignar una puntuación de opinión a un cuerpo de texto (por ejemplo, si la reseña de un


producto es positiva o negativa)
- Clasificar un cuerpo de texto por su tema (por ejemplo, si un artículo de noticias
es sobre política, finanzas, deportes, salud, clima o varios)
- Convertir una entrada de texto en una salida de texto (por ejemplo, para estandarizar el
formato o la traducción automática)
- Predecir las próximas partes de un texto (por ejemplo, funciones de sugerencias inteligentes de
métodos de entrada móviles)

Esta lista es solo un subconjunto muy pequeño de problemas interesantes de aprendizaje automático que
involucran texto, que se estudian sistemáticamente en el campo del procesamiento del lenguaje natural.
Si bien en este capítulo solo rascaremos la superficie de las técnicas de procesamiento del lenguaje
natural basadas en redes neuronales, los conceptos y ejemplos que se presentan aquí deberían brindarle
un buen punto de partida para una mayor exploración (consulte la sección "Materiales de lectura
adicional" al final). de este capítulo).
Tenga en cuenta que ninguna de las redes neuronales profundas de este capítulo comprende
realmente el texto o el lenguaje en un sentido humano. Más bien, estos modelos pueden asignar la
estructura estadística del texto a un determinado espacio de destino, ya sea una puntuación de opinión
continua, un resultado de clasificación multiclase o una nueva secuencia. Esto resulta ser suficiente para
resolver muchas tareas prácticas relacionadas con el texto. El aprendizaje profundo para el
procesamiento del lenguaje natural no es más que el reconocimiento de patrones aplicado a caracteres y
palabras, de la misma manera que la visión artificial basada en el aprendizaje profundo (capítulo 4) es el
reconocimiento de patrones aplicado a los píxeles.
Antes de sumergirnos en las redes neuronales profundas diseñadas para texto, primero debemos
comprender cómo se representa el texto en el aprendizaje automático.

9.2.1 Cómo se representa el texto en el aprendizaje automático:


codificación one-hot y multi-hot

La mayoría de los datos de entrada que hemos encontrado en este libro hasta ahora son continuos. Por
ejemplo, la longitud de los pétalos de una flor de iris varía continuamente en un rango determinado; las
lecturas de los instrumentos meteorológicos en el conjunto de datos meteorológicos de Jena son todos
números reales. Estos valores se representan directamente como tensores de tipo flotante (números de
coma flotante). Sin embargo, el texto es diferente. Los datos de texto se presentan como una cadena de
caracteres o palabras, no como números reales. Los caracteres y las palabras son discretos. Por ejemplo,
no existe una letra entre "j" y "k" en el mismo sentido en que existe un número entre 0,13 y 0,14. En este
sentido, los caracteres y las palabras son similares a las clases en la clasificación multiclase (como las tres
especies de flores de iris o las 1000 clases de salida de MobileNet). Los datos de texto deben convertirse
en vectores (matrices de números) antes de que puedan incorporarse a los modelos de aprendizaje
profundo. Este proceso de conversión se llamavectorización de texto.
Hay varias formas de vectorizar texto.Codificación one-hot(como hemos presentado en el
capítulo 3) es una de las opciones. En inglés, dependiendo de dónde dibujes la línea,
Creación de modelos de aprendizaje profundo para texto 307

hay alrededor de 10.000 palabras más utilizadas. Podemos recopilar estas 10.000 palabras y formar
unavocabulario. Las palabras únicas en el vocabulario se pueden ordenar en cierto orden (por
ejemplo, orden descendente de frecuencia) para que a cualquier palabra se le pueda dar un índice
entero.7Entonces, cada palabra en inglés se puede representar como un vector de longitud 10,000,
en el que solo el elemento que corresponde al índice es 1, y todos los elementos restantes son 0.
Este es elvectorización en calientede la palabra. El panel A de la figura 9.6 presenta esto
gráficamente.
¿Qué pasa si tenemos una oración en lugar de una sola palabra? Podemos obtener los vectores
one-hot para todas las palabras que componen la oración y juntarlos para formar un 2D

A
Codificación one-hot de una palabra

en

B
Codificación one-hot de una secuencia de palabras

los

gato

se sentó
Longitud
de
oración
en

los

estera Tamaño del vocabulario

C
Representación multicaliente de palabras en la secuencia

Figura 9.6 Codificación one-hot (vectorización) de una palabra (panel A) y de una oración como secuencia de palabras (panel B). El
panel C muestra una codificación multi-caliente simplificada de la misma oración que en el panel B. Es una representación más
sucinta y escalable de la secuencia, pero descarta la información del pedido. En aras de la visualización, asumimos que el tamaño del
vocabulario es solo 14. En realidad, el tamaño del vocabulario de las palabras en inglés utilizadas en el aprendizaje profundo es
mucho mayor (del orden de miles o decenas de miles, por ejemplo, 10 000) .

7
Una pregunta obvia es: ¿qué pasa si obtenemos una palabra rara que cae fuera del vocabulario de 10,000 palabras? Este es un
problema práctico al que se enfrenta cualquier algoritmo de aprendizaje profundo orientado a texto. En la práctica, resolvemos
este problema agregando un elemento especial llamadoOOVal vocabulario. OOV significafuera de vocabulario. Por lo tanto, todas
las palabras raras que no pertenecen al vocabulario se agrupan en ese elemento especial y tendrán la misma codificación one-hot
o vector de incrustación. Las técnicas más sofisticadas tienen múltiples cubos OOV y usan una función hash para asignar palabras
raras a esos cubos.
308 CPASADO9Deep learning para secuencias y texto

representación de las palabras de la oración (ver panel B de la figura 9.6). Este enfoque es
simple e inequívoco. Conserva perfectamente la información sobre qué palabras aparecen en
la oración y en qué orden.8Sin embargo, cuando el texto es largo, el tamaño del vector puede
ser tan grande que ya no sea manejable. Por ejemplo, una oración en inglés contiene
alrededor de 18 palabras en promedio. Dado que nuestro vocabulario tiene un tamaño de
10.000, se necesitan 180.000 números para representar una sola frase, que ya ocupa un
espacio mucho mayor que la propia frase. Esto sin mencionar que algunos problemas
relacionados con el texto tratan con párrafos o artículos completos, que tienen muchas más
palabras y harán que el tamaño de la representación y la cantidad de cómputo se disparen.

Una forma de lidiar con este problema es incluir todas las palabras en un solo vector para que cada
elemento del vector represente si la palabra correspondiente ha aparecido en el texto. El panel C de la
figura 9.6 ilustra. En esta representación, múltiples elementos del vector pueden tener el valor 1. Es por
eso que la gente a veces se refiere a él como codificación multi-caliente. La codificación multi-caliente
tiene una longitud fija (el tamaño del vocabulario) independientemente de la longitud del texto, por lo que
resuelve el problema de la explosión de tamaño. Pero esto tiene el costo de perder la información del
pedido: no podemos saber a partir del vector multicaliente qué palabras vienen primero y qué palabras
vienen después. Para algunos problemas, esto podría estar bien; para otros, esto es inaceptable. Hay
representaciones más sofisticadas que resuelven el problema de la explosión de tamaño mientras
conservan la información de orden, que exploraremos más adelante en este capítulo. Pero primero,
echemos un vistazo a un problema concreto de aprendizaje automático relacionado con texto que se
puede resolver con una precisión razonable utilizando el enfoque multi-caliente.

9.2.2 Primer intento del problema de análisis de sentimientos


Usaremos el conjunto de datos de Internet Movie Database (IMDb) en nuestro primer ejemplo de aplicación del
aprendizaje automático al texto. El conjunto de datos es una colección de aproximadamente 25,000 reseñas
textuales de películas en imdb.com, cada una de las cuales ha sido etiquetada como positiva o negativa. La tarea
de aprendizaje automático es una clasificación binaria: es decir, si una reseña de una película determinada es
positiva o negativa. El conjunto de datos está equilibrado (50 % de críticas positivas y 50 % de críticas negativas).
Al igual que lo que espera de las reseñas en línea, los ejemplos varían en la longitud de las palabras. Algunos de
ellos son tan cortos como 10 palabras, mientras que otros pueden tener hasta 2000 palabras. El siguiente es un
ejemplo de cómo se ve una revisión típica. Este ejemplo está etiquetado como negativo. La puntuación se omite
en el conjunto de datos:

La madre en esta película es imprudente con sus hijos hasta el punto de descuidarlos. Ojalá
no estuviera tan enojado con ella y sus acciones porque, de lo contrario, habría disfrutado la
película. ¡Qué número! verla hacer hasta el final también si alguien más se cansa de ver
películas que se filman tan oscuras que ya casi no se puede ver lo que se está filmando
como audiencia, estamos increíblemente involucrados con las acciones en la pantalla,
entonces, ¿por qué demonios no podemos? tenemos vision nocturna

8Esto supone que no hay palabras OOV.


Creación de modelos de aprendizaje profundo para texto 309

Los datos se dividen en un conjunto de entrenamiento y un conjunto de evaluación, los cuales se


descargan automáticamente de la web y se escriben en su directorio tmp cuando ejecuta un comando de
entrenamiento de modelos como

clon de git https://github.com/tensorflow/tfjs-examples.git cd tfjs-examples/


hilo de sentimiento

hilo tren multicaliente

Si examina sentiment/data.js detenidamente, puede ver que los archivos de datos que descarga y lee no
contienen las palabras reales como cadenas de caracteres. En cambio, las palabras se representan como
enteros de 32 bits en esos archivos. Aunque no cubriremos en detalle el código de carga de datos en ese
archivo, vale la pena mencionar una parte que realiza la vectorización multi-caliente de las oraciones, que
se muestra en la siguiente lista.

Listado 9.5 Vectorización multi-caliente de oraciones de lacargarCaracterísticas()función

const buffer = tf.buffer([secuencias.longitud, numPalabras]);


secuencias.forEach((seq, i) => {
Itera sobre todos los ejemplos, cada
seq.forEach(wordIndex => {
uno de los cuales es una oración
if (ÍndicePalabra !== OOV_INDEX) {
buffer.set(1, i, wordIndex); Cada secuencia (oración) es una
} matriz de números enteros.
});
}); Omite palabras fuera del vocabulario
(OOV) para la codificación multi-caliente
Crea un TensorBuffer en lugar de un
tensor porque estableceremos los valores Establece el índice correspondiente en el búfer a1.Tenga en cuenta
de sus elementos a continuación. El búfer que cada índice puede tener varios valores de wordIndex
comienza desde cero. establecidos en1,de ahí la codificación multi-caliente.

Las características codificadas en caliente múltiples se representan como un tensor de forma 2D [


numEjemplos, numPalabras],dondenumPalabrases el tamaño del vocabulario (10.000 en este caso). Esta
forma no se ve afectada por la longitud de las oraciones individuales, lo que lo convierte en un paradigma
de vectorización simple. Los objetivos cargados desde los archivos de datos tienen una forma de [num-
Ejemplos, 1]y contienen las etiquetas negativas y positivas representadas como 0 y 1, respectivamente.

El modelo que aplicamos a los datos multicalientes es un MLP. De hecho, con la pérdida de
información secuencial con la codificación multi-hot, no hay forma de aplicar un modelo RNN
a los datos, incluso si quisiéramos. Hablaremos sobre los enfoques basados en RNN en la
siguiente sección. El código que crea el modelo MLP es delconstruirModelo() funciona en
sentiment/train.js, con simplificación, y se parece a la siguiente lista.

Listado 9.6 Construyendo un modelo MLP para las reseñas de películas de IMDb con codificación múltiple en caliente

const modelo = tf.secuencial();


modelo.add(tf.layers.dense({
Agrega dos capas densas ocultas con
unidades: 16,
activación relu para mejorar el poder
activación: 'relu',
de representación
310 CPASADO9Deep learning para secuencias y texto

inputShape: [vocabularySize] }));


La forma de entrada es del tamaño del
modelo.add(tf.layers.dense({
vocabulario debido a la vectorización multi-
unidades: 16, caliente que estamos tratando aquí.
activación: 'relu'
}));
modelo.add(tf.layers.dense({ Utiliza activación sigmoide para la
unidades: 1, capa de salida para adaptarse a la
activación: 'sigmoide' tarea de clasificación binaria
}));

Al ejecutar eltren de hilo multihot --maxLen 500comando, puede ver que el modelo logra una
mejor precisión de validación de aproximadamente 0,89. Esta precisión está bien y es
significativamente más alta que la probabilidad (0.5). Esto demuestra que es posible lograr un
grado razonable de precisión en este problema de análisis de sentimientos al observar qué
palabras aparecen en la reseña. Por ejemplo, palabras comoagradableysublimese asocian
con críticas positivas y palabras comoapestayamablese asocian con los negativos con un
grado relativamente alto de confiabilidad. Por supuesto, hay muchos escenarios en los que
mirar solo qué palabras hay puede ser engañoso. Como ejemplo artificial, comprender el
verdadero significado de una oración como "No me malinterpreten, no estoy en desacuerdo,
esta es una película excelente" requiere tener en cuenta la información secuencial, no solo
cuáles son las palabras sino también en qué orden aparecen. . En la siguiente sección,
mostraremos que mediante el uso de una vectorización de texto que no descarta la
información secuencial y un modelo que puede utilizar la información secuencial, podemos
superar esta precisión de referencia. Veamos ahora cómo funcionan las incrustaciones de
palabras y las convnets 1D.

9.2.3 Una representación más eficiente del texto: incrustaciones de palabras

Qué esincrustación de palabras? Al igual que la codificación one-hot (figura 9.6), la incrustación de
palabras es una forma de representar una palabra como un vector (un tensor 1D en TensorFlow.js). Sin
embargo, las incrustaciones de palabras permiten entrenar los valores de los elementos del vector, en
lugar de codificarlos de acuerdo con una regla rígida, como el mapa de palabra a índice en la codificación
one-hot. En otras palabras, cuando una red neuronal orientada a texto utiliza la incrustación de palabras,
los vectores de incrustación se convierten en parámetros de peso entrenables del modelo. Se actualizan
mediante la misma regla de retropropagación que todos los demás parámetros de ponderación del
modelo.
Esta situación se ilustra esquemáticamente en la figura 9.7. El tipo de capa en Tensor-Flow.js
que le permite realizar incrustaciones de palabras estf.capa.incrustación().Contiene una matriz de
peso entrenable de forma [vocabularioTamaño, incrustaciónDims],donde vocabularioTamañoes el
número de palabras únicas en el vocabulario yincrustaciónDims es la dimensionalidad seleccionada
por el usuario de los vectores de incrustación. Cada vez que le den una palabra, digalos, encuentra
la fila correspondiente en la matriz de incrustación usando una tabla de búsqueda de palabra a
índice, y esa fila es el vector de incrustación para su palabra. Tenga en cuenta que la tabla de
búsqueda de palabra a índice no forma parte de la capa de incrustación; se mantiene como una
entidad separada del modelo (ver listado 9.9, por ejemplo).
Creación de modelos de aprendizaje profundo para texto 311

matriz de incrustación

los

gato
...

se sentó

... Longitud
de
en oración
...

los
...
incrustación
dimensiones
estera

incrustación
dimensiones

Figura 9.7 Una ilustración esquemática de cómo funciona una matriz de inclusión. Cada fila de la matriz de
incrustación corresponde a una palabra del vocabulario y cada columna es una dimensión de incrustación. Los
valores de los elementos de la matriz de inclusión, representados como tonos de gris en el diagrama, se eligen al
azar.

Si tiene una secuencia de palabras, como una oración como se muestra en la figura 9.7, repite este
proceso de búsqueda para todas las palabras en el orden secuencial correcto y apila los vectores de
incrustación resultantes en un tensor de forma 2D [secuenciaLongitud, incrustaciónDims], donde
longitud de secuenciaes el número de palabras en la oración.9¿Qué pasa si hay palabras que se
repiten en la oración (como la palabralosen el ejemplo de la figura 9.7)? No importa: simplemente
deje que el mismo vector de incrustación aparezca repetidamente en el tensor 2D resultante.

La incrustación de palabras nos brinda los siguientes beneficios:

- Aborda el problema del tamaño con codificaciones one-hot.incrustaciónDimssuele ser mucho más
pequeño quevocabularioTamaño.Por ejemplo, en la convnet 1D que estamos a punto de usar en
el conjunto de datos de IMDb,vocabularioTamañoes 10,000, yincrustación-Dimses 128. Entonces,
con una revisión de 500 palabras del conjunto de datos de IMDb, representar el ejemplo requiere
500 * 128 = 64k números flotantes, en lugar de 500 * 10,000 = 5M números, como en la
codificación one-hot, una vectorización mucho más económica.
- Al no tener opiniones acerca de cómo ordenar las palabras en el vocabulario y al permitir que la
matriz de incrustación se entrene a través de la retropropagación al igual que todos los demás
pesos de las redes neuronales, las incrustaciones de palabras pueden aprender relaciones
semánticas entre palabras. Las palabras con significados similares deben tener vectores de
incrustación que estén más cerca en el espacio de incrustación. Por ejemplo, palabras con similar

9
Este proceso de búsqueda de incrustación de varias palabras se puede realizar de manera efectiva utilizando eltf.reunir()método,
que es cómo se implementa la capa de incrustación en TensorFlow.js bajo el capó.
312 CPASADO9Deep learning para secuencias y texto

significados, comomuyyverdaderamentedeben tener vectores que están más juntos que las
palabras que tienen un significado más diferente, comomuyyapenas. ¿Porqué debería ser
este el caso? Una forma intuitiva de entenderlo es darse cuenta de lo siguiente: suponga
que reemplaza una cantidad de palabras en la entrada de una reseña de una película con
palabras con un significado similar; una red bien entrenada debería generar el mismo
resultado de clasificación. Esto podría suceder solo si los vectores de incrustación para cada
par de palabras, que son la entrada a la parte posterior del modelo, están cerca uno del
otro.
- Además, el hecho de que el espacio de incrustación tenga múltiples dimensiones (por ejemplo,
128) debería permitir que los vectores de incrustación capturen diferentes aspectos de las
palabras. Por ejemplo, puede haber una dimensión que represente una parte del discurso, junto
con un adjetivo comorápidoestá más cerca de otro adjetivo (comocálido) que a un sustantivo
(comocasa). Puede haber otra dimensión que codifique el aspecto de género de una palabra, una
a lo largo de la cual una palabra comoActrizestá más cerca de otra palabra de significado
femenino (comoreina) que a uno de significado masculino (como actor). En la siguiente sección
(consulte el cuadro de información 9.2), le mostraremos una manera de visualizar la palabra
incrustaciones y explorar sus estructuras interesantes después de que emergen del
entrenamiento de una red neuronal basada en incrustaciones en el conjunto de datos de IMDb.

La Tabla 9.1 ofrece un resumen más sucinto de las diferencias entre la codificación de uno/múltiples
puntos en caliente y la incrustación de palabras, los dos paradigmas más utilizados para la vectorización
de palabras.

Tabla 9.1 Comparación de dos paradigmas de vectorización de palabras: codificación one-hot/multi-hot e incrustación de
palabras

Codificación one-hot o multi-hot incrustación de palabras

codificado o Codificado. Aprendido: la matriz de incrustación es un parámetro de


¿aprendió? peso entrenable; los valores a menudo reflejan la estructura
semántica del vocabulario después del entrenamiento.

Escaso o Escaso: la mayoría de los elementos son Denso: los elementos toman valores que varían
¿denso? cero; algunos son uno. continuamente.

Escalabilidad No escalable a grandes vocabularios: Escalable a grandes vocabularios: el tamaño de


el tamaño del vector es proporcional incrustación (número de dimensiones de
al tamaño del vocabulario. incrustación) no tiene que aumentar con la
cantidad de palabras en el vocabulario.

9.2.4 Convenciones 1D
En el capítulo 4, mostramos el papel clave que desempeñan las capas convolucionales 2D en las redes
neuronales profundas para la entrada de imágenes. Las capas conv2d aprenden a representar
características locales en pequeños parches 2D en imágenes. La idea de convolución se puede extender a
las secuencias. El algoritmo resultante se llamaconvolución 1Dy está disponible a través de latf.layers .
conv1d()función en TensorFlow.js. Las ideas subyacentes a conv1d y conv2d son
Creación de modelos de aprendizaje profundo para texto 313

lo mismo: ambos son extractores entrenables de características locales traduccionalmente invariantes.


Por ejemplo, una capa conv2d puede volverse sensible a patrones de esquinas de cierta orientación y de
cierto cambio de color después de entrenar en una tarea de imagen, mientras que una capa conv1d
puede volverse sensible a un patrón de "un verbo negativo seguido de un adjetivo elogioso". después del
entrenamiento en una tarea relacionada con el texto.10
La figura 9.8 ilustra con mayor detalle cómo funciona una capa conv1d. Recordar de la figura
4.3 en el capítulo 4 que una capa conv2d implica deslizar un kernel sobre todas las ubicaciones posibles
en la imagen de entrada. El algoritmo de convolución 1D también involucra el deslizamiento de un kernel,
pero es más simple porque el movimiento de deslizamiento ocurre en una sola dimensión. En cada
posición de deslizamiento, se extrae una porción del tensor de entrada. La rebanada tiene la longitud
tamaño del núcleo (un campo de configuración para la capa conv1d), y en el caso de este ejemplo, tiene
una segunda dimensión igual al número de dimensiones de incrustación. Entonces unpunto La operación
(multiplicar y sumar) se realiza entre el segmento de entrada y el núcleo de la capa conv1d, lo que
produce un solo segmento de la secuencia de salida. Esta operación se repite para todas las posiciones
deslizantes válidas hasta que se genera la salida completa. Al igual que el tensor de entrada de la capa
conv1d, la salida completa es una secuencia, aunque con una longitud diferente (determinada por la
longitud de la secuencia de entrada, latamaño del núcleo,y otras configuraciones de la capa conv1d) y un
número diferente de dimensiones de características (determinado por elfiltrosconfiguración de la capa
conv1d). Esto hace posible apilar varias capas de conv1d para formar una convnet 1D profunda, del
mismo modo que apilar varias capas de conv2d es un truco que se usa con frecuencia en las convnets 2D.

Aporte Producción
dimensiones dimensiones
producto punto
con núcleo

Núcleo
Talla

Secuencia de salida
longitud
Aporte Producción
Secuencia de entrada

dimensiones dimensiones
longitud

Hora

Figura 9.8 Ilustración esquemática de cómo la convolución 1D (tf.capas.conv1d()) obras. En aras de la simplicidad, solo se muestra
un ejemplo de entrada (en el lado izquierdo de la imagen). Suponemos que la secuencia de entrada tiene una longitud de 12 y la
capa conv1d tiene un tamaño de kernel de 5. En cada posición de la ventana deslizante, se extrae un segmento de longitud 5 de la
secuencia de entrada. El segmento se multiplica por puntos con el kernel de la capa conv1d, lo que genera una diapositiva de la
secuencia de salida. Esto se repite para todas las posiciones posibles de la ventana deslizante, lo que da lugar a la secuencia de
salida (en el lado derecho del diagrama).

10Como habrás adivinado, de hecho existe una convolución 3D, y es útil para tareas de aprendizaje profundo que involucran
Datos 3D (volumétricos), como ciertos tipos de imágenes médicas y datos geológicos.
314 CPASADO9Deep learning para secuencias y texto

STRUNCACIÓN Y RELLENO DE ECUENCIAS


Ahora que tenemos conv1d en nuestro arsenal para el aprendizaje automático orientado a texto,
¿estamos listos para entrenar una convnet 1D en los datos de IMDb? Todavía no. Hay una cosa más que
explicar: el truncamiento y el relleno de secuencias. ¿Por qué necesitamos hacer truncamiento y relleno?
Los modelos TensorFlow.js requieren las entradas paraencajar()ser un tensor, y un tensor debe tener una
forma concreta. Por lo tanto, aunque nuestras reseñas de películas no tienen una longitud fija
(recordemos que varían entre 10 y 2400 palabras), tenemos que elegir una longitud específica como la
segunda dimensión del tensor de características de entrada (maxLen),de modo que la forma completa del
tensor de entrada es [numEjemplos, maxLen].No existía tal problema cuando usamos la codificación
multicaliente en la sección anterior porque los tensores de la codificación multicaliente tenían una
segunda dimensión de tensor que no se veía afectada por la longitud de la secuencia.
Las consideraciones que intervienen en la elección del valor demaxLenson como sigue:

- Debe ser lo suficientemente largo para capturar la parte útil de la mayoría de las reseñas. si
elegimosmaxLenpara ser 20, tal vez será tan corto que eliminará la parte útil para la mayoría
de las revisiones.
- No debería ser tan grande que la mayoría de las revisiones sean mucho más cortas
que esa longitud, porque eso conduciría a una pérdida de memoria y tiempo de
cálculo.

La compensación de los dos nos lleva a elegir un valor de 500 palabras por revisión (como máximo)
para este ejemplo. Esto se especifica en la bandera --maxLenen el comando para entrenar el
convnet 1D:

tren de hilo --maxLen 500 cnn

Una vez elmaxLenes elegido, todos los ejemplos de revisión deben ser moldeados en esta
longitud particular. En particular, los que son más largos se truncan; los que son más cortos
son acolchados. Esto es lo que la funciónpadSequences()hace (listado 9.7). Hay dos formas de
truncar una secuencia larga: cortar la parte inicial (el 'pre' opción en el listado 9.7) o la parte
final. Aquí, usamos el primer enfoque, basado en el razonamiento de que es más probable
que la parte final de una reseña de una película contenga información relevante para el
sentimiento que la parte inicial. Del mismo modo, hay dos formas de rellenar una secuencia
corta con la longitud deseada: agregando el carácter de relleno (PAD_CHAR) antes de 'pre'
opción en el listado 9.7) o después de la oración. Aquí, también elegimos arbitrariamente la
primera opción. El código de esta lista es de sentiment/secuence_utils.js.

Listado 9.7 Truncar y rellenar una secuencia como un paso para cargar funciones de texto

exportar función padSequences( Recorre todas las


secuencias, maxLen, secuencias de entrada
relleno = 'pre',
truncando = 'pre', Esta secuencia en particular
value = PAD_CHAR) { return es más larga que la longitud
secuencias.map(seq => { prescrita (maxLen): truncarla
if (longitud sec. > maxLen) { a esa longitud.
Creación de modelos de aprendizaje profundo para texto 315

if (truncando === 'pre') {


Hay dos formas de truncar una
seq.empalme(0, seq.longitud - maxLen); }
secuencia: cortar el
demás {
comienzo ('pre') o final
seq.splice(maxLen, seq.length - maxLen);
}
}

if (longitud sec. < maxLen) {


La secuencia es más corta
almohadilla constante = [];
que la longitud prescrita:
for (sea i = 0; i < maxLen - seq.length; ++i) {
necesita ser rellenada.
pad.push(valor);
} Genera el
if (relleno === 'pre') { secuencia de relleno
= pad.concat(seg);
secuencia

} demás { Al igual que el truncamiento, hay dos formas


seq = seq.concat(pad); de rellenar la secuencia de sublongitud:
} desde el principio ("pre") o desde atrás.
} Nota: si la longitud de seq es
exactamente maxLen, se
regreso siguiente;
devolverá sin cambios.

});
}

BCONSTRUYENDO Y EJECUTANDO UN1DCONVENIO EN ELIMDCONJUNTO DE DATOS B


Ahora tenemos todas las piezas listas para el convnet 1D; vamos a juntarlos y ver si podemos
obtener una mayor precisión en la tarea de análisis de sentimiento de IMDb. El código del listado
9.8 crea nuestra convnet 1D (extraído de sentiment/train.js, con simplificación). El resumen de los
resultadosmodelo tfel objeto se muestra después de eso.

Listado 9.8 Construyendo una convnet 1D para el problema de IMDb

El modelo comienza con una capa de incrustación, que

const modelo = tf.secuencial(); convierte los índices enteros de entrada en los vectores
de palabras correspondientes.
modelo.add(tf.layers.embedding({
inputDim: tamaño de vocabulario,
La capa de incrustación necesita saber el
outputDim: incrustación de tamaño,
tamaño del vocabulario. Sin esto, no puede
inputLength: maxLen
determinar el tamaño de la matriz de inclusión.
}));
modelo.add(tf.layers.dropout({rate: 0.5}));
Agrega una capa de abandono
modelo.add(tf.layers.conv1d({ para combatir el sobreajuste
filtros: 250,
Aquí viene el
tamaño del núcleo: 5,
conversión1capa D.
pasos: 1, El MaxPool global1La capa d colapsa la
relleno: 'válido', dimensión del tiempo al extraer el valor
activación: 'relu' máximo del elemento en cada filtro. La
})); salida está lista para las próximas capas
modelo.add(tf.layers.globalMaxPool1d({})); densas (MLP).
modelo.add(tf.layers.dense({
unidades: 250, Agrega un MLP de dos capas en la
activación: 'relu' parte superior del modelo

}));
model.add(tf.layers.dense({unidades: 1, activación: 'sigmoide'}));
316 CPASADO9Deep learning para secuencias y texto

________________________________________________________________ Capa (tipo)


Forma de salida Parámetro #
================================================== ===============
incrustación_Incrustación1 (Incrustar [null,500,128] 1280000
________________________________________________________________ abandono_Abandono1
(Abandono) [null,500,128] 0
_________________________________________________________________ conv1d_Conv1D1 (Conv1D)
[nulo,496,250] 160250
_________________________________________________________________
global_max_pooling1d_GlobalM [nulo, 250] 0
________________________________________________________________ denso_Dense1 (Denso)
[nulo, 250] 62750
________________________________________________________________ denso_Dense2 (Denso)
[nulo, 1] 251
================================================== ===============
Parámetros totales: 1503251
Parámetros entrenables: 1503251 Parámetros no entrenables: 0
_________________________________________________________________

Es útil mirar el código JavaScript y el resumen de texto juntos. Hay algunas cosas que
vale la pena mencionar aquí:
- El modelo tiene una forma de [nulo, 500],dondenuloes la dimensión indeterminada del
lote (el número de ejemplos) y 500 es la longitud de palabra máxima permitida de
cada reseña (maxLen).El tensor de entrada contiene las secuencias truncadas y
rellenadas de índices de palabras enteras.
- La primera capa del modelo es una capa de incrustación. Convierte los índices de palabras
en sus correspondientes vectores de palabras, lo que conduce a una forma de [nulo, 500,
128]. Como puede ver, la longitud de la secuencia (500) se conserva y la dimensión de
incrustación (128) se refleja como el último elemento de la forma.
- La capa que sigue a la capa de incrustación es una capa conv1d, la parte central de este modelo.
Está configurado para tener un tamaño de kernel de 5, un tamaño de paso predeterminado de 1 y
un relleno "válido". Como resultado, hay 500 – 5 + 1 = 496 posiciones de deslizamiento posibles a
lo largo de la dimensión de la secuencia. Esto conduce a un valor de 496 en el segundo elemento
de la forma de salida ([nula, 496, 250]).El último elemento de la forma (250) refleja la cantidad de
filtros que la capa conv1d está configurada para tener.
- La capa globalMaxPool1d que sigue a la capa conv1d es algo similar a la capa maxPooling2d
que hemos visto en las convnets de imágenes. Sin embargo, realiza una agrupación más
espectacular, en la que todos los elementos a lo largo de la dimensión de la secuencia se
colapsan en un solo valor máximo. Esto conduce a la forma de salida
de [nulo, 250].
- Ahora que el tensor tiene una forma 1D (ignorando la dimensión del lote), podemos
construir dos capas densas encima para formar un MLP como la parte superior de todo el
modelo.
Creación de modelos de aprendizaje profundo para texto 317

Comience a entrenar el convnet 1D con el comandotren de hilo --maxLen 500 cnn.Después de dos o tres
épocas de entrenamiento, puede ver que el modelo alcanza una mejor precisión de validación de
alrededor de 0,903, que es una ganancia pequeña pero sólida en relación con la precisión que obtuvimos
del MLP basado en la vectorización multicaliente (0,890). Esto refleja la información de orden secuencial
que nuestra convnet 1D logró aprender pero que fue imposible de aprender por el MLP multi-caliente.

Entonces, ¿cómo captura un convnet 1D el orden secuencial? Lo hace a través de su kernel


convolucional. El producto escalar del núcleo es sensible al orden de los elementos. Por
ejemplo, si una entrada consta de cinco palabras,Me gusta mucho, la convolución 1D
generará un valor particular; sin embargo, si el orden de las palabras se altera para sertanto
que me gusta, provocará una salida diferente de la convolución 1D, aunque el conjunto de
elementos sea exactamente el mismo.
Sin embargo, debe señalarse que una capa conv1d por sí sola no puede aprender patrones
secuenciales más allá del tamaño de su kernel. Por ejemplo, supongamos que el orden de dos
palabras muy alejadas afecta el significado de la oración; una capa conv1d con un tamaño de
kernel más pequeño que la distancia no podrá aprender la interacción de largo alcance. Este es un
aspecto en el que RNN como GRU y LSTM eclipsan la convolución 1D.
Una forma en que la convolución 1D puede mejorar esta deficiencia es profundizar, es decir, apilar
varias capas conv1d para que el "campo receptivo" de las capas conv1d de nivel superior sea lo
suficientemente grande como para capturar dependencias de tan largo alcance. Sin embargo, en muchos
problemas de aprendizaje automático relacionados con texto, estas dependencias de largo alcance no
juegan un papel importante, por lo que basta con usar una convnet 1D con una pequeña cantidad de
capas conv1d. En el ejemplo de sentimiento de IMDb, puede intentar entrenar un modelo basado en
LSTM basado en el mismomaxLenvalor y dimensiones de incrustación como el convnet 1D:

tren de hilo --maxLen 500 lstm

Tenga en cuenta que la mejor precisión de validación del LSTM (similar pero un poco más complejo
que el GRU; consulte la figura 9.4) es casi la misma que la del convnet 1D. Quizás esto se deba a
que las interacciones de largo alcance entre palabras y frases no importan mucho para este cuerpo
de reseñas de películas y la tarea de clasificación de sentimientos.
Entonces, puede ver que las convnets 1D son una alternativa atractiva a las RNN para este tipo
de problema de texto. Esto es especialmente cierto si se tiene en cuenta el costo computacional
mucho más bajo de las convnets 1D en comparación con el de las RNN. Desde elCNNylstm
comandos, puede ver que entrenar el convnet 1D es aproximadamente seis veces más rápido que
entrenar el modelo LSTM. El rendimiento más lento de LSTM y RNN está relacionado con sus
operaciones internas paso a paso, que no se pueden paralelizar; las circunvoluciones son
susceptibles de paralelización por diseño.
318 CPASADO9Deep learning para secuencias y texto

ICAJA NFO9.2 Uso del proyector de incrustación para visualizar los vectores de
incrustación aprendidos

Palabras negativas como:


apesta, asqueroso, pretencioso,
sin valor, sin sentido

Palabras positivas como:


excelente, inspirador,
encantador,
impresionado, brillante

Visualización de las incrustaciones de palabras entrenadas de la convnet 1D mediante la reducción de la dimensión t-SNE en el
proyector de incrustaciones

¿Surge alguna estructura interesante en las incrustaciones de palabras de la convnet 1D después


del entrenamiento? Para averiguarlo, puede usar la bandera opcional:prefijo de archivos
incrustadosde Eltren de hilomando:

tren de hilo --maxLen 500 cnn --epochs 2 --embeddingFilesPrefix


/tmp/imdb_embed

Este comando generará dos archivos:

- /tmp/imdb_embed_vectors.tsv: un archivo de valores separados por tabuladores para los valores


numéricos de las incrustaciones de palabras. Cada línea contiene el vector de incrustación de una
palabra. En nuestro caso, hay 10.000 líneas (el tamaño de nuestro vocabulario) y cada línea
contiene 128 números (nuestras dimensiones de incrustación).
- /tmp/imdb_embed_labels.tsv: un archivo que consta de etiquetas de palabras que
corresponden a los vectores del archivo anterior. Cada línea es una palabra.
Creación de modelos de aprendizaje profundo para texto 319

Estos archivos se pueden cargar en el proyector integrado (https://proyector.tensorflow. organización)


para la visualización (ver la figura anterior). Debido a que nuestros vectores de incrustación residen en un
espacio de alta dimensión (128D), es necesario reducir su dimensionalidad a tres o menos dimensiones
para que puedan ser entendidos por un ser humano. La herramienta Embedding Projector proporciona
dos algoritmos para la reducción de dimensiones: incrustación de vecinos estocásticos distribuidos en t (t-
SNE) y análisis de componentes principales (PCA), que no analizaremos en detalle. Pero brevemente, estos
métodos asignan los vectores de incrustación de alta dimensión a 3D al tiempo que garantizan una
pérdida mínima en las relaciones entre los vectores. t-SNE es el método más sofisticado y
computacionalmente más intensivo entre los dos. La visualización que produce se muestra en la figura.

Cada punto en la nube de puntos corresponde a una palabra de nuestro vocabulario. Mueva el cursor del
mouse y colóquelo sobre los puntos para ver a qué palabras corresponden. Nuestros vectores de
incrustación, entrenados en el pequeño conjunto de datos de análisis de sentimientos, ya muestran una
estructura interesante relacionada con la semántica de las palabras. En particular, un extremo de la nube
de puntos contiene una gran proporción de palabras que aparecen con frecuencia en críticas positivas de
películas (comoexcelente,inspirador, yencantador), mientras que el extremo opuesto contiene muchas
palabras que suenan negativas (apesta,bruto, ypretencioso). Pueden surgir estructuras más interesantes
del entrenamiento de modelos más grandes en conjuntos de datos de texto más grandes, pero este
pequeño ejemplo ya le da una idea del poder del método de incrustación de palabras.

Debido a que las incrustaciones de palabras son una parte importante de las redes neuronales profundas
orientadas al texto, los investigadores han creado incrustaciones de palabras previamente entrenadas que los
profesionales del aprendizaje automático pueden usar de forma inmediata, renunciando a la necesidad de
entrenar sus propias incrustaciones de palabras como hicimos en nuestro Ejemplo de convnet de IMDb. Uno de
los conjuntos de incrustación de palabras preentrenados más conocidos es GloVe (para vectores globales) del
Stanford Natural Language Processing Group (verhttps://nlp.stanford.edu/projects/glove/).

La ventaja de usar incrustaciones de palabras previamente entrenadas como GloVe es doble. Primero, reduce la
cantidad de cómputo durante el entrenamiento porque la capa de incrustación no necesita ser entrenada más y,
por lo tanto, simplemente se puede congelar. En segundo lugar, las incrustaciones previamente entrenadas, como
GloVe, se entrenan a partir de miles de millones de palabras y, por lo tanto, tienen una calidad mucho mayor de lo
que sería posible entrenando en un conjunto de datos pequeño, como el conjunto de datos de IMDb aquí. En
estos sentidos, el papel que desempeñan las incrustaciones de palabras preentrenadas en los problemas de
procesamiento del lenguaje natural es similar al papel de las bases de redes de comunicación profundas
preentrenadas (como MobileNet, que vimos en el capítulo 5) en la visión por computadora.

tuCANTA EL1DCONVNET PARA INFERENCIA EN UNA PAGINA WEB


En sentiment/index.js, puede encontrar el código que implementa el modelo entrenado en Node.js para
usarlo en el lado del cliente. Para ver la aplicación del lado del cliente en acción, ejecute el comando reloj
de hilocomo en la mayoría de los otros ejemplos de este libro. El comando compilará el código, iniciará un
servidor web y automáticamente abrirá una pestaña del navegador para mostrar la página index.html. En
la página, puede hacer clic en un botón para cargar el modelo entrenado a través de solicitudes HTTP y
usar el modelo cargado para realizar un análisis de opinión sobre reseñas de películas en un cuadro de
texto. La muestra de reseña de la película en el cuadro de texto es editable, por lo que puede
320 CPASADO9Deep learning para secuencias y texto

realice ediciones arbitrarias y observe cómo eso afecta la predicción binaria en tiempo real. La página
viene con dos reseñas de ejemplos de stock (una positiva y otra negativa) que puede usar como punto de
partida para su manipulación. La convnet 1D cargada se ejecuta lo suficientemente rápido como para
generar la puntuación de sentimiento sobre la marcha a medida que escribe en el cuadro de texto.

El núcleo del código de inferencia es sencillo (consulte el listado 9.9, de


sentiment/index.js), pero hay varias cosas interesantes que señalar:
- El código convierte todo el texto de entrada a minúsculas, descarta la puntuación y borra los
espacios en blanco adicionales antes de convertir el texto en índices de palabras. Esto se debe a
que el vocabulario que usamos contiene solo palabras en minúsculas.
-Las palabras fuera del vocabulario (palabras que quedan fuera del vocabulario) se representan con
un índice de palabras especial (OOV_ÍNDICE).Estos incluyen palabras raras y errores tipográficos.

- Lo mismopadSequences()La función que usamos para el entrenamiento (ver listado 9.7) se


usa aquí para asegurarnos de que la entrada del tensor al modelo tenga la longitud
correcta. Esto se logra mediante el truncamiento y el relleno, como hemos visto
anteriormente. Este es un ejemplo de un beneficio de usar TensorFlow.js para tareas de
aprendizaje automático como esta: puede usar el mismo código de preprocesamiento de
datos para el entorno de capacitación de back-end y el entorno de servicio de front-end, lo
que reduce el riesgo de sesgo de datos (consulte el capítulo 6 para una discusión más
profunda de los riesgos de sesgo).

Listado 9.9 Usando el convnet 1D entrenado para inferencia en el frontend

Convierte a minúsculas; elimina la puntuación


predecir (texto) { y espacios en blanco adicionales del texto de entrada
texto de entrada const =
text.trim().toLowerCase().replace(/(\.|\,|\!)/g, const secuencia = '').separar(' ');
inputText.map(palabra => {
let wordIndex =
this.wordIndex[palabra] + this.indexFrom; if Asigna todas las palabras a los

(wordIndex > this.vocabularySize) { índices de palabras.

índicePalabra = OOV_INDEX; this.wordIndex se cargó


Las palabras que quedan fuera del
} desde un archivo JSON.
vocabulario se representan como
return índicePalabra; un índice de palabras especial:
}); OOV_INDEX.
const paddedSequence =
padSequences([secuencia], const this.maxLen);
Trunca reseñas largas
input = tf.tensor2d( y almohadillas cortas a la
paddedSequence, [1, this.maxLen]); longitud deseada

Realiza un seguimiento de
const beginMs = rendimiento.ahora(); Convierte los datos en una

cuanto tiempo const predictOut = this.model.predict(input); puntuación representación de tensor, de modo que

se gasta en el const = predictOut.dataSync()[0]; predecirOut.dispose(); pueda introducirse en el modelo

del modelo

inferencia const endMs = rendimiento.ahora(); la inferencia real


(paso hacia adelante en el
return {puntuación: puntuación, transcurrido: (endMs - beginMs)}; modelo) sucede aquí.
}
Tareas de secuencia a secuencia con mecanismo de atención 321

9.3 Tareas de secuencia a secuencia con mecanismo de atención


En los ejemplos de sentimiento de IMDb y el clima de Jena, mostramos cómo predecir un solo número o
una clase a partir de una secuencia de entrada. Sin embargo, algunos de los problemas secuenciales más
interesantes implican generar unsecuencia de salidabasado en una entrada. Este tipo de tareas se
denominan acertadamentesecuencia a secuencia(o seq2seq, para abreviar) tareas. Existe una gran
variedad de tareas seq2seq, de las cuales la siguiente lista es solo un pequeño subconjunto:

-Resumen de texto—Dado un artículo que puede contener decenas de miles de palabras,


generar un resumen sucinto del mismo (por ejemplo, en 100 o menos palabras).
- Máquina traductora—Dado un párrafo en un idioma (como el inglés), generar una
traducción del mismo en otro (como el japonés).
- Predicción de palabras para autocompletar—Dadas unas pocas primeras palabras en una oración, predecir qué palabras

vendrán después de ellas. Esto es útil para el autocompletado y la sugerencia en aplicaciones de correo electrónico e

interfaces de usuario para motores de búsqueda.

- Composición musical—Dada una secuencia principal de notas musicales, generar una


melodía que comience con esas notas.
- robots de chat—Dada una oración ingresada por un usuario, generar una respuesta que cumpla
con algún objetivo conversacional (por ejemplo, cierto tipo de atención al cliente o simplemente
chatear por diversión).

losmecanismo de atención11es un método poderoso y popular para tareas seq2seq. A menudo se


usa junto con RNN. En esta sección, mostraremos cómo podemos usar la atención y los LSTM para
resolver una tarea simple de seq2seq, es decir, convertir una gran cantidad de formatos de fecha
de calendario en un formato de fecha estándar. Aunque este es un ejemplo intencionalmente
simple, el conocimiento que obtendrá de él se aplica a tareas seq2seq más complejas como las
enumeradas anteriormente. Primero formulemos el problema de conversión de fechas.

9.3.1 Formulación de la tarea de secuencia a secuencia


Si eres como nosotros, te habrá confundido (o incluso un poco molesto) la gran cantidad
de formas posibles de escribir las fechas del calendario, especialmente si has viajado a
diferentes países. Algunas personas prefieren usar el orden mes-día-año, algunas
adoptan el orden díames-año y otras usan el orden año-mes-día. Incluso dentro del
mismo orden, existen variaciones con respecto a si el mes se escribe como una palabra
(enero), una abreviatura (Jan), un número (1) o un número de dos dígitos con ceros (01).
Las opciones para el día incluyen si lo prepara con un cero y si lo escribe como un
número ordinal (4º frente a 4). En cuanto al año, puedes escribir los cuatro dígitos
completos o solo los dos últimos. Además, las partes del año, mes y día se pueden
concatenar con espacios, comas, puntos o barras, o se pueden concatenar

11Véase Alex Graves, “Generating Sequences with Recurrent Neural Networks”, presentado el 4 de agosto de 2013,
https://arxiv.org/abs/1308.0850; y Dzmitry Bahdanau, Kyunghyun Cho y Yoshua Bengio, “Neural Machine
Translation by Jointly Learning to Align and Translate”, presentado el 1 de septiembre de 2014,https://arxiv.org/
abs/1409.0473.
322 CPASADO9Deep learning para secuencias y texto

sin ningún personaje intermedio en absoluto! Todas estas opciones se unen de forma
combinatoria, lo que da lugar a al menos unas decenas de formas de escribir una misma fecha.
Por lo tanto, sería bueno tener un algoritmo que pueda tomar una cadena de fecha de calendario en
estos formatos como entrada y generar la cadena de fecha correspondiente en el formato ISO-8601 (por
ejemplo, 2019-02-05). Podríamos resolver este problema de una manera que no sea de aprendizaje
automático escribiendo un programa tradicional. Pero dada la gran cantidad de formatos posibles, esta
es una tarea un tanto engorrosa y lenta, y el código resultante puede llegar fácilmente a cientos de líneas.
Probemos un enfoque de aprendizaje profundo, en particular, con una arquitectura de codificador-
descodificador de atención basada en LSTM.
Para limitar el alcance de este ejemplo, comenzamos con los 18 formatos de fecha comúnmente vistos que se
muestran en los siguientes ejemplos. Tenga en cuenta que todas estas son formas diferentes de escribir la
misma fecha:

"23 de enero de 2015", "012315", "23/01/15", "23/1/15",


"23/01/2015","23/01/2015","23-01-2015","23-1-2015", "23 ENE, 15","23 de
enero de 2015","23.01.2015","23.1.2015", "2015.01.23","2015.1.23","
20150123","2015/01/23", "2015-01-23","2015-1-23"

Por supuesto, hay otros formatos de fecha.12Pero agregar soporte para formatos adicionales será
básicamente una tarea repetitiva una vez que se hayan sentado las bases del entrenamiento y la
inferencia del modelo. Dejamos la parte de agregar más formatos de fecha de entrada como un
ejercicio para usted al final de este capítulo (ejercicio 3).
Primero, pongamos en marcha el ejemplo. Al igual que el ejemplo de análisis de sentimientos anterior,
este ejemplo consta de una parte de entrenamiento y una parte de inferencia. La parte de entrenamiento
se ejecuta en el entorno de back-end usando tfjs-node o tfjs-node-gpu. Para iniciar el entrenamiento, use
los siguientes comandos:

clon de git https://github.com/tensorflow/tfjs-examples.git cd tfjs-examples/


hilo de sentimiento

tren de hilo

Para realizar el entrenamiento usando una GPU CUDA, use --GPUbandera con latren de hilo
mando:

tren de hilo --gpu

El entrenamiento se ejecuta durante dos épocas de forma predeterminada, lo que debería ser suficiente para que
el valor de pérdida se acerque a cero y la precisión de la conversión sea casi perfecta. En los resultados de
inferencia de muestra impresos al final del trabajo de entrenamiento, la mayoría de los resultados, si no todos,
deberían ser correctos. Estas muestras de inferencia se extraen de un conjunto de prueba que no se superpone
con el conjunto de entrenamiento. El modelo entrenado se guarda en la ruta relativa

12Otra cosa que quizás haya notado es que usamos un conjunto de formatos de fecha sin ninguna ambigüedad. Si nosotros
incluido tanto MM/DD/AAAA como DD/MM/AAAA en nuestro conjunto de formatos, habría cadenas de fechas
ambiguas: es decir, que no se pueden interpretar con certeza. Por ejemplo, la cadena "02/01/2019" se puede
interpretar como el 2 de enero de 2019 o el 1 de febrero de 2019.
Tareas de secuencia a secuencia con mecanismo de atención 323

dist/model y se usará durante la etapa de inferencia basada en navegador. Para abrir la interfaz de
usuario de inferencia, utilice

reloj de hilo

En la página web que aparece, puede escribir fechas en el cuadro de texto Cadena de fecha de
entrada, pulsar Intro y observar cómo cambia la cadena de fecha de salida en consecuencia.
Además, el mapa de calor con diferentes tonos muestra la matriz de atención utilizada durante la
conversión (ver figura 9.9). La matriz de atención contiene información interesante y es
fundamental para este modelo seq2seq. Es especialmente susceptible de interpretación por parte
de los humanos. Deberías familiarizarte con él jugando con él.

Figura 9.9 El codificador-descodificador basado en la atención para la conversión de fechas en el trabajo, con la matriz de atención para el par de entrada-
salida en particular que se muestra en la parte inferior derecha

Tomemos como ejemplo el resultado que se muestra en la figura 9.9. La salida del modelo
("2034-07-18")traduce correctamente la fecha de entrada ("18 de julio de 2034").Las filas de la
matriz de atención corresponden a los caracteres de entrada ("JUL", " ",y así sucesivamente),
mientras que las columnas corresponden a los caracteres de salida ("2", "0", "3",Etcétera).
Entonces, cada elemento de la matriz de atención indica cuánta atención se presta al carácter
de entrada correspondiente cuando se genera el carácter de salida correspondiente. Cuanto
mayor sea el valor del elemento, más atención se presta. Por ejemplo, observe la cuarta
columna de la última fila: es decir, la que corresponde al último carácter de entrada ("4")y el
cuarto carácter de salida ("4").Tiene un valor relativamente alto, como lo indica la escala de
colores. Esto tiene sentido porque el último dígito de la parte del año de la salida debería
depender principalmente del último dígito de la parte del año en el
324 CPASADO9Deep learning para secuencias y texto

cadena de entrada Por el contrario, otros elementos de esa columna tienen valores más bajos, lo
que indica que la generación del carácter "4"en la cadena de salida no usó mucha información de
otros caracteres de la cadena de entrada. Se pueden ver patrones similares en las partes de mes y
día de la cadena de salida. Le recomendamos que experimente con otros formatos de fecha de
entrada y vea cómo cambia la matriz de atención.

9.3.2 La arquitectura codificador-decodificador y el mecanismo de atención


Esta sección lo ayuda a desarrollar la intuición sobre cómo la arquitectura de codificador-
decodificador resuelve el problema seq2seq y qué papel juega el mecanismo de atención en
él. Se presenta una discusión detallada de los mecanismos junto con el código en la siguiente
sección detallada.
Hasta este punto, todas las redes neuronales que hemos visto generan un solo elemento. Para una
red de regresión, la salida es solo un número; para una red de clasificación, es una única distribución de
probabilidad sobre un número de categorías posibles. Pero el problema de conversión de fechas al que
nos enfrentamos es diferente: en lugar de predecir un solo elemento, debemos predecir varios de ellos.
Específicamente, necesitamos predecir exactamente 10 caracteres para el formato de fecha ISO-8601.
¿Cómo deberíamos lograr esto usando una red neuronal?
La solución es crear una red que genere una secuencia de elementos. En particular, dado que la
secuencia de salida está hecha de símbolos discretos de un "alfabeto" con exactamente 11
elementos (del 0 al 9, así como el guión), dejamos que la forma del tensor de salida de la
la red tiene una forma 3D: [numEjemplos, SALIDA_LONGITUD, SALIDA_VOCAB_ TAMAÑO].
La primera dimensión (numEjemplos)es la dimensión de ejemplo convencional que permite el
procesamiento por lotes como todas las demás redes que hemos visto en este libro.SALIDA_
LONGITUDes 10, es decir, la longitud fija de la cadena de fecha de salida en el formato
ISO-8601.SALIDA_VOCAB_SIZEes el tamaño del vocabulario de salida (o más exactamente,
"alfabeto de salida"), que incluye los dígitos del 0 al 9 y el guión (-), además de un par de
caracteres con significados especiales que veremos más adelante.
Eso cubre la salida del modelo. ¿Qué hay de las entradas del modelo? Resulta que el modelo
tomadosentradas en lugar de una. El modelo se puede dividir aproximadamente en dos partes, el
codificador y el decodificador, como se muestra esquemáticamente en la figura 9.10. La primera
entrada del modelo va a la parte del codificador. Es la propia cadena de fecha de entrada,
representada como una secuencia de índices de caracteres de forma [numEjemplos, INPUT_LENGTH].
ENTRADA_LONGITUDes la longitud máxima posible entre los formatos de fecha de entrada
admitidos (que resulta ser 12). Las entradas más cortas que esa longitud se rellenan con ceros al
final. La segunda entrada va a la parte del decodificador del modelo. Es el resultado de la
conversión desplazado a la derecha por un paso de tiempo, y tiene una forma de [numEjemplos,
SALIDA_LONGITUD].
Espere, la primera entrada tiene sentido porque es la cadena de fecha de entrada, pero ¿por qué el
modelo toma el resultado de la conversión como una entrada adicional? ¿No está destinado a ser el
produccióndel modelo? La clave está en el cambio del resultado de la conversión. Tenga en cuenta que la
segunda entrada esnoexactamente el resultado de la conversión. En cambio, es una versión retrasada del
resultado de la conversión. El tiempo de retardo es exactamente de un paso. Por ejemplo, si durante
Traducido del inglés al español - www.onlinedoctranslator.com

Tareas de secuencia a secuencia con mecanismo de atención 325

UN.Primer paso de la conversión B.Segundo paso de la conversión

Salida del decodificador Salida del decodificador

Atención "2" ” Atención "2" "0"”

Descifrador Descifrador

codificador codificador

"J" “U” “L”..."4" ST "J" ... ST "2"

Entrada de codificador Entrada del decodificador Entrada de codificador Entrada del decodificador

Figura 9.10 Cómo la arquitectura codificador-decodificador convierte una cadena de datos de entrada en una de salida.S Tes el
token de inicio especial para la entrada y salida del decodificador. Los paneles A y B muestran los dos primeros pasos de la
conversión, respectivamente. Después del primer paso de conversión, el primer carácter en la salida ("2") es generado. Después
del segundo paso, el segundo carácter ("0") es generado. Los pasos restantes siguen el mismo patrón y, por lo tanto, se omiten.

entrenamiento, el resultado de conversión deseado es "2034-07-18",entonces la segunda entrada al modelo será


"<ST>2034-07-1",donde <ST>es un símbolo especial de inicio de secuencia. Esta entrada desplazada permite que el
decodificador sea consciente de la secuencia de salida que se ha generado hasta el momento. Hace que sea más
fácil para el decodificador realizar un seguimiento de dónde se encuentra en el proceso de conversión.

Esto es análogo a cómo hablan los humanos. Cuando pones un pensamiento en palabras, tu esfuerzo
mental se gasta en dos cosas: el concepto en sí mismo y lo que has dicho hasta ahora. La última parte es
importante para garantizar un discurso coherente, completo y no repetitivo. Nuestro modelo funciona de
manera similar: para generar cada carácter de salida, utiliza la información tanto de la cadena de fecha de
entrada como de los caracteres de salida que se han generado hasta el momento.

El retraso en el tiempo del resultado de la conversión funciona durante la fase de


entrenamiento porque ya sabemos cuál es el resultado de conversión correcto. Pero, ¿cómo
funciona durante la inferencia? La respuesta se puede ver en los dos paneles de la figura 9.10:
generamos los caracteres de salida uno por uno.13Como muestra el panel A de la figura,
empezamos pegando un S Tsímbolo al comienzo de la entrada del decodificador. A través de un
paso de inferencia (unoModelo.predecir()llamada), obtenemos un nuevo elemento de salida (el "2"en
el tablero). Este nuevo elemento de salida se agrega luego a la entrada del decodificador. Luego se
produce el siguiente paso de conversión. Ve el carácter de salida recién generado "2"en la entrada
del decodificador (ver panel B de la figura 9.10). Este paso implica otroModelo.predecir()llama y
genera un nuevo carácter de salida ("0"),que se añade de nuevo a la entrada del decodificador.

13Elcódigo que implementa el algoritmo de conversión paso a paso es la funciónejecutarSeq2SeqInference()


en date-conversion-attention/model.js.
326 CPASADO9Deep learning para secuencias y texto

Este proceso se repite hasta que se alcanza la longitud deseada de la salida (10 en este caso).
Observe que la salida no incluye elS Telemento, por lo que se puede utilizar directamente como el
resultado final de todo el algoritmo.

TEL PAPEL DEL MECANISMO DE ATENCIÓN


El papel del mecanismo de atención es permitir que cada carácter de salida "atienda" a los
caracteres correctos en la secuencia de entrada. Por ejemplo, el "7"parte de la cadena de salida "
2034-07-18"debe atender a la "JUL"parte de la cadena de fecha de entrada. Esto es nuevamente
análogo a cómo los humanos generan lenguaje. Por ejemplo, cuando traducimos una oración del
idioma A al idioma B, cada palabra en la oración de salida generalmente está determinada por una
pequeña cantidad de palabras de la oración de entrada.
Esto puede parecer una obviedad: es difícil imaginar qué otros enfoques podrían
funcionar mejor. Pero la introducción del mecanismo de atención presentado por
investigadores de aprendizaje profundo alrededor de 2014-2015 fue un gran avance en
el campo. Para comprender la razón histórica detrás de esto, mire la flecha que conecta
la caja del Codificador con la caja del Decodificador en el panel A de la figura 9.10. Esta
flecha representa la última salida de un LSTM en la parte del codificador del modelo,
que se pasa a un LSTM en la parte del decodificador del modelo como su estado inicial.
Recuerde que el estado inicial de los RNN suele ser todo cero (por ejemplo, el RNN
simple que usamos en la sección 9.1.2); sin embargo, TensorFlow.js le permite
establecer el estado inicial de un RNN en cualquier valor de tensor dado de la forma
correcta. Esto se puede usar como una forma de pasar información ascendente a un
LSTM. En este caso,
Sin embargo, el estado inicial es una secuencia de entrada completa empaquetada en un solo vector.
Resulta que esta representación está un poco demasiado condensada para que el decodificador la
descomprima, especialmente para secuencias más largas y complejas (como las oraciones que se ven en
los problemas típicos de traducción automática). Aquí es donde entra en juego el mecanismo de atención.

El mecanismo de atención amplía el "campo de visión" disponible para el decodificador. En lugar


de usar solo la salida final del codificador, el mecanismo de atención accede a la secuencia
completa de la salida del codificador. En cada paso del proceso de conversión, el mecanismo
atiende a pasos de tiempo específicos en la secuencia de salida del codificador para decidir qué
carácter de salida generar. Por ejemplo, el primer paso de conversión puede prestar atención a los
dos primeros caracteres de entrada, mientras que el segundo paso de conversión presta atención a
los caracteres de entrada segundo y tercero, y así sucesivamente (ver figura 9.10 para un ejemplo
concreto de tal matriz de atención). Al igual que todos los parámetros de peso de la red neuronal,
un modelo de atenciónaprendela forma en que asignó la atención, en lugar de codificar una
política. Esto hace que el modelo sea flexible y poderoso: puede aprender a prestar atención a
diferentes partes de la secuencia de entrada dependiendo tanto de la secuencia de entrada en sí
misma como de lo que se haya generado en la secuencia de salida hasta el momento.
Esto es lo más lejos que podemos llegar al hablar sobre el mecanismo codificador-decodificador
sin mirar el código o abrir las cajas negras que son el mecanismo codificador, decodificador y de
atención. Si este tratamiento le parece demasiado elevado o demasiado vago, lea
Tareas de secuencia a secuencia con mecanismo de atención 327

la siguiente sección, donde profundizaremos un poco más en las tuercas y tornillos del modelo. Vale la
pena el esfuerzo mental para aquellos que deseen obtener una comprensión más profunda de la
arquitectura de codificador-decodificador basada en la atención. Para motivarlo a leerlo, tenga en cuenta
que la misma arquitectura subyace en sistemas como los modelos de traducción automática de última
generación (traducción automática neuronal de Google o GNMT), aunque estos modelos de producción
emplean más capas de LSTM y están capacitados en cantidades mucho mayores de datos que el modelo
simple de conversión de fecha que estamos tratando aquí.

9.3.3 Profundización en el modelo codificador-decodificador basado en la atención

La figura 9.11 amplía las cajas de la figura 9.10 y proporciona una vista más detallada de
sus estructuras internas. Es más ilustrativo verlo junto con el código que construye el
modelo:crearModelo()función en date-conversion-attention/model.js. A continuación,
veremos los aspectos importantes del código.
Primero, definimos un par de constantes para las capas de incrustación y LSTM en el codificador
y decodificador:

const incrustaciónDims = 64; const


lstmUnidades = 64;

"2" "0"
MLP
(distribuido en el tiempo)

Concatenar h1 h2 h3 h12

X X X X
Contexto Decodificador LSTM

X X X X
Atención incrustación

Decodificador actual
h1 h2 h3 h12 Estado inicial
la salida se convierte en
para decodificador LSTM
entrada del decodificador

para el siguiente paso


Codificador LSTM

incrustación

"J" "tú" "L" ...


"4" ST "2"

Figura 9.11 Inmersión profunda en el modelo codificador-decodificador basado en la atención. Puede pensar en esta figura como una vista
ampliada de la arquitectura del codificador-decodificador descrita en la figura 9.10, con detalles más detallados representados.
328 CPASADO9Deep learning para secuencias y texto

El modelo que construiremos toma dos entradas, por lo que debemos usar la API del modelo funcional en
lugar de la API secuencial. Partimos de las entradas simbólicas del modelo para la entrada del codificador
y la entrada del decodificador, respectivamente:

const encoderInput = tf.input({forma: [inputLength]}); const decoderInput


= tf.input({shape: [outputLength]});

Tanto el codificador como el decodificador aplican una capa de incrustación en sus respectivas secuencias
de entrada. El código para el codificador parece

let codificador = tf.layers.embedding({


entradaDim: tamaño de vocabulario de entrada,

salidaDim: incrustaciónDims,
longitud de entrada,
maskZero: verdadero
}).apply(entrada del codificador);

Esto es similar a las capas incrustadas que usamos en el problema de sentimiento de IMDb, pero incrusta
caracteres en lugar de palabras. Esto demuestra que el método de incrustación no se limita a las palabras.
De hecho, es lo suficientemente flexible como para aplicarse a cualquier conjunto discreto y finito, como
géneros musicales, artículos en un sitio web de noticias, aeropuertos en un país, etc. los maskZero:
verdaderola configuración de la capa de incrustación le indica al LSTM descendente que omita los pasos
con valores todos cero. Esto ahorra cálculos innecesarios en secuencias que ya han finalizado.

LSTM es un tipo de RNN que aún no hemos cubierto en detalle. No entraremos aquí en su
estructura interna. Baste decir que es similar a GRU (figura 9.4) en el sentido de que aborda el
problema del gradiente de fuga al facilitar el transporte de un estado en varios pasos de tiempo. La
publicación de blog de Chris Olah "Comprender las redes LSTM", para la cual se proporciona un
indicador en "Materiales de lectura adicional" al final del capítulo, presenta una excelente revisión y
visualización de la estructura y los mecanismos de los LSTM. Nuestro codificador LSTM se aplica en
los vectores de incrustación de caracteres:

codificador = tf.layers.lstm({
unidades: lstmUnits,
secuencias de retorno: verdadero
}).apply(codificador);

lossecuencias de retorno: verdaderoLa configuración permite que la salida del LSTM sea una secuencia de
vectores de salida en lugar de la salida predeterminada de un solo vector que es la salida final (como
hicimos en los modelos de predicción de temperatura y análisis de sentimiento). Este paso es requerido
por el mecanismo de atención aguas abajo.
losObtenerLastTimestepLayerLa capa que sigue al codificador LSTM es una capa definida de forma
personalizada:

const encoderLast = new GetLastTimestepLayer({


nombre: 'encoderLast'
}).apply(codificador);

Simplemente corta el tensor de secuencia de tiempo a lo largo de la dimensión de tiempo (la


segunda dimensión) y genera el último paso de tiempo. Esto nos permite enviar el estado final del
Tareas de secuencia a secuencia con mecanismo de atención 329

codificador LSTM al decodificador LSTM como su estado inicial. Esta conexión es una de las formas
en que el decodificador obtiene información sobre la secuencia de entrada. Esto se ilustra en la
figura 9.11 con la flecha que conectah12en el bloque codificador verde a la capa LSTM del
decodificador en el bloque decodificador azul.
La parte del decodificador del código comienza con una capa de incrustación y una capa LSTM
que recuerda la topología del codificador:

dejar decodificador = tf.layers.embedding({


entradaDim: tamaño de vocabulario de salida,

salidaDim: incrustaciónDims,
longitud de entrada: longitud de salida,
maskZero: verdadero
}).apply(entrada del decodificador);
decodificador = tf.layers.lstm({
unidades: lstmUnits,
secuencias de retorno: verdadero
}).apply(decodificador, {initialState: [encoderLast, encoderLast]});

En la última línea de este fragmento de código, observe cómo el estado final del codificador se usa como
estado inicial del decodificador. En caso de que te preguntes por qué el tensor simbólicocodificador-último
se repite en la última línea de código aquí, se debe a que una capa LSTM contiene dos estados, a
diferencia de la estructura de un estado que hemos visto en simpleRNN y GRU.
La forma adicional y más poderosa en la que el decodificador obtiene una vista de las
secuencias de entrada es, por supuesto, el mecanismo de atención. La atención es un producto
punto (producto elemento por elemento) entre la salida del codificador LSTM y la salida del
decodificador LSTM, seguido de una activación de softmax:

dejar atención = tf.layers.dot({ejes: [2, 2]}).apply([decodificador, codificador]); atención =


tf.layers.activation({
activación: 'softmax',
nombre: 'atención'
}).apply(atención);

La salida del codificador LSTM tiene una forma de [nulo, 12, 64],donde 12 es la longitud de la
secuencia de entrada y 64 es el tamaño de la LSTM. La salida del decodificador LSTM tiene una
forma de [nulo, 10, 64],donde 10 es la longitud de la secuencia de salida y 64 es el tamaño del LSTM.
Se realiza un producto punto entre los dos a lo largo de la última dimensión (características LSTM),
lo que da lugar a una forma de [nulo, 10, 12] (es decir, [nulo, longitud de entrada, longitud de salida]).El
softmax aplicado en el producto escalar convierte los valores en puntajes de probabilidad, que se
garantiza que son positivos y suman 1 en cada columna de la matriz. Esta es la matriz de atención
que es fundamental para nuestro modelo. Su valor es el que se visualiza en la figura anterior 9.9.

Luego, la matriz de atención se aplica a la salida secuencial del codificador LSTM. Así es
como el proceso de conversión aprende a prestar atención a los diferentes elementos de la
secuencia de entrada (en su forma codificada) en cada paso. El resultado de aplicar la
atención a la salida del codificador se denominacontexto:

const contexto = tf.layers.dot({


ejes: [2, 1],
330 CPASADO9Deep learning para secuencias y texto

nombre: 'contexto'
}).apply([atención, codificador]);

El contexto tiene una forma de [nulo, 10, 64] (es decir, [nulo, longitud de salida, lstm-
Unidades]).Está concatenado con la salida del decodificador, que también tiene forma de
[nulo, 10, 64].Entonces, el resultado de la concatenación tiene una forma de [nulo, 10, 128]:

const decoderCombinedContext =
tf.layers.concatenate().apply([contexto, decodificador]);

decodificadorCombinedContextcontiene los vectores de características que van a la etapa final


del modelo, es decir, la etapa que genera los caracteres de salida.
Los caracteres de salida se generan utilizando un MLP que contiene una capa oculta y una
capa de salida softmax:

dejar salida = tf.layers.timeDistributed({


capa: tf.layers.dense({
unidades: lstmUnits,
activación: 'tanh'
})
}).apply(decoderCombinedContext); salida =

capa: tf.layers.timeDistributed({ tf.layers.dense({


unidades: tamaño de vocabulario de salida,

activación: 'softmax'
})
}).apply(salida);

Gracias aTiempo Distribuidocapa, todos los pasos comparten el mismo MLP. losDistribuido en el
tiempolayer toma una capa y la llama repetidamente en todos los pasos a lo largo de la dimensión
de tiempo (es decir, la segunda dimensión) de su entrada. Esto convierte la forma de la
característica de entrada de [nulo, 10, 128]para [nulo, 10, 13],donde 13 corresponde a los 11
caracteres posibles del formato de fecha ISO-8601, además de los 2 caracteres especiales (relleno e
inicio de secuencia).
Con todas las piezas en su lugar, las ensamblamos en unmodelo tfobjeto con dos
entradas y una salida:
const modelo = tf.modelo({
entradas: [entrada del codificador, decodificador de entrada],

salidas: producción

});

Para prepararnos para el entrenamiento, llamamos alcompilar()método con una función categórica de
pérdida de entropía cruzada. La elección de esta función de pérdida se basa en el hecho de que el
problema de conversión es esencialmente un problema de clasificación: en cada paso de tiempo,
elegimos un carácter del conjunto de todos los caracteres posibles:

modelo.compilar({
pérdida: 'categoricalCrossentropy',
optimizador: 'adam'
});
Ejercicios 331

En el momento de la inferencia, unargMax()La operación se aplica en el tensor de salida del modelo para
obtener el carácter de salida ganador. En cada paso de la conversión, el carácter de salida ganador se
agrega a la entrada del decodificador, por lo que el próximo paso de conversión puede usarlo (vea la
flecha en el extremo derecho de la figura 9.11). Como mencionamos antes, este proceso iterativo
finalmente produce la secuencia de salida completa.

Materiales para lectura adicional


- Chris Olah, “Understanding LSTM Networks”, blog, 27 de agosto de 2015,http://mng.
bz/m4Wa.
- Chris Olah y Shan Carter, "Atención y redes neuronales recurrentes aumentadas",
Distill, 8 de septiembre de 2016,https://distill.pub/2016/aumentada-rnns/. Andrej
- Karpathy, “La eficacia irrazonable de las redes neuronales recurrentes”, blog, 21 de
mayo de 2015,http://mng.bz/6wK6.
- Zafarali Ahmed, "Cómo visualizar su red neuronal recurrente con atención en
Keras", Medium, 29 de junio de 2017,http://mng.bz/6w2e.
- En el ejemplo de conversión de fechas, describimos una técnica de decodificación
basada en argMax().Este enfoque a menudo se conoce como eldecodificación codiciosa
técnica porque extrae el símbolo de salida de mayor probabilidad en cada paso. Una
alternativa popular al enfoque de decodificación codiciosa eshaz de búsqueda
decodificación, que examina una gama más amplia de posibles secuencias de salida
para determinar la mejor. Puede leer más sobre esto en Jason Brownlee, "How to
Implement a Beam Search Decoder for Natural Language Processing", 5 de enero de
2018,https://machinelearningmastery.com/beam-search-decoder-naturallanguage-
processing/.
- Stephan Raaijmakers,Aprendizaje profundo para el procesamiento del lenguaje natural,
Publicaciones de Manning, en prensa,www.manning.com/books/deep-learning-for-
naturallanguage-processing.

Ejercicios
1 Intente reorganizar el orden de los elementos de datos para varios datos no secuenciales.
Confirme que dicho reordenamiento no tiene ningún efecto sobre los valores de métrica de
pérdida (por ejemplo, precisión) del modelado (más allá de la fluctuación aleatoria causada por la
inicialización aleatoria de los parámetros de peso). Puede hacer esto para los siguientes dos
problemas:
aEn el ejemplo de la flor de lirio (del capítulo 3), cambie el orden de los cuatro
características numéricas (longitud del pétalo, ancho del pétalo, longitud del sépalo y ancho del
sépalo) haciendo cambios en la línea

shuffledData.push(datos[índices[i]]);
en el archivo iris/data.js del repositorio tfjs-examples. En particular, alterar el orden de
los cuatro elementos endatos[índices[i]].Esto se puede hacer a través de llamadas al
rodaja()yconcat()métodos de la matriz de JavaScript. Tenga en cuenta que el
332 CPASADO9Deep learning para secuencias y texto

el reordenamiento del orden debe ser el mismo para todos los ejemplos. Puede escribir una
función de JavaScript para realizar el reordenamiento.
B En el regresor lineal y MLP que desarrollamos para el problema del clima de Jena,
intente reordenar los 240 pasos de tiempoylas 14 características numéricas
(medidas de instrumentos meteorológicos). Específicamente, puede lograr esto
modificando elsiguienteBatchFn()función en jena-weather/data.js. La línea donde es
más fácil implementar el reordenamiento es
muestras.set(valor, j, ejemploFila, ejemploCol++);
donde se puede mapear el índicefila de ejemploa un nuevo valor usando una
función que realiza una permutación fija y mapeaejemploColde forma similar. La
2 convnet 1D que construimos para el análisis de opinión de IMDb constaba de una sola
capa conv1d (consulte el listado 9.8). Como comentamos, apilar más capas conv1d
encima puede darnos una convnet 1D más profunda capaz de capturar información de
pedidos en un lapso más largo de palabras. En este ejercicio, practique la modificación
del código en elconstruirModelo()función de sentimiento/train.js. El objetivo es agregar
otra capa conv1d después de la existente, volver a entrenar el modelo y observar si
hay alguna mejora en la precisión de su clasificación. La nueva capa conv1d puede
usar la misma cantidad de filtros y tamaño de kernel que la existente. Además, lea las
formas de salida en el resumen del modelo modificado y asegúrese de comprender
cómofiltrosykernelSizeLos parámetros conducen a la forma de salida de la nueva capa
conv1d.
3 En el ejemplo de conversión de fecha y atención, intente agregar un par de formatos de fecha de entrada
más. Los siguientes son los nuevos formatos entre los que puede elegir, clasificados en orden creciente
de dificultad de codificación. También puede crear sus propios formatos de fecha:

a El formato AAAA-MMM-DD: por ejemplo, "2012-MAR-08" o "2012-MAR-18". Dependiendo


de si los números de día de un solo dígito se rellenan con un cero (como en 03/12/2015),
en realidad pueden ser dos formatos diferentes. Sin embargo, independientemente del
relleno, la longitud máxima de este formato es inferior a 12, y todos los caracteres
posibles ya están en elENTRADA_VOCABen dateconversion-attention/date_format.js. Por
lo tanto, todo lo que se necesita es agregar una función o dos al archivo, y esas
funciones se pueden modelar a partir de las funciones existentes.
unos, comodateTupleToMMMSpaceDDSpaceYY().Asegúrate de agregar el
nueva(s) función(es) para elENTRADA_FNSarray en el archivo, para que puedan incluirse en el
entrenamiento. Como práctica recomendada, también debe agregar pruebas unitarias para sus
nuevas funciones de formato de fecha en date-conversion-attention/date_format_test.js.
B Un formato con números ordinales como parte del día, como "8 de marzo de 2012".
Tenga en cuenta que esto es lo mismo que el existente
dateTupleToMMMSpaceDDComma-SpaceYYYY()formato, excepto que el número de
día tiene como sufijo el ordinal suficiente ("st", "nd",y "th").Su nueva función debe
incluir la lógica para determinar el sufijo en función del valor del día. Además, debe
revisar elENTRADA_LONGITUDconstante en date_format_test.js a un valor mayor
porque la longitud máxima posible de la cadena de fecha en este formato
Resumen 333

excede el valor actual de 12. Además, las letras "t"y "h"hay que añadir a
ENTRADA_VOCAB,ya que no aparecen en ninguna de las cadenas de mes de
tres letras.
C Ahora considere un formato con el nombre completo en inglés del mes, como "8 de
marzo de 2012". ¿Cuál es la longitud máxima posible de la cadena de fecha de
entrada? ¿Cómo deberías cambiar?ENTRADA_VOCABen date_format.js en
consecuencia?

Resumen
-En virtud de poder extraer y aprender información contenida en el orden secuencial de
las cosas, las RNN pueden superar a los modelos feedforward (por ejemplo, MLP) en
tareas que involucran datos de entrada secuenciales. Vemos esto a través del ejemplo
de aplicar simpleRNN y GRU al problema de predicción de temperatura.

- Hay tres tipos de RNN disponibles en TensorFlow.js: simpleRNN, GRU y LSTM. Los dos
últimos tipos son más sofisticados que los RNN simples, ya que utilizan una estructura
interna más compleja para permitir el transporte del estado de la memoria durante muchos
pasos de tiempo, lo que mitiga el problema del gradiente de fuga. GRU es
computacionalmente menos intensivo que LSTM. En la mayoría de los problemas prácticos,
probablemente querrá usar GRU y LSTM.
- Al crear redes neuronales para texto, las entradas de texto deben representarse
primero como vectores de números. Esto se llama vectorización de texto. Los métodos
de vectorización de texto más utilizados incluyen la codificación one-hot y multi-hot,
así como el método de incrustación más potente.
- En la incrustación de palabras, cada palabra se representa como un vector no disperso, del cual
los valores de los elementos se aprenden a través de la retropropagación, al igual que todos los
demás parámetros de peso de la red neuronal. La función en TensorFlow.js que
realiza la incrustación estf.capas.embedding().
- Los problemas de seq2seq son diferentes de los problemas de clasificación y regresión basados
en secuencias en que implican generar una nueva secuencia como salida. Los RNN se pueden
usar (junto con otros tipos de capas) para formar una arquitectura de codificador-decodificador
para resolver problemas de seq2seq.
- En los problemas seq2seq, el mecanismo de atención permite que diferentes elementos de la
secuencia de salida dependan selectivamente de elementos específicos de la secuencia de
entrada. Demostramos cómo entrenar una red de codificador-decodificador basada en atención
para resolver un problema simple de conversión de fecha y visualizar la matriz de atención
durante la inferencia.
Aprendizaje profundo generativo

Este capítulo cubre


- Qué es el aprendizaje profundo generativo, sus aplicaciones y en qué se
diferencia de las tareas de aprendizaje profundo que hemos visto hasta ahora

- Cómo generar texto usando un RNN


- Qué es el espacio latente y cómo puede formar la base para generar
imágenes novedosas, a través del ejemplo de los autocodificadores
variacionales

- Los fundamentos de las redes antagónicas generativas

Algunas de las tareas más impresionantes demostradas por las redes neuronales profundas
involucraron la generación de imágenes, sonidos y texto que parecen o suenan reales. Hoy en día, las
redes neuronales profundas son capaces de crear imágenes de rostros humanos muy realistas,1
sintetizar el habla que suena natural,2y componer convincentemente coherente

1
Tero Karras, Samuli Laine y Timo Aila, “A Style-Based Generator Architecture for Generative Adversarial Networks”,
presentado el 12 de diciembre de 2018,https://arxiv.org/abs/1812.04948. Vea una demostración en vivo en https://
thispersondoesnotexist.com/.
2
Aäron van den Oord y Sander Dieleman, “WaveNet: A Generative Model for Raw Audio”, blog, 8 de septiembre de
2016,http://mng.bz/MOrn.

334
Generando texto con LSTM 335

texto,3solo por nombrar algunos logros. TalgenerativoLos modelos son útiles por varias razones, incluida
la ayuda a la creación artística, la modificación condicional del contenido existente y el aumento de los
conjuntos de datos existentes para respaldar otras tareas de aprendizaje profundo.4
Además de las aplicaciones prácticas, como maquillar la selfie de un posible cliente de cosméticos,
también vale la pena estudiar los modelos generativos por razones teóricas. El modelado generativo y
discriminativo son dos tipos de modelos fundamentalmente diferentes en el aprendizaje automático.
Todos los modelos que hemos estudiado en este libro hasta ahora sondiscriminatoriomodelos Dichos
modelos están diseñados para mapear una entrada en un valor discreto o continuo sin preocuparse por
el proceso a través del cual se genera la entrada. Recuerde los clasificadores para sitios web de phishing,
flores de iris, dígitos MNIST y sonidos del habla, así como el regresor para los precios de la vivienda que
hemos creado. Por el contrario, los modelos generativos están diseñados para imitar matemáticamente el
proceso a través del cual se generan los ejemplos de diferentes clases. Pero una vez que un modelo
generativo ha aprendido este conocimiento generativo, también puede realizar tareas discriminatorias.
Por lo tanto, se puede decir que los modelos generativos "comprenden" mejor los datos en comparación
con los modelos discriminativos.
Esta sección cubre los fundamentos de modelos generativos profundos para texto e imágenes. Al final
del capítulo, debería estar familiarizado con las ideas detrás de los modelos de lenguaje basados en
RNN, los codificadores automáticos orientados a imágenes y las redes antagónicas generativas. También
debe estar familiarizado con el patrón en el que se implementan dichos modelos en TensorFlow.js y ser
capaz de aplicar estos modelos a su propio conjunto de datos.

10.1 Generando texto con LSTM


Empecemos por la generación de texto. Para hacer eso, usaremos RNN, que presentamos en el
capítulo anterior. Aunque la técnica que verá aquí genera texto, no se limita a este dominio de
salida en particular. La técnica se puede adaptar para generar otros tipos de secuencias, como
música, dada la capacidad de representar notas musicales de manera adecuada y encontrar un
conjunto de datos de entrenamiento adecuado.5Se pueden aplicar ideas similares para generar
trazos de pluma en bocetos para que los bocetos se vean bien.6o incluso kanjis de aspecto realista7
se puede generar.

10.1.1 Predictor del siguiente carácter: una forma sencilla de generar texto

Primero, definamos la tarea de generación de texto. Supongamos que tenemos un corpus de datos de texto de
un tamaño decente (al menos unos pocos megabytes) como entrada de entrenamiento, como las obras
completas de Shakespeare (una cadena muy larga). Queremos entrenar un modelo para generar

"Mejores modelos de lenguaje y sus implicaciones", OpenAI, 2019,https://openai.com/blog/better-


3
lenguaje-modelos/.
4
Antreas Antoniou, Amos Storkey y Harrison Edwards, “Data Augmentation Generative Adversarial Networks”, presentado
el 12 de noviembre de 2017,https://arxiv.org/abs/1711.04340.
5Por ejemplo, vea Performance-RNN del Proyecto Magenta de Google:https://magenta.tensorflow.org/
rendimiento-rnn.
6
Por ejemplo, vea Sketch-RNN de David Ha y Douglas Eck:http://mng.bz/omyv.
7
David Ha, “Recurrent Net Dreams Up Fake Chinese Characters in Vector Format with TensorFlow”, blog, 28 de diciembre
de 2015,http://mng.bz/nvX4.
336 CPASADO10Aprendizaje profundo generativo

nuevos textos queparecelos datos de entrenamiento tanto como sea posible. La frase clave
aquí es, por supuesto, "parecer". Por ahora, contentémonos con no definir con precisión qué
significa “parecer”. El significado se aclarará después de que mostremos el método y los
resultados.
Pensemos en cómo formular esta tarea en el paradigma del aprendizaje profundo. En el
ejemplo de conversión de fecha cubierto en el capítulo anterior, vimos cómo se puede generar una
secuencia de salida con formato preciso a partir de una de entrada con formato casual. Esa tarea
de conversión de texto a texto tenía una respuesta bien definida: la cadena de fecha correcta en el
formato ISO-8601. Sin embargo, la tarea de generación de texto aquí no parece encajar en este
proyecto de ley. No hay una secuencia de entrada explícita y la salida "correcta" no está bien
definida; solo queremos generar algo que "parece real". ¿Qué podemos hacer?
Una solución es construir un modelo para predecir qué carácter vendrá después de una
secuencia de caracteres. Se llamapredicción del siguiente carácter. Por ejemplo, un modelo bien
entrenado en el conjunto de datos de Shakespeare debería predecir el carácter "u" con una alta
probabilidad cuando se le da la cadena de caracteres "El amor no mira con los ojos, b" como
entrada. Sin embargo, eso genera solo un carácter. ¿Cómo usamos el modelo para generar una
secuencia de caracteres? Para hacer eso, simplemente formamos una nueva secuencia de entrada
de la misma longitud que antes, desplazando la entrada anterior un carácter hacia la izquierda,
descartando el primer carácter y pegando el carácter recién generado ("u") al final. Esto nos da una
nueva entrada para nuestro predictor del siguiente carácter, a saber, "ove no mira con los ojos, bu"
en este caso. Dada esta nueva secuencia de entrada, el modelo debería predecir el carácter "t" con
una alta probabilidad. Este proceso, que se ilustra en la figura 10.1, puede repetirse tantas veces
como sea necesario para generar una secuencia tan larga como se desee. Por supuesto,
necesitamos un fragmento de texto inicial como punto de partida. Para eso, podemos tomar
muestras al azar del corpus de texto.
Esta formulación convierte la tarea de generación de secuencias en un problema de clasificación
basado en secuencias. Este problema es similar al que vimos en el problema de análisis de sentimientos
de IMDb en el capítulo 9, en el que se predijo una clase binaria a partir de una entrada de una longitud
fija. El modelo para la generación de texto hace esencialmente lo mismo, aunque es un problema de
clasificación multiclase que implicanorteposibles clases, dondenortees el tamaño del conjunto de
caracteres, es decir, el número de todos los caracteres únicos en el conjunto de datos de texto.
Esta formulación de predicción del siguiente carácter tiene una larga historia en el
procesamiento del lenguaje natural y la informática. Claude Shannon, el pionero de la teoría de la
información, llevó a cabo un experimento en el que se pidió a los participantes humanos que
adivinaran la siguiente letra después de ver un breve fragmento de texto en inglés.8A través de
este experimento, pudo estimar la cantidad promedio de incertidumbre en cada letra de los textos
típicos en inglés, dado el contexto. Esta incertidumbre, que resultó ser de unos 1,3 bits de entropía,
nos indica la cantidad media de información que contiene cada letra en inglés.

8El artículo original de 1951 está disponible enhttp://mng.bz/5AzB.


Generando texto con LSTM 337

Texto semilla inicial


Probabilidad
puntuaciones
Aleatorio
RNN muestreo
El amor no mira con los ojos, b siguiente carácter tu
vaticinador

– L + tu

Probabilidad
puntuaciones
Aleatorio
RNN muestreo
ave no mira con los ojos, bu siguiente carácter t
vaticinador

...

Figura 10.1 Una ilustración esquemática de cómo se puede usar un predictor de siguiente carácter basado en RNN para generar
una secuencia de texto a partir de un fragmento de texto de entrada inicial como semilla. En cada paso, la RNN predice el
siguiente carácter utilizando el texto de entrada. Luego, el texto de entrada se concatena con el siguiente carácter predicho y
descarta el primer carácter. El resultado forma la entrada para el siguiente paso. En cada paso, la RNN genera las puntuaciones
de probabilidad de todos los caracteres posibles del conjunto de caracteres. Para determinar el próximo carácter real, se lleva a
cabo un muestreo aleatorio.

El resultado de 1,3 bits es menor que el número de bits si las 26 letras aparecieran de forma
completamente aleatoria, lo que sería log2(26) = 4,7 bits. Esto coincide con nuestra intuición porque
sabemos que las letras no aparecen al azar en inglés. En cambio, siguen patrones. En un nivel
inferior, solo ciertas secuencias de letras son palabras válidas en inglés. En un nivel superior, solo
un cierto orden de las palabras satisface la gramática inglesa. En un nivel aún más alto, solo un
subconjunto de oraciones gramaticalmente válidas tiene realmente sentido.
Si lo piensas bien, de eso se trata fundamentalmente nuestra tarea de generación de texto: aprender estos
patrones en todos estos niveles. Tenga en cuenta que nuestro modelo está esencialmente capacitado para hacer
lo que hicieron los sujetos de Shannon, es decir, adivinar el siguiente personaje. Ahora echemos un vistazo al
código de ejemplo y cómo funciona. Tenga en cuenta el resultado de Shannon de 1,3 bits porque volveremos a él
más adelante.

10.1.2 El ejemplo de generación de texto LSTM

El ejemplo de generación de texto lstm en el repositorio tfjs-examples implica entrenar un


predictor de siguiente carácter basado en LSTM y usarlo para generar texto nuevo. Los pasos de
entrenamiento y generación ocurren en JavaScript usando TensorFlow.js. Puede ejecutar el ejemplo
en el navegador o en el entorno de back-end con Node.js. Mientras que el primer enfoque
proporciona una interfaz más visual e interactiva, el segundo le brinda una velocidad de
entrenamiento más rápida.
338 CPASADO10Aprendizaje profundo generativo

Para ver el ejemplo ejecutándose en el navegador, use estos comandos:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


lstm-text-generación
discos compactos

hilo y reloj de hilo

En la página que aparece, puede seleccionar y cargar uno de los cuatro conjuntos de datos de texto
provistos para entrenar el modelo. Usaremos el conjunto de datos de Shakespeare en la siguiente
discusión. Una vez que se cargan los datos, puede crear un modelo para ellos haciendo clic en el
botón Crear modelo. Un cuadro de texto le permite ajustar la cantidad de unidades que tendrá el
LSTM creado. Está configurado en 128 de forma predeterminada. Pero puede experimentar con
otros valores, como 64. Si ingresa varios números separados por comas (por ejemplo,128,128),el
modelo creado contendrá múltiples capas LSTM apiladas una encima de la otra.
Para realizar entrenamiento en el backend usando tfjs-node o tfjs-node-gpu, use el comando com-
mandotren de hiloen lugar dereloj de hilo:
tren de hilo shakespeare \
- - lstmLayerSize 128,128 \
- - épocas 120 \
- - savePath ./mi-modelo-de-shakespeare

Si tiene una GPU habilitada para CUDA configurada correctamente, puede agregar --GPUmarque el
comando para permitir que el entrenamiento se realice en su GPU, lo que aumentará aún más la
velocidad de entrenamiento. La bandera --lstmLayerSizejuega el mismo papel que el cuadro de texto de
tamaño LSTM en la versión del navegador del ejemplo. El comando anterior creará y entrenará un modelo
que consta de dos capas LSTM, ambas con 128 unidades, apiladas una encima de la otra.
El modelo que se entrena aquí tiene una arquitectura LSTM apilada. Que haceapilado ¿Qué
significan las capas LSTM? Es conceptualmente similar a apilar varias capas densas en un MLP, lo
que aumenta la capacidad del MLP. De manera similar, el apilamiento de múltiples LSTM permite
que una secuencia de entrada pase por múltiples etapas de transformación de representación
seq2seq antes de convertirse en una regresión final o salida de clasificación por la capa final de
LSTM. La Figura 10.2 da una ilustración esquemática de esta arquitectura. Una cosa importante a
tener en cuenta es el hecho de que el primer LSTM tiene suSecuencia de retornopropiedad
establecida enciertoy, por lo tanto, genera una secuencia de salida que incluye la salida de cada uno
de los elementos de la secuencia de entrada. Esto hace posible alimentar la salida del primer LSTM
al segundo, ya que una capa LSTM espera una entrada secuencial en lugar de una entrada de un
solo elemento.
El listado 10.1 contiene el código que crea modelos de predicción del siguiente carácter con la
arquitectura que se muestra en la figura 10.2 (extraído de lstm-text-generation/model.js). Tenga en
cuenta que, a diferencia del diagrama, el código incluye una capa densa como resultado final del
modelo. La capa densa tiene una activación softmax. Recuerde que la activación de softmax
normaliza las salidas para que tengan valores entre 0 y 1 y sumen 1, como una distribución de
probabilidad. Entonces, la salida de la capa densa final representa las probabilidades previstas de
los caracteres únicos.
Generando texto con LSTM 339

LSTM 2
(secuencia de retorno: falso)

Figura 10.2 Cómo funciona el apilamiento de varias capas LSTM


y y y ... y en un modelo. En este caso, se apilan juntas dos capas de LSTM.
1 2 3 norte

El primero tiene suvolverSecuencia propiedad establecida en


ciertoy, por lo tanto, genera una secuencia de elementos. La
LSTM 1 salida secuencial del primer LSTM es recibida por el segundo
(secuencia de retorno: verdadero) LSTM como su entrada. El segundo LSTM genera un solo
elemento en lugar de una secuencia de elementos. El único
elemento podría ser la predicción de regresión o una matriz de
X X X ... X probabilidades softmax, que forman el resultado final del
1 2 3 norte

modelo.

loslstmLayerSizeargumento de lacrearModelo()La función controla el número de capas LSTM y


el tamaño de cada una. La primera capa LSTM tiene su forma de entrada configurada en
función demuestraLen (cuántos caracteres toma el modelo a la vez) ycharSet-Tamaño (cuántos
caracteres únicos hay en los datos de texto). Para el ejemplo basado en navegador,
muestraLenestá codificado a 40; para el script de entrenamiento basado en Node.js, se puede
ajustar a través de --muestraLenbandera.charSetSizetiene un valor de 71 para el conjunto de
datos de Shakespeare. El conjunto de caracteres incluye las letras inglesas en mayúsculas y
minúsculas, la puntuación, el espacio, el salto de línea y varios otros caracteres especiales.
Dados estos parámetros, el modelo creado por la función del listado 10.1 tiene una forma de
entrada de [40, 71] (ignorando la dimensión del lote). Esta forma corresponde a 40 caracteres
one-hotencoded. La forma de salida del modelo es [71] (nuevamente, ignorando la dimensión
del lote), que es el valor de probabilidad softmax para las 71 opciones posibles del siguiente
carácter.

Listado 10.1 Construyendo un modelo LSTM multicapa para la predicción del siguiente carácter

La longitud de la secuencia de entrada del modelo.


El número de todos los
caracteres únicos posibles
exportar función createModel(muestraLen,
tamaño del conjunto de caracteres,

lstmLayerSizes) {
Tamaño de las capas LSTM del
if (!Array.isArray(lstmLayerSizes)) {
modelo, como un solo número o una
lstmLayerSizes = [lstmLayerSizes];
matriz de números
}
const modelo = tf.secuencial();
for (sea i = 0; i < lstmLayerSizes.length; ++i) {
const lstmLayerSize = lstmLayerSizes[i]; El modelo comienza con
una pila de capas LSTM.
modelo.add(tf.layers.lstm({
unidades: lstmLayerSize,
returnSequences: i < lstmLayerSizes.length - 1, Establece returnSequences en
verdadero para que se puedan
apilar varias capas LSTM
340 CPASADO10Aprendizaje profundo generativo

forma de entrada: i === 0 ?


[sampleLen, charSetSize]: indefinido
La primera capa LSTM es
}));
especial porque necesita
}
especificar su forma de entrada.
modelo.añadir(
tf.capas.densas({
unidades: charSetSize,
activación: 'softmax'
}));
El modelo termina con una capa densa con una activación
modelo de retorno; softmax sobre todos los caracteres posibles, lo que refleja
} la naturaleza de clasificación del problema de predicción
del siguiente carácter.

Para preparar el modelo para el entrenamiento, lo compilamos con la pérdida de entropía cruzada categórica, ya
que el modelo es esencialmente un clasificador de 71 vías. Para el optimizador, usamos RMSProp, que es una
opción popular para modelos recurrentes:

optimizador const = tf.train.rmsprop(learningRate); model.compile ({optimizador: optimizador,


pérdida: 'categoricalCrossentropy'});

Los datos que entran en el entrenamiento del modelo consisten en pares de fragmentos de texto de
entrada y los caracteres que siguen a cada uno de ellos, todos codificados como vectores one-hot
(consulte la figura 10.1). La claseDatos de textodefinido en lstm-text-generation/data.js contiene la lógica
para generar dichos datos de tensor a partir del corpus de texto de entrenamiento. El código allí es algo
tedioso, pero la idea es simple: muestras aleatorias de fragmentos de longitud fija de la cadena muy larga
que es nuestro corpus de texto y convertirlos en representaciones de tensor de un solo uso.
Si está utilizando la demostración basada en la web, la sección Model Training de la página le permite
ajustar hiperparámetros como la cantidad de épocas de capacitación, la cantidad de ejemplos que se
incluyen en cada época, la tasa de aprendizaje, etc. Haga clic en el botón Entrenar modelo para iniciar el
proceso de entrenamiento del modelo. Para el entrenamiento basado en Node.js, estos hiperparámetros
se pueden ajustar a través de los indicadores de la línea de comandos. Para obtener más información,
puede obtener mensajes de ayuda ingresando eltren de hilo --ayudamando.
Según el número de épocas de entrenamiento que haya especificado y el tamaño del modelo, el
entrenamiento debería durar entre unos minutos y un par de horas. El trabajo de entrenamiento basado
en Node.js imprime automáticamente una cantidad de fragmentos de texto de muestra generados por el
modelo después de cada período de entrenamiento (consulte la tabla 10.1). A medida que avanza el
entrenamiento, debería ver que el valor de pérdida desciende continuamente desde el valor inicial de
aproximadamente 3,2 y converge en el rango de 1,4 a 1,5. A medida que la pérdida disminuye después de
aproximadamente 120 épocas, la calidad del texto generado debería mejorar, de modo que hacia el final
del entrenamiento, el texto debería versealgoShakesperiano, y la pérdida de validación debería acercarse
a la vecindad de 1,5, no muy lejos de la incertidumbre de información de 1,3 bits/carácter del experimento
de Shannon. Pero tenga en cuenta que dado nuestro paradigma de entrenamiento y la capacidad del
modelo, el texto generado nunca se parecerá a la escritura real de Shakespeare.
Generando texto con LSTM 341

Tabla 10.1 Ejemplos de texto generado por el modelo de predicción del siguiente carácter basado en LSTM. la generacion es
basado en el texto semilla. Texto semilla inicial:"en sínodo cada hora sobre tu prosperidad particular, y he aquí".aTexto real
que sigue al texto semilla (para comparación): "¡No te ves peor que tu anciano padre Menenio! ...".

Épocas
de Validación
capacitación pérdida t=0 t = 0,25 t = 0,5 t = 0,75

5 2.44 "rle los los "te ans y y "te peaje "p, af a mí


los los los y y y warl torle an en hormiga nlatese pfleh; Fovo
los los los un ana, tomdenl, ¿esta?
los los los Yawl y tand teurteeinlndti Iretltard
los los los y an an ind an an en ng otoño ald efidestinado
los los los thall ang antetetar hormigas y het
los los los encontrar un tord y linde ing inserto
los los los y y wa" eso aquí taod loellr ard,
los los los viento mlinl
el el el " theens torcido y"

25 1.96 "ve bandeja los "ve a la entrada una "ve de marter "rd; no es un
estandarte un truina a la surt una en eso no soy yo beilloters
fiel a la truina a mí verdad vástago a un un doblado
estandarte para me la voluntad él truece similares tener
los estandarte bandeja melena predicar el bencesto en eso
paralos pero un frijol para Beaty Atweath amor gris para
estandarte para el stanter un y eso avalancha de terror
los estandarte confía en tra" marient deberá el gravamen que soy
paralos me los hombre sácame, m"
estandarte para en tiene s"
el estandarte "

50 1.67 "rds los "ngs son "dedo de los "ngs, él coló,


mundo los su será el tiene los Como
herederos

mundo los olvidar como tu a mi cual


englents
mundo por marchito sobre parami
el mundo el mundo los dispositivo de luz primero
el mundo el los mundo los El e deberá, protagonismo
mundo los soportar el ellos en a foir
mundo los proceso sus hame, seré desafiado
mundo los ahora hacer vall twell.
cadena será el
el mundo el tendría SEÑOR C"
mundo
el mundo el Bo"
mundo" I"

100 1.61 y el susurro "y el susurro como el "rds como el "y su


los buscado buscado conducta. consiente
Que los En el Al Que tú
más los hombre considérelo más caridad y la ser tres como yo
el adelante y de los principes extraño y a piensas tú
los extraño y mostrar su arte el casa un hacer final,
comoel buscado componente " tarrón los
Que los A tommern más largos y un
más los hombre los oso usted corazón y no
los " Arte esta a extraño.
contenido, " AG"
342 CPASADO10Aprendizaje profundo generativo

Tabla 10.1 Ejemplos de texto generado por el modelo de predicción del siguiente carácter basado en LSTM. la generacion es
basado en el texto semilla. Texto semilla inicial:"en sínodo cada hora sobre tu prosperidad particular, y he aquí".aTexto real
que sigue al texto semilla (para comparación): "¡No te ves peor que tu anciano padre Menenio! ...".(continuado)

Épocas
de Validación
capacitación pérdida t=0 t = 0,25 t = 0,5 t = 0,75

120 1.49 "ve el "ve el justo "ve el "ve de su


Huelga los hermano, estratega por Trudum él.
Huelga Y esta en alma. puntos
La huelga los Huelga mi Monty para piensa él
los Huelga ordenar el Huelga, cavando él donde de sudy
los huelgas los Huelga tu aplomo. tal entonces tú;
los Huelga el sonido en la Esto para Y alma
Y los querida huelga su hermano sea ellos lo haré
Huelga los Y" esta hizo el tonto haría vengo de
Huelga los A burlado" mi que s"
Huelga
A"

un. DesdeCoriolano de Shakespeare, acto 5, escena 2. Tenga en cuenta que la muestra incluye saltos de línea y se detiene en medio de una palabra (amor).

La tabla 10.1 muestra algunos textos muestreados bajo cuatro diferentesvalores de temperatura, un
parámetro que controla la aleatoriedad del texto generado. En las muestras de texto generado, es posible
que haya notado que los valores de temperatura más bajos se asocian con un texto más repetitivo y de
aspecto mecánico, mientras que los valores más altos se asocian con un texto menos predecible. El valor
de temperatura más alto demostrado por el script de entrenamiento basado en Node.js es 0,75 de forma
predeterminada y, a veces, conduce a secuencias de caracteres que se parecen al inglés pero que en
realidad no son palabras en inglés (como "stratter" y "points" en las muestras de la mesa). En la siguiente
sección, examinaremos cómo funciona la temperatura y por qué se llama temperatura.

10.1.3 Temperatura: Aleatoriedad ajustable en el texto generado


La funciónmuestra()en el listado 10.2 es responsable de determinar qué carácter se elegirá
según las probabilidades de salida del modelo en cada paso del proceso de generación de
texto. Como puede ver, el algoritmo es algo complejo: implica llamadas a tres operaciones
TensorFlow.js de bajo nivel:tf.div(), tf.log(),ytf.multinomial().¿Por qué usamos este complicado
algoritmo en lugar de simplemente elegir la opción con el puntaje de probabilidad más alto,
lo que tomaría un soloargMax()¿llamada?
Si hiciéramos eso, la salida del proceso de generación de texto seríadeterminista. Es decir, le daría
exactamente el mismo resultado si lo ejecutara varias veces. Las redes neuronales profundas que hemos
visto hasta ahora son todas deterministas, en el sentido de que dado un tensor de entrada, el tensor de
salida está completamente determinado por la topología de la red y los valores de sus pesos. Si lo desea,
puede escribir una prueba unitaria para afirmar su valor de salida (consulte el capítulo 12 para obtener
una discusión sobre la prueba de algoritmos de aprendizaje automático). Este determinismo esnoideal
para nuestra tarea de generación de texto. Después de todo, escribir es un proceso creativo.
Generando texto con LSTM 343

Es mucho más interesante tener algo de aleatoriedad en el texto generado, incluso cuando se
proporciona el mismo texto inicial. Esto es lo quetf.multinomial()la operación y el parámetro
de temperatura son útiles para.tf.multinomial()es la fuente de aleatoriedad, mientras que la
temperatura controla el grado de aleatoriedad.

Listado 10.2 La función de muestreo estocástico, con un parámetro de temperatura

La capa densa del modelo genera puntajes de


probabilidad normalizados; usamos log() para
convertirlos en logits no normalizados antes
de dividirlos por la temperatura.

muestra de la función de exportación (problemas, temperatura) {


volver tf.ordenado(() => { Protegemos contra errores de división
const logPreds = tf.div( por cero con un pequeño número
tf.log(problemas), positivo. El resultado de la división es
Math.max(temperatura, 1e-6)); const logits con incertidumbre ajustada.

isNormalized = false;
return tf.multinomial(logPreds, 1, null, isNormalized).dataSync()[0]; });

} tf.multinomial() es una función de muestreo estocástico. Es como


un dado de varias caras con probabilidades desiguales por cara según
lo determinado por logPreds, los logit escalados por temperatura.

La parte más importante de lamuestra()función en el listado 10.2 es la siguiente línea:


const logPreds = tf.div(tf.log(problemas),
Math.max(temperatura, 1e-6));

toma elproblemas (las salidas de probabilidad del modelo) y las convierte en logpreds,los logaritmos
de las probabilidades escaladas por un factor. ¿Qué significa la operación logarítmica (tf.log())y la
escala (tf.div())¿hacer? Lo explicaremos a través de un ejemplo. En aras de la simplicidad,
supongamos que solo hay tres opciones (tres caracteres en nuestro conjunto de caracteres).
Supongamos que nuestro predictor del siguiente carácter produce los siguientes tres puntajes de
probabilidad dada una determinada secuencia de entrada:

[0,1, 0,7, 0,2]

Veamos cómo dos valores de temperatura diferentes alteran estas probabilidades. Primero, veamos una
temperatura relativamente más baja: 0,25. Los logits escalados son

registro ([0,1, 0,7, 0,2]) / 0,25 = [-9,2103, -1,4267, -6,4378]

Para entender lo que significan los logits, los volvemos a convertir en puntajes de
probabilidad reales usando la ecuación softmax, que implica tomar el exponencial de los
logits y normalizarlos:

Exp([-9.2103, -1.4267, -6.4378]) / suma(Exp([-9.2103, -1.4267, -6.4378])) = [0,0004, 0,9930, 0,0066]


344 CPASADO10Aprendizaje profundo generativo

Como puede ver, nuestros logits de temperatura = 0,25 corresponden a una distribución de probabilidad
altamente concentrada en la que la segunda opción tiene una probabilidad mucho mayor en
comparación con las otras dos opciones (consulte el segundo panel en la figura 10.3).
¿Qué pasa si usamos una temperatura más alta, digamos 0.75? Repitiendo el mismo cálculo,
obtenemos

registro ([0,1, 0,7, 0,2]) / 0,75 = [-3,0701, -0,4756, -2,1459] exp([-3.0701, -0.4756, -2.1459]) /
suma([-3.0701, -0.4756, -2.1459]) = [0.0591, 0.7919 0.1490]

Esta es una distribución mucho menos “pico” en comparación con la anterior, cuando la
temperatura era de 0,25 (ver el cuarto panel en la figura 10.3). Pero aún tiene un pico más alto en
comparación con la distribución original. Como te habrás dado cuenta, una temperatura de 1 te
dará exactamente las probabilidades originales (figura 10.3, quinto panel). Una temperatura
superior a 1 conduce a una distribución de probabilidad más "igualada" entre las opciones (figura
10.3, sexto panel), mientras que la clasificación entre las opciones siempre es la misma.

t = 0,00 t = 0,25 t = 0,50


1 1 1

0.8 0.8 0.8


Probabilidad

0.6 0.6 0.6

0.4 0.4 0.4

0.2 0.2 0.2

0 0 0
1 2 3 1 2 3 1 2 3

t = 0,75 t = 1,00 t = 1,25


1 1 1

0.8 0.8 0.8


Probabilidad

0.6 0.6 0.6

0.4 0.4 0.4

0.2 0.2 0.2

0 0 0
1 2 3 1 2 3 1 2 3
índice de clase

Figura 10.3 Las puntuaciones de probabilidad después de escalar por diferentes valores de
temperatura (T). Un valor más bajo de T conduce a una distribución más concentrada (menos
estocástica); un valor más alto de T hace que la distribución sea más equitativa entre las clases
(más estocástica). Un valor T de 1 corresponde a las probabilidades originales (sin cambios).
Tenga en cuenta que la clasificación relativa de las tres opciones siempre se conserva
independientemente del valor de T.
Codificadores automáticos variacionales: encontrar una representación vectorial eficiente y estructurada de imágenes 345

Estas probabilidades convertidas (o más bien, los logaritmos de ellas) se alimentan luego al
tf.multinomial()función, que actúa como un dado de múltiples caras, con probabilidades desiguales
de las caras controladas por el argumento de entrada. Esto nos da la elección final del siguiente
personaje.
Entonces, así es como el parámetro de temperatura controla la aleatoriedad del texto generado.
El terminotemperaturatiene su origen en la termodinámica, por lo que sabemos que un sistema
con mayor temperatura tiene un mayor grado de caos en su interior. La analogía es apropiada aquí
porque cuando aumentamos el valor de la temperatura en nuestro código, obtenemos un texto de
aspecto más caótico. Hay un "medio dulce" para el valor de la temperatura. Debajo, el texto
generado parece demasiado repetitivo y mecánico; encima, el texto parece demasiado
impredecible y excéntrico.
Esto concluye nuestro recorrido por el LSTM generador de texto. Tenga en cuenta que esta
metodología es muy general y es aplicable a muchas otras secuencias con las modificaciones adecuadas.
Por ejemplo, si se entrena en un conjunto de datos suficientemente grande de partituras musicales, se
puede usar un LSTM para componer música mediante la predicción iterativa de la siguiente nota musical
a partir de las anteriores.9

10.2 Codificadores automáticos variacionales: encontrar una


representación vectorial eficiente y estructurada de imágenes
La sección anterior le dio un recorrido rápido de cómo se puede usar el aprendizaje profundo para
generar datos secuenciales como texto. En las partes restantes de este capítulo, veremos cómo construir
redes neuronales para generar imágenes. Examinaremos dos tipos de modelos: autocodificador
variacional (VAE) y red antagónica generativa (GAN). Comparado con un GAN, el VAE tiene una historia
más larga y es estructuralmente más simple. Por lo tanto, constituye una buena vía de acceso para que
ingrese al mundo en rápido movimiento de la generación de imágenes basadas en el aprendizaje
profundo.

10.2.1 Autoencoder clásico y VAE: Ideas básicas


La figura 10.4 muestra esquemáticamente la arquitectura general de un codificador automático. A
primera vista, un codificador automático es un modelo divertido porque sus modelos de entrada y salida
son imágenes del mismo tamaño. En el nivel más básico, la función de pérdida de un codificador
automático es el MSE entre la entrada y la salida. Esto significa que, si se entrena correctamente, un
codificador automático tomará una imagen y generará una imagen esencialmente idéntica. ¿Para qué
diablos sería útil un modelo como ese?
De hecho, los codificadores automáticos son un tipo importante de modelo generativo y están lejos de
ser inútiles. La respuesta a la pregunta anterior está en la arquitectura en forma de reloj de arena (figura
10.4). La parte central más delgada de un codificador automático es un vector con una cantidad mucho
menor de elementos en comparación con las imágenes de entrada y salida. Por lo tanto, la
transformación de imagen a imagen realizada por un codificador automático no es trivial: primero

9
Allen Huang y Raymond Wu, “Deep Learning for Music”, presentado el 15 de junio de 2016,https://arxiv.org/abs/
1606.04930.
346 CPASADO10Aprendizaje profundo generativo

vector latente
(vector z)

codificador Descifrador

Aporte Producción

imagen imagen

Figura 10.4 La arquitectura de un


autocodificador clásico

convierte la imagen de entrada en una representación altamente comprimida y luego reconstruye


la imagen a partir de esa representación sin utilizar ninguna información adicional. La
representación eficiente en el medio se conoce como lavector latente, o lavector z. Usaremos estos
dos términos indistintamente. El espacio vectorial en el que residen estos vectores se denomina
espacio latente, o laespacio z. La parte del codificador automático que convierte la imagen de
entrada en el vector latente se puede llamarcodificador; la última parte que vuelve a convertir el
vector latente en una imagen se denominadescifrador.
El vector latente puede ser cientos de veces más pequeño en comparación con la imagen en sí, como
mostraremos a través de un ejemplo concreto en breve. Por lo tanto, la parte del codificador de un
codificador automático entrenado es un reductor de dimensionalidad notablemente eficiente. Su
resumen de la imagen de entrada es muy sucinto, pero contiene suficiente información esencial para
permitir que el decodificador reproduzca fielmente la imagen de entrada sin utilizar bits de información
adicionales. El hecho de que el decodificador pueda hacer eso también es notable.
También podemos ver un codificador automático desde el punto de vista de la teoría de la información.
Digamos que las imágenes de entrada y salida contienen cada unanortepedacitos de información.
Ingenuamente,nortees el número de píxeles multiplicado por la profundidad de bits de cada píxel. Por el
contrario, el vector latente en el medio del codificador automático solo puede contener una cantidad muy
pequeña de información debido a su pequeño tamaño (digamos,metrobits). Simetroeran más pequeños que
norte, sería teóricamente imposible reconstruir la imagen a partir del vector latente. Sin embargo, los píxeles de
las imágenes no son completamente aleatorios (una imagen hecha de píxeles completamente aleatorios parece
ruido estático). En cambio, los píxeles siguen ciertos patrones, como la continuidad del color y las características
del tipo de objetos del mundo real que se representan. Esto hace que el valor de norteser mucho más pequeño
que el cálculo ingenuo basado en el número y la profundidad de los píxeles. El trabajo del codificador automático
es aprender este patrón; esta es también la razón por la que los codificadores automáticos pueden funcionar.
Codificadores automáticos variacionales: encontrar una representación vectorial eficiente y estructurada de imágenes 347

Una vez que se entrena un codificador automático, su parte del decodificador se puede usar sin
el codificador. Dado cualquier vector latente, puede generar una imagen que se ajuste a los
patrones y estilos de las imágenes de entrenamiento. Esto encaja muy bien con la descripción de
un modelo generativo. Además, es de esperar que el espacio latente contenga una estructura
agradable e interpretable. En particular, cada dimensión del espacio latente puede estar asociada
con un aspecto significativo de la imagen. Por ejemplo, supongamos que hemos entrenado un
codificador automático en imágenes de rostros humanos; quizás una de las dimensiones del
espacio latente estará asociada con el grado de sonrisa. Cuando fija los valores en todas las demás
dimensiones de un vector latente y varía solo el valor en la "dimensión de la sonrisa", las imágenes
producidas por el decodificador serán exactamente la misma cara pero con diversos grados de
sonrisa (ver, por ejemplo, figura 10.5). Esto habilitará aplicaciones interesantes, como cambiar el
grado de sonrisa de una imagen facial de entrada mientras se dejan todos los demás aspectos sin
cambios. Esto se puede hacer a través de los siguientes pasos. Primero, obtenga el vector latente
de la entrada aplicando el codificador. Luego, modifique solo la "dimensión de la sonrisa" del
vector; finalmente, ejecute el vector latente modificado a través del decodificador.

Figura 10.5 La “dimensión de la sonrisa”. Un ejemplo de estructura deseada en espacios latentes aprendido por
autocodificadores.

Desafortunadamente,codificadores automáticos clásicosde la arquitectura que se muestra en la


figura 10.4 no conducen a espacios latentes particularmente útiles o bien estructurados. Tampoco
son muy buenos en compresión. Por estas razones, pasaron de moda en gran medida en 2013.
VAE: descubiertos casi simultáneamente por Diederik Kingma y Max Welling en diciembre de 2013
y Danilo Rezende, Shakir Mohamed y Daan Wiestra en enero de 201411— aumente los
10

codificadores automáticos con un poco de magia estadística, lo que obliga a los modelos a
aprender espacios latentes continuos y altamente estructurados. Los VAE han resultado ser un tipo
poderoso de modelo de imagen generativa.
Un VAE, en lugar de comprimir su imagen de entrada en un vector fijo en el espacio latente,
convierte la imagen en los parámetros de una distribución estadística, específicamente, los de un
distribución gaussiana. Como recordará de las matemáticas de la escuela secundaria, una
distribución gaussiana tiene dos parámetros: la media y la varianza (o, de manera equivalente, la
desviación estándar). Un VAE asigna cada imagen de entrada a una media. La única com-

10Diederik P. Kingma y Max Welling, “Auto-Encoding Variational Bayes”, presentado el 20 de diciembre de 2013,
https://arxiv.org/abs/1312.6114.
11Danilo Jiménez Rezende, Shakir Mohamed y Daan Wierstra, “Stochastic Backpropagation and Approxi-
mate Inference in Deep Generative Models”, presentado el 16 de enero de 2014,https://arxiv.org/abs/1401.4082.
348 CPASADO10Aprendizaje profundo generativo

La complejidad es que la media y la varianza pueden ser más altas que unidimensionales si el
espacio latente es más de 1D, como veremos en el siguiente ejemplo. Esencialmente, asumimos
que las imágenes se generan a través de un proceso estocástico y que la aleatoriedad de este
proceso debe tenerse en cuenta durante la codificación y decodificación. Luego, el VAE usa los
parámetros de media y varianza para muestrear aleatoriamente un vector de la distribución y
decodificar ese elemento nuevamente al tamaño de la entrada original (consulte la figura 10.6).
Esta estocasticidad es una de las formas clave en que VAE mejora la solidez y obliga al espacio
latente a codificar representaciones significativas en todas partes: cada punto muestreado en el
espacio latente debe ser una salida de imagen válida cuando el decodificador lo decodifica.

UN.Cómo funciona un autocodificador clásico

espacio latente

codificador vector z Descifrador

B.Cómo funciona un codificador automático variacional

espacio latente
distribución gaussiana
en el espacio latente

codificador

Aleatorio
muestreo
Descifrador

Figura 10.6 Comparación del funcionamiento de un autocodificador clásico (panel A) y un VAE (panel B). Un
autocodificador clásico asigna una imagen de entrada a un vector latente fijo y realiza la decodificación utilizando
ese vector. Por el contrario, un VAE asigna una imagen de entrada a una distribución, descrita por una media y una
varianza, extrae un vector latente aleatorio de esta distribución y genera la imagen decodificada usando ese vector
aleatorio. La imagen de la camiseta es un ejemplo del conjunto de datos Fashion-MNIST.

A continuación, le mostraremos un VAE en acción utilizando el conjunto de datos Fashion-MNIST. Como su


nombre lo indica, Moda-MNIST12está inspirado en el conjunto de datos de dígitos escritos a mano del MNIST,
pero contiene imágenes de ropa y artículos de moda. Al igual que las imágenes MNIST, las imágenes Fashion-
MNIST son imágenes en escala de grises de 28 × 28. Hay exactamente 10 clases de prendas de vestir y artículos
de moda (como camisetas, suéteres, zapatos y bolsos; consulte la figura 10.6 para ver un ejemplo). Sin embargo,
el conjunto de datos Fashion-MNIST es ligeramente "más difícil" para el aprendizaje automático.

12Han Xiao, Kashif Rasul y Roland Vollgraf, “Fashion-MNIST: un nuevo conjunto de datos de imágenes para la evaluación comparativa
Algoritmos de aprendizaje automático”, presentado el 25 de agosto de 2017,https://arxiv.org/abs/1708.07747.
Codificadores automáticos variacionales: encontrar una representación vectorial eficiente y estructurada de imágenes 349

algoritmos en comparación con el conjunto de datos MNIST, con la precisión actual del conjunto de
prueba de última generación de aproximadamente el 96,5 %, mucho más baja en comparación con la
precisión del 99,75 % de última generación en el conjunto de datos MNIST.13Usaremos TensorFlow.js para
construir un VAE y entrenarlo en el conjunto de datos Fashion-MNIST. Luego usaremos el decodificador
del VAE para muestrear el espacio latente 2D y observar la estructura dentro de ese espacio.

10.2.2 Un ejemplo detallado de VAE: El ejemplo Fashion-MNIST


Para ver el ejemplo fashion-mnist-vae, use los siguientes comandos:
git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/
moda-mnist-vae
discos compactos

hilo
hilo descargar-datos

Este ejemplo consta de dos partes: entrenar el VAE en Node.js y usar el decodificador VAE para
generar imágenes en el navegador. Para iniciar la parte de entrenamiento, utilice

tren de hilo

Si tiene una GPU habilitada para CUDA configurada correctamente, puede usar el --GPUbandera para obtener un impulso

en la velocidad de entrenamiento:

tren de hilo --gpu

La capacitación debería tomar alrededor de cinco minutos en una computadora de escritorio


razonablemente actualizada equipada con una GPU CUDA y menos de una hora sin la GPU. Una vez
completada la capacitación, use el siguiente comando para compilar e iniciar la interfaz del navegador:

reloj de hilo

La interfaz cargará el decodificador de VAE, generará una cantidad de imágenes mediante el uso de una
cuadrícula 2D de vectores latentes espaciados regularmente y mostrará las imágenes en la página. Esto le
dará una apreciación de la estructura del espacio latente.
En términos técnicos, así es como funciona un VAE:

1 El codificador convierte las muestras de entrada en dos parámetros en un espacio latente: Media z
yzLogVar,la media y el logaritmo de la varianza (log varianza), respectivamente. Cada uno de los
dos vectores tiene la misma longitud que la dimensionalidad del espacio latente.14Por ejemplo,
nuestro espacio latente será 2D, entoncesMedia zy zLogVarcada uno será un vector de longitud 2.
¿Por qué usamos la varianza logarítmica (zLogVar) en lugar de la varianza misma? Debido a que,
por definición, se requiere que las variaciones no sean negativas, pero no hay una manera fácil de
hacer cumplir ese requisito de signo en la salida de una capa. Por el contrario, se permite que la
varianza logarítmica tenga cualquier signo. Al usar el logaritmo, no tenemos que preocuparnos
por el signo de las salidas de las capas. Tronco

13 Fuente: "Resultado de vanguardia para todos los problemas de aprendizaje automático", GitHub, 2019,http://mng.bz/6w0o.
14Estrictamente hablando, la matriz de covarianza de la longitud-nortevector latente es unnorte×nortematriz. Sin embargo,zLogVares
una longitud-nortevector porque restringimos la matriz de covarianza para que sea diagonal, es decir, no hay correlación
entre dos elementos diferentes del vector latente.
350 CPASADO10Aprendizaje profundo generativo

La varianza se puede convertir fácilmente a la varianza correspondiente a través de una


exponenciación simple (tf.exp())operación.
2 El algoritmo VAE muestra aleatoriamente un vector latente de la distribución normal
latente mediante el uso de un vector llamadoépsilon—un vector aleatorio de la misma
longitud queMedia zyzLogVar.En ecuaciones matemáticas simples, este paso, que se
conoce comoreparametrizaciónen la literatura, parece
z = zMedia + exp(zLogVar * 0.5) * épsilon
La multiplicación por 0,5 convierte la varianza en la desviación estándar, que se
basa en el hecho de que la desviación estándar es la raíz cuadrada de la varianza.
El código JavaScript equivalente es
z = zMean.add(zLogVar.mul(0.5).exp().mul(épsilon));

(Ver listado 10.3.) Entonces,zse alimentará a la parte del decodificador del VAE para que se
pueda generar una imagen de salida.

En nuestra implementación de VAE, el paso de muestreo de vectores latentes se realiza mediante una
capa personalizada llamadaZCapa (listado 10.3). Vimos brevemente una capa TensorFlow.js personalizada
en el capítulo 9 (laObtenerLastTimestepLayercapa que usamos en el conversor de fechas basado en la
atención). La capa personalizada utilizada por nuestro VAE es un poco más compleja y merece alguna
explicación.
losCapa ZLa clase tiene dos métodos clave:calcularFormaSalida()yllamada(). calcular-
SalidaForma()son utilizados por TensorFlow.js para inferir la forma de salida delCapa
instancia dada la(s) forma(s) de la entrada. losllamada()El método contiene las
matemáticas reales. Contiene la recta de ecuación presentada anteriormente. El
siguiente código se extrajo de fashion-mnist-vae/model.js.

Listado 10.3 Muestreo del espacio latente (z-space) con una capa personalizada

clase ZLayer extiende tf.layers.Layer {


constructor(config) {
super(config);
}

calcularFormaSalida(formaEntrada) {
tf.util.assert(formaEntrada.longitud === 2 && Array.isArray(formaEntrada[0]),
() => `Se esperaban exactamente 2 formas de entrada. ` +
`Pero obtuve: ${inputShape}`);
Comprobaciones para asegurarse de
devuelve forma de entrada [0];
que tenemos exactamente dos
}
entradas: zMean y zLogVar

llamada (entradas, kwargs) {


const [zMean, zLogVar] = entradas; const La forma de la salida (z) será la
lote = zMedia.forma[0]; const dim = misma que la forma de zMean.
zMedia.forma[1];

media constante = 0;
estándar constante = 1,0;
const épsilon = tf.randomNormal( Obtiene un lote aleatorio de épsilon de la
[lote, dim], media, estándar); distribución gaussiana unitaria
Codificadores automáticos variacionales: encontrar una representación vectorial eficiente y estructurada de imágenes 351

devuelve zMedia.añadir(
zLogVar.mul(0.5).exp().mul(épsilon)); Aquí es donde ocurre el
} muestreo de vectores z: zMean
+ standardDeviation * epsilon.
estático obtener ClassName () {
El nombre de clase estático
devuelve 'ZLayer';
La propiedad se establece en caso
} de que la capa se serialice.
}
tf.serialización.registerClass(ZLayer);
Registra la clase para
admitir la deserialización.

Como muestra el listado 10.4,Capa Zse crea una instancia y se utiliza como parte del
codificador. El codificador está escrito como un modelo funcional, en lugar del modelo
secuencial más simple, porque tiene una estructura interna no lineal y produce tres salidas:
zMedia, zLogVar,yz (ver el esquema en la figura 10.7). Las salidas del codificadorzporque será
utilizado por el decodificador, pero ¿por qué el codificador incluyeMedia zyzLogVaren las
salidas? Es porque se utilizarán para calcular la función de pérdida del VAE, como verás en
breve.
Además deCapa Z,el codificador consta de dos MLP de una capa oculta. Se utilizan para
convertir las imágenes de Fashion-MNIST de entrada aplanadas enMedia zyzLog-Varvectores,
respectivamente. Los dos MLP comparten la misma capa oculta pero usan capas de salida
separadas. Esta topología de modelo ramificado también es posible por el hecho de que el
codificador es un modelo funcional.

codificador
aplanado Denso Media z
aporte Denso
imagen Denso zLogVar Capa Z

Descifrador aplanado
Denso Denso producción

imagen

Paso de entrenamiento

divergencia KL
optimizador
vaeLoss . minimizar()

Error medio cuadrado

Figura 10.7 Ilustración esquemática de la implementación de TensorFlow.js de VAE, incluidos los detalles internos de las partes del
codificador y decodificador y la función de pérdida personalizada y el optimizador que admiten el entrenamiento de VAE.
352 CPASADO10Aprendizaje profundo generativo

Listado 10.4 La parte del codificador de nuestro VAE (extracto de fashion-mnist-vae/model.js)

codificador de funciones (opciones) {


const {originalDim, middleDim, latentDim} = opciones;

const entradas = tf.input({forma: [originalDim], nombre: 'encoder_input'}); const x =


tf.layers.dense({unidades: middleDim, activación: 'relu'})
. aplicar (entradas);
const zMean = tf.layers.dense({unidades: latentDim, nombre: 'z_mean'}).apply(x); const zLogVar =
tf.layers.dense({
unidades: atenuación latente,

nombre: 'z_log_var'
}).aplicar(x);
constante z =
new ZLayer({name: 'z', outputShape: [latentDim]}).apply([zMean, zLogVar]);

Crea una instancia de nuestro ZLayer personalizado y


const enc = tf.model({ lo usa para extraer muestras aleatorias
entradas: entradas, que siguen la distribución
salidas: [zMean, zLogVar, z], nombre: especificada por zMean y zLogVar
'codificador',
})
A diferencia de un MLP normal, ponemos dos capas
retorno enc;
aguas abajo de la capa densa oculta para
} predecir zMean y zLogVar, respectivamente.
Esta es también la razón por la que usamos
En la base del codificador hay un MLP un modelo funcional en su lugar.
simple con una capa oculta. del tipo de modelo secuencial más simple.

El código del listado 10.5 construye el decodificador. Comparado con el codificador, el decodificador tiene
una topología más simple. Utiliza un MLP para convertir el vector z de entrada (es decir, el vector latente)
en una imagen de la misma forma que la entrada del codificador. Tenga en cuenta que la forma en que
nuestro VAE maneja las imágenes es algo simplista e inusual, ya que aplana las imágenes en vectores 1D
y, por lo tanto, descarta la información espacial. Los VAE orientados a imágenes generalmente usan capas
convolucionales y de agrupación, pero debido a la simplicidad de nuestras imágenes (su pequeño tamaño
y el hecho de que solo hay un canal de color), el enfoque de aplanamiento funciona lo suficientemente
bien para el propósito de este ejemplo.

Listado 10.5 La parte del decodificador de nuestro VAE (extracto de fashion-mnist-vae/model.js)

decodificador de funciones (opciones) {


const {originalDim, middleDim, latentDim} = opciones;

const dec = tf.secuencial({nombre: 'decodificador'});


El decodificador es un MLP simple
dec.add(tf.layers.dense({ que convierte un vector latente (z) en
unidades: intermedioDim, una imagen (aplanada).
activación: 'relú',
forma de entrada: [Dim latente]
}));
dec.add(tf.layers.dense({
unidades: originalDim,
activación: 'sigmoide'
La activación sigmoide es una buena opción
})); para la capa de salida porque asegura que
volver diciembre; los valores de píxel de la imagen de salida
} estén delimitados entre 0 y1.
Codificadores automáticos variacionales: encontrar una representación vectorial eficiente y estructurada de imágenes 353

Para combinar el codificador y el decodificador en un solotf.LayerModelobjeto que es el


VAE, el código del listado 10.6 extrae la tercera salida (vector z) del codificador y lo
ejecuta a través del decodificador. Luego, el modelo combinado expone la imagen
decodificada como su salida, junto con tres salidas adicionales: lazMedia, zLogVar,y z-
vectores. Esto completa la definición de la topología del modelo VAE. Para entrenar el
modelo, necesitamos dos cosas más: la función de pérdida y un optimizador. El código
de la siguiente lista se extrajo de fashion-mnist-vae/model.js.

Listado 10.6 Juntando el codificador y el decodificador en el VAE

La entrada al VAE es la misma que


función vae (codificador, decodificador) { la entrada al codificador: la
const entradas = codificador.entradas; imagen de entrada original.
const encoderOutputs = encoder.apply(entradas);
const codificado = codificadorSalidas[2];
De las tres salidas del
const decoderOutput = decoder.apply(codificado); const v = codificador, solo la última (z)
tf.modelo({ entra en el decodificador.
entradas: entradas,
salidas: [salida del decodificador, ... salidas del codificador],
nombre: 'vae_mlp',
})
La salida del objeto modelo VAE
volver v;
incluye la imagen decodificada
} además de zMean,
zLogVar, yz.
Usamos la API del modelo funcional debido a la
topología no lineal del modelo.

Cuando visitábamos el modelo de detección de objetos simples en el capítulo 5, describimos la forma en


que se pueden definir las funciones de pérdida personalizadas en TensorFlow.js. Aquí, se necesita una
función de pérdida personalizada para entrenar el VAE. Esto se debe a que la función de pérdida será la
suma de dos términos: uno que cuantifica la discrepancia entre la entrada y la salida y otro que cuantifica
las propiedades estadísticas del espacio latente. Esto recuerda a la función de pérdida personalizada del
modelo de detección de objetos simples, que era la suma de un término para la clasificación de objetos y
otro para la localización de objetos.
Como puede ver en el código del listado 10.7 (extraído de fashion-mnist-vae/model.js), definir el
término de discrepancia de entrada-salida es sencillo. Simplemente calculamos el MSE entre la
entrada original y la salida del decodificador. Sin embargo, el término estadístico, llamadoKullbach-
Liebler(KL) divergencia, está más matemáticamente involucrado. Le ahorraremos las matemáticas
detalladas,15pero en un nivel intuitivo, el término de divergencia KL (klpérdidaen el código) fomenta
que las distribuciones de diferentes imágenes de entrada se distribuyan de manera más uniforme
alrededor del centro del espacio latente, lo que facilita que el decodificador interpole entre las
imágenes. Por lo tanto, los klpérdidaEl término se puede considerar como un término de
regularización agregado sobre el término principal de discrepancia de entrada-salida del VAE.

15 Esta publicación de blog de Irhum Shafkat incluye una discusión más profunda de las matemáticas detrás de la divergencia KL:
http://mng.bz/vlvr.
354 CPASADO10Aprendizaje profundo generativo

Listado 10.7 La función de pérdida para el VAE

function vaeLoss(entradas, salidas) {


const originalDim = entradas.forma[1]; const
Calcula un término de "pérdida de reconstrucción".
decoderOutput = salidas[0]; const zMedia =
El objetivo de minimizar este término es hacer
salidas[1];
las salidas del modelo coinciden con los datos de entrada.
const zLogVar = salidas[2];

const reconstrucciónPérdida =
tf.losses.meanSquaredError(entradas, decoderOutput).mul(originalDim);

let klLoss = zLogVar.add(1).sub(zMean.square()).sub(zLogVar.exp()); klPérdida =


klPérdida.sum(-1).mul(-0.5);
volver reconstrucciónLoss.add(klLoss).mean();
Suma la imagen
} pérdida de reconstrucción y la
pérdida de divergencia KL
Calcula la divergencia KL entre zLogVar y zMean. Minimizar
en la pérdida final de VAE
este término tiene como objetivo hacer que la distribución
de la variable latente se distribuya más normalmente
alrededor del centro del espacio latente.

Otra pieza que falta en nuestro entrenamiento VAE es el optimizador y el paso de entrenamiento
que lo usa. El tipo de optimizador es el popular optimizador ADAM (tf.tren .adam()). El paso de
entrenamiento para el VAE difiere de todos los otros modelos que hemos visto en este libro en que
no usa elencajar()oajusteDataset()método del objeto modelo. En cambio, llama a laminimizar()
método del optimizador (listado 10.8). Esto se debe a que el término KLdivergence de la función de
pérdida personalizada usa dos de las cuatro salidas del modelo, pero en TensorFlow.js, elencajar()y
ajusteDataset()Los métodos funcionan solo si cada una de las salidas del modelo tiene una función
de pérdida que no depende de ninguna otra salida.
Como muestra el listado 10.8, elminimizar()La función se llama con una función de flecha como
único argumento. Esta función de flecha devuelve la pérdida en el lote actual de imágenes
aplanadas (reformadoen el código), que está cerrado por la función. minimizar()calcula el gradiente
de la pérdida con respecto a todos los pesos entrenables del VAE (incluidos el codificador y el
decodificador), los ajusta de acuerdo con el algoritmo ADAM y luego aplica actualizaciones a los
pesos en direcciones opuestas a los gradientes ajustados. Esto completa un solo paso de
entrenamiento. Este paso se realiza repetidamente, sobre todas las imágenes en el conjunto de
datos Fashion-MNIST, y constituye una época de entrenamiento. lostren de hiloEl comando realiza
varias épocas de entrenamiento (predeterminado: 5 épocas), después de lo cual el valor de pérdida
converge y la parte del decodificador del VAE se guarda en el disco. La razón por la que la parte del
codificador no se guarda es que no se usará en el siguiente paso de demostración basado en
navegador.

Listado 10.8 El ciclo de entrenamiento del VAE (extracto de fashion-mnist-vae/train.js)

for (sea i = 0; i < épocas; i++) {


console.log(`\nEpoch #${i} of ${epochs}\n`) for (let j = 0; j <
lotes.length; j++) {
const currentBatchSize = lotes[j].length const lotedImages = Obtiene un lote de imágenes

loteImages(lotes[j]); Fashion-MNIST (aplanadas)


Codificadores automáticos variacionales: encontrar una representación vectorial eficiente y estructurada de imágenes 355

const reformado =
imágenes por lotes.reshape([currentBatchSize, vaeOpts.originalDim]);

optimizador.minimizar(() => {
Un solo paso del
const salidas = vaeModel.apply(remodelado); const loss =
entrenamiento VAE: hace un
vaeLoss(remodelado, salidas, vaeOpts); proceso.stdout.write('.'); predicción con el VAE y
calcula la pérdida para que
si (j % 50 === 0) { Optimizer.Minimize pueda
console.log('\nPérdida:', pérdida.dataSync()[0]); ajustar todos los
} pesos entrenables del
pérdida de retorno; modelo
});
tf.dispose([imágenes por lotes, remodeladas]);
}
consola.log('');
esperar generar (modelo decodificador, vaeOpts.latentDim);
}
Al final de cada época de entrenamiento, genera
Dado que no estamos usando el método stock fit(), no podemos usar la una imagen usando el decodificador y la imprime en
barra de progreso incorporada y, por lo tanto, debemos imprimir las la consola para obtener una vista previa
actualizaciones de estado en la consola nosotros mismos.

La página web creada por elreloj de hiloEl comando cargará el decodificador guardado y lo usará para generar
una cuadrícula de imágenes similar a la que se muestra en la figura 10.8. Estas imágenes se obtienen de una
cuadrícula regular de vectores latentes en el espacio latente 2D. El límite superior e inferior a lo largo de cada una
de las dos dimensiones latentes se puede ajustar en la interfaz de usuario.
La cuadrícula de imágenes muestra una distribución completamente continua de diferentes tipos de
ropa del conjunto de datos Fashion-MNIST, con un tipo de ropa transformándose gradualmente en otro
tipo a medida que sigue una ruta continua a través del espacio latente (por ejemplo, de un suéter a una
camiseta, Camiseta a pantalones, botas a zapatos). Las direcciones específicas en el espacio latente tienen
un significado dentro de un subdominio del espacio latente. Por ejemplo, cerca de la sección superior del
espacio latente, la dimensión horizontal parece representar

Figura 10.8 Muestreo del espacio latente de la VAE después del


entrenamiento. Esta figura muestra una cuadrícula de 20 × 20
de salidas de decodificador. Esta cuadrícula corresponde a una
cuadrícula regularmente espaciada de 20 × 20 vectores
latentes 2D, de los cuales cada dimensión está en el intervalo
de [–4, 4].
356 CPASADO10Aprendizaje profundo generativo

“botín versus calzado”; alrededor de la esquina inferior derecha del espacio latente, la
dimensión horizontal parece representar "camiseta versus pantalones", y así sucesivamente.
En la siguiente sección, cubriremos otro tipo importante de modelo para generar
imágenes: GAN.

10.3 Generación de imágenes con GAN


Desde que Ian Goodfellow y sus colegas introdujeron las GAN en 2014,dieciséisla técnica ha
experimentado un rápido crecimiento en interés y sofisticación. Hoy en día, las GAN se han
convertido en una poderosa herramienta para generar imágenes y otras modalidades de datos.
Son capaces de generar imágenes de alta resolución que, en algunos casos, son indistinguibles de
las reales para los ojos humanos. Vea las imágenes de rostros humanos generadas por StyleGAN
de NVIDIA en la figura 10.9.17Si no fuera por las manchas ocasionales de artefactos en la cara y las
escenas de aspecto poco natural en el fondo, sería prácticamente imposible para un espectador
humano distinguir estas imágenes generadas de las reales.

Figura 10.9 Imágenes de rostro humano de ejemplo generadas por StyleGAN de NVIDIA, muestreadas de
https://estapersonanoexiste.comen abril de 2019

Además de generar imágenes convincentes "de la nada", las imágenes generadas por las GAN
pueden condicionarse a ciertos datos o parámetros de entrada, lo que conduce a una variedad de
aplicaciones más útiles y específicas de la tarea. Por ejemplo, las GAN se pueden usar para generar
una imagen de mayor resolución a partir de una entrada de baja resolución (superresolución de
imagen), completar partes faltantes de una imagen (imagen en pintura), convertir una imagen en
blanco y negro en una imagen en color. one (colorización de imagen), generar una imagen dada
una descripción de texto y generar la imagen de una persona en una pose determinada dada una
imagen de entrada de la misma persona en otra pose. Además, se han desarrollado nuevos tipos
de GAN para generar salidas sin imágenes, como música.18Además del valor obvio de generar una
cantidad ilimitada de material de apariencia realista, lo cual se desea en dominios como

Goodfellow et al., "Redes adversarias generativas",Procedimientos NIPS, 2014,http://mng.bz/4ePv.


dieciséisIan

17Sitio web enhttps://estapersonanoexiste.com. Para el artículo académico, véase Tero Karras, Samuli Laine y
Timo Aila, "Una arquitectura de generador basada en estilos para redes adversas generativas", presentado el 12 de diciembre de
2018,https://arxiv.org/abs/1812.04948.
18 Vea el proyecto MuseGAN de Hao-Wen Dong et al.:https://salu133445.github.io/musegan/.
Generación de imágenes con GAN 357

arte, producción musical y diseño de juegos, las GAN tienen otras aplicaciones, como ayudar al
aprendizaje profundo mediante la generación de ejemplos de capacitación en los casos en que dichos
ejemplos son costosos de adquirir. Por ejemplo, las GAN se utilizan para generar escenas callejeras de
aspecto realista para entrenar redes neuronales autónomas.19
Aunque VAE y GAN son modelos generativos, se basan en ideas diferentes. Mientras que los VAE
garantizan la calidad de los ejemplos generados mediante el uso de una pérdida de MSE entre la entrada
original y la salida del decodificador, una GAN se asegura de que sus resultados sean realistas mediante
el empleo de undiscriminado, como pronto explicaremos. Además, muchas variantes de GAN permiten
que las entradas consistan no solo en el vector de espacio latente, sino también en entradas
condicionantes, como una clase de imagen deseada. El ACGAN que exploraremos a continuación es un
buen ejemplo de esto. En este tipo de GAN con entradas mixtas, los espacios latentes ya no son ni
siquiera continuos con respecto a las entradas de la red.
En esta sección, nos sumergiremos en un tipo de GAN relativamente simple.
Específicamente, entrenaremos a unclasificador auxiliarGAN (ACGAN)20en el conocido
conjunto de datos de dígitos escritos a mano del MNIST. Esto nos dará un modelo capaz de
generar imágenes de dígitos que se parecen a los dígitos reales del MNIST. Al mismo tiempo,
podremos controlar a qué clase de dígitos (0 a 9) pertenece cada imagen generada, gracias al
“clasificador auxiliar” de ACGAN. Para entender cómo funciona ACGAN, hagámoslo paso a
paso. Primero, explicaremos cómo funciona la parte base "GAN" de ACGAN. Luego,
describiremos los mecanismos adicionales por los cuales ACGAN hace que la identidad de
clase sea controlable.

10.3.1 La idea básica detrás de las GAN

¿Cómo aprende una GAN a generar imágenes de aspecto realista? Lo logra a través de una
interacción entre dos subpartes que comprende: unageneradory undiscriminado. Piense en
el generador como un falsificador cuyo objetivo es crear pinturas falsas de Picasso de alta
calidad; el discriminador es como un marchante de arte cuyo trabajo es diferenciar las
pinturas falsas de Picasso de las reales. El falsificador (generador) se esfuerza por crear cada
vez mejores pinturas falsas para engañar al comerciante de arte (el discriminador), mientras
que el trabajo del comerciante de arte es convertirse en un crítico cada vez mejor de las
pinturas paranodejarse engañar por el falsificador. Este antagonismo entre nuestros dos
jugadores es la razón detrás de la parte "contradictoria" del nombre "GAN". Curiosamente, el
falsificador y el marchante de arte terminanAyudarunos a otros se vuelven mejores, a pesar
de ser aparentemente adversarios.
Al principio, el falsificador (generador) es malo para crear Picassos de aspecto realista
porque sus pesos se inicializan al azar. Como resultado, el marchante de arte (discriminador)
aprende rápidamente a diferenciar los Picassos reales de los falsos. Aquí hay una parte
importante de cómo funciona todo esto: cada vez que el falsificador trae una nueva pintura al

19 James Vincent, "Nvidia usa IA para hacer que nieve en las calles que siempre están soleadas"el borde, 5 de diciembre de 2017,
http://mng.bz/Q0oQ.
20Augustus Odena, Christopher Olah y Jonathon Shlens, “Síntesis de imágenes condicionales con clasificación auxiliar
Sifier GANs”, presentado el 30 de octubre de 2016,https://arxiv.org/abs/1610.09585.
358 CPASADO10Aprendizaje profundo generativo

comerciante de arte, reciben comentarios detallados (del comerciante de arte) sobre qué partes de
la pintura se ven mal y cómo cambiar la pintura para que parezca más real. El falsificador aprende y
recuerda esto para que la próxima vez que vaya al marchante de arte, su pintura se vea un poco
mejor. Este proceso se repite muchas veces. Resulta que, si todos los parámetros se configuran
correctamente, terminaremos con un hábil falsificador (generador). Por supuesto, también
obtendremos un discriminador hábil (marchante de arte), pero generalmente solo necesitamos el
generador después de entrenar el GAN.
La figura 10.10 proporciona una visión más detallada de cómo se entrena la parte del
discriminador de un modelo GAN genérico. Para entrenar al discriminador, necesitamos un lote de
imágenes generadas y un lote de imágenes reales. Los generados son generados por el generador.
Pero el generador no puede crear imágenes de la nada. En su lugar, debe recibir un vector
aleatorio como entrada. Los vectores latentes son conceptualmente similares a los que usamos
para VAE en la sección 10.2. Para cada imagen generada por el generador, el vector latente es un
tensor 1D de forma [tamaño latente].Pero como la mayoría de los procedimientos de entrenamiento
en este libro, realizamos el paso para un lote de imágenes a la vez. Por lo tanto, el vector latente
tiene una forma de [tamaño de lote, tamaño latente].Las imágenes reales se extraen directamente
del conjunto de datos MNIST real. Por simetría, dibujamostamaño del loteimágenes reales
(exactamente el mismo número que las generadas) para cada paso del entrenamiento.

Entrenando al discriminador
[tamaño del lote, 28, 28, 1]
[tamaño del lote, tamaño latente]
[2 * tamaño de lote, 28, 28, 1]
Latente Falso
Generador
vectores imágenes [2 * tamaño de lote, 1]

Conjunto Discriminado predicciones


imágenes

[tamaño del lote, 28, 28, 1] Actualizaciones a través

Etiquetas de realidad: retropropagación


Verdadero
[2 * tamaño de lote, 28, 28, 1]
imágenes
0
0
...
0 cruz binaria-
1
1 pérdida de entropía
...
1

Figura 10.10 Un diagrama esquemático que ilustra el algoritmo mediante el cual se entrena la parte discriminadora de una GAN. Tenga en
cuenta que este diagrama omite la parte de la clase de dígitos del ACGAN en aras de la simplicidad. Para ver un diagrama completo del
entrenamiento del generador en ACGAN, vea la figura 10.13.

Las imágenes generadas y las reales se concatenan en un solo lote de imágenes, representado
como un tensor de forma [2 * tamaño de lote, 28, 28, 1].El discriminador se ejecuta en este lote de
imágenes combinadas, lo que genera puntajes de probabilidad pronosticados para determinar si
cada imagen es real. Estos puntajes de probabilidad se pueden comparar fácilmente con la realidad
básica (¡sabemos cuáles son reales y cuáles se generan!) a través de la función de pérdida de
entropía cruzada binaria. Luego, el conocido algoritmo de retropropagación hace su trabajo,
actualizando los parámetros de peso del discriminador.
Generación de imágenes con GAN 359

con la ayuda de un optimizador (no se muestra en la figura). Este paso empuja un poco al discriminador
para que haga predicciones correctas. Tenga en cuenta que el generador simplemente participa en este
paso de capacitación proporcionando muestras generadas, pero no se actualiza mediante el proceso de
retropropagación. Es el siguiente paso de entrenamiento que actualiza el generador (figura 10.11).

Entrenando al generador

[tamaño del lote, 28, 28, 1]


[tamaño del lote, tamaño latente] [tamaño del lote, 1]

Latente Falso Discriminado


Generador predicciones
vectores imágenes (congelado)

Actualizaciones a través
retropropagación
Etiquetas de realidad
(¡las imágenes de “fingir” son reales!)
[tamaño del lote, 1]

1
1 cruz binaria-
1 pérdida de entropía
1

Figura 10.11 Un diagrama esquemático que ilustra el algoritmo mediante el cual se entrena la parte del generador de una GAN. Tenga en
cuenta que este diagrama omite la parte de la clase de dígitos del ACGAN en aras de la simplicidad. Para ver un diagrama completo del
proceso de entrenamiento del generador de ACGAN, vea la figura 10.14.

La figura 10.11 ilustra el paso de entrenamiento del generador. Dejamos que el generador haga otro lote
de imágenes generadas. Pero a diferencia del paso de entrenamiento del discriminador, no necesitamos
ninguna imagen MNIST real. El discriminador recibe este lote de imágenes generadas junto con un lote de
etiquetas de realidad binaria. Nosotrospretenderque las imágenes generadas son reales configurando las
etiquetas de realidad a todos 1s. Haga una pausa por un momento y deje que se asiente: este es el truco
más importante en el entrenamiento GAN. Por supuesto, todas las imágenes son generadas (no reales),
pero dejamos que la etiqueta de realidad diga que son reales de todos modos. El discriminador puede
(correctamente) asignar bajas probabilidades de realismo a algunas o todas las imágenes de entrada.
Pero si lo hace, la pérdida de entropía cruzada binaria terminará con un valor alto, gracias a las etiquetas
de realidad falsas. Esto hará que la retropropagación actualice el generador de una manera que aumente
un poco más las puntuaciones de realidad del discriminador. Tenga en cuenta que las actualizaciones de
backpropagationsolamenteel generador. Deja intacto al discriminador. Este es otro truco importante:
asegura que el generador termine creando imágenes con un aspecto un poco más realista, en lugar de
que el discriminador baje el listón de lo que es real. Esto se logra congelando la parte discriminadora del
modelo, una operación que hemos usado para transferir el aprendizaje en el capítulo 5.

Para resumir el paso de entrenamiento del generador: congelamos el discriminador y le


damos una etiqueta de realismo todo 1, a pesar de que recibe imágenes generadas por el
generador. Como resultado, las actualizaciones de peso del generador harán que genere
360 CPASADO10Aprendizaje profundo generativo

imágenes que parecen un poco más reales para el discriminador. Esta forma de entrenar al
generador funcionará solo si el discriminador es razonablemente bueno para decir qué es real y
qué se genera. ¿Cómo aseguramos eso? La respuesta es el paso de entrenamiento del
discriminador del que ya hablamos. Por lo tanto, puede ver que los dos pasos de entrenamiento
forman una intrincada dinámica de yin y yang, en la que las dos partes del GAN se contrarrestan y
se ayudan mutuamente al mismo tiempo.
Eso concluye nuestra descripción general de alto nivel del entrenamiento genérico de GAN. En
la siguiente sección, veremos la arquitectura interna del discriminador y el generador y cómo
incorporan la información sobre la clase de imagen.

10.3.2 Los componentes básicos de ACGAN

El listado 10.9 muestra el código TensorFlow.js que crea la parte discriminadora del MNIST ACGAN
(extraído de mnist-acgan/gan.js). En el núcleo del discriminador hay un convnet profundo similar a
los que vimos en el capítulo 4. Su entrada tiene la forma canónica de las imágenes MNIST, a saber [
28, 28, 1].La imagen de entrada pasa a través de cuatro capas convolucionales 2D (conv2d) antes de
ser aplanada y procesada por dos capas densas. Una capa densa genera una predicción binaria
para la realidad de la imagen de entrada, mientras que la otra genera las probabilidades softmax
para las clases de 10 dígitos. El discriminador es un modelo funcional que tiene salidas de ambas
capas densas. El panel A de la figura 10.12 proporciona una vista esquemática de la topología de
una entrada, dos salidas del discriminador.

Listado 10.9 Creando la parte discriminadora de ACGAN

función construirDiscriminador() {
const cnn = tf.secuencial();

cnn.add(tf.layers.conv2d({
filtros: 32,
tamaño del núcleo: 3,
relleno: 'igual',
pasos: 2, El discriminador toma solo una
inputShape: [IMAGE_SIZE, IMAGE_SIZE, 1] })); entrada: imágenes en formato MNIST.

cnn.add(tf.layers.leakyReLU({alpha: 0.2})); Las capas de abandono se utilizan

cnn.add(tf.layers.dropout({rate: 0.3})); para contrarrestar el sobreajuste.

cnn.add(tf.layers.conv2d(
{filtros: 64, kernelSize: 3, relleno: 'igual', zancadas: 1}));
cnn.add(tf.layers.leakyReLU({alpha: 0.2}));
cnn.add(tf.layers.dropout({rate: 0.3}));

cnn.add(tf.layers.conv2d(
{filtros: 128, kernelSize: 3, relleno: 'igual', zancadas: 2}));
cnn.add(tf.layers.leakyReLU({alpha: 0.2}));
cnn.add(tf.layers.dropout({rate: 0.3}));

cnn.add(tf.layers.conv2d(
{filtros: 256, kernelSize: 3, relleno: 'igual', zancadas: 1}));
cnn.add(tf.layers.leakyReLU({alpha: 0.2}));
Generación de imágenes con GAN 361

La primera de las dos salidas del discriminador:


cnn.add(tf.layers.dropout({rate: 0.3})); la puntuación de probabilidad de

cnn.add(tf.layers.flatten()); la clasificación de realidad binaria

imagen const = tf.input({forma: [TAMAÑO_IMAGEN, TAMAÑO_IMAGEN, 1]}); const


características = cnn.apply(imagen);

const realnessScore =
tf.layers.dense({unidades: 1, activación: 'sigmoide'}).apply(características); const aux =
tf.layers.dense({unidades: NUM_CLASSES, activación: 'softmax'})
. aplicar (características);

return tf.model({entradas: imagen, salidas: [realnessScore, aux]});


}
La segunda salida son las
probabilidades softmax para el
10 Clases de dígitos MNIST.

A. Topología interna del discriminador

Denso
sigmoideo
Realidad
Aporte
conv2d conv2d conv2d conv2d
imagen
Denso Dígito
softmax clase

B. Topología interna del generador

Latente

multiplicar remodelar conv2dTransponer conv2dTransponer conv2dTransponer Realidad


Dígito
incrustación
clase

Figura 10.12 Diagramas esquemáticos de la topología interna de las partes del discriminador (panel A) y del generador (panel B)
de ACGAN. Ciertos detalles (las capas de abandono en el discriminador) se omiten por simplicidad. Consulte los listados 10.9 y
10.10 para obtener el código detallado.

El código del listado 10.10 es responsable de crear el generador ACGAN. Como hemos
aludido antes, el proceso de generación del generador requiere una entrada llamadavector
latente(llamadolatenteen el código). Esto se refleja en elforma de entradaparámetro de su
primera capa densa. Sin embargo, si examina el código con más cuidado, puede ver que el
generador en realidad tomadosentradas. Esto se ilustra en el panel B de la figura 10.12.
Además del vector latente, que es un tensor 1D de forma [tamaño latente],el generador
requiere una entrada adicional, que se denominaclase de imageny tiene una forma simple de [
1].Esta es la forma en que le decimos al modelo qué clase de dígitos MNIST (0 a 9) se le
ordena generar. Por ejemplo, si queremos que el modelo genere una imagen para el dígito 8,
debemos alimentar un valor de tensor detf.tensor2d([[8]])a la segunda entrada (recuerde que
el modelo siempre espera tensores por lotes, incluso si solo hay un ejemplo). Asimismo, si
queremos que el modelo genere dos imágenes, una para el dígito 8 y otra para el 9, entonces
el tensor alimentado debe sertensor2d([[8], [9]]).
362 CPASADO10Aprendizaje profundo generativo

Tan pronto comoclase de imagenentrada entra en el generador, una capa de incrustación lo


transforma en un tensor de la misma forma quelatente ([tamaño latente]).Este paso es
matemáticamente similar al procedimiento de búsqueda incrustada que usamos en los modelos de
análisis de opinión y conversión de fecha en el capítulo 9. La clase de dígito deseada es una
cantidad entera análoga a los índices de palabras en los datos de análisis de opinión y los índices
de caracteres en los datos de conversión de fechas. Se transforma en un vector 1D de la misma
manera que los índices de palabras y caracteres se transformaron en vectores 1D. Sin embargo,
utilizamos la búsqueda incrustada enclase de imagenaquí con un propósito diferente: fusionarlo con
ellatente vector y forman un solo vector combinado (llamadohen el listado 10.10.) Esta fusión se
realiza a través de unmultiplicarcapa, que realiza la multiplicación elemento por elemento entre los
dos vectores de formas idénticas. El tensor resultante tiene la misma forma que las entradas ([
tamaño latente])y entra en partes posteriores del generador.
El generador aplica inmediatamente una capa densa sobre el vector latente combinado (h)
y lo remodela en una forma 3D de [3, 3, 384].Esta remodelación produce un tensor similar a
una imagen, que luego puede ser transformado por las siguientes partes del generador en
una imagen que tiene la forma canónica MNIST ([28, 28, 1]).
En lugar de usar las conocidas capas conv2d para transformar la entrada, el generador usa la
capa conv2dTranspose para transformar sus tensores de imagen. En términos generales,
conv2dTranspose realiza la operación inversa a conv2d (a veces denominada desconvolución). La
salida de una capa conv2d generalmente tiene una altura y un ancho más pequeños en
comparación con su entrada (excepto en los casos excepcionales en los que lakernelSizees 1), como
puede ver en los convnets del capítulo 4. Sin embargo, una capa conv2dTranspose generalmente
tiene una altura y un peso mayores en su salida que en su entrada. En otras palabras, mientras que
una capa conv2d normalmentese encogelas dimensiones de su entrada, una típica capa
conv2dTranspose se expandeellos. Por eso, en el generador, la primera capa conv2dTranspose
toma una entrada con altura 3 y ancho 3, pero la última capa conv2dTranspose genera una altura
28 y un ancho 28. Así es como el generador convierte un vector latente de entrada y un índice de
dígitos en un imagen en las dimensiones de imagen estándar de MNIST. El código de la siguiente
lista se extrajo de mnist-acgan/gan.js; algunos códigos de verificación de errores se eliminan para
mayor claridad.

Listado 10.10 Creando la parte generadora de ACGAN

función buildGenerator (tamaño latente) { El número de unidades se elige de modo que


cuando la salida se reforma y pasa a través de las
const cnn = tf.secuencial();
capas subsiguientes de conv2dTranspose, el tensor
cnn.add(tf.layers.dense({ que sale al final tiene la forma exacta que coincide
unidades: 3 * 3 * 384, forma con las imágenes MNIST ([28, 28,1]).
de entrada: [tamaño latente],
activación: 'relú'
}));
cnn.add(tf.layers.reshape({targetShape: [3, 3, 384]}));

cnn.add(tf.layers.conv2dTranspose({
Muestras ascendentes de [3,
filtros: 192,
3, ...] a [7, 7, ...]
tamaño del núcleo: 5,
Generación de imágenes con GAN 363

zancadas: 1,
relleno: 'válido',
activación: 'relú',
kernelInitializer: 'glorotNormal'
}));
cnn.add(tf.layers.batchNormalization());

cnn.add(tf.layers.conv2dTranspose({ Upsamples a [14,14, ...]


filtros: 96,
tamaño del núcleo: 5,
zancadas: 2,
relleno: 'mismo',
activación: 'relú',
kernelInitializer: 'glorotNormal'
}));
cnn.add(tf.layers.batchNormalization());

cnn.add(tf.layers.conv2dTranspose({ Submuestrea a [28, 28, ...]


filtros: 1,
tamaño del núcleo: 5,
zancadas: 2,
relleno: 'mismo',
Esta es la primera de las dos
activación: 'tanh',
entradas del generador: el
kernelInitializer: 'glorotNormal'
vector latente (espacio z) que se
}));
utiliza como "semilla" de la
const latent = tf.input({forma: [latentSize]}); generación de imágenes falsas.

const imageClass = tf.input({forma: [1]});


La segunda entrada del generador:

const classEmbedding = tf.layers.embedding({ etiquetas de clase que controlan cuál


de los10 Clases de dígitos MNIST a las
entradaDim: NUM_CLASES,
que deben pertenecer las imágenes
salidaDim: tamaño latente,
generadas
embeddingsInitializer:
'glorotNormal' }).apply(imageClass);
Convierte lo deseado
const h = tf.layers.multiply().apply( etiqueta a un vector de longitud
latentSize a través de
[latente, classEmbedding]); búsqueda incrustada

const imagenfalsa = cnn.apply(h); return Combina el vector latente y la


tf.modelo({ incrustación condicional de clase a
entradas: [latente, clase de imagen], través de la multiplicación.
salidas: imagen falsa
}); El modelo finalmente se crea, con el
} convnet secuencial como núcleo.

10.3.3 Profundizando en el entrenamiento de ACGAN


La última sección debería haberle dado una mejor comprensión de la estructura interna del
discriminador y generador de ACGAN y cómo incorporan la información de clase de dígito (la
parte "AC" del nombre de ACGAN). Con este conocimiento, estamos listos para ampliar las
figuras 10.10 y 10.11 para formar una comprensión completa de cómo se entrena ACGAN.
364 CPASADO10Aprendizaje profundo generativo

La figura 10.13 es una versión ampliada de la figura 10.10. Muestra el entrenamiento de la parte discriminadora de ACGAN. En comparación

con el anterior, este paso de entrenamiento no solo mejora la capacidad del discriminador para diferenciar las imágenes reales de las generadas

(falsas), sino que también perfecciona su capacidad para determinar a qué clase de dígitos pertenece una imagen determinada (incluidas las

reales y las generadas). Para que sea más fácil de comparar con el diagrama más simple de antes, atenuamos las partes que ya se ven en la

figura 10.10 y resaltamos las partes nuevas. Primero, tenga en cuenta que el generador ahora tiene una entrada adicional (Clase de dígito), que

permite especificar qué dígitos debe generar el generador. Además, el discriminador genera no solo una predicción de realidad sino también

una predicción de clase de dígito. Como resultado, ambos cabezales de salida del discriminador necesitan ser entrenados. El entrenamiento de

la parte predictora de realidad sigue siendo el mismo que antes (figura 10.10); el entrenamiento de la parte de predicción de clases se basa en el

hecho de que sabemos a qué clases de dígitos pertenecen las imágenes generadas y reales. Las dos cabezas del modelo se compilan con

diferentes funciones de pérdida, lo que refleja la diferente naturaleza de las dos predicciones. Para la predicción de realidad, usamos la pérdida

de entropía cruzada binaria, pero para la predicción de clase de dígito, usamos la pérdida de entropía cruzada categórica escasa. Puede ver esto

en la siguiente línea de mnist-acgan/gan.js: reflejando la diferente naturaleza de las dos predicciones. Para la predicción de realidad, usamos la

pérdida de entropía cruzada binaria, pero para la predicción de clase de dígito, usamos la pérdida de entropía cruzada categórica escasa. Puede

ver esto en la siguiente línea de mnist-acgan/gan.js: reflejando la diferente naturaleza de las dos predicciones. Para la predicción de realidad,

usamos la pérdida de entropía cruzada binaria, pero para la predicción de clase de dígito, usamos la pérdida de entropía cruzada categórica

escasa. Puede ver esto en la siguiente línea de mnist-acgan/gan.js:

discriminador.compilar({
optimizador: tf.train.adam(args.learningRate, args.adamBeta1), loss:
['binaryCrossentropy', 'sparseCategoricalCrossentropy'] });

Como muestran las dos flechas curvas de la figura 10.13, los gradientes
retropropagados de ambas pérdidas se suman uno encima del otro cuando se
actualizan los pesos del discriminador. La figura 10.14 es una versión ampliada de la
figura 10.11 y proporciona una vista esquemática detallada de cómo se entrena la parte
del generador de ACGAN. Este diagrama muestra cómo el generador aprende a generar
imágenes correctas dada una clase de dígito específica, además de aprender a generar
imágenes reales. Similar a la figura 10.13, las partes nuevas están resaltadas, mientras
que las partes que ya existen en la figura 10.11 están atenuadas. De las partes
resaltadas, puede ver que las etiquetas que ingresamos en el paso de entrenamiento
ahora incluyen no solo las etiquetas de realidad sino también las etiquetas de clase de
dígito. Como antes, las etiquetas de realidad son todas intencionalmente falsas.
Anteriormente, vimos que cualquier discrepancia entre las etiquetas de realidad
falsas y la salida de probabilidad de realidad del discriminador se usa para
actualizar el generador de ACGAN de una manera que lo hace mejor para
"engañar" al discriminador. Aquí, la predicción de clase de dígito del discriminador
juega un papel similar. Por ejemplo, si le decimos al generador que genere una
imagen para el dígito 8, pero el discriminador clasifica la imagen como 9, el valor de
la entropía cruzada categórica dispersa será alto y los gradientes asociados con ella
tendrán grandes magnitudes. Como resultado, las actualizaciones de los pesos del
generador harán que el generador genere una imagen que se parezca más a un 8
(según el discriminador). Obviamente,
Generación de imágenes con GAN 365

Entrenamiento del discriminador


(con información de clase de dígito)

Latente Falso
Generador Realidad
vectores imágenes
predicciones
tamaño de lote, 1]
Conjunto Discriminado
[2 * tamaño de lote, 10]
Clase de dígito imágenes Clase
predicciones
Actualizaciones a través
Realidad retropropagación
Verdadero
etiquetas
imágenes Actualizaciones a través
0
0 retropropagación
...
0 Binario
1
1 pérdida de entropía cruzada
...
1
Etiquetas de clase combinadas
[2 * tamaño de lote, 1]
multiclase
0
pérdida de entropía cruzada
1
3
6
7
4
...
5

Figura 10.13 Un diagrama esquemático que ilustra el algoritmo mediante el cual se entrena la parte discriminadora de
ACGAN. Este diagrama se suma al de la figura 10.10 mostrando las partes que tienen que ver con la clase digit. Las partes
restantes del diagrama, que ya han aparecido en la figura 10.10, están atenuadas.

Entrenando al generador
(con información de clase de dígito)

Latente Falso Discriminado Realidad


Generador
vectores imágenes (congelado) predicciones
[tamaño del lote, 1]
[tamaño del lote, 10]

Clase de dígito predicciones de clase


Actualizaciones a través
retropropagación
Realidad
etiquetas Actualizaciones a través
(las imágenes “fingidas” son reales) retropropagación
1
1 Binario
1
pérdida de entropía cruzada
1

Etiquetas de clase combinadas


[tamaño del lote, 1]
0
1
...
multiclase
6 pérdida de entropía cruzada

Figura 10.14 Un diagrama esquemático que ilustra el algoritmo mediante el cual se entrena la parte del generador de
ACGAN. Este diagrama se suma al de la figura 10.11 mostrando las partes que tienen que ver con la clase digit. Las partes
restantes del diagrama, que ya han aparecido en la figura 10.11, están atenuadas.
366 CPASADO10Aprendizaje profundo generativo

clases de dígitos. Esto es lo que ayuda a garantizar el paso anterior de entrenamiento del discriminador.
Nuevamente, estamos viendo la dinámica del yin y el yang entre las porciones del discriminador y del
generador en juego durante el entrenamiento de ACGAN.

GANCAPACITACIÓN: ABOLSA DE TRUCOS


El proceso de entrenamiento y ajuste de las GAN es notoriamente difícil. Los guiones de entrenamiento
que ve en el ejemplo de mnist-acgan son la cristalización de una gran cantidad de pruebas y errores por
parte de los investigadores. Como la mayoría de las cosas en el aprendizaje profundo, es más un arte que
una ciencia exacta: estos trucos son heurísticos, no están respaldados por teorías sistemáticas. Están
respaldados por un nivel de comprensión intuitiva del fenómeno en cuestión, y se sabe que funcionan
bien empíricamente, aunque no necesariamente en todas las situaciones.
La siguiente es una lista de trucos notables utilizados en el ACGAN en esta sección:

- Usamos tanh como la activación de la última capa conv2dTranspose en el generador. La


activación de tanh se ve con menos frecuencia en otros tipos de modelos. La aleatoriedad
- es buena para inducir robustez. Debido a que el entrenamiento GAN puede resultar en un
equilibrio dinámico, las GAN son propensas a atascarse de muchas formas. Introducir la
aleatoriedad durante el entrenamiento ayuda a prevenir esto. Introducimos la aleatoriedad
de dos maneras: usando abandono en el discriminador y usando un valor "suave" (0,95)
para las etiquetas de realidad del discriminador.
- Los gradientes escasos (gradientes en los que muchos valores son cero) pueden dificultar el
entrenamiento de GAN. En otros tipos de aprendizaje profundo, la escasez suele ser una propiedad
deseable, pero no en las GAN. Dos cosas pueden causar escasez en los gradientes: la operación de
agrupación máxima y las activaciones de relu. En lugar de agrupación máxima, se recomiendan
convoluciones estriadas para la reducción de resolución, que es exactamente lo que se muestra en el
código de creación del generador en el listado 10.10. En lugar de la activación habitual de relu, se
recomienda utilizar la activación de LeakyReLU, cuya parte negativa tiene un pequeño valor negativo, en
lugar de estrictamente cero. Esto también se muestra en el listado 10.10.

10.3.4 Ver la formación y generación del MNIST ACGAN


El ejemplo mnist-acgan se puede verificar y preparar con los siguientes comandos:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


mnist-acganyarn
discos compactos

Ejecutar el ejemplo involucra dos etapas: entrenamiento en Node.js y generación en el navegador.


Para iniciar el proceso de entrenamiento, simplemente use el siguiente comando:

tren de hilo

El entrenamiento usa tfjs-node por defecto. Sin embargo, como en los ejemplos que involucran
convnets que hemos visto antes, usar tfjs-node-gpu puede mejorar significativamente la velocidad
de entrenamiento. Si tiene una GPU habilitada para CUDA configurada correctamente en su
máquina, puede agregar el --GPUbandera a latren de hilocomando para lograrlo. Entrenando al
Generación de imágenes con GAN 367

ACGAN tarda al menos un par de horas. Para este trabajo de entrenamiento de larga duración,
puede monitorear el progreso con TensorBoard usando el --logDirbandera:

tren de hilo --logDir /tmp/mnist-acgan-logs

Una vez que el proceso de TensorBoard se haya iniciado con el siguiente comando en una
terminal separada,

tensorboard --logdir /tmp/mnist-acgan-logs

puede navegar a la URL de TensorBoard (tal como la imprimió el proceso del servidor de TensorBoard) en
su navegador para ver las curvas de pérdida. La figura 10.15 muestra algunos ejemplos de curvas de
pérdida del proceso de entrenamiento. Una característica distintiva de las curvas de pérdida del
entrenamiento GAN es el hecho de que no siempre tienen una tendencia descendente como las curvas de
pérdida de la mayoría de los otros tipos de redes neuronales. En cambio, las pérdidas del discriminador
(dLoss en la figura) y el generador (gLoss en la figura) cambian de manera no monótona y forman una
danza intrincada entre sí.

Figura 10.15 Ejemplo de curvas de pérdida del trabajo de entrenamiento ACGAN. dLoss
es la pérdida del paso de entrenamiento del discriminador. Específicamente, es la suma
de la entropía cruzada binaria de la predicción de realidad y la entropía cruzada
categórica escasa de la predicción de clase de dígito. gLoss es la pérdida del paso de
entrenamiento del generador. Al igual que dLoss, gLoss es la suma de las pérdidas de la
clasificación de realidad binaria y la clasificación de dígitos multiclase.
368 CPASADO10Aprendizaje profundo generativo

Hacia el final del entrenamiento, ninguna pérdida se acerca a cero. En cambio, simplemente se nivelan
(convergen). En ese punto, el proceso de entrenamiento finaliza y guarda la parte generadora del modelo
en el disco para servir durante el paso de generación en el navegador:

espera generador.guardar(guardarURL);

Para ejecutar la demostración de generación en el navegador, use el comandoreloj de hilo


Compilará mnist-acgan/index.js y los activos HTML y CSS asociados, después de lo cual abrirá una
pestaña en su navegador y mostrará la página de demostración.21
La página de demostración carga el generador ACGAN entrenado que se guardó en la etapa
anterior. Dado que el discriminador no es realmente útil para esta etapa de demostración, no se
guarda ni se carga. Con el generador cargado, podemos construir un lote de vectores latentes,
junto con un lote de índices de clase de dígito deseados, y llamar al generadorpredecir()con ellos. El
código que hace esto está en mnist-acgan/index.js:

const latentVectors = getLatentVectors(10); const


sampledLabels = tf.tensor2d(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 1]); imágenes generadas
const =
generator.predict([vectores latentes, etiquetas de muestra]).add(1).div(2);

Nuestro lote de etiquetas de clase de dígitos es siempre un vector ordenado de 10 elementos, del 0
al 9. Es por eso que el lote de imágenes generadas es siempre una matriz ordenada de imágenes
del 0 al 9. Estas imágenes se unen con eltf.concat()función y renderizado en undivisiónelemento en
la página (ver la imagen superior en la figura 10.16). En comparación con las imágenes MNIST
reales muestreadas aleatoriamente (consulte la imagen inferior en la figura 10.16), estas imágenes
generadas por ACGAN se parecen a las reales. Además, sus identidades de clase de dígito parecen
correctas. Esto demuestra que nuestra capacitación ACGAN fue exitosa. Si desea ver más salidas
del generador ACGAN, haga clic en el botón Generador en la página. Cada vez que se haga clic en
el botón, se generará y mostrará en la página un nuevo lote de 10 imágenes falsas. Puedes jugar
con eso y tener una idea intuitiva de la calidad de la generación de imágenes.

Materiales para lectura adicional


- Ian Goodfellow, Yoshua Bengio y Aaron Courville, “Modelos generativos profundos”,
Aprendizaje profundo, capítulo 20, MIT Press, 2017.
- Jakub Langr y Vladimir Bok,GAN en acción: aprendizaje profundo con redes
antagónicas generativas, Publicaciones de Manning, 2019.
- Andrej Karpathy, “La eficacia irrazonable de las redes neuronales
recurrentes”, blog, 21 de mayo de 2015,http://karpathy.github.io/2015/05/21/
rnnefectividad/.

21También puede omitir el paso de capacitación y construcción por completo y navegar directamente a la página de demostración alojada en
http://mng.bz/4eGw.
Ejercicios 369

Imágenes falsas (la generación tardó 68,33 ms)

Imágenes reales para comparar (10 ejemplos por clase)

Figura 10.16 Muestra de imágenes generadas (el panel


superior de 10 x 1) de la parte del generador de un
ACGAN capacitado. El panel inferior, que contiene una
cuadrícula de 10 x 10 de imágenes MNIST reales, se
muestra a modo de comparación. Al hacer clic en el
botón Mostrar controles deslizantes de vector Z, puede
abrir una sección con 100 controles deslizantes. Estos
controles deslizantes le permiten cambiar los
elementos del vector latente (el vector z) y observar los
efectos en las imágenes MNIST generadas. Tenga en
cuenta que si cambia los controles deslizantes uno a la
vez, la mayoría de ellos tendrán efectos pequeños e
imperceptibles en las imágenes. Pero ocasionalmente,
podrá encontrar un control deslizante con un efecto
más grande y más notable.

- Jonathan Hui, "GAN: ¿Qué es GAN de redes adversarias generativas?" Medio, 19 de


junio de 2018,http://mng.bz/Q0N6.
-GAN Lab, un entorno interactivo basado en la web para comprender y explorar
cómo funcionan las GAN, creado con TensorFlow.js: Minsuk Kahng et al.,
https://poloclub.github.io/ganlab/.

Ejercicios
1 Además del corpus de texto de Shakespeare, el ejemplo de generación de texto de lstm tiene
otros conjuntos de datos de texto configurados y listos para explorar. Ejecute el entrenamiento en
ellos y observe los efectos. Por ejemplo, use el código Tensor-Flow.js no minimizado como
conjunto de datos de entrenamiento. Durante y después del entrenamiento del modelo, observe
si el texto generado muestra los siguientes patrones de código fuente de JavaScript y cómo el
parámetro de temperatura afecta los patrones:
a Patrones de rango más corto, como palabras clave (por ejemplo, "para" y "función")
B Patrones de rango medio, como la organización línea por línea del código Patrones
C de rango más largo, como emparejamiento de paréntesis y corchetes, y el hecho de
que cada palabra clave de "función" debe ir seguida de un par de paréntesis y un
par de llaves
2 En el ejemplo de fashion-mnist-vae, ¿qué sucede si elimina el término de divergencia
KL de la pérdida personalizada de VAE? Pruebe que modificando elvaeLoss()func-
370 CPASADO10Aprendizaje profundo generativo

ción en fashion-mnist-vae/model.js (listado 10.7). ¿Las imágenes de muestra del


espacio latente todavía se parecen a las imágenes de Fashion-MNIST? ¿El espacio
todavía exhibe patrones interpretables?
3 En el ejemplo de mnist-acgan, intente colapsar las clases de 10 dígitos en 5 (0 y 1 se
convertirán en la primera clase, 2 y 3 en la segunda clase, y así sucesivamente), y
observe cómo eso cambia la salida del ACGAN después del entrenamiento. ¿Qué
esperas ver en las imágenes generadas? Por ejemplo, ¿qué espera que genere ACGAN
cuando especifica que se desea la primera clase?
Sugerencia: para realizar este cambio, debe modificar elcargar etiquetas()
función en mnist-acgan/data.js. El constanteNUM_CLASESen gan.js debe
modificarse en consecuencia. además, eletiquetas muestreadasvariables en el
generar y visualizar imágenes()La función (en index.js) también debe revisarse.

Resumen
-Los modelos generativos son diferentes de los discriminativos que hemos estudiado en
capítulos anteriores de este libro en que están diseñados para modelar el proceso en el que
se generan ejemplos del conjunto de datos de entrenamiento, junto con sus distribuciones
estadísticas. Debido a este diseño, son capaces de generar nuevos ejemplos que se ajustan
a las distribuciones y, por lo tanto, parecen similares a los datos de entrenamiento reales.

- Presentamos una forma de modelar la estructura de los conjuntos de datos


de texto: la predicción del siguiente carácter. Los LSTM se pueden usar para
realizar esta tarea de manera iterativa para generar texto de longitud
arbitraria. El parámetro de temperatura controla la estocasticidad (qué tan
- aleatorio e impredecible) es el texto generado. Los autocodificadores son un
tipo de modelo generativo que consta de un codificador y un decodificador.
Primero, el codificador comprime los datos de entrada en una representación
concisa llamada vector latente o vector z. Luego, el decodificador intenta
reconstruir los datos de entrada usando solo el vector latente. A través del
proceso de entrenamiento, el codificador se convierte en un resúmen de
datos eficiente y el decodificador está dotado del conocimiento de la
distribución estadística de los ejemplos.

- Las GAN se basan en la idea de una competencia y cooperación simultáneas entre un


discriminador y un generador. El discriminador trata de distinguir ejemplos de datos
reales de los generados, mientras que el generador apunta a generar ejemplos falsos
que “engañan” al discriminador. A través del entrenamiento conjunto, la parte del
generador eventualmente será capaz de generar ejemplos que parezcan realistas. Un
ACGAN agrega información de clase a la arquitectura GAN básica para que sea posible
especificar qué clase de ejemplos generar.
Fundamentos de profundidad

aprendizaje reforzado

Este capítulo cubre


- En qué se diferencia el aprendizaje por refuerzo del aprendizaje
supervisado discutido en los capítulos anteriores

- El paradigma básico del aprendizaje por refuerzo: agente,


entorno, acción y recompensa, y las interacciones entre ellos.

- Las ideas generales detrás de dos enfoques principales para resolver problemas de
aprendizaje por refuerzo: métodos basados en políticas y basados en valores

Hasta este punto de este libro, nos hemos centrado principalmente en un tipo de aprendizaje
automático llamadoaprendizaje supervisado. En el aprendizaje supervisado, entrenamos un modelo
para que nos dé la respuesta correcta dada una entrada. Ya sea asignando una etiqueta de clase a una
imagen de entrada (capítulo 4) o prediciendo la temperatura futura con base en datos meteorológicos
pasados (capítulos 8 y 9), el paradigma es el mismo: asignar una entrada estática a una salida
estática. Los modelos de generación de secuencias que visitamos en los capítulos 9 y 10 eran un poco
más complicados porque la salida es una secuencia de elementos en lugar de un solo elemento. Pero

371
372 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

esos problemas aún pueden reducirse a un mapeo de una entrada, una salida al dividir las
secuencias en pasos.
En este capítulo, veremos un tipo muy diferente de aprendizaje automático llamadoaprendizaje
reforzado(RL). En RL, nuestra principal preocupación no es una salida estática; en su lugar,
entrenamos un modelo (o unagenteen lenguaje RL) para tomar acciones en un entorno con el
objetivo de maximizar una métrica de éxito llamadarecompensa. Por ejemplo, RL se puede usar
para entrenar a un robot para navegar por el interior de un edificio y recolectar basura. De hecho,
el entorno no tiene que ser físico; puede ser cualquier espacio real o virtual en el que un agente
realiza acciones. El tablero de ajedrez es el entorno en el que se puede entrenar a un agente para
jugar al ajedrez; el mercado de valores es el entorno en el que un agente puede ser capacitado
para negociar acciones. La generalidad del paradigma RL lo hace aplicable a una amplia gama de
problemas del mundo real (figura 11.1). Además, algunos de los avances más espectaculares en la
revolución del aprendizaje profundo implican combinar el poder del aprendizaje profundo con RL.
Estos incluyen bots que pueden vencer a los juegos de Atari con habilidades sobrehumanas y
algoritmos que pueden vencer a los campeones mundiales en los juegos de Go y ajedrez.1

Figura 11.1 Ejemplos de aplicaciones del mundo real del aprendizaje por refuerzo. Arriba a la izquierda: resolución de
juegos de mesa como ajedrez y Go. Arriba a la derecha: negociación algorítmica de acciones. Abajo a la izquierda: gestión
de recursos automatizada en centros de datos. Abajo a la derecha: control y planificación de acciones en robótica. Todas
las imágenes son de licencia libre y se descargan dewww.pexels.com.

1
David Silver et al., "Mastering Chess and Shogi by Self-Play with a General Reinforcement Learning Algorithm",
presentado el 5 de diciembre de 2017,https://arxiv.org/abs/1712.01815.
La formulación de problemas de aprendizaje por refuerzo 373

El tema fascinante de RL difiere de los problemas de aprendizaje supervisado que vimos en los
capítulos anteriores en algunos aspectos fundamentales. A diferencia del aprendizaje de mapeos
de entrada y salida en el aprendizaje supervisado, RL se trata de descubrir procesos óptimos de
toma de decisiones al interactuar con un entorno. En RL, no se nos dan conjuntos de datos de
entrenamiento etiquetados; en cambio, se nos dan diferentes tipos de entornos para explorar.
Además, el tiempo es una dimensión indispensable y fundamental en los problemas de RL, a
diferencia de muchos problemas de aprendizaje supervisado, que carecen de una dimensión
temporal o tratan el tiempo más o menos como una dimensión espacial. Como resultado de las
características únicas de RL, este capítulo involucrará un vocabulario y una forma de pensar muy
diferente a los capítulos anteriores. Pero no te preocupes. Usaremos ejemplos simples y concretos
para ilustrar los conceptos y enfoques básicos. Además, nuestros viejos amigos, las redes
neuronales profundas y sus implementaciones en TensorFlow.js, seguirán estando con nosotros.
Formarán un pilar importante (¡aunque no el único!) de los algoritmos de RL que encontraremos en
este capítulo.
Al final del capítulo, debe estar familiarizado con la formulación básica de los
problemas de RL, comprender las ideas básicas que subyacen a dos tipos de redes
neuronales de uso común en RL (redes de políticas y redes Q) y saber cómo entrenar
tales redes usando la API de TensorFlow.js.

11.1 La formulación de problemas de aprendizaje por refuerzo


La figura 11.2 presenta los principales componentes de un problema de RL. El agente es aquello sobre lo
que nosotros (los practicantes de RL) tenemos control directo. El agente (como un robot que recoge
basura en un edificio) interactúa con el entorno de tres formas:

- En cada paso, el agente toma unaacción, que cambia el estado del medio ambiente. En
el contexto de nuestro robot recolector de basura, por ejemplo, el conjunto de
las acciones para elegir pueden ser {avanzar, retroceder, girar a la izquierda, girar a la
derecha, recoger la basura, tirar la basura en el contenedor}.
- De vez en cuando, el entorno proporciona al agente unarecompensa, que puede entenderse en
términos antropomórficos como una medida de placer o satisfacción instantáneos. Pero en
términos más abstractos, una recompensa (o mejor dicho, una suma de recompensas a lo largo
del tiempo, como veremos más adelante) es un número que el agente intenta maximizar. Es un
valor numérico importante que guía los algoritmos de RL de manera similar a cómo los valores de
pérdida guían los algoritmos de aprendizaje supervisado. Una recompensa puede ser positiva o
negativa. En el ejemplo de nuestro robot recolector de basura, se puede dar una recompensa
positiva cuando una bolsa de basura se tira con éxito en el contenedor de basura del robot.
Además, se debe otorgar una recompensa negativa cuando el robot vuelca un bote de basura,
choca contra personas o muebles, o arroja basura fuera de su contenedor.

-Además de la recompensa, el agente puede observar el estado del entorno a


través de otro canal, a saber,observación. Puede ser el estado completo del
entorno o solo la parte visible para el agente, posiblemente distorsionada.
374 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

Ambiente
Acción

Recompensa

Agente

Observación

Figura 11.2 Diagrama esquemático de la formulación básica de los problemas de RL. En


cada paso de tiempo, un agente selecciona una acción del conjunto de acciones
posibles, lo que provoca un cambio en el estado del entorno. El entorno proporciona al
agente una recompensa según su estado actual y la acción seleccionada. El estado del
entorno es observado total o parcialmente por el agente, que utilizará ese estado para
tomar decisiones sobre acciones futuras.

a través de cierto canal imperfecto. Para nuestro robot recolector de basura, las
observaciones son los flujos de imágenes y señales de las cámaras y varios tipos de
sensores en su cuerpo.

La formulación que acabamos de definir es algo abstracta. Veamos algunos problemas concretos de RL y
tengamos una idea del rango de posibilidades que abarca la formulación. En este proceso, también
echaremos un vistazo a la taxonomía de todos los problemas de RL que existen. Primero consideremos
las acciones. El espacio a partir del cual el agente puede elegir sus acciones puede ser discreto o continuo.
Por ejemplo, los agentes de RL que juegan juegos de mesa generalmente tienen espacios de acción
discretos porque en tales problemas, solo hay un conjunto finito de movimientos para elegir. Sin
embargo, un problema de RL que consiste en controlar un robot humanoide virtual para caminar bípedo2
involucra un espacio de acción continuo porque los pares de torsión en las articulaciones son cantidades
que varían continuamente. Los problemas de ejemplo que cubriremos en este capítulo serán sobre
espacios de acción discretos. Tenga en cuenta que en algunos problemas de RL, los espacios de acción
continuos se pueden convertir en discretos a través de la discretización. Por ejemplo, el agente del juego
StarCraft II de DeepMind divide la pantalla 2D de alta resolución en rectángulos más gruesos para
determinar dónde mover las unidades o lanzar ataques.3
Las recompensas, que juegan un papel central en los problemas de RL, también muestran variaciones. Primero,
algunos problemas de RL implican solo recompensas positivas. Por ejemplo, como veremos en breve, un agente de RL
cuyo objetivo es equilibrar un poste en un carro en movimiento solo obtiene recompensas positivas. Obtiene una
pequeña recompensa positiva por cada paso de tiempo que mantiene el poste en pie. Sin embargo, muchos problemas
de RL implican una combinación de recompensas positivas y negativas. Recompensas negativas

2
Vea el entorno humanoide en OpenAI Gym:https://gym.openai.com/envs/Humanoid-v2/.
3
Oriol Vinyals et al., “StarCraft II: A New Challenge for Reinforcement Learning”, presentado el 16 de agosto de 2017,
https://arxiv.org/abs/1708.04782.
Traducido del inglés al español - www.onlinedoctranslator.com

La formulación de problemas de aprendizaje por refuerzo 375

puede pensarse como "sanciones" o "castigo". Por ejemplo, un agente que aprende a tirar una
pelota de baloncesto al aro debería recibir recompensas positivas por los goles y negativas por los
fallos.
Las recompensas también pueden variar en la frecuencia de ocurrencia. Algunos problemas de
RL involucran un flujo continuo de recompensas. Tome el problema del poste del carro antes
mencionado, por ejemplo: mientras el poste siga en pie, el agente recibe una recompensa (positiva)
en cada paso de tiempo. Por otro lado, considere a un agente de RL que juega al ajedrez: la
recompensa llega solo al final, cuando se determina el resultado del juego (ganar, perder o
empatar). También hay problemas de RL entre estos dos extremos. Por ejemplo, nuestro robot
recolector de basura puede no recibir ninguna recompensa en los pasos entre dos vertederos de
basura exitosos, es decir, cuando se está moviendo del lugar A al lugar B. Además, un agente de RL
entrenado para jugar el juego Atari Pong no no recibir una recompensa en cada paso (fotograma)
del videojuego; en cambio, se recompensa positivamente una vez cada pocos pasos, cuando el bate
que controla golpea la pelota y rebota hacia el oponente. Los problemas de ejemplo que
visitaremos en este capítulo contienen una mezcla de problemas de RL con frecuencias de
ocurrencia de recompensa alta y baja.
La observación es otro factor importante en los problemas de RL. Es una ventana a través de la
cual el agente puede echar un vistazo al estado del entorno y formar una base sobre la que tomar
decisiones al margen de cualquier recompensa. Al igual que las acciones, las observaciones pueden
ser discretas (como en un juego de mesa o de cartas) o continuas (como en un entorno físico). Una
pregunta que quizás desee hacer es por qué nuestra formulación RL separa la observación y la
recompensa en dos entidades, aunque ambas pueden verse como retroalimentación
proporcionada por el entorno al agente. La respuesta es claridad conceptual y simplicidad. Aunque
la recompensa puede considerarse como una observación, es lo que en última instancia le
“importa” al agente. La observación puede contener tanto información relevante como irrelevante,
que el agente debe aprender a filtrar y hacer un uso inteligente de ella.
Algunos problemas de RL revelan todo el estado del entorno al agente a través de la observación,
mientras que otros ponen a disposición solo partes de sus estados. Los ejemplos del primer tipo incluyen
juegos de mesa como el ajedrez y el Go. Para este último tipo, buenos ejemplos son los juegos de cartas
como el póquer, en los que no puedes ver la mano de tu oponente, así como el comercio de acciones. Los
precios de las acciones están determinados por muchos factores, como las operaciones internas de las
empresas y la mentalidad de otros comerciantes de acciones en el mercado. Pero muy pocos de estos
estados son directamente observables por el agente. Como resultado, las observaciones del agente se
limitan al historial momento a momento de los precios de las acciones, tal vez además de la información
disponible públicamente, como las noticias financieras.
Esta discusión establece el patio de recreo en el que sucede RL. Un aspecto
interesante a destacar de esta formulación es que el flujo de información entre el
agente y el entorno es bidireccional: el agente actúa sobre el entorno; el entorno, a su
vez, proporciona al agente recompensas e información de estado. Esto distingue a RL
del aprendizaje supervisado, en el que el flujo de información es en gran medida
unidireccional: la entrada contiene suficiente información para que un algoritmo
prediga la salida, pero la salida no actúa sobre la entrada de manera significativa.
376 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

Otro hecho interesante y único sobre los problemas de RL es que deben ocurrir a lo largo de la
dimensión del tiempo para que la interacción agente-entorno consista en múltiples rondas o pasos.
El tiempo puede ser discreto o continuo. Por ejemplo, los agentes de RL que resuelven juegos de
mesa generalmente operan en un eje de tiempo discreto porque tales juegos se juegan en turnos
discretos. Lo mismo se aplica a los videojuegos. Sin embargo, un agente de RL que controla un
brazo robótico físico para manipular objetos se enfrenta a un eje de tiempo continuo, aunque
todavía puede optar por realizar acciones en puntos discretos en el tiempo. En este capítulo, nos
centraremos en los problemas de RL en tiempo discreto.
Esta discusión teórica de RL debería ser suficiente por ahora. En la siguiente sección,
comenzaremos a explorar algunos problemas reales de RL y algoritmos prácticos.

11.2 Redes de políticas y gradientes de políticas: el ejemplo del poste del carro
El primer problema de RL que resolveremos es una simulación de un sistema físico en el que un carro que
transporta un poste se mueve sobre una pista unidimensional. Acertadamente llamado elcarro-posteproblema,
fue propuesto por primera vez por Andrew Barto, Richard Sutton y Charles Anderson en 1983.4
Desde entonces, se ha convertido en un problema de referencia para la ingeniería de sistemas de
control (algo análogo al problema de reconocimiento de dígitos del MNIST para el aprendizaje
supervisado), debido a su simplicidad y física y matemáticas bien formuladas, así como al hecho de
que es no es del todo trivial de resolver. En este problema, el objetivo del agente es controlar el
movimiento de un carro ejerciendo fuerzas hacia la izquierda o hacia la derecha para mantener un
poste en equilibrio el mayor tiempo posible.

11.2.1 Cart-pole como un problema de aprendizaje por refuerzo

Antes de continuar, debe jugar con el ejemplo del poste del carro para obtener una comprensión
intuitiva del problema. El problema del poste del carro es lo suficientemente simple y liviano como
para realizar la simulación y el entrenamiento completamente en el navegador. La figura 11.3
ofrece una representación visual del problema del poste del carro, que puede encontrar en la
página abierta por elreloj de hilomando. Para pagar y ejecutar el ejemplo, use

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


cart-pole
discos compactos

hilo y reloj de hilo

Haga clic en el botón Crear modelo y luego en el botón Entrenar. Luego debería ver una animación en la
parte inferior de la página que muestra a un agente no capacitado que realiza la tarea de carpole. Dado
que el modelo del agente tiene sus pesos inicializando valores aleatorios (más sobre el modelo más
adelante), tendrá un rendimiento bastante bajo. Todos los pasos de tiempo desde el comienzo de un
juego hasta el final a veces se denominan colectivamente como unepisodioen terminología RL. Usaremos
los términosjuegoyepisodioindistintamente aquí.

4
Andrew G. Barto, Richard S. Sutton y Charles W. Anderson, "Elementos adaptativos similares a neuronas que pueden
resolver problemas de control de aprendizaje difíciles"Transacciones IEEE sobre sistemas, hombre y cibernética, sept./oct.
1983, págs. 834–846,http://mng.bz/Q0rG.
Redes de políticas y gradientes de políticas: el ejemplo del poste del carro 377

'

UN.Acciones y observaciones en el
problema del carro-poste

X'

fuerzaL fuerzaR
X

B.Condición final 1: el carro sale C.Condición final 2: poste


fuera de los límites sobre inclinado

Figura 11.3 Representación visual del problema del poste del carro. Panel A: cuatro cantidades físicas (posición del carro X,
velocidad del carroX', ángulo de inclinación del poste - y velocidad angular del poste -') conforman el estado del entorno y la
observación. En cada paso de tiempo, el agente puede elegir una acción de fuerza hacia la izquierda o una de fuerza hacia la
derecha, lo que cambiará el estado del entorno en consecuencia. Paneles B y C: las dos condiciones que harán que termine un
juego: o el carro va demasiado hacia la izquierda o hacia la derecha (B) o el poste se inclina demasiado desde la posición vertical
(C).

Como muestra el panel A de la figura 11.3, la posición del carro a lo largo de la vía en cualquier
paso de tiempo es capturada por una variable llamadaX. Su velocidad instantánea se denotaX'.
Además, el ángulo de inclinación del poste es capturado por otra variable llamada -. La velocidad
angular del polo (qué tan rápido - cambia y en qué dirección) se denota -'. Juntas, las cuatro
cantidades físicas (X,X', - y -') son observados completamente por el agente en cada paso y
constituyen la parte de observación de este problema de RL.
La simulación finaliza cuando se cumple cualquiera de las dos condiciones:

- El valor deXse sale de un límite preestablecido o, en términos físicos, el carro choca


contra una de las paredes en los dos extremos de la pista (panel B en la figura 11.3).

- El valor absoluto de - supera un cierto umbral o, en términos físicos, el poste se


inclina demasiado lejos de la posición vertical (panel C en la figura 11.3).

El entorno también finaliza un episodio después del paso de simulación número 500. Esto evita que el
juego dure demasiado (lo que puede suceder una vez que el agente se vuelve muy bueno en el juego a
través del aprendizaje). Este límite superior en el número de pasos se puede ajustar en la interfaz de
usuario. Hasta que finaliza el juego, el agente obtiene una recompensa de una unidad (1)en cada paso de
la simulación. Por lo tanto, para lograr una recompensa acumulada más alta, el agente necesita encontrar
una manera de mantener el poste en pie. Pero, ¿cómo controla el agente el sistema cart-pole? Esto nos
lleva a la parte de acción de este problema de RL.
378 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

Como muestran las flechas de Fuerza en el panel A de la figura 11.3, el agente está
limitado a dos acciones posibles en cada paso: ejercer una fuerza hacia la izquierda o hacia la
derecha sobre el carrito. El agente debe elegir una de las dos direcciones de fuerza. La
magnitud de la fuerza es fija. Una vez que se ejerce la fuerza, la simulación promulgará un
conjunto de ecuaciones matemáticas para calcular el siguiente estado (nuevos valores deX,X',
- y -') del entorno. Los detalles involucran la mecánica newtoniana familiar. No cubriremos las
ecuaciones detalladas, ya que comprenderlas no es esencial aquí, pero están disponibles en
el archivo cartpole/cart_pole.js en el directorio cart-pole si está interesado.
Asimismo, el código que representa el sistema cart-pole en un lienzo HTML se puede
encontrar en cart-pole/ui.js. Este código destaca una ventaja de escribir algoritmos RL en
JavaScript (en particular, en TensorFlow.js): la interfaz de usuario y el algoritmo de
aprendizaje se pueden escribir convenientemente en el mismo idioma y estar estrechamente
integrados entre sí. Esto facilita la visualización y comprensión intuitiva del problema y
acelera el proceso de desarrollo. Para resumir el problema del poste del carro, podemos
describirlo en la formulación RL canónica (ver tabla 11.1).

Tabla 11.1 Describiendo el problema del carro-poste en la formulación canónica de RL

Concepto abstracto de RL Realización en el problema del carro-poste

Ambiente Un carro que lleva un poste y se mueve sobre una pista unidimensional.

Acción (Discreta) Elección binaria entre una fuerza hacia la izquierda y una hacia la
derecha en cada paso. La magnitud de la fuerza es fija.

Recompensa (Frecuente y solo positivo) Por cada paso del episodio del juego, el agente recibe una
recompensa fija (1). El episodio termina tan pronto como el carro golpea una pared en
un extremo de la vía, o el poste se inclina demasiado desde la posición vertical.

Observación (Estado completo, continuo) En cada paso, el agente puede acceder al estado completo
del sistema carro-poste, incluida la posición del carro (X) y velocidad (X'), además del
ángulo de inclinación del poste (-) y la velocidad angular (-').

11.2.2 Red de políticas


Ahora que el problema de RL del poste del carro está planteado, veamos cómo resolverlo.
Históricamente, los teóricos del control han ideado soluciones ingeniosas para este problema. Sus
soluciones se basan en la física subyacente de este sistema.5Eso esnocómo abordaremos el
problema en este libro. En el contexto de este libro, hacer eso sería algo análogo a escribir
heurísticas para analizar bordes y esquinas en imágenes MNIST con el fin de clasificar los dígitos.
En su lugar, ignoraremos la física del sistema y dejaremos que nuestro agente aprenda a través de
repetidos ensayos y errores. Esto concuerda con el espíritu del resto de este

5
Si está interesado en el enfoque tradicional, no RL, para el problema del poste del carro y no le asustan las matemáticas,
puede leer el material didáctico abierto de un curso de teoría de control en el MIT por Russ Tedrake:http://mng. bz/j5lp.
Redes de políticas y gradientes de políticas: el ejemplo del poste del carro 379

libro: en lugar de codificar de forma rígida un algoritmo o diseñar manualmente características basadas en el
conocimiento humano, diseñamos un algoritmo que permite que el modelo aprenda por sí mismo.
¿Cómo podemos dejar que el agente decida la acción (fuerza hacia la izquierda versus fuerza hacia la
derecha) a tomar en cada paso? Dadas las observaciones disponibles para el agente y la decisión que el
agente debe tomar en cada paso, este problema se puede reformular como un simple problema de
mapeo de entrada y salida como los del aprendizaje supervisado. Una solución natural es construir una
red neuronal para seleccionar una acción basada en la observación. Esta es la idea básica detrás de lared
de políticas.
Esta red neuronal toma un vector de observación de longitud 4 (X,X', - y -') y genera un
número que se puede traducir en una decisión de izquierda contra derecha. La arquitectura
de la red es similar al clasificador binario que construimos para los sitios web de phishing en
el capítulo 3. En resumen, en cada paso, observaremos el entorno y usaremos nuestra red
para decidir qué acción tomar. Al permitir que nuestra red juegue varias rondas,
recopilaremos algunos datos con los que evaluar esas decisiones. Luego, inventaremos una
forma de asignar calidad a esas decisiones para que podamos ajustar los pesos de nuestra
red para que tome decisiones más como las "buenas" y menos como las "malas" en el futuro.

Los detalles de este sistema difieren de nuestro trabajo anterior de clasificador en los siguientes
aspectos:

- El modelo se invoca muchas veces en el transcurso de un episodio del juego (en cada paso de
tiempo).
- El resultado del modelo (el resultado del cuadro de la Red de políticas en la figura 11.4) son logits
en lugar de puntajes de probabilidad. Los logits se convierten posteriormente en puntajes de
probabilidad a través de una función sigmoidea. La razón por la que no incluimos la no linealidad
sigmoidea directamente en la última capa (de salida) de la red de políticas es que necesitamos los
logits para el entrenamiento, como veremos en breve.

Ambiente

Agente

Observación Red de políticas

X
forma: forma: Acción
densonorte
denso1

X' [nulo, 4] [nulo, 1]


...
tf.sigmoide()

' Aleatorio
muestreo
tf.multinomial()

Figura 11.4 Cómo encaja la red de políticas en nuestra solución al problema del poste del carro. La red de políticas es un
modelo de TensorFlow.js que genera la probabilidad de la acción de fuerza hacia la izquierda mediante el uso del vector de
observación (X,X', -, y -') como entrada. La probabilidad se convierte en una acción real a través de un muestreo aleatorio.
380 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

- La salida de probabilidad de la función sigmoidea debe convertirse en una acción concreta


(izquierda versus derecha). Esto se hace a través del muestreo aleatorio tf.multinomial()
Llamada de función. Recuerda que usamostf.multinomial()en el ejemplo de generación de
texto lstm en el capítulo 10, cuando muestreamos el siguiente carácter utilizando
probabilidades softmax sobre letras del alfabeto para muestrear el siguiente carácter. La
situación aquí es un poco más simple porque solo hay dos opciones.

El último punto tiene implicaciones más profundas. Considere el hecho de que nosotrospudo
convertir la salida deltf.sigmoide()función directamente en una acción mediante la aplicación de un
umbral (por ejemplo, seleccionar la acción de la izquierda cuando la salida de la red es mayor que
0,5 y la acción de la derecha en caso contrario). ¿Por qué preferimos el enfoque de muestreo
aleatorio más complicado contf.multinomial()sobre este enfoque más simple? La respuesta es que
nosotrosquererla aleatoriedad que viene contf.multinomial().En la fase inicial del entrenamiento, la
red de políticas no tiene idea de cómo seleccionar la dirección de la fuerza porque sus pesos se
inicializan aleatoriamente. Mediante el uso de muestreo aleatorio, lo alentamos a probar acciones
aleatorias y ver cuáles funcionan mejor. Algunas de las pruebas aleatorias terminarán siendo
malas, mientras que otras darán buenos resultados. Nuestro algoritmo recordará las buenas
elecciones y hará más de ellas en el futuro. Pero estas buenas opciones no estarán disponibles a
menos que se le permita al agente intentarlo al azar. Si hubiéramos elegido el enfoque de umbral
determinista, el modelo se quedaría con sus elecciones iniciales.

Esto nos lleva a un tema clásico e importante en RL llamadoexploración versus explotación.


Exploraciónse refiere a intentos aleatorios; es la base sobre la que el agente RL descubre las
buenas acciones.Explotaciónsignifica hacer las soluciones óptimas que el agente ha aprendido para
maximizar la recompensa. Los dos son incompatibles entre sí. Encontrar un buen equilibrio entre
ellos es fundamental para diseñar algoritmos de RL que funcionen. Al principio, queremos explorar
una amplia gama de posibles estrategias, pero a medida que convergemos en mejores estrategias,
queremos afinar esas estrategias. Por lo tanto, generalmente hay una reducción gradual de la
exploración con entrenamiento en muchos algoritmos. En el problema del poste del carro, la
rampa está implícita en eltf.multinomial()función de muestreo porque da resultados cada vez más
deterministas cuando el nivel de confianza del modelo aumenta con el entrenamiento.

El listado 11.1 (extraído de cart-pole/index.js) muestra las llamadas a TensorFlow.js que


crean la red de políticas. El código del listado 11.2 (también extraído de cartpole/index.js)
convierte la salida de la red de políticas en la acción del agente, además de devolver los logits
con fines de capacitación. Comparado con los modelos de aprendizaje supervisado que
encontramos en los capítulos anteriores, el código relacionado con el modelo aquí no es muy
diferente.
Sin embargo, lo que es fundamentalmente diferente aquí es el hecho de que no tenemos un conjunto
de datos etiquetados que se puedan usar para enseñarle al modelo qué opciones de acción son buenas y
cuáles son malas. Si tuviéramos tal conjunto de datos, simplemente podríamos llamarencajar()o
ajusteDataset() en la red de políticas para resolver el problema, como hicimos con los modelos en los
capítulos anteriores. Pero el hecho es que no lo hacemos, por lo que el agente tiene que averiguar qué
Redes de políticas y gradientes de políticas: el ejemplo del poste del carro 381

las acciones son buenas al jugar el juego y mirar las recompensas que obtiene. En otras palabras, tiene
que “aprender a nadar nadando”, una característica clave de los problemas de RL. A continuación,
veremos cómo se hace eso en detalle.

Listado 11.1 Red de políticas MLP: selección de acciones en base a observaciones

createModel(hiddenLayerSizes) {
hiddenLayerSize controla los tamaños
if (!Array.isArray(hiddenLayerSizes)) {
de todas las capas de la red de políticas
ocultoLayerSizes = [hiddenLayerSizes];
excepto la última (capa de salida).
}
este.modelo = tf.secuencial();
ocultoLayerSizes.forEach((hiddenLayerSize, i) => {
este.modelo.añadir(tf.layers.dense({
unidades: hiddenLayerSize,
activación: 'elu', se necesita inputShape
forma de entrada: i === 0 ? [4] : indefinido })); solo para la primera capa.

});
this.model.add(tf.layers.dense({units: 1}));
}
La última capa está codificada para tener una unidad. los
}
el número de salida único se convertirá en una probabilidad
de seleccionar la acción de fuerza hacia la izquierda.

Listado 11.2 Obtener los logits y las acciones de la salida de la red de políticas

getLogitsAndActions(entradas) {
volver tf.ordenado(() => {
Convierte los logits a los valores
const logits = this.policyNet.predict(entradas);
de probabilidad de la acción

const izquierdaProb = tf.sigmoid(logits); const hacia la izquierda

leftRightProbs = tf.concat(
[ProbIzquierda, tf.sub(1, ProbIzquierda)], 1); Calcula los valores de probabilidad
const acciones = tf.multinomial( para ambas acciones, ya que son
leftRightProbs, 1, nulo, verdadero); return requeridos por tf.multinomial()
[logits, acciones]; });
Muestrea aleatoriamente acciones basadas en los
} valores de probabilidad. Los cuatro argumentos
son valores de probabilidad, número de muestras,
semilla aleatoria (sin usar) y un indicador que
indica que los valores de probabilidad están
normalizados.

11.2.3 Entrenamiento de la red de políticas: el algoritmo REINFORCE


Ahora la pregunta clave es cómo calcular qué acciones son buenas y cuáles son malas. Si podemos
responder a esta pregunta, podremos actualizar los pesos de la red de políticas para que sea más
probable que elija buenas acciones en el futuro, de una manera similar al aprendizaje supervisado.
Lo que rápidamente viene a la mente es que podemos usar la recompensa para medir qué tan
buenas son las acciones. Pero el problema del poste del carro involucra recompensas que 1)
382 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

siempre tienen un valor fijo (1)y 2) ocurrir en cada paso siempre que el episodio no haya terminado. Por lo tanto,
no podemos simplemente usar la recompensa paso a paso como una métrica, o terminaremos etiquetando todas
las acciones como igualmente buenas. Tenemos que tener en cuenta cuánto dura cada episodio.

Un enfoque ingenuo es sumar todas las recompensas en un episodio, lo que nos da la duración
del episodio. Pero, ¿puede la suma ser una buena evaluación de las acciones? No es difícil darse
cuenta de que no funcionará. La razón son los pasos al final de un episodio. Supongamos que en
un episodio largo, el agente equilibra bastante bien el sistema de carros y postes hasta casi el final,
cuando toma algunas malas decisiones que hacen que el episodio finalmente termine. El enfoque
de suma ingenua asignará una evaluación igualmente buena a las malas acciones al final ya las
buenas de antes. En cambio, queremos asignar puntajes más altos a las acciones en las partes
temprana y media del episodio y asignar puntajes más bajos a las acciones cerca del final.

Esto nos lleva a la idea dedescuento de recompensa, una idea simple pero importante en RL de
que el valor de un determinado paso debe ser igual a la recompensa inmediata más la recompensa
que se espera para el futuro. La recompensa futura puede ser tan importante como la recompensa
inmediata, o puede ser menos importante. El saldo relativo se puede cuantificar con un factor de
descuento llamado - (gamma). - normalmente se establece en un valor cercano pero ligeramente
inferior a 1, como 0,95 o 0,99. Escribimos esto en una ecuación matemática como

norte–I-rnorte
vI=rI+ - -rI+1+ - 2-rI+2+...+ - (Ecuación 11.1)

En la ecuación 11.1,vIes la recompensa total descontada del estado en el pasoI, que puede
entenderse como el valor de ese estado en particular. Es igual a la recompensa inmediata otorgada
al agente en ese paso (rI), más la recompensa del siguiente paso (rI+1) descontado por -, más una
recompensa adicional con descuento de dos pasos después, y así sucesivamente, hasta el final del
episodio (pasonorte).
Para ilustrar el descuento de recompensas, mostramos cómo esta ecuación
transforma nuestras recompensas originales en una métrica de valor más útil en la
figura 11.5. La trama superior en el panel A muestra las recompensas originales de los
cuatro pasos de un episodio corto. La gráfica inferior muestra las recompensas
descontadas (basadas en la ecuación 11.1). El panel B muestra las recompensas totales
originales y con descuento de un episodio más largo (duración = 20) para comparar. En
los dos paneles, podemos ver que el valor de la recompensa total con descuento es más
alto al principio y más bajo al final, lo que tiene sentido porque queremos asignar
valores más bajos a las acciones hacia el final de un episodio, lo que hace que el juego
termine. . Además, los valores al comienzo y en la mitad del episodio más largo (panel B)
son más altos que los del comienzo del episodio más corto (panel A).

La ecuación de descuento de recompensa nos da un conjunto de valores que tienen más sentido que
la suma ingenua anterior. Pero todavía nos enfrentamos a la cuestión de cómo utilizar estos
Redes de políticas y gradientes de políticas: el ejemplo del poste del carro 383

UN.Duración del episodio = 4 B.Duración del episodio = 20


Recompensas originales Recompensas originales

1.0 1.0
recompensa original

recompensa original
0.8 0.8
0.6 0.6
0.4 0.4
0.2 0.2
0.0 0.0
Recompensas con descuento Recompensas con descuento

12.5 12.5
Recompensa con descuento

Recompensa con descuento


10.0 10.0
7.5 7.5
5.0 5.0
2.5 2.5
0.0 0.0
0 1 2 3 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Paso Paso

Figura 11.5 Panel A: aplicación del descuento de recompensas (ecuación 11.1) sobre las recompensas de un episodio con
cuatro pasos. Panel B: igual que el panel A, pero de un episodio con 20 pasos (es decir, cinco veces más largo que el del panel
A). Como resultado del descuento, se asignan valores más altos a las acciones al comienzo de cada episodio en comparación
con las acciones cercanas al final.

valores de recompensa con descuento para capacitar a la red de políticas. Para ello utilizaremos un
algoritmo llamado REFORZAR, inventado por Ronald Williams en 1992.6La idea básica detrás de
REINFORCE es ajustar los pesos de la red de políticas para que sea más probable que se tomen buenas
decisiones (las opciones a las que se asignan recompensas con descuentos más altos) y menos probable
que se tomen malas decisiones (a las que se asignan recompensas con descuentos más bajos).
Con este fin, necesitamos calcular la dirección en la que cambiar los parámetros para
hacer que una acción sea más probable dadas las entradas de observación. Esto se hace con
el código del listado 11.3 (extraído de cart-pole/index.js). La funcióngetGradientsAnd-
SaveActions()se invoca en cada paso del juego. Compara los logits (puntajes de probabilidad
no normalizados) y la acción real seleccionada en el paso y devuelve el gradiente de la
discrepancia entre los dos con respecto a los pesos de la red de políticas. Esto puede sonar
complicado, pero intuitivamente, es bastante sencillo. El gradiente devuelto le dice a la red de
políticas cómo cambiar sus pesos para que las opciones se parezcan más a las opciones que
realmente se seleccionaron. Los gradientes, junto con las recompensas de los episodios de
entrenamiento, forman la base de nuestro método RL. Es por esto que este método
pertenece a la familia de algoritmos RL denominadagradientes de política.

Listado 11.3 Comparación de logits y acciones reales para obtener gradientes para pesos

getGradientsAndSaveActions(inputTensor) {
const f = () => tf.tidy(() => {
const [logits, acciones] = getLogitsAndActions()
this.getLogitsAndActions(inputTensor); se define en la lista112.
this.currentActions_ = acciones.dataSync();

6
Ronald J. Williams, "Algoritmos estadísticos simples de seguimiento de gradientes para el aprendizaje por refuerzo conexionista"
Aprendizaje automático, vol. 8, núms. 3 y 4, págs. 229 a 256,http://mng.bz/WOyw.
384 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

etiquetas constantes =
tf.sub(1, tf.tensor2d(this.currentActions_, return acciones.forma));
tf.losses.sigmoidCrossEntropy(
etiquetas, logits).asScalar(); La pérdida de entropía cruzada sigmoidea
}); cuantifica la discrepancia entre la acción real
return tf.variableGrads(f); realizada durante el juego y los logits de
} salida de la red de políticas.
Calcula el gradiente de la pérdida con
respecto a los pesos de la red de políticas

Durante el entrenamiento, dejamos que el agente juegue varios juegos (digamos,nortejuegos) y


recoge todas las recompensas con descuento según la ecuación 11.1, así como los gradientes de
todos los pasos. Luego, combinamos las recompensas con descuento y los gradientes
multiplicando los gradientes con una versión normalizada de las recompensas con descuento. La
normalización de recompensas aquí es un paso importante. Cambia y escala linealmente todas las
recompensas con descuento delnortejuegos para que tengan un valor medio general de 0 y una
desviación estándar general de 1. En la figura 11.6 se muestra un ejemplo de aplicación de esta
normalización en las recompensas con descuento. Ilustra las recompensas con descuento
normalizadas de un episodio corto (duración = 4) y uno más largo (duración = 20). A partir de esta
figura, debe quedar claro qué pasos se ven favorecidos por el algoritmo REINFORCE: son las
acciones realizadas en las partes temprana y media de episodios más largos. Por el contrario, todos
los pasos del episodio más corto (duración 4) se asignannegativovalores. ¿Qué significa una
recompensa negativa normalizada? Significa que cuando se usa para actualizar los pesos de la red
de políticas más tarde, dirigirá la redfuerade hacer una elección similar de acciones

Recompensas con descuento normalizadas

UN.Longitud = 4 B.Longitud = 20

1.5 1.5

1.0 1.0
Recompensa con descuento normalizada

Recompensa con descuento normalizada

0.5 0.5

0.0 0.0

– 0.5 – 0.5

– 1.0 – 1.0

– 1.5 – 1.5
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 1 2 3 4 5 6 7 8 9 10111213141516171819
Paso Paso

Figura 11.6 Normalización de las recompensas con descuento de los dos episodios con una duración de 4 (panel A) y
20 (panel B). Podemos ver que las recompensas con descuento normalizadas tienen los valores más altos al comienzo
del episodio de 20 de duración. El método de gradiente de políticas usará estos valores de recompensa descontados
para actualizar los pesos de la red de políticas, lo que hará que sea menos probable que la red tome las decisiones de
acción que resultaron en malas recompensas en el primer caso (longitud = 4) y más probable que tomar las
decisiones que resultaron en las buenas recompensas en la parte inicial del segundo caso (longitud = 20) (es decir,
dadas las mismas entradas de estado que antes).
Redes de políticas y gradientes de políticas: el ejemplo del poste del carro 385

dadas entradas de estado similares en el futuro. Esto contrasta con una recompensa
normalizada positiva, que dirigirá la red de políticashaciaelegir acciones similares dadas
entradas similares en el futuro.
El código para la normalización de las recompensas con descuento, y usarlo para escalar los
gradientes, es algo tedioso pero no complicado. Eso esta en elscaleAndAverage-Gradients()función
en cart-pole/index.js, que no se incluye aquí por motivos de brevedad. Los gradientes escalados se
utilizan para actualizar los pesos de la red de políticas. Con las ponderaciones actualizadas, la red
de políticas generará logits más altos para las acciones de los pasos a los que se les asignó
recompensas con descuento más altas y logits más bajos para las acciones de los pasos a los que
se les asignó las más bajas.
Así es básicamente como funciona el algoritmo REINFORCE. La lógica de entrenamiento central
del ejemplo del poste del carro, que se basa en REFORZAR, se muestra en el listado 11.4. Es una
reiteración de los pasos descritos anteriormente:

1 Invoque la red de políticas para obtener registros basados en la observación actual del agente.

2 Muestra aleatoriamente una acción basada en los logits.


3 Actualice el entorno utilizando la acción muestreada.
4 Recuerde lo siguiente para actualizar los pesos posteriormente (paso 7): los logits y la
acción seleccionada, así como los gradientes de la función de pérdida con respecto a los
pesos de la red de políticas. Estos gradientes se conocen comogradientes de política. Recibe
5 una recompensa del entorno y recuérdala para más adelante (paso 7). Repita los pasos 1 a 5
6 hasta quenumJuegosse completan los episodios.
7 Una vez que todosnumJuegoslos episodios han finalizado, descuente y normalice las
recompensas y use los resultados para escalar los gradientes del paso 4. Luego actualice los
pesos de la red de políticas usando los gradientes escalados. (Aquí es donde se actualizan los
pesos de la red de políticas).
8 (No se muestra en el listado 11.4) Repita los pasos 1 a 7numeroIteracionesveces.

Compare estos pasos con el código de la lista (extraído de cart-pole/index.js) para


asegurarse de que puede ver la correspondencia y seguir la lógica.

Listado 11.4 Ciclo de entrenamiento del ejemplo Cart-pole implementando el algoritmo REINFORCE

tren asíncrono
cartPoleSystem, Optimizer, descuentoRate, numGames, maxStepsPerGame) { const
allGradients = [];
const todasRecompensas = [];
const pasos del juego = []; Bucles sobre especificado
onGameEnd(0, numGames); número de episodios
for (sea i = 0; i < numGames; ++i) {
cartPoleSystem.setRandomState(); const
Inicializa aleatoriamente
juegoRecompensas = [];
un episodio de juego
const gameGradients = [];
for (sea j = 0; j < maxStepsPerGame; ++j) {
bucles sobre const gradientes = tf.tidy(() => {
pasos de la const inputTensor = cartPoleSystem.getStateTensor();
juego
386 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

devolver esto.getGradientsAndSaveActions(
inputTensor).grados; Realiza un seguimiento de
}); los gradientes de cada
paso para más tarde
this.pushGradients(gameGradients, gradientes); acción const
REFORZAR la formación
= this.currentActions_[0]; const isDone =
cartPoleSystem.update(acción); el agente toma
esperar quizásRenderDuringTraining(cartPoleSystem); una acción en
el entorno.
si (está hecho) {
juegoRecompensas.push(0);
descanso; Mientras el juego no haya
} demás { terminado, el agente recibe una
juegoRecompensas.push(1); unidad de recompensa por paso.
}
}
onGameEnd(i + 1, numGames); Pasos del
juego.push(recompensas del juego.longitud);
this.pushGradients(allGradients, Gradientes del juego);
allRewards.push(gameRewards); await
tf.nextFrame();
Descuentos y
}
normaliza las recompensas
tf.ordenado(() => { (paso clave de REFORZAR)
const normalizadoRecompensas =
descuentoYNormalizarRecompensas(todasRecompensas, tasa de descuento);
Optimizer.applyGradients(
scaleAndAverageGradients(allGradients, Recompensas normalizadas));
});
tf.dispose(todoslosGradientes); Actualiza la política de la red.
pesos usando los gradientes
devuelve los pasos del juego;
escalados de todos los pasos
}

Para ver el algoritmo REINFORCE en acción, especifique 25 épocas en la página de demostración y haga
clic en el botón Entrenar. De forma predeterminada, el estado del entorno se muestra en tiempo real
durante el entrenamiento para que pueda ver los intentos repetidos del agente de aprendizaje. Para
acelerar el entrenamiento, desactive la casilla de verificación Procesar durante el entrenamiento.
Veinticinco épocas de entrenamiento tomarán unos minutos en una computadora portátil
razonablemente actualizada y deberían ser suficientes para lograr un rendimiento máximo (500 pasos por
episodio de juego en la configuración predeterminada). La Figura 11.7 muestra una curva de
entrenamiento típica, que traza la duración promedio del episodio en función de la iteración de
entrenamiento. Tenga en cuenta que el progreso del entrenamiento muestra una fluctuación dramática,
con el número medio de pasos cambiando de una manera no monótona y con mucho ruido durante las
iteraciones.
Una vez completada la capacitación, haga clic en el botón Probar y debería ver que el agente hace un
buen trabajo manteniendo el sistema de carro-poste equilibrado en muchos pasos. Dado que la fase de
prueba no implica un número máximo de pasos (500 por defecto), es posible que el agente pueda
mantener el episodio durante más de 1000 pasos. Si continúa demasiado tiempo, puede hacer clic en el
botón Detener para finalizar la simulación.
Redes de políticas y gradientes de políticas: el ejemplo del poste del carro 387

500 Serie
Serie 1

400
Media de pasos por juego

300
Figura 11.7 Una curva que muestra el número
promedio de pasos que sobrevive el agente en los
200 episodios de poste del carro en función del número
de iteraciones de entrenamiento. La puntuación
perfecta (500 pasos en este caso) se alcanza
100 alrededor de la iteración 20. Este resultado se
obtiene con un tamaño de capa oculta de 128. La
forma altamente no monótona y fluctuante de la
0 curva no es infrecuente entre los problemas de RL.
0 5 10 15 20 25
Iteración de entrenamiento

Para concluir esta sección, la figura 11.8 recapitula la formulación del problema y el rol del
algoritmo de gradiente de políticas REINFORCE. Todas las partes principales de la solución se
representan en esta figura. En cada paso, el agente utiliza una red neuronal llamadared de políticas
para estimar la probabilidad de que la acción hacia la izquierda (o, de manera equivalente, hacia la
derecha) sea la mejor opción. Esta probabilidad se convierte en una acción real a través de un
proceso de muestreo aleatorio que anima al agente a explorar desde el principio.

Ambiente

Recompensa

REFORZARSE Gradientes de política

(contabilidad de recompensas)

Actualizaciones de peso Sigmoideo


entropía cruzada
Agente

Observación Red de políticas

X
forma: Acción
forma:
X'
densonorte
denso1

[nulo, 4] [nulo, 1]
...
tf.sigmoide()
θ

θ' Aleatorio
tf.multinomial()
muestreo

Figura 11.8 Un diagrama esquemático que ilustra la solución basada en el algoritmo REINFORCE para el problema del poste del
carro. Este diagrama es una vista ampliada del diagrama de la figura 11.4.
388 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

y obedece a la certeza de las estimaciones posteriores. La acción impulsa el sistema cart-pole


en el entorno, que a su vez proporciona recompensas al agente hasta el final del episodio.
Este proceso repite una serie de episodios, durante los cuales el algoritmo REINFORCE
recuerda la recompensa, la acción y la estimación de la red de políticas en cada paso. Cuando
llega el momento de que REINFORCE actualice la red de políticas, distingue las buenas
estimaciones de la red de las malas mediante el descuento de recompensas y la
normalización, y luego usa los resultados para empujar las ponderaciones de la red en la
dirección de hacer mejores estimaciones en el futuro. Este proceso itera varias veces hasta el
final del entrenamiento (por ejemplo, cuando el agente alcanza un umbral de rendimiento).

Dejando a un lado todos los elegantes detalles técnicos, demos un paso atrás y veamos el panorama
general de RL representado en este ejemplo. El enfoque basado en RL tiene claras ventajas sobre los
métodos que no son de aprendizaje automático, como la teoría de control tradicional: la generalidad y la
economía del esfuerzo humano. En los casos en que el sistema tenga características complejas o
desconocidas, el enfoque de RL puede ser la única solución viable. Si las características del sistema
cambian con el tiempo, no tendremos que derivar nuevas soluciones matemáticas desde cero:
simplemente podemos volver a ejecutar el algoritmo RL y dejar que el agente se adapte a la nueva
situación.
La desventaja del enfoque RL, que aún es una cuestión sin resolver en el campo
de la investigación RL, es que requiere muchos ensayos repetidos en el entorno. En
el caso del ejemplo del poste del carro, se necesitaron alrededor de 400 episodios
de juego para alcanzar el nivel objetivo de competencia. Algunos enfoques
tradicionales que no son de RL pueden no requerir ninguna prueba. Implemente el
algoritmo basado en la teoría de control, y el agente debería poder equilibrar el
poste del episodio 1. Para un problema como el poste del carro, el hambre de RL
por ensayos repetidos no es un problema importante porque la simulación por
computadora del entorno es simple. , rápido y barato. Sin embargo, en problemas
más realistas, como los automóviles que se conducen solos y los brazos robóticos
que manipulan objetos, este problema de RL se convierte en un desafío más agudo
y apremiante.

Esto concluye nuestro primer ejemplo de RL. El problema del poste del carro tiene algunas
características especiales que no se cumplen en otros problemas de RL. Por ejemplo, muchos entornos de
RL no brindan una recompensa positiva al agente en cada paso. En algunas situaciones, el agente puede
necesitar tomar docenas de decisiones, si no más, antes de que pueda ser recompensado positivamente.
En las brechas entre las recompensas positivas, puede que no haya recompensa, o puede que solo haya
recompensas negativas (¡se puede argumentar que muchos esfuerzos del mundo real, como estudiar,
hacer ejercicio e invertir, son así!). Además, el sistema cart-pole es “sin memoria” en el sentido de que la
dinámica del sistema no depende de lo que el agente hizo en el pasado. Muchos problemas de RL son
más complejos que eso, en el sentido de que la acción del agente cambia ciertos aspectos del entorno. El
problema de RL lo haremos
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 389

El estudio de la siguiente sección mostrará recompensas positivas dispersas y un entorno que


cambia con el historial de acciones. Para abordar el problema, presentaremos otro algoritmo RL
útil y popular, llamadoQ-aprendizaje profundo.

11.3 Redes de valor y Q-learning: el ejemplo del juego de la serpiente


Usaremos el clásico juego de acción llamadoserpientecomo nuestro problema de ejemplo para cubrir el
Q-learning profundo. Como hicimos en la última sección, primero describiremos el problema de RL y el
desafío que plantea. Al hacerlo, también discutiremos por qué los gradientes de política y REIN-FORCE no
serán muy efectivos en este problema.

11.3.1 La serpiente como problema de aprendizaje por refuerzo

Snake, que apareció por primera vez en los juegos de arcade de la década de 1970, se ha convertido en un
género de videojuegos muy conocido. El directorio snake-dqn en tfjs-examples contiene una implementación de
JavaScript de una variante simple del mismo. Puedes consultar el código con

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


snake-dqn
discos compactos

hilo
hilo reloj

En la página web abierta por elhilo relojcomando, se puede ver un tablero de la


juego de serpientes Puede cargar un modelo de red Q profunda (DQN) preentrenado y alojado y observar
cómo funciona. Más adelante, hablaremos sobre cómo puede entrenar un modelo de este tipo desde
cero. Por ahora, deberías poder tener una idea intuitiva de cómo funciona este juego a través de la
observación. En caso de que aún no esté familiarizado con el juego de la serpiente, su configuración y
reglas se pueden resumir de la siguiente manera.
Primero, todas las acciones ocurren en un mundo de cuadrícula de 9 × 9 (ver un ejemplo en la figura
11.9). El mundo (o tablero) puede hacerse más grande, pero 9 × 9 es el tamaño predeterminado en
nuestro ejemplo. Hay tres tipos de cuadrados en el tablero: la serpiente, la fruta y el espacio vacío. La
serpiente está representada por cuadrados azules, excepto la cabeza, que es de color naranja con un
semicírculo que representa la boca de la serpiente. La fruta está representada por un cuadrado verde con
un círculo en su interior. Los cuadrados vacíos son blancos. El juego ocurre en pasos o, en la terminología
de los videojuegos,marcos. En cada fotograma, el agente debe elegir entre tres acciones posibles para la
serpiente: seguir recto, girar a la izquierda o girar a la derecha (quedarse quieto no es una opción). El
agente es recompensado positivamente cuando la cabeza de la serpiente entra en contacto con un
cuadrado de fruta, en cuyo caso el cuadrado de fruta desaparecerá (sera "comido" por la serpiente), y la
longitud de la serpiente aumentará en uno en la cola. . Aparecerá una nueva fruta en uno de los
cuadrados vacíos. El agente será recompensado negativamente si no come una fruta en un paso. El juego
termina (la serpiente “muere”) cuando la cabeza de la serpiente se sale de los límites (como en el panel B
de la figura 11.9) o choca contra su propio cuerpo (como en el panel C).
390 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

A B C

Figura 11.9 El juego de la serpiente: un mundo cuadriculado en el que el jugador controla una serpiente para que coma fruta.
El “objetivo” de la serpiente es comer tantas frutas como sea posible a través de un patrón de movimiento eficiente (panel A).
La longitud de la serpiente crece en 1 cada vez que se come una fruta. El juego termina (la serpiente “muere”) tan pronto
como la serpiente se sale de los límites (panel B) o choca contra su propio cuerpo (panel C). Tenga en cuenta que en el panel B,
la cabeza de la serpiente alcanza la posición del borde y luego se produce un movimiento hacia arriba (una acción de ir en
línea recta) que hace que el juego termine. El simple hecho de alcanzar los cuadrados del borde con la cabeza de la serpiente
no resultará en la terminación. Comer cada fruta conduce a una gran recompensa positiva. Mover un cuadrado sin comer una
fruta genera una recompensa negativa de menor magnitud. La finalización del juego (la muerte de la serpiente) también
genera una recompensa negativa.

Un desafío clave en el juego de la serpiente es el crecimiento de la serpiente. Si no fuera por esta regla, el
juego sería mucho más sencillo. Simplemente navegue la serpiente hacia la fruta una y otra vez, y no hay
límite para la recompensa que puede obtener el agente. Sin embargo, con la regla de crecimiento de
longitud, el agente debe aprender a evitar chocar con su propio cuerpo, que se vuelve más difícil a
medida que la serpiente come más fruta y crece. Este es el aspecto no estático del problema de la VR de la
serpiente del que carece el entorno del poste de la carreta, como mencionamos al final de la última
sección.
La tabla 11.2 describe el problema de la serpiente en la formulación canónica de la RL. En comparación
con la formulación del problema del poste del carro (tabla 11.1), la mayor diferencia está en la estructura
de recompensas. En el problema de la serpiente, las recompensas positivas (+10 por cada fruta
consumida) se dan con poca frecuencia, es decir, solo después de una serie de recompensas negativas
debido al movimiento que necesita la serpiente para alcanzar la fruta. Dado el tamaño del tablero, dos
recompensas positivas pueden espaciarse hasta 17 pasos, incluso si la serpiente se mueve de la manera
más eficiente. La pequeña recompensa negativa es una penalización que anima a la serpiente a moverse
por un camino más directo. Sin esta penalización, la serpiente puede moverse de forma serpenteante e
indirecta y aun así recibir las mismas recompensas, lo que hará que el proceso de juego y entrenamiento
sea innecesariamente largo. Esta estructura de recompensas dispersa y compleja también es la razón
principal por la cual el gradiente de políticas y el método REINFORCE no funcionarán bien en este
problema. El método del gradiente de políticas funciona mejor cuando las recompensas son frecuentes y
simples, como en el problema del poste del carro.
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 391

Tabla 11.2 Descripción del problema del juego de la serpiente en la formulación canónica de la RL

Concepto abstracto de RL Realización en el problema de la serpiente.

Ambiente Un mundo cuadriculado que contiene una serpiente en movimiento y una fruta que se autoabastece.

Acción (Discreto) Elección ternaria: seguir recto, girar a la izquierda o girar a la derecha.

Recompensa (Recompensas positivas negativas frecuentes y mixtas)


• Comer fruta: gran recompensa positiva (+10)
• Moverse sin comer fruta: pequeña recompensa negativa (–0,2)
• Morir: gran recompensa negativa (–10)

Observación (Estado completo, discreto) En cada paso, el agente puede acceder al estado
completo del juego, es decir, a lo que hay en cada casilla del tablero.

TÉLjAVASCRIPTAAPIDE SERPIENTE
Nuestra implementación JavaScript de serpiente se puede encontrar en el archivo snake-
dqn/snake_ game.js. Describiremos sólo la API de laSerpienteJuegoclass y te ahorramos
los detalles de implementación, que puedes estudiar a tu gusto si te interesan. El
constructor de laSerpienteJuegoclase tiene la siguiente sintaxis:
juego const = new SnakeGame({alto, ancho, numFruits, initLen});

Aquí, los parámetros de tamaño del tablero,alturayancho,tienen valores predeterminados de 9.


numeroFrutases el número de frutas presentes en el tablero en un momento dado; tiene un valor
predeterminado de 1.InitLen,la longitud inicial de la serpiente, por defecto es 2.
lospaso()método expuesto por eljuegoEl objeto permite a la persona que llama jugar un paso en
el juego:

const {estado, recompensa, hecho, fruitEaten} = juego.paso(acción);

El argumento a lapaso()El método representa la acción: 0 para ir recto, 1 para girar a la


izquierda y 2 para girar a la derecha. El regreso de lapaso()valor tiene los siguientes campos:

-estado-El nuevo estado del tablero inmediatamente después de la acción, representado como un
objeto JavaScript simple con dos campos:
– s-Los cuadrados ocupados por la serpiente, como una matriz de [x, y]coordenadas Los
elementos de este arreglo están ordenados de manera que el primer elemento
corresponde a la cabeza y el último elemento a la cola.
– F-Los [x, y]coordenadas del cuadrado(s) ocupado(s) por la(s) fruta(s).
Tenga en cuenta que esta representación del estado del juego está diseñada para ser
eficiente, lo cual es necesario debido al almacenamiento del algoritmo Q-learning de una
gran cantidad (por ejemplo, decenas de miles) de tales objetos de estado, como veremos
pronto. Una alternativa es utilizar una matriz o matriz anidada para registrar el estado de
cada casilla del tablero, incluidas las vacías. Esto sería mucho menos eficiente en espacio.
-recompensa-La recompensa que se le da a la serpiente en el paso, inmediatamente después de que

se lleva a cabo la acción. Este es un solo número.


392 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

- hecho-Una bandera booleana que indica si el juego termina inmediatamente después de que se
lleva a cabo la acción.
- frutacomida—Una bandera booleana que indica si la serpiente se comió una fruta en el paso
como resultado de la acción. Tenga en cuenta que este campo es parcialmente redundante
con elrecompensacampo porque podemos inferir derecompensasi se comió una fruta. Se
incluye por simplicidad y para desvincular los valores exactos de las recompensas (que
pueden ser hiperparámetros ajustables) del evento binario de fruta consumida versus fruta
no consumida.

Como veremos más adelante, los tres primeros campos (estado, recompensa,yhecho)jugará un papel importante
en el algoritmo Q-learning, mientras que el último campo (fruta comido)es principalmente para el seguimiento.

11.3.2 Proceso de decisión de Markov y valores Q


Para explicar el algoritmo de Q-learning profundo que aplicaremos al problema de la serpiente, primero
es necesario ser un poco abstracto. En particular, presentaremos elProceso de decisión de Markov(MDP) y
sus matemáticas subyacentes a un nivel básico. No se preocupe: usaremos ejemplos simples y concretos
y vincularemos los conceptos con el problema de la serpiente que tenemos entre manos.
Desde el punto de vista de MDP, la historia de un entorno RL es una secuencia de
transiciones a través de un conjunto finito de estados discretos. Además, las transiciones
entre los estados siguen un tipo particular de regla:

El estado del entorno en el siguiente paso está completamente determinado por el estado y la
acción realizada por el agente en el paso actual.

La clave es que el siguiente estado depende desolamentedos cosas: el estado actual y la


acción tomada, y nada más. En otras palabras, MDP asume que su historial (cómo llegó a su
estado actual) es irrelevante para decidir qué debe hacer a continuación. Es una poderosa
simplificación que hace que el problema sea más manejable. que es unproceso de decisión
sin Markov? Ese sería un caso en el que el siguiente estado depende no solo del estado actual
y la acción actual, sino también de los estados o acciones en los pasos anteriores, y
posiblemente se remonte al comienzo del episodio. En el escenario sin Markov, las
matemáticas serían mucho más complejas y se requeriría una cantidad mucho mayor de
recursos computacionales para resolver las matemáticas.
El requisito de MDP tiene sentido intuitivo para muchos problemas de RL. Un juego de ajedrez es un
buen ejemplo de esto. En cualquier paso del juego, la configuración del tablero (más el turno del jugador)
caracteriza completamente el estado del juego y brinda toda la información que el jugador necesita para
calcular el siguiente movimiento. En otras palabras, es posible reanudar un juego de ajedrez desde la
configuración del tablero sin conocer los movimientos anteriores. (Dicho sea de paso, esta es la razón por
la cual los periódicos pueden publicar acertijos de ajedrez de una manera muy eficiente en el espacio). Los
videojuegos como Snake también son consistentes con la formulación MDP. Las posiciones de la
serpiente y la fruta en el tablero caracterizan completamente el estado del juego y son todo lo que se
necesita para reanudar el juego desde ese punto o para que el agente decida la siguiente acción.
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 393

Aunque problemas como el ajedrez y la serpiente son totalmente compatibles con MDP,
cada uno implica una cantidad astronómica de estados posibles. Para presentar MDP de una
manera intuitiva y visual, necesitamos un ejemplo más simple. En la figura 11.10, mostramos
un problema MDP muy simple en el que solo hay siete estados posibles y dos acciones
posibles del agente. La transición entre los estados se rige por las siguientes reglas:
- El estado inicial es siempres1.
- del estado s1, si el agente actúaa1, el entorno entrará en estados2. Si el
agente actúaa2, el entorno entrará en estados3.
- De cada uno de los estadoss2ys3, la transición al siguiente estado sigue un conjunto similar
de reglas de bifurcación.
- estadoss4,s5,s6, ys7son estados terminales: si se alcanza alguno de los estados, el episodio
finaliza.

Entonces, cada episodio en este problema RL dura exactamente tres pasos. ¿Cómo debería el agente en
este problema de RL decidir qué acción tomar en el primer y segundo paso? Dado que estamos tratando
con un problema de RL, la pregunta tiene sentido solo si se consideran las recompensas. En MDP, cada
acción no solo provoca una transición de estado, sino que también genera una recompensa. En la figura
11.10, las recompensas se representan como flechas que conectan acciones con los siguientes estados,
etiquetados conr = <valor_recompensa>.El objetivo del agente es, por supuesto, maximizar la recompensa
total (descontada por un factor). Ahora imagina que somos el agente en el primer paso. Examinemos el
proceso de pensamiento a través del cual decidiremos cuál dea1oa2es la mejor opción. Supongamos que
el factor de descuento de la recompensa (-) tiene un valor de 0,9.
El proceso de pensamiento es así. Si elegimos la accióna1, obtendremos una recompensa
inmediata de -3 y pasaremos al estados2. Si elegimos la accióna2, obtendremos una recompensa
inmediata de 3 y pasaremos al estados3. Eso significaa2es una mejor elección porque 3 es mayor
que –3? La respuesta es no, porque 3 y -3 son solo las recompensas inmediatas y no hemos tenido
en cuenta las recompensas de los siguientes pasos. Deberíamos mirar elmejor posibleresultado de
cada uno des2ys3. ¿Cuál es el mejor resultado de s2? Es el resultado engendrado por la acción.a2,
que da una recompensa de 11. Eso lleva a la mejor recompensa con descuento que podemos
esperar si tomamos la accióna1del estados1:

Mejor recompensa del estados1tomando accióna1 = recompensa inmediata + recompensa futura descontada
= –3 + - * 10
= –3 + 0,9 * 10
=6

Del mismo modo, el mejor resultado del estados3es si tomamos medidasa1, lo que nos da una recompensa de -4.
Por lo tanto, si tomamos medidasa2del estados1, la mejor recompensa con descuento para nosotros es

Mejor recompensa del estados1tomando accióna2 = recompensa inmediata + recompensa futura con
descuento = 3 + - * –4
= 3 + 0,9 * –4
= 0,6
394 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

r = –5 s4

a1

r = –3 s2

a1
a2
r = 10
s5

s1

r = –4 s6

a2 a3
r=3
s3

a4
r = –9
s7

Figura 11.10 Un ejemplo concreto muy simple del proceso de decisión de Markov (MDP). Los
estados se representan como círculos grises etiquetados consnorte, mientras que las acciones se
representan como círculos grises etiquetados conametro. La recompensa asociada con cada
transición de estado provocada por una acción se etiqueta conr=X.

Las recompensas con descuento que calculamos aquí son ejemplos de lo que llamamos
valores Q. Un valor Q es la recompensa acumulada total esperada (con descuento) por una
acción en un estado dado. A partir de estos valores Q, está claro quea1es la mejor opción en
el estados1—una conclusión diferente a la que llegaríamos si consideráramos sólo la
recompensa inmediata provocada por la primera acción. El ejercicio 3 al final del capítulo lo
guía a través del cálculo del valor Q para escenarios más realistas de MDP que involucran
estocasticidad.
El proceso de pensamiento de ejemplo descrito puede parecer trivial. Pero nos lleva a una
abstracción que juega un papel central en Q-learning. Un valor Q, denotadoq(s,a), es una
función del estado actual (s) y la acción (a). En otras palabras,q(s,a) es una función que mapea
un par estado-acción al valor estimado de tomar la acción particular en el estado particular.
Este valor es previsor, en el sentido de que representa las mejores recompensas futuras, bajo
el supuesto de acciones óptimas en todos los pasos futuros.
Gracias a su visión de futuro,q(s,a) es todo lo que necesitamos para decidir sobre la mejor
acción en cualquier estado dado. En particular, dado que sabemos lo queq(s,a) es, la mejor acción
es la que nos da el valor Q más alto entre todas las acciones posibles:
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 395

losa1que nos da el valor máximo entre q-sI-s1--


q-sI-a2--...-q-sI-anorte- (Ecuación 11.2)

dondenortees el número de todas las acciones posibles. Si tenemos una buena estimación deq(s,a),
podemos simplemente seguir este proceso de decisión en cada paso, y tendremos la garantía de obtener
la recompensa acumulativa más alta posible. Por lo tanto, el problema de RL de encontrar el mejor
proceso de toma de decisiones se reduce a aprender la funciónq(s,a). Es por eso que este algoritmo de
aprendizaje se llama Q-learning.
Detengámonos por un momento y veamos cómo Q-learning difiere del método de gradiente de
políticas que vimos en el problema del poste del carro. El gradiente de políticas consiste en predecir la
mejor acción; Q-learning consiste en predecir los valores de todas las acciones posibles (valores Q).
Mientras que el gradiente de política nos dice qué acción elegir directamente, Q-learning requiere un
paso adicional de "elegir el máximo" y, por lo tanto, es un poco más indirecto. El beneficio que brinda esta
indirección es que facilita la formación de una conexión entre las recompensas y los valores de los pasos
sucesivos, lo que facilita el aprendizaje en problemas que implican escasas recompensas positivas como
la serpiente.
¿Cuáles son las conexiones entre las recompensas y los valores de los pasos sucesivos? Ya
vislumbramos esto al resolver el problema MDP simple de la figura 11.10. Esta conexión se
puede escribir matemáticamente como

q-sI-a- =r+ - - -El valor máximo entre q-spróximo-a


1--q-spróximo-a2--...-q-spróximo-anorte--
(Ecuación 11.3)

dondespróximoes el estado al que llegaremos después de elegir la acciónadel estadosI. Esta


ecuación, conocida comoEcuación de Bellman,7es una abstracción de cómo obtuvimos los números
6 y -0.6 para las accionesa1ya2en el sencillo ejemplo anterior. En palabras simples, la ecuación dice

El valor Q de tomar la acción a en el estado sIes una suma de dos términos:

1. La recompensa inmediata debida a a, y


2. El mejor valor Q posible de ese siguiente estado multiplicado por un factor de descuento ("mejor" en
el sentido de elección óptima de acción en el siguiente estado)

La ecuación de Bellman es lo que hace posible el Q-learning y, por lo tanto, es importante comprenderla.
El programador que hay en usted notará de inmediato que la ecuación de Bellman (ecuación 11.3) es
recursiva: todos los valores Q en el lado derecho de la ecuación se pueden expandir aún más utilizando la
ecuación misma. El ejemplo de la figura 11.10 en el que trabajamos termina después de dos pasos,
mientras que los problemas reales de MDP generalmente involucran una cantidad mucho mayor de
pasos y estados, que potencialmente incluso contienen ciclos en el gráfico de estado-acción-transición.
Pero la belleza y el poder de la ecuación de Bellman es

7
Atribuido al matemático aplicado estadounidense Richard E. Bellman (1920–1984). ver su libroProgramación dinámica,
Prensa de la Universidad de Princeton, 1957.
396 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

que nos permite convertir el problema de Q-learning en un problema de aprendizaje supervisado, incluso
para grandes espacios de estado. Explicaremos por qué ese es el caso en la siguiente sección.

11.3.3 Red Q profunda


Elaboración manual de la función.q(s,a) puede ser difícil, por lo que dejaremos que la función sea
una red neuronal profunda (la DQN mencionada anteriormente en la sección) y entrenaremos sus
parámetros. Este DQN recibe un tensor de entrada que representa el estado completo del entorno,
es decir, la configuración del tablero de serpientes, que está disponible para el agente como
observación. Como muestra la figura 11.11, el tensor tiene una forma [9, 9, 2] (excluyendo la
dimensión del lote). Las dos primeras dimensiones corresponden a la altura y anchura del tablero
de juego. Por lo tanto, el tensor se puede ver como una representación de mapa de bits de todos
los cuadrados en el tablero. La última dimensión (2) son dos canales que representan la serpiente y
la fruta, respectivamente. En particular, la serpiente se codifica en el primer canal, con la cabeza
etiquetada como 2 y el cuerpo como 1. La fruta se codifica en el segundo canal, con un valor 1. En
ambos canales, los cuadrados vacíos se representan con 0. Tenga en cuenta que estos valores de
píxeles y el número de canales son más o menos arbitrarios. Es probable que también funcionen
otros arreglos de valores (como 100 para la cabeza de serpiente y 50 para el cuerpo de serpiente, o
separar la cabeza y el cuerpo de la serpiente en dos canales), siempre que mantengan los tres tipos
de entidades (cabeza de serpiente, cuerpo de serpiente ,

Tenga en cuenta que esta representación de tensor del estado del juego es mucho menos
eficiente en espacio que la representación JSON que consiste en los campossyFque describimos en
el apartado anterior, porque siempre incluye todas las casillas del tablero independientemente de
lo larga que sea la serpiente. Esta representación ineficiente se usa solo cuando usamos

Rebanada 1 de 2: Serpiente
Θ

Observación
2
(estado del juego)
1 1
Θ
1 1
Θ 1
Recompensa=60,6; Frutas=9
Θ 1
Θ 1
Convertir a Θ
tensor Θ
Θ
representación Θ
Θ

Rebanada 2 de 2: Fruta
Θ

1 Θ

Θ
Θ
Θ
Θ
Θ
Θ
Θ
Θ
Θ

Figura 11.11 Cómo se representa el estado del tablero del juego de la serpiente como un tensor de forma 3D[9, 9, 2]
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 397

retropropagación para actualizar los pesos de DQN. Además, sólo un pequeño número (tamaño del lote)de los
estados del juego están presentes de esta manera en un momento dado, debido al paradigma de entrenamiento
basado en lotes que pronto veremos.
El código que convierte una representación eficiente del estado del tablero en el tipo de
tensores ilustrados en la figura 11.11 se puede encontrar en elgetStateTensor()función en snake-
dqn/snake_game.js. Esta función se usará mucho durante el entrenamiento de DQN, pero
omitimos sus detalles aquí porque solo asigna valores mecánicamente a los elementos de un búfer
de tensor en función de dónde se encuentran la serpiente y la fruta.

Observación
(estado del juego)

Convertir a DQN en línea


tensor
representación

Abandonar
conv2d

conv2d

conv2d

aplanar

denso

denso
estadoTensor

BN

BN
[1, 9, 9, 2]

Selección de acción

qs argMax()

[1, 3]

Figura 11.12 Una ilustración esquemática de la DQN que usamos como una aproximación a la funciónq(s,a) para el
problema de la serpiente. En el cuadro "Online DQN", "BN" significa BatchNormalization.

Es posible que haya notado que este [alto, ancho, canal]El formato de entrada es exactamente lo
que convnets están diseñados para procesar. El DQN que usamos es de la conocida arquitectura
convnet. El código que define la topología del DQN se puede encontrar en el listado 11.5 (extraído
de snake-dqn/dqn.js, con parte del código de verificación de errores eliminado para mayor
claridad). Como muestran el código y el diagrama de la figura 11.12, la red consta de una pila de
capas conv2d seguidas de un MLP. Se insertan capas adicionales que incluyen normalización por
lotes y abandono para aumentar el poder de generalización del DQN. La salida del DQN tiene una
forma de [3] (excluyendo la dimensión del lote). Los tres elementos de la salida son los valores Q
predichos de las acciones correspondientes (girar a la izquierda, seguir recto y girar a la derecha).
Así nuestro modelo deq(s,a) es una red neuronal que toma un estado como entrada y genera los
valores Q para todas las acciones posibles dado ese estado.

Listado 11.5 Creando el DQN para el problema de la serpiente

función de exportación createDeepQNetwork(h, w, numActions) {


const modelo = tf.secuencial();
modelo.add(tf.layers.conv2d({
El DQN tiene una arquitectura convnet típica:
filtros: 128,
comienza con una pila de capas conv2d.
tamaño del núcleo: 3,
398 CPASADO11 Conceptos básicos del aprendizaje por refuerzo profundo

zancadas: 1,
activación: 'relu', La forma de entrada coincide con la representación tensorial de la

forma de entrada: [h, w, observación del agente, como se muestra en la figura11.11.

2] }));
modelo.add(tf.layers.batchNormalization());
Se agregan capas de normalización por
modelo.add(tf.layers.conv2d({
lotes para contrarrestar el sobreajuste y
filtros: 256, mejorar la generalización.
tamaño del núcleo: 3,
pasos: 1,
activación: 'relú'
}));
modelo.add(tf.layers.batchNormalization());
modelo.add(tf.layers.conv2d({
filtros: 256,
tamaño del núcleo: 3,
pasos: 1,
activación: 'relu' La porción MLP del DQN
})); comienza con una capa plana.
modelo.add(tf.layers.flatten()); model.add(tf.layers.dense({unidades: 100,
activación: 'relu'})); modelo.add(tf.layers.dropout({rate: 0.25}));
Al igual que la normalización por lotes, la
model.add(tf.layers.dense({units: numActions})); modelo de
capa de exclusión se agrega para
retorno; contrarrestar el sobreajuste.
}

Hagamos una pausa por un momento y pensemos por qué tiene sentido usar una red neuronal
como la funciónq(s,a) en este problema. El juego de la serpiente tiene un espacio de estado
discreto, a diferencia del espacio de estado continuo en el problema del poste del carro, que
constaba de cuatro números de coma flotante. Por lo tanto, losq(s,a) podría, en principio,
implementarse como una tabla de búsqueda, es decir, una que mapee cada combinación posible
de configuración y acción del tablero en un valor deq. Entonces, ¿por qué preferimos un DQN a una
tabla de búsqueda de este tipo? La razón: hay demasiadas configuraciones de tablero posibles
incluso con el tamaño de tablero relativamente pequeño (9 × 9),8lo que conduce a dos deficiencias
principales del enfoque de la tabla de búsqueda. Primero, la RAM del sistema no puede contener
una tabla de búsqueda tan grande. En segundo lugar, incluso si logramos construir un sistema con
suficiente RAM, el agente tardará un tiempo prohibitivo en visitar todos los estados durante RL. El
DQN aborda el primer problema (espacio de memoria) gracias a su tamaño moderado (alrededor
de 1 millón de parámetros). Aborda el segundo problema (estado-tiempo de visita) debido a

8
Un cálculo al dorso del sobre conduce a la estimación aproximada de que el número de posibles configuraciones de placa es del
orden de al menos 1015, incluso si limitamos la longitud de la serpiente a 20. Por ejemplo, considere la longitud particular de la
serpiente de 20. Primero, elija una ubicación para la cabeza de la serpiente, para la cual hay 9 * 9 = 81 posibilidades. Luego hay
cuatro ubicaciones posibles para el primer segmento del cuerpo, seguidas de tres ubicaciones posibles para el segundo segmento,
y así sucesivamente. Por supuesto, en algunas configuraciones de pose corporal, habrá menos de tres posibilidades, pero eso no
debería alterar significativamente el orden de magnitud. Por lo tanto, podemos estimar que el número de configuraciones
corporales posibles de una serpiente de longitud 20 es aproximadamente 81 * 4 * 318-1012. Teniendo en cuenta que hay 61 posibles
ubicaciones de frutas para cada configuración corporal, la estimación de posibles configuraciones conjuntas de frutas y serpientes
asciende a 1014. Se pueden aplicar estimaciones similares a longitudes de serpientes más cortas, de 2 a 19. La suma de todos los
números estimados de las longitudes de 2 a 20 nos da el orden de magnitud de 1015. Los videojuegos como los juegos de Atari
2600 involucran una cantidad mucho mayor de píxeles en comparación con la cantidad de cuadrados en nuestro tablero de
serpientes y, por lo tanto, son aún menos susceptibles al enfoque de la tabla de búsqueda. Esta es una de las razones por las que
los DQN son una técnica adecuada para resolver dichos videojuegos usando RL, como se demuestra en el artículo histórico de 2015
de Volodymyr Mnih y colegas de DeepMind.
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 399

Poder de generalización de las redes neuronales. Como hemos visto amplia evidencia en los
capítulos anteriores, una red neuronal no necesita ver todas las entradas posibles; aprende a
interpolar entre ejemplos de entrenamiento a través de la generalización. Por lo tanto, al usar
DQN, matamos dos pájaros de un tiro.

11.3.4 Entrenamiento de la red Q profunda

Ahora tenemos un DQN que estima los valores Q de las tres acciones posibles en cada paso del
juego de la serpiente. Para lograr la mayor recompensa acumulativa posible, todo lo que tenemos
que hacer es ejecutar el DQN utilizando la observación en cada paso y elegir la acción con el valor Q
más alto. ¿Ya terminamos? ¡No, porque el DQN aún no está entrenado! Sin la capacitación
adecuada, el DQN contendrá solo pesos inicializados aleatoriamente, y las acciones que nos
proporcione no serán mejores que conjeturas aleatorias. Ahora, el problema del RL de la serpiente
se ha reducido a la cuestión de cómo entrenar el DQN, un tema que trataremos en esta sección. El
proceso es algo complicado. Pero no te preocupes: usaremos muchos diagramas, acompañados de
extractos de código, para detallar el algoritmo de entrenamiento paso a paso.

INTUICIÓN DETRÁS DE LAS PROFUNDIDADESq-LA RED'ENTRENAMIENTO DE ESFUERZO

Entrenaremos nuestro DQN presionándolo para que coincida con la ecuación de Bellman. Si todo va bien,
esto significa que nuestro DQN reflejará tanto las recompensas inmediatas como las recompensas
futuras óptimas con descuento.
¿Cómo podemos hacer eso? Lo que necesitaremos son muchas muestras de pares de entrada-
salida, siendo la entrada el estado y la acción realmente realizada y la salida siendo el valor
"correcto" (objetivo) de Q. Calcular muestras de entrada requiere el estado actualsIy la acción que
tomamos en ese estado,aj, ambos disponibles directamente en el historial del juego. Calcular el
valor objetivo de Q requiere la recompensa inmediatarIy el siguiente estado sI+1, que también están
disponibles en el historial del juego. Nosotros podemos usarrIysI+1para calcular el valor Q objetivo
aplicando la ecuación de Bellman, cuyos detalles se cubrirán en breve. Luego calcularemos la
diferencia entre el valor Q predicho por el DQN y el valor Q objetivo de la ecuación de Bellman y lo
llamaremos nuestra pérdida. Reduciremos la pérdida (en un sentido de mínimos cuadrados)
utilizando la propagación hacia atrás estándar y el descenso de gradiente. La maquinaria que hace
esto posible y eficiente es algo complicada, pero la intuición es bastante sencilla. Queremos una
estimación de la función Q para poder tomar buenas decisiones. Sabemos que nuestra estimación
de Q debe coincidir con las recompensas ambientales y la ecuación de Bellman, por lo que
usaremos el gradiente descendente para que así sea. ¡Sencillo!

RMEMORIA EPLAY: ACONJUNTO DE DATOS RODANTE PARA ELDQN'ENTRENAMIENTO DE ESFUERZO

Nuestro DQN es un convnet familiar implementado como una instancia detf.LayersModelen TensorFlow.js.
Con respecto a cómo entrenarlo, lo primero que se me ocurre es llamar a suencajar()oajusteDataset()
método. Sin embargo, no podemos usar ese enfoque habitual aquí porque no tenemos un conjunto de
datos etiquetado que contenga los estados observados y los valores Q correspondientes. Considere esto:
antes de que se entrene el DQN, no hay forma de conocer los valores Q. Si tuviéramos un método que nos
diera los verdaderos valores Q, simplemente lo usaríamos en nuestro proceso de decisión de Markov y
terminaríamos con él. Entonces, si nos limitamos
400 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

al enfoque tradicional de aprendizaje supervisado, nos enfrentaremos al problema del huevo y la gallina:
sin un DQN capacitado, no podemos estimar los valores Q; sin una buena estimación de los valores Q, no
podemos entrenar el DQN. El algoritmo RL que estamos a punto de presentar nos ayudará a resolver este
problema del huevo y la gallina.
Específicamente, nuestro método es dejar que el agente juegue aleatoriamente (al menos
inicialmente) y recuerde lo que sucedió en cada paso del juego. La parte de reproducción aleatoria se
logra fácilmente usando un generador de números aleatorios. La parte de recordar se logra con una
estructura de datos conocida comomemoria de reproducción. La figura 11.13 ilustra cómo funciona la
memoria de repetición. Almacena cinco elementos para cada paso del juego:

1 sI, observación del estado actual en el pasoI(la configuración de la placa).


2 aI, acción realmente realizada en el paso actual (seleccionado por el DQN como se muestra
en la figura 11.12 o mediante una selección aleatoria).
3 rI, la recompensa inmediata recibida en este paso.
4 DI, una bandera booleana que indica si el juego termina inmediatamente después del paso actual.
A partir de esto, puedes ver el hecho de que la memoria de repetición no es solo para un solo
episodio del juego. En su lugar, concatena los resultados de varios episodios del juego. Una vez
que finaliza un juego anterior, el algoritmo de entrenamiento simplemente inicia uno nuevo y
sigue agregando los nuevos registros a la memoria de reproducción.
5 sI+1, la observación del siguiente paso siDIEs falso. (SiDIes verdadero, se almacena un valor nulo como
marcador de posición).

memoria de repetición(tamaño =METRO)

Actual Seleccionado Recibió Hecho Próximo

observación acción recompensa (juego terminado) bandera observación

sI aI rI DI sI+1
Registro
en
cada paso sI+1 aI+1 rI+1 DI+1 sI+2

sI+2 aI+2 rI+2 DI+2 sI+3


...

...

...

...
po
m
tie

sI+METRO aI+METRO rI+METRO DI+METRO sI+METRO+1 Hora


e
sd

pasos
so
Pa

Figura 11.13 La memoria de reproducción utilizada durante el entrenamiento del DQN. Cinco piezas de datos se envían al final de la
memoria de reproducción en cada paso. Estos datos se muestrean durante el entrenamiento de DQN.

Estos datos se incluirán en el entrenamiento basado en la retropropagación del DQN. La memoria de


reproducción se puede considerar como un "conjunto de datos" para el entrenamiento de DQN. Sin
embargo, es diferente del tipo de conjuntos de datos en el aprendizaje supervisado, en el sentido de que
sigue actualizándose a medida que avanza el entrenamiento. La memoria de reproducción tiene una
longitud fija. METRO(METRO=10.000 por defecto en el código de ejemplo). Cuando un registro (sI,aI,rI,DI,sI
+1) se empuja hasta el final después de un nuevo paso del juego, se extrae un registro anterior desde el
principio, que mantiene una longitud fija de memoria de reproducción. Esto asegura que la reproducción
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 401

la memoria mantiene un registro de lo que sucedió en el más recienteMETROpasos del


entrenamiento, además de evitar problemas de falta de memoria. Es beneficioso entrenar siempre
el DQN utilizando los últimos registros del juego. ¿Por qué? Considere lo siguiente: una vez que el
DQN haya sido entrenado por un tiempo y comience a "dominar" el juego, no querremos enseñarlo
utilizando registros de juegos antiguos como los del comienzo del entrenamiento porque estos
pueden contener Movimientos ingenuos que ya no son relevantes o propicios para una mayor
formación de la red.
El código que implementa la memoria de reproducción es muy simple y se puede
encontrar en el archivo snake-dqn/replay_memory.js. No describiremos los detalles del
código, excepto sus dos métodos públicos,adjuntar()ymuestra():
-adjuntar()permite a la persona que llama enviar un nuevo registro al final de la memoria de reproducción.

-muestra (tamaño del lote)seleccionatamaño del loteregistros de la memoria de reproducción al


azar. Los registros se muestrean de manera completamente uniforme y, en general,
incluirán registros de múltiples episodios diferentes. losmuestra()El método se utilizará para
extraer lotes de entrenamiento durante el cálculo de la función de pérdida y la posterior
propagación hacia atrás, como veremos en breve.

TEL EPSILON-ALGORITMO CODICIOSO: BALIMENTANDO LA EXPLORACIÓN Y LA EXPLOTACIÓN


Un agente que sigue probando cosas al azar se topará con algunos buenos movimientos (comer una fruta
o dos en un juego de serpientes) por pura suerte. Esto es útil para impulsar el proceso de aprendizaje
temprano del agente. De hecho, es la única manera porque al agente nunca se le dicen las reglas del
juego. Pero si el agente continúa comportándose al azar, no llegará muy lejos en el proceso de
aprendizaje, porque las elecciones aleatorias conducen a muertes accidentales y porque algunos estados
avanzados solo se pueden lograr a través de rachas de buenos movimientos.
Esta es la manifestación del dilema de exploración versus explotación en el juego de la
serpiente. Hemos visto este dilema en el ejemplo del poste del carro, donde el método del
gradiente de políticas aborda el problema gracias al aumento gradual en el determinismo del
muestreo multinomial con entrenamiento. En el juego de la serpiente, no tenemos este lujo
porque nuestra selección de acciones no se basa entf.multinomial()sino al seleccionar el valor
Q máximo entre las acciones. La forma en que abordamos el dilema es parametrizando la
aleatoriedad del proceso de selección de acciones y reduciendo gradualmente el parámetro
de aleatoriedad. En particular, utilizamos los llamados política epsilon-codiciosa. Esta política
se puede expresar en pseudocódigo como

x = Muestra uniformemente un número aleatorio entre 0 y 1. si x <


épsilon:
Elija una acción al azar más:

qValores = DQN.predict(observación)
Elija la acción que corresponda al elemento máximo de qValues

Esta lógica se aplica en cada paso del entrenamiento. Cuanto mayor sea el valor de épsilon (cuanto
más cerca esté de 1), más probable es que la acción se elija al azar. Por el contrario, un valor más
pequeño de épsilon (más cercano a 0) conduce a una mayor probabilidad de elegir la acción en
función de los valores Q predichos por el DQN. Elegir acciones al azar puede
402 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

ser visto como explorar el entorno ("épsilon" significa "exploración"), mientras que
elegir acciones para maximizar el valor Q se conoce comoavaro. Ahora entiendes dónde
está el nombre.épsilon-codiciosoviene de.
Como se muestra en el listado 11.6, el código real de TensorFlow.js que implementa el
algoritmo epsilongreedy en el ejemplo de snake-dqn tiene una estrecha correspondencia
biunívoca con el pseudocódigo anterior. Este código se extrajo de snake-dqn/agent.js.

Listado 11.6 La parte del código snake-dqn que implementa el algoritmo epsilon-greedy

dejar acción;
const estado = this.game.getState(); if
(Math.random() < this.epsilon) { Exploración: selecciones

acción = obtenerAcciónAleatoria(); acciones al azar


} demás {
tf.ordenado(() => {
const estadoTensor =
getStateTensor(estado, representa el juego
esta.altura.del.juego, estado como un tensor

este.juego.ancho);
acción = TODAS_ACCIONES[ Política codiciosa: obtiene los
esta.red.online.predict( valores Q predichos del DQN y
stateTensor).argMax(-1).dataSync()[0]]; encuentra el índice de la acción
}); que corresponde al valor Q
} más alto

La política de avaricia épsilon equilibra la necesidad temprana de exploración y la necesidad


posterior de un comportamiento estable. Lo hace reduciendo gradualmente el valor de épsilon
desde un valor relativamente grande hasta un valor cercano (pero no exactamente) cero. En
nuestro ejemplo de serpiente-dqn, epsilon se reduce de forma lineal de 0,5 a 0,01 durante los
primeros 1 × 105 pasos del entrenamiento. Tenga en cuenta que no reducimos el épsilon hasta
cero porque necesitamos un grado moderado de exploración, incluso en etapas avanzadas del
entrenamiento del agente, para ayudarlo a descubrir nuevos movimientos inteligentes. En los
problemas de RL basados en la política ávida de épsilon, los valores inicial y final de épsilon son
hiperparámetros ajustables, al igual que el curso temporal de la disminución gradual de épsilon.
Con el telón de fondo de nuestro algoritmo de Q-learning profundo establecido por la política ávida de
épsilon, a continuación, examinemos los detalles de cómo se entrena el DQN.

miEXTRACCIÓN PREDICCIÓNq-VALORES
Aunque estamos usando un nuevo enfoque para atacar el problema de RL, todavía queremos
moldear nuestro algoritmo en aprendizaje supervisado porque eso nos permitirá usar el enfoque
familiar de retropropagación para actualizar los pesos de DQN. Tal formulación requiere tres cosas:

- Valores Q predichos.
- Valores Q “verdaderos”. Tenga en cuenta que la palabra "verdadero" está entre comillas aquí porque
realmente no hay una manera de obtener las verdades fundamentales para los valores Q. Estos valores
son simplemente las mejores estimaciones deq(s,a) que podemos encontrar en una etapa determinada
del algoritmo de entrenamiento. Por esta razón, nos referiremos a ellos como los valores Q objetivo.
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 403

- Una función de pérdida que toma los valores Q previstos y objetivo y genera un número
que cuantifica la falta de coincidencia entre los dos.

En esta subsección, veremos cómo se pueden obtener los valores Q predichos de la memoria de
reproducción. Las siguientes dos subsecciones hablarán sobre cómo obtener los valores Q objetivo
y la función de pérdida. Una vez que tengamos los tres, nuestro problema de serpiente RL se
convertirá básicamente en un problema directo de retropropagación.
La figura 11.14 ilustra cómo se extraen los valores Q predichos de la memoria de
reproducción en un paso del entrenamiento del DQN. El diagrama debe verse junto con el
código de implementación en el listado 11.7 para facilitar la comprensión.

memoria de repetición

sI aI rI DI sI+1

sI+1 aI+1 rI+1 DI+1 sI+2

sI+2 aI+2 rI+2 DI+2 sI+3


...

...

...

...

...

sI+METRO aI+METRO rI METRO


+
DI+METRO sI+METRO+1

Seleccione

Muestra un lote denorteal azar acciones reales

acciónTensor 0
DQN en línea
2
[NORTE]
1 actionQs
Abandonar
conv2d

conv2d

conv2d

aplanar

denso

denso

...

...

...
estadoTensor qs [NORTE]
BN

BN

1
[N, 9, 9, 2] [N, 3]

Figura 11.14 Cómo se obtienen los valores Q predichos de la memoria de reproducción y el DQN en línea. Esta es la primera de las
dos partes que forman parte de la parte de aprendizaje supervisado del algoritmo de entrenamiento DQN. El resultado de este
flujo de trabajo,actionQs—es decir, los valores Q predichos por el DQN— es uno de los dos argumentos que entrarán en el
cálculo de la pérdida de MSE junto contargetQs. Consulte la figura 11.15 para ver el flujo de trabajo en el quetargetQs es
calculado.

En particular, muestreamostamaño de lote (N = 128por defecto) graba aleatoriamente desde la memoria de


reproducción. Como se describió antes, cada registro tiene cinco elementos. Con el fin de obtener los
valores Q predichos, solo necesitamos los dos primeros. Los primeros elementos, consistentes en elnorte
observaciones de estado, se convierten juntas en un tensor. Este tensor de observación por lotes es
procesado por el DQN en línea, que proporciona los valores Q predichos (qstanto en el diagrama como en
el código). Sin embargo,qsincluye los valores Q no solo para las acciones realmente seleccionadas sino
también para las no seleccionadas. Para nuestro entrenamiento, queremos ignorar los valores Q de las
acciones no seleccionadas porque no hay forma de conocer sus valores Q objetivo. Aquí es donde entra
en juego el segundo elemento de la memoria de reproducción.
Los segundos elementos contienen las acciones realmente seleccionadas. Están formateados en
una representación tensorial (acciónTensoren el diagrama y el código).acciónTensorluego se utiliza
para seleccionar los elementos deqsque queremos Este paso, ilustrado en el cuadro llamado
Seleccionar acciones reales en el diagrama, se logra mediante tres funciones de TensorFlow.js:
tf.oneHot(), mul(),ysuma() (ver la última línea en el listado 11.7). Esto es un poco más
404 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

complejo que cortar un tensor porque se pueden seleccionar diferentes acciones en


diferentes pasos del juego. El código del listado 11.7 se extrajo delSnakeGameAgent.trenOn-
ReplayBatch()método en snake-dqn/agent.js, con omisiones menores para mayor claridad.

Listado 11.7 Extrayendo un lote de valores Q predichos de la memoria de reproducción

Obtiene un lote de registros de juego de tamaño de


El primer elemento de todo registro de
lote elegidos al azar de la memoria de reproducción
juego es la observación del estado del
agente (ver figura11.13). Se convierte de
const lote = this.replayMemory.sample(batchSize); const stateTensor un objeto JSON en un tensor mediante la

= getStateTensor( función getStateTensor() (ver figura


11.11).
lote.mapa(ejemplo => ejemplo[0]),
este.juego.altura, este.juego.ancho); const
actionTensor = tf.tensor1d(
lote.map(ejemplo => ejemplo[1]), 'int32');
El segundo elemento del registro del juego es
la acción realmente seleccionada. También se
const qs = this.onlineNetwork.apply(
representa como un tensor.
stateTensor, {entrenamiento: verdadero})
. mul(tf.oneHot(actionTensor, NUM_ACTIONS)).sum(-1);

El método apply() es similar al método Usamos tf.oneHot(), mul() y sum() para


predict(), pero el indicador "entrenamiento: aislar los valores Q solo para las acciones
verdadero" se especifica explícitamente para realmente seleccionadas y descartar los de
habilitar la retropropagación. las acciones no seleccionadas.

Estas operaciones nos dan un tensor llamadoacciones,que tiene forma de [norte], nortesiendo el
tamaño del lote. Este es el valor Q predicho que buscamos, es decir, el valor predicho q(s,a) para el
estadosestábamos en y la acciónaen realidad tomamos. A continuación, examinaremos cómo se
obtienen los valores Q objetivo.

miOBJETIVO DE EXTRACCIÓNq-VALORES: tuCANTA ELBECUACIÓN DE ELLMAN


Es un poco más complicado obtener los valores Q objetivo que los predichos. Aquí es donde
se pondrá en práctica la ecuación teórica de Bellman. Recuerde que la ecuación de Bellman
describe el valor Q de un par estado-acción en términos de dos cosas: 1) la recompensa
inmediata y 2) el valor Q máximo disponible del estado del siguiente paso (descontado por un
factor). El primero es fácil de obtener. Está disponible directamente como el tercer elemento
de la memoria de reproducción. losrecompensaTensoren la figura 11.15 ilustra esto
esquemáticamente.
Para calcular este último (valor Q máximo del siguiente paso), necesitamos la observación de estado
del siguiente paso. Afortunadamente, la observación del siguiente paso se almacena en la memoria de
reproducción como el quinto elemento. Tomamos la observación del siguiente paso del lote muestreado
aleatoriamente, lo convertimos en un tensor y lo ejecutamos a través de una copia del DQN llamada el
objetivo DQN(ver figura 11.15). Esto nos da los valores Q estimados para los estados del siguiente paso.
Una vez que tengamos estos, realizamos unmáx()llamar a lo largo de la última dimensión (acciones), lo
que conduce a los valores Q máximos alcanzables desde el estado del siguiente paso (representado como
nextMax-QTensoren el listado 11.8). Siguiendo la ecuación de Bellman, este valor máximo es
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 405

DQN objetivo

memoria de repetición

Abandonar
conv2d

conv2d

conv2d

aplanar

denso

denso
nextStateTensor

BN

BN
próximas preguntas

sI aI rI DI sI+1
[N, 9, 9, 2] [N, 3]
sI+1 aI+1 rI+1 DI+1 sI+2

sI+2 aI+2 rI+2 DI+2 sI+3


Ecuación de Bellman
...

...

...

...

...
r+ max(siguienteQ), si !hecho
sI+METRO aI+METRO rI+METRO DI+METRO sI+METRO+1
Q= targetQs
r, si se hace
[NORTE]

Muestra un lote denorteal azar

hechoMáscara

[NORTE]

recompensaTensor

[NORTE]

Figura 11.15 Cómo los valores Q objetivo (targetQs) se obtienen de la memoria de reproducción y el DQN de destino. Esta figura
comparte las partes de memoria de reproducción y muestreo por lotes con la figura 11.14. Debe examinarse junto con el código
del listado 11.8. Esta es la segunda de las dos partes que forman parte de la parte de aprendizaje supervisado del algoritmo de
entrenamiento DQN.targetQsjuega un papel similar al de las etiquetas de verdad en los problemas de aprendizaje supervisado
vistos en los capítulos anteriores (por ejemplo, etiquetas verdaderas conocidas en los ejemplos del MNIST o valores de
temperatura futuros verdaderos conocidos en el ejemplo del clima de Jena). La ecuación de Bellman juega un papel fundamental
en el cálculo detargetQs. Junto con el DQN objetivo, la ecuación nos permite calcular los valores de targetQsmediante la
formación de una conexión entre los valores Q del paso actual y los valores Q del paso siguiente.

tipificado por el factor de descuento (-en la figura 11.15 ygamaen el listado 11.8) y combinado
con la recompensa inmediata, que produce los valores Q objetivo (targetQstanto en el
diagrama como en el código).
Tenga en cuenta que el valor Q del siguiente paso existe solo cuando el paso actual no es el
último paso de un episodio del juego (es decir, no causa la muerte de la serpiente). Si es así, el lado
derecho de la ecuación de Bellman incluirá solo el término de recompensa inmediata, como se
muestra en la figura 11.15. Esto corresponde a lahechoMáscaratensor en el listado 11.8. El código
en este listado se extrajo de laSnakeGameAgent.trainOnReplayBatch() método en snake-dqn/agent.js,
con omisiones menores para mayor claridad.

Listado 11.8 Extrayendo un lote de valores Q objetivo ("verdaderos") de la memoria de reproducción

const recompensaTensor = tf.tensor1d( El tercer elemento de un registro de reproducción contiene

lote.map(ejemplo => ejemplo[2])); const el valor de la recompensa inmediata.

nextStateTensor = getStateTensor(
lote.mapa(ejemplo => ejemplo[4]),
El quinto elemento de un registro
este.juego.altura, este.juego.ancho);
contiene la observación del siguiente
estado. Se transforma en una
representación tensorial.
406 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

const nextMaxQTensor =
this.targetNetwork.predict(nextStateTensor) . máx(-1);

const doneMask = tf.scalar(1).sub( El DQN de destino se usa en el tensor


tf.tensor1d(batch.map(ejemplo => ejemplo[3])) del siguiente estado, que produce los
. asType('float32')); valores Q para todas las acciones en el
const targetQs = siguiente paso.
recompensaTensor.add(siguienteMaxQTensor.mul(
doneMask).mul(gamma)); doneMask tiene el valor 0
Utiliza la función max() para extraer la
para los pasos que terminan
mayor recompensa posible en el
el juego y1para otros pasos.
siguiente paso. Esto está en el lado
Utiliza la ecuación de Bellman para
derecho de la ecuación de Bellman.
calcular los valores Q objetivo.

Como habrá notado, un truco importante en el algoritmo de Q-learning profundo aquí es el


uso de dos instancias de DQN. se llaman losen líneaDQN y elobjetivoDQN, respectivamente.
El DQN en línea es responsable de calcular los valores Q previstos (consulte la figura 11.14 en
la subsección anterior). También es el DQN que usamos para elegir la acción de la serpiente
cuando el algoritmo épsilon-codicioso decide el enfoque codicioso (sin exploración). Por eso
se llama la red “online”. Por el contrario, el DQN objetivo se usa solo para calcular los valores
Q objetivo, como acabamos de ver. Es por eso que se llama el DQN "objetivo". ¿Por qué
usamos dos DQN en lugar de uno? Para romper bucles de retroalimentación no deseados,
que pueden causar inestabilidades en el proceso de entrenamiento.
El DQN en línea y el DQN de destino son creados por el mismocrearDeepQNetwork() función
(listado 11.5). Son dos convnets profundos con topologías idénticas. Por lo tanto, tienen
exactamente el mismo conjunto de capas y pesos. Los valores de peso se copian del DQN en línea
al de destino periódicamente (cada 1000 pasos en la configuración predeterminada de snake-dqn).
Esto mantiene el DQN de destino actualizado con el DQN en línea. Sin esta sincronización, el DQN
de destino quedará desactualizado y obstaculizará el proceso de capacitación al producir
estimaciones deficientes de los mejores valores Q del próximo paso en la ecuación de Bellman.

LFUNCIÓN OSS PARAq-PREDICCIÓN DE VALOR Y PROPAGACIÓN HACIA ATRÁS


Con los valores Q predichos y objetivo a la mano, usamos el conocidoerror medio cuadradofunción
de pérdida para calcular la discrepancia entre los dos (figura 11.16). En este punto, hemos logrado
convertir nuestro proceso de capacitación de DQN en un problema de regresión, similar a los
ejemplos anteriores, como Boston-vivienda y Jena-weather. La señal de error delerror cuadrático
mediola pérdida impulsa la retropropagación; las actualizaciones de peso resultantes se utilizan
para actualizar el DQN en línea.
El diagrama esquemático de la figura 11.16 incluye partes que ya mostramos en las figuras
11.12 y 11.13. Une esas partes y agrega los nuevos cuadros y flechas para elerror medio cuadrado
pérdida y la retropropagación basada en ella (ver la parte inferior derecha del diagrama). Esto
completa la imagen completa del algoritmo de Q-learning profundo que usamos para entrenar a
nuestro agente del juego de serpientes.
El código del listado 11.9 tiene una estrecha correspondencia con el diagrama de la figura
11.16. Es elentrenarEnRepetirLote()metodo de laSerpienteJuegoAgenteclase de serpiente-
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 407

DQN objetivo

memoria de repetición

Abandonar
conv2d

conv2d

conv2d

aplanar

denso

denso
nextStateTensor

BN

BN
próximas preguntas

sI aI rI DI sI+1
[N, 9, 9, 2] [N, 3]
sI+1 aI+1 rI+1 DI+1 sI+2

sI+2 aI+2 rI+2 DI+2 sI+3


Ecuación de Bellman
...

...

...

...

...
r+ max(siguienteQ), si !hecho
sI+METRO aI+METRO rI+METRO DI+METRO sI+METRO+1
Q= targetQs
r, si se hace
[NORTE]

Muestra un lote denorteal azar error medio cuadrado

acciónTensor Seleccione
DQN en línea
acciones reales
[NORTE]
actionQs

Abandonar
conv2d

conv2d

conv2d

aplanar

denso

denso
qs
[NORTE]
estadoTensor
BN

BN

[N, 9, 9, 2] [N, 3]

retropropagación

Figura 11.16 Poner elactionQsytargetQsjuntos para calcular los DQN en línea error medio cuadradoerror de
predicción y, por lo tanto, utiliza la retropropagación para actualizar sus pesos. La mayoría de las partes de este
diagrama ya se han mostrado en las figuras 11.12 y 11.13. Las piezas recién añadidas son las error medio cuadrado
función de pérdida y el paso de retropropagación basado en ella, ubicado en la parte inferior derecha del diagrama.

dqn/agent.js, que juega un papel central en nuestro algoritmo RL. El método define una
función de pérdida que calcula laerror medio cuadradoentre los valores Q predichos y objetivo.
A continuación, calcula los gradientes de laerror medio cuadradocon respecto a los pesos de
los DQN en línea usando eltf.variableGrads()función (el apéndice B, sección B.4 contiene una
discusión detallada de las funciones de cálculo de gradientes de TensorFlow.js, como
tf.graduaciones variables()).Los gradientes calculados se utilizan para actualizar los pesos de
DQN con la ayuda de un optimizador. Esto empuja al DQN en línea en la dirección de hacer
estimaciones más precisas de los valores Q. Repetido durante millones de iteraciones, esto
conduce a un DQN que puede guiar a la serpiente a un rendimiento decente. Para la
siguiente lista, la parte del código responsable de calcular los valores Q objetivo (objetivo Qs)
ya se ha mostrado en el listado 11.8.

Listado 11.9 La función central que entrena el DQN

trainOnReplayBatch(tamaño de lote, gamma, optimizador) {


lote constante =
this.replayMemory.sample(batchSize); const
Obtiene un lote aleatorio de ejemplos
lossFunction = () => tf.tidy(() => {
del búfer de reproducción
const stateTensor = getStateTensor(
lote.mapa(ejemplo => ejemplo[0]), lossFunction devuelve un escalar y
esta.altura.del.juego, se usará para retropropagación.
este.juego.ancho);
408 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

const actionTensor = tf.tensor1d(


lote.map(ejemplo => ejemplo[1]), 'int32'); const qs =
this.onlineNetwork
los . aplicar (stateTensor, {entrenamiento: verdadero})
predicho . mul(tf.oneHot(actionTensor, NUM_ACTIONS)).sum(-1);
valores Q
const tensorrecompensa = tf.tensor1d(batch.map(ejemplo => ejemplo[2])); const
nextStateTensor = getStateTensor(
lote.mapa(ejemplo => ejemplo[4]), Los valores Q objetivo

esta.altura.del.juego, esta.anchura.del.juego); calculado aplicando


la ecuación de Bellman
const nextMaxQTensor =
this.targetNetwork.predict(nextStateTensor).max(-1); const doneMask
= tf.scalar(1).sub(
tf.tensor1d(batch.map(ejemplo => ejemplo[3])).asType('float32'));
const targetQs =
recompensaTensor.add(nextMaxQTensor.mul(doneMask).mul(gamma)); return
tf.pérdidas.meanSquaredError(objetivoQs, qs);
Utiliza MSE como una
}); medida de la discrepancia
const grads = tf.variableGrads( entre los valores Q
lossFunction, this.onlineNetwork.getWeights()); predichos y objetivo

Optimizer.applyGradients(graduados.graduados); Calcula el gradiente de


tf.dispose(graduados); lossFunction con
} Actualiza los pesos usando los
respecto a pesos
gradientes a través de un optimizador
del DQN online

Eso es todo por los detalles internos del algoritmo profundo de Q-learning. El
entrenamiento basado en este algoritmo se puede iniciar con el siguiente comando en
el entorno Node.js:
tren de hilo --logDir /tmp/snake_logs

Añade el --GPUmarque el comando para acelerar el entrenamiento si tiene una GPU habilitada para
CUDA. Esta --logDirflag permite que el comando registre las siguientes métricas en el directorio de
registro de TensorBoard durante el entrenamiento: 1) el promedio móvil de las recompensas
acumuladas de los 100 episodios de juego más recientes (recompensa acumulativa100);2) el
promedio móvil de la cantidad de frutas consumidas en los 100 episodios más recientes (
comido-100);3) el valor del parámetro de exploración (épsilon);y 4) la velocidad de entrenamiento en
número de pasos por segundo (cuadros por segundo).Estos registros se pueden ver iniciando
TensorBoard con los siguientes comandos y navegando a la URL HTTP de la interfaz de
TensorBoard (de forma predeterminada: http://localhost:6006):

pip instalar tensorboard tensorboard --logdir /tmp/snake_logs

La figura 11.17 muestra un conjunto de curvas logarítmicas típicas del proceso de entrenamiento. Como se ve

frecuentemente en el entrenamiento de RL, elRecompensa acumulativa100ycomido100ambas curvas muestran

fluctuación. Después de unas horas de entrenamiento, el modelo es capaz de alcanzar un mejorRecompensa

acumulativa100de 70–80 y un mejorcomido100de unos 12.

El script de entrenamiento también guarda el modelo en la ruta relativa ./models/dqn cada vez
que aparece un nuevo mejor.Recompensa acumulativa100se ha logrado el valor. El modelo
guardado se sirve desde la interfaz web cuando elreloj de hilose invoca el comando. la interfaz
Redes de valor y Q-learning: el ejemplo del juego de la serpiente 409

Recompensa Acumulativa100 Comido100 épsilon cuadros por segundo

90 0.5 32
70 12 0.4 30
50 28
8 0.3
30 26
0.2
10 4 24
– 10 0.1 22
– 30 0 0 20
50k 100k 150k 200k 250k 300k 50k 100k 150k 200k 250k 300k 50k 100k 150k 200k 250k 300k 50k 100k 150k 200k 250k 300k

Figura 11.17 Registros de ejemplo de un proceso de entrenamiento de serpiente-dqn en tfjs-node. Los paneles muestran
1)Recompensa acumulativa100, una media móvil de la recompensa acumulada obtenida en los 100 juegos más
recientes; 2)comido100, un promedio móvil de la cantidad de frutas consumidas en los 100 juegos más recientes; 3)
épsilon, el valor de épsilon, a partir del cual puede ver el curso temporal de la política ávida de épsilon; y 4)cuadros por
segundo, una medida de la velocidad de entrenamiento.

muestra los valores Q predichos por el DQN en cada paso del juego (ver figura 11.18). La política de
épsilon codicioso utilizada durante el entrenamiento se reemplaza con la política de "siempre
codicioso" durante el juego posterior al entrenamiento. La acción que corresponde al valor Q más
alto (por ejemplo, 33,9 para ir en línea recta en la figura 11.18) siempre se elige como la acción de
la serpiente. Esto le brinda una comprensión intuitiva de cómo juega el juego el DQN capacitado.

Hay un par de observaciones interesantes del comportamiento de la serpiente. En primer lugar, la


cantidad de frutas que realmente comió la serpiente en la demostración frontal (~18) es en promedio
mayor que lacomido100curva de los registros de entrenamiento (~12). Esto se debe a la eliminación de la
política de avaricia épsilon, que elimina las acciones aleatorias en el juego. Recuerde que épsilon se
mantiene como un valor pequeño pero distinto de cero durante la última etapa del entrenamiento de
DQN (consulte el tercer panel de la figura 11.17). Las acciones aleatorias provocadas por esto conducen
ocasionalmente a muertes prematuras, y este es el costo del comportamiento exploratorio. En segundo
lugar, la serpiente ha desarrollado
Opó una estrategia interesante de ir a los bordes y esquinas
del tablero antes de acercarse a la fruta, incluso cuando la Recompensa=60,6; Frutas=9

fruta se encuentra cerca del centro del tablero. Esta


estrategia es efectiva para ayudar a la serpiente a reducir la
probabilidad de chocar consigo misma cuando su longitud es
moderadamente grande (por ejemplo, en el rango de 10 a
18). Esto no está mal, pero tampoco es perfecto porque hay
estrategias más inteligentes que la serpiente no ha 29,0
desarrollado. Por ejemplo, la serpiente frecuentemente se 33,9

atrapa a sí misma en un círculo cuando su longitud supera 26,8

los 20. Esto es lo más lejos que puede llevarnos el algoritmo


en el snakedqn. Para mejorar aún más al agente serpiente, Figura 11.18 Los valores Q estimados
por un DQN capacitado se muestran
necesitamos modificar el algoritmo ávido de épsilon para
como números y se superponen como
animar a la serpiente a explorar mejores movimientos diferentes tonos de verde en la interfaz
cuando su longitud es larga.9En el algoritmo actual, el del juego.

Por ejemplo, verhttps://github.com/carsonprindle/OpenAIExam2018.


9
410 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

el grado de exploración es demasiado bajo una vez que la serpiente crece a una longitud que requiere maniobras
hábiles alrededor de su propio cuerpo.
Esto concluye nuestro recorrido por la técnica DQN para RL. Nuestro algoritmo se
basa en el documento de 2015 "Control a nivel humano a través del aprendizaje de
refuerzo profundo".10en el que los investigadores de DeepMind demostraron por
primera vez que la combinación del poder de las redes neuronales profundas y RL
permite que las máquinas resuelvan muchos videojuegos estilo Atari 2600. La solución
de serpiente-dqn que hemos demostrado es una versión simplificada del algoritmo de
DeepMind. Por ejemplo, nuestro DQN analiza la observación solo del paso actual,
mientras que el algoritmo de DeepMind combina la observación actual con las
observaciones de varios pasos anteriores como entrada para el DQN. Pero nuestro
ejemplo captura la esencia de la técnica innovadora, es decir, usar una convnet
profunda como un poderoso aproximador de funciones para estimar los valores de las
acciones que dependen del estado y entrenarlo usando MDP y la ecuación de Bellman.
Las hazañas posteriores de los investigadores de RL, como conquistar los juegos de Go y
ajedrez,

Materiales para lectura adicional


- Richard S. Sutton y Andrew G. Barto,Aprendizaje por refuerzo: una introducción, Un
libro de Bradford, 2018.
-Notas de la conferencia de David Silver sobre el aprendizaje por refuerzo en el University
College London:http://www0.cs.ucl.ac.uk/staff/d.silver/web/Teaching.html. Alexander
- Zai y Brandon Brown,Aprendizaje por refuerzo profundo en acción, Publicaciones de
Manning, en prensa,www.manning.com/books/deep-reinforcementlearning-in-action.

- Máximo Laplan,Aprendizaje práctico de refuerzo profundo: aplique métodos modernos de


RL, con redes Q profundas, iteración de valor, gradientes de políticas, TRPO, AlphaGo Zero y
más, Packt Publishing, 2018.

Ejercicios
1 En el ejemplo del poste del carro, usamos una red de políticas que consiste en una capa densa
oculta con 128 unidades, ya que era la configuración predeterminada. ¿Cómo afecta este
hiperparámetro al entrenamiento basado en gradientes de políticas? Intente cambiarlo a un valor
pequeño, como 4 u 8, y compare la curva de aprendizaje resultante (promedio de pasos por juego
frente a la curva de iteración) con la del tamaño de capa oculta predeterminado. ¿Qué le dice eso
acerca de la relación entre la capacidad del modelo y su efectividad para estimar la mejor acción?

2 Mencionamos que una de las ventajas de usar el aprendizaje automático para resolver un
problema como el poste de un carro es la economía del esfuerzo humano. Específicamente, si el

10Volodymyr Mnih et al., "Control a nivel humano a través del aprendizaje de refuerzo profundo"Naturaleza, vol. 518,
2015, págs. 529–533,www.nature.com/articles/nature14236/.
Ejercicios 411

el entorno cambia inesperadamente, no necesitamos averiguarcómorealmente ha


cambiado y reelaborado las ecuaciones físicas. En cambio, podemos dejar que el agente
vuelva a aprender el problema por sí mismo. Demuéstrate a ti mismo que este es el caso
siguiendo estos pasos. Primero, asegúrese de que el ejemplo de poste de carro se inicie
desde el código fuente y no desde la página web alojada. Capacitar a una red de políticas de
postes de carro que funcione utilizando el enfoque regular. En segundo lugar, edite el valor
deesta . gravedaden cart-pole/cart_pole.js y cámbielo a un nuevo valor (digamos, 12, si
quiere pretender que hemos movido la configuración de cart-pole a un exoplaneta con una
gravedad mayor que la Tierra). Vuelva a iniciar la página, cargue la red de políticas que ha
entrenado en el primer paso y pruébela. ¿Puedes confirmar que funciona significativamente
peor que antes, solo por el cambio de gravedad? Finalmente, entrene la red de políticas
algunas iteraciones más. ¿Puede ver que la política vuelve a mejorar en el juego
(adaptándose al nuevo entorno)?
3 (Ejercicio sobre MDP y ecuación de Bellman) El ejemplo de MDP que presentamos en la
sección 11.3.2 y la figura 11.10 era simple en el sentido de que era completamente
determinista porque no hay aleatoriedad en las transiciones de estado y las recompensas
asociadas. Pero muchos problemas del mundo real se describen mejor como MDP
estocásticos (aleatorios). En un MDP estocástico, el estado en el que terminará el agente y la
recompensa que obtendrá después de realizar una acción sigue una distribución
probabilística. Por ejemplo, como muestra la figura 11.19, si el agente actúaA1en el estadoS
1, terminará en el estadoS2con una probabilidad de 0.5 y en estadoS3con una probabilidad
de 0.5. Las recompensas asociadas con las dos transiciones de estado son diferentes. En tal
estocásticoMDP, el agente debe tener en cuenta la aleatoriedad calculando laprevisto
recompensa futura. La recompensa futura esperada es un promedio ponderado de todas
las recompensas posibles, siendo los pesos las probabilidades. ¿Puede aplicar este enfoque
probabilístico y estimar los valores Q paraa1ya2ens1¿en la figura? Con base en la respuesta,
esa1oa2la mejor acción en el estado s1?

p = 0,5
r = –3 s2

a1 p = 0,5
r=6
s3

s1

p = 0,8
r = –2 s4

a2 p = 0,2
r = 10

s5 Figura 11.19 El diagrama para el MDP


en la primera parte del ejercicio 3
412 CPASADO11Conceptos básicos del aprendizaje por refuerzo profundo

Ahora veamos un MDP estocástico un poco más complicado, uno que involucra más
de un paso (vea la figura 11.20). En este caso un poco más complejo, debe aplicar la
ecuación recursiva de Bellman para tener en cuenta las mejores recompensas futuras
posibles después de la primera acción, que son en sí mismas estocásticas. Tenga en
cuenta que a veces el episodio termina después del primer paso y, a veces, durará otro
paso. ¿Puedes decidir qué acción es mejor ens1? Para este problema, puede utilizar un
factor de descuento de recompensa de 0,9.

p = 0,6
r=9 s6

a1 p = 0,4
r=3
s7
p = 0,5
r = –3 s2
p = 1,0
r=5
a2
a1 p = 0,5 s8
r=6

s3
p = 1,0
r=4 s9
s1
a1

p = 0,8
p = 0,8 r = 10
s4 s10
r = –2

a2 p = 0,2
a2 p = 0,2 r = –5
r = 10
s11
s5

Figura 11.20 El diagrama para el MDP en la segunda parte del ejercicio 3

4 En el ejemplo de serpiente-dqn, usamos la política de avaricia de épsilon para equilibrar las


necesidades de exploración y explotación. La configuración predeterminada reduce épsilon
desde un valor inicial de 0,5 hasta un valor final de 0,01 y lo mantiene allí. Intente cambiar el
valor final de épsilon a un valor grande (como 0,1) o uno más pequeño (como 0) y observe
los efectos sobre qué tan bien aprende el agente serpiente. ¿Puede explicar la diferencia
resultante en términos del papel que juega épsilon?
Resumen 413

Resumen
- Como un tipo de aprendizaje automático, RL se trata de aprender a tomar decisiones óptimas. En
un problema de RL, un agente aprende a seleccionar acciones en un entorno para maximizar una
métrica llamadarecompensa acumulativa.
-A diferencia del aprendizaje supervisado, no hay conjuntos de datos de entrenamiento etiquetados

en RL. En cambio, el agente debe aprender qué acciones son buenas en diferentes circunstancias
probando acciones aleatorias.
- Exploramos dos tipos de algoritmos RL de uso común: métodos basados en políticas (usando el
ejemplo del poste del carro) y métodos basados en el valor Q (usando el ejemplo de la serpiente).

- Una política es un algoritmo mediante el cual el agente elige una acción en función de la
observación del estado actual. Una política se puede encapsular en una red neuronal que
toma la observación del estado como entrada y produce una selección de acción como
salida. Tal red neuronal se llamared de políticas. En el problema del poste del carro, usamos
gradientes de políticas y el método REFUERZO para actualizar y entrenar una red de
políticas.
- A diferencia de los métodos basados en políticas, Q-learning utiliza un modelo llamadoQ-redpara
estimar los valores de las acciones bajo un estado observado dado. En el ejemplo de serpiente-
dqn, demostramos cómo una convnet profunda puede servir como red Q y cómo se puede
entrenar utilizando la suposición MDP, la ecuación de Bellman y una construcción llamada
memoria de reproducción.
parte 4

Resumen y palabras de cierre

T a parte final de este libro consta de dos capítulos. El Capítulo 12 aborda las inquietudes que
los usuarios de TensorFlow.js pueden tener al implementar modelos en entornos de producción.
Analiza las prácticas recomendadas que ayudan a los desarrolladores a obtener una mayor
confianza en la corrección del modelo, las técnicas que hacen que los modelos sean más pequeños
y los ayudan a funcionar de manera más eficiente, y la gama de entornos de implementación que
admiten los modelos de TensorFlow.js. El capítulo 13 es un resumen de todo el libro y proporciona
una revisión de los conceptos, flujos de trabajo y técnicas clave.
Probar, optimizar,
y despliegue de modelos

—CON APORTES DE
YANNICKASSOGBA, PAGSEN GYtu,
YnorteICKkREEGER

Este capítulo cubre


- La importancia y las pautas prácticas para probar y monitorear el
código de aprendizaje automático
- Cómo optimizar modelos entrenados en TensorFlow.js o convertidos a
TensorFlow.js para una carga e inferencia más rápidas

- Cómo implementar modelos de TensorFlow.js en varias plataformas y


entornos, desde extensiones de navegador hasta aplicaciones móviles,
y desde aplicaciones de escritorio hasta computadoras de placa única

Como mencionamos en el capítulo 1, el aprendizaje automático se diferencia de la ingeniería de


software tradicional en que automatiza el descubrimiento de reglas y heurísticas. Los capítulos
anteriores del libro deberían haberle dado una comprensión sólida de esta singularidad del
aprendizaje automático. Sin embargo, los modelos de aprendizaje automático y el código que los
rodea siguen siendo código; se ejecutan como parte de su sistema de software general. Para
asegurarse de que los modelos de aprendizaje automático se ejecuten de manera confiable y eficiente,
los profesionales deben tomar precauciones similares a las que toman cuando administran código que
no es de aprendizaje automático.

417
418 CPASADO12Probar, optimizar e implementar modelos

Este capítulo está dedicado a los aspectos prácticos del uso de TensorFlow.js para el aprendizaje
automático como parte de su paquete de software. La primera sección explora el tema de suma
importancia, pero a menudo descuidado, de probar y monitorear el código y los modelos de aprendizaje
automático. La segunda sección presenta herramientas y trucos que lo ayudan a reducir el tamaño y la
huella de cómputo de sus modelos entrenados, acelerando la descarga y la ejecución, lo cual es una
consideración fundamental para la implementación de modelos tanto del lado del cliente como del lado
del servidor. En la sección final, le daremos un recorrido por los diversos entornos en los que se pueden
implementar los modelos creados con TensorFlow.js. Al hacerlo, discutiremos los beneficios únicos, las
limitaciones y las estrategias que implica cada una de las opciones de implementación.
Al final de este capítulo, estará familiarizado con las mejores prácticas relacionadas con las pruebas, la
optimización y la implementación de modelos de aprendizaje profundo en TensorFlow.js.

12.1 Prueba de modelos de TensorFlow.js


Hasta ahora, hemos hablado sobre cómo diseñar, construir y entrenar modelos de
aprendizaje automático. Ahora vamos a sumergirnos en algunos de los temas que
surgen cuando implementa sus modelos entrenados, comenzando con las pruebas,
tanto del código de aprendizaje automático como del código relacionado que no es de
aprendizaje automático. Algunos de los desafíos clave que enfrenta cuando busca
rodear su modelo y su proceso de entrenamiento con pruebas son el tamaño del
modelo, el tiempo requerido para entrenar y el comportamiento no determinista que
ocurre durante el entrenamiento (como la aleatoriedad en la inicialización de pesos y
ciertas operaciones de redes neuronales como el abandono). A medida que nos
expandimos de un modelo individual a una aplicación completa, también encontrará
varios tipos de sesgos o desviaciones entre las rutas de código de entrenamiento e
inferencia, problemas de control de versiones del modelo y cambios de población en sus
datos.
Una consideración clave es: "¿Cómo se controla la versión de su modelo?" En la mayoría de los casos,
el modelo se ajusta y entrena hasta que se alcanza una precisión de evaluación satisfactoria, y luego el
modelo no necesita más ajustes. El modelo no se reconstruye ni se vuelve a entrenar como parte del
proceso de construcción normal. En su lugar, la topología del modelo y los pesos entrenados deben
verificarse en su sistema de control de versiones, más similar a un objeto binario grande (BLOB) que a un
artefacto de texto/código. Cambiar el código circundante no debería causar una actualización del número
de versión de su modelo. Del mismo modo, volver a entrenar un modelo y registrarlo no debería requerir
cambiar el código fuente que no es del modelo.
¿Qué aspecto de un sistema de aprendizaje automático deben cubrir las pruebas? En nuestra opinión,
la respuesta es “cada parte”. La figura 12.1 explica esta respuesta. Un sistema típico que pasa de datos de
entrada sin procesar a un modelo entrenado listo para su implementación consta de varios componentes
clave. Algunos de ellos se parecen al código que no es de aprendizaje automático y se pueden cubrir con
pruebas unitarias tradicionales, mientras que otros muestran características más específicas del
aprendizaje automático y, por lo tanto, requieren tratamientos de supervisión o pruebas especialmente
diseñados. Pero el mensaje importante para llevar a casa aquí es nunca ignorar o subestimar la
importancia de las pruebas solo porque se trata de un sistema de aprendizaje automático. En su lugar,
argumentaríamos que las pruebas unitarias son aún más importantes para
Prueba de modelos de TensorFlow.js 419

código de aprendizaje automático, quizás incluso más que las pruebas para el desarrollo de
software tradicional, porque los algoritmos de aprendizaje automático suelen ser más opacos y
más difíciles de entender que los que no son de aprendizaje automático. Pueden fallar
silenciosamente frente a entradas incorrectas, lo que genera problemas que son difíciles de notar y
depurar, y la defensa contra tales problemas es la prueba y el monitoreo. En las siguientes
subsecciones, ampliaremos varias partes de la figura 12.1.

Modelo
código
Capacitación Entrenado Postprocesamiento Predicción
proceso modelo código salidas
preprocesamiento
Datos
código

Tradicional Ejemplo Tradicional Tradicional Tradicional Modelo


examen de la unidad validador examen de la unidad examen de la unidad examen de la unidad validador
y
evaluador
Tamaño y velocidad
supervisión

Figura 12.1 La cobertura de un sistema de aprendizaje automático listo para producción mediante pruebas y monitoreo. La mitad superior
del diagrama incluye los componentes clave de una canalización típica para la creación y el entrenamiento de modelos de aprendizaje
automático. La mitad inferior muestra la práctica de prueba que se puede aplicar a cada uno de los componentes. Algunos de los
componentes son aptos para la práctica tradicional de pruebas unitarias: el código que crea y entrena el código, y el código que realiza el
procesamiento previo y posterior de los datos de entrada y los resultados de salida del modelo. Otros componentes requieren más pruebas
y prácticas de monitoreo específicas del aprendizaje automático. Estos incluyen la validación de ejemplo para la calidad de los datos, el
monitoreo del tamaño de byte y la velocidad de inferencia del modelo entrenado, y la validación y evaluación detalladas de las predicciones
hechas por el modelo entrenado.

12.1.1 Pruebas unitarias tradicionales

Al igual que con los proyectos que no son de aprendizaje automático, las pruebas unitarias confiables y livianas deben formar la

base de sus suites de prueba. Sin embargo, se requieren consideraciones especiales para configurar pruebas unitarias en torno a

modelos de aprendizaje automático. Como ha visto en capítulos anteriores, las métricas, como la precisión en un conjunto de datos

de evaluación, a menudo se usan para cuantificar la calidad final del modelo después de un entrenamiento y ajuste de

hiperparámetros exitosos. Tales métricas de evaluación son importantes para el monitoreo por parte de ingenieros humanos, pero

no son adecuadas para pruebas automatizadas. Es tentador agregar una prueba que afirme que una determinada métrica de

evaluación es mejor que un cierto umbral (por ejemplo, el AUC para una tarea de clasificación binaria es superior a 0,95 o el MSE

para una tarea de regresión es inferior a 0,2). Sin embargo, estos tipos de aserciones basadas en umbrales deben usarse con

precaución, si no se evitan por completo, porque tienden a ser frágiles. El proceso de entrenamiento del modelo contiene múltiples

fuentes de aleatoriedad, incluida la inicialización de pesos y la mezcla de ejemplos de entrenamiento. Esto lleva al hecho de que el

resultado del entrenamiento del modelo varía ligeramente de una ejecución a otra. Si sus conjuntos de datos cambian (por ejemplo,

debido a que se agregan nuevos datos regularmente), esto formará una fuente adicional de variabilidad. Como tal, elegir el umbral

es una tarea difícil. Un umbral demasiado indulgente no captaría la realidad debido a que se agregan nuevos datos regularmente),

esto formará una fuente adicional de variabilidad. Como tal, elegir el umbral es una tarea difícil. Un umbral demasiado indulgente

no captaría la realidad debido a que se agregan nuevos datos regularmente), esto formará una fuente adicional de variabilidad.

Como tal, elegir el umbral es una tarea difícil. Un umbral demasiado indulgente no captaría la realidad
420 CPASADO12Probar, optimizar e implementar modelos

problemas cuando ocurren. Un umbral demasiado estricto daría lugar a una prueba inestable, es
decir, que falla con frecuencia sin un problema subyacente genuino.
La aleatoriedad en un programa TensorFlow.js generalmente se puede desactivar llamando al
Matemáticas.seedrandom()antes de crear y ejecutar el modelo. Por ejemplo, la siguiente línea
sembrará el estado aleatorio de los inicializadores de peso, el barajador de datos y las capas de
abandono con una semilla determinada para que el entrenamiento posterior del modelo produzca
resultados deterministas:
42 es simplemente seleccionado

Math.seedrandom(42); arbitrariamente, semilla aleatoria fija.

Este es un truco útil en caso de que necesite escribir pruebas que hagan afirmaciones sobre la pérdida o los
valores métricos.
Sin embargo, incluso con la siembra determinista, probar solomodelo.fit()o llamadas
similares no es suficiente para una buena cobertura de su código de aprendizaje automático.
Al igual que otras secciones de código difíciles de realizar pruebas unitarias, debe intentar
realizar pruebas unitarias completas del código circundante que es fácil de realizar pruebas
unitarias y explorar soluciones alternativas para la parte del modelo. Todo su código para la
carga de datos, el preprocesamiento de datos, el posprocesamiento de los resultados del
modelo y otros métodos de utilidad deben ser compatibles con las prácticas de prueba
normales. Además, algunas pruebas no estrictas en el modelo en sí mismo, sus formas de
entrada y salida, por ejemplo, junto con una prueba de estilo "garantizar que el modelo no
arroje una excepción cuando se entrena en un paso" puede proporcionar el mínimo de un
arnés de prueba alrededor del modelo que permite confianza durante la refactorización.
(Como habrás notado al jugar con el código de ejemplo de los capítulos anteriores,

Para ver un ejemplo de esto en la práctica, podemos ver las pruebas para los ejemplos de
análisis de opinión que exploramos en el capítulo 9. Al examinar el código, debería ver
data_test.js, embedding_test.js, sequence_utils_test.js y train_test.js . Los primeros tres de
estos archivos cubren el código que no es modelo y se ven como pruebas de unidad
normales. Su presencia nos brinda una mayor confianza de que los datos que ingresan al
modelo durante el entrenamiento y la inferencia están en el formato de origen esperado, y
nuestras manipulaciones en él son válidas.
El archivo final de esa lista se refiere al modelo de aprendizaje automático y merece un poco
más de nuestra atención. La siguiente lista es un extracto de la misma.

Listado 12.1 Pruebas unitarias de la API de un modelo: sus formas de entrada-salida y capacidad de entrenamiento

describir('construirModelo', () => {
it('aplanar entrenamiento e inferencia', async () => { asegura que el
const maxLen = 5; const entrada y salida del
vocabSize = 3; const modelo tienen la
forma esperada
incrustaciónTamaño = 8;
const model = buildModel('flatten', maxLen, vocabSize, embeddingSize);
expect(modelo.entradas.longitud).toEqual(1);
esperar(modelo.entradas[0].forma).toEqual([null, maxLen]);
esperar(modelo.salidas.longitud).toEqual(1);
esperar(modelo.salidas[0].forma).toEqual([null, 1]);
Prueba de modelos de TensorFlow.js 421

modelo.compilar({
pérdida: 'binaryCrossentropy',
optimizador: 'rmsprop',
métricas: ['acc']
});
const xs = tf.ones([2, maxLen]) const ys =
tf.ones([2, 1]);
Entrena el const historial = esperar modelo.fit(xs, ys, { Comprueba que el entrenamiento
modelo muy
épocas: 2, está reportando métricas para
brevemente; esta
tamaño del lote: 2 cada paso de entrenamiento como una
debiera ser
}); señal de que el entrenamiento
rápido, pero es
expect(historia.historia.pérdida.longitud).toEqual(2); ocurrió
no será
expect(historia.historia.acc.longitud).toEqual(2);
preciso.
const predecirSalidas = modelo.predecir(xs);
expect(predictOuts.shape).toEqual([2, 1]); valores const =
predecirOuts.arraySync(); expect(valores[0]
[0]).toBeGreaterThanOrEqual(0); expect(valores[0]
[0]).toBeLessThanOrEqual(1); expect(valores[1]
[0]).toBeGreaterThanOrEqual(0); expect(valores[1]
[0]).toBeLessThanOrEqual(1); });

Se asegura de que la predicción esté en el rango de posibles


});
respuestas; no queremos verificar el valor real, ya
Ejecuta una predicción a través del modelo que la capacitación fue excepcionalmente breve y
enfocada en verificar que la API sea la esperada podría ser inestable.

Esta prueba cubre mucho terreno, así que analicemos un poco. Primero construimos un
modelo usando una función auxiliar. Para esta prueba, no nos importa la estructura del
modelo y lo trataremos como una caja negra. Luego hacemos afirmaciones sobre la forma de
las entradas y salidas:

expect(modelo.entradas.longitud).toEqual(1);
esperar(modelo.entradas[0].forma).toEqual([null, maxLen]);
esperar(modelo.salidas.longitud).toEqual(1);
esperar(modelo.salidas[0].forma).toEqual([null, 1]);

Estas pruebas pueden detectar problemas en términos de identificación errónea de la regresión de la dimensión
del lote frente a la clasificación, la forma de salida, etc. A continuación, compilamos y entrenamos el modelo en
un número muy pequeño de pasos. Nuestro objetivo es simplemente asegurarnos de que el modelo se pueda
entrenar; no estamos preocupados por la precisión, la estabilidad o la convergencia en este punto:

const history = await model.fit(xs, ys, {epochs: 2, batchSize: 2})


expect(historia.historia.pérdida.longitud).toEqual(2);
expect(historia.historia.acc.longitud).toEqual(2);

Este fragmento también comprueba que el entrenamiento informó las métricas requeridas para el
análisis: si entrenamos de verdad, ¿podríamos inspeccionar el progreso del entrenamiento y la precisión
del modelo resultante? Finalmente, intentamos un simple:

const predecirSalidas = modelo.predecir(xs);


expect(predictOuts.shape).toEqual([2, 1]); valores const =
predecirOuts.arraySync(); expect(valores[0]
[0]).toBeGreaterThanOrEqual(0); expect(valores[0]
[0]).toBeLessThanOrEqual(1);
422 CPASADO12Probar, optimizar e implementar modelos

expect(valores[1][0]).toBeGreaterThanOrEqual(0);
expect(valores[1][0]).toBeLessThanOrEqual(1);

No buscamos ningún resultado de predicción en particular, ya que podría cambiar en función de la


inicialización aleatoria de los valores de ponderación o posibles revisiones futuras de la
arquitectura del modelo. Lo que sí comprobamos es que obtenemos una predicción y que la
predicción está en el rango esperado, en este caso, entre 0 y 1.
La lección más importante aquí es darse cuenta de que no importa cómo cambiemos el interior
de la arquitectura del modelo, mientras no cambiemos su entrada o su API de salida, esta prueba
siempre debería pasar. Si la prueba falla, tenemos un problema en nuestro modelo. Estas siguen
siendo pruebas livianas y rápidas que brindan un alto grado de corrección de la API, y son
adecuadas para incluirlas en cualquier gancho de prueba que se ejecute comúnmente.

12.1.2 Pruebas con valores dorados


En la sección anterior, hablamos sobre las pruebas unitarias que podemos hacer sin afirmar un valor de métrica
de umbral o requerir un entrenamiento estable o convergente. Ahora exploremos los tipos de pruebas que las
personas a menudo quieren ejecutar con un modelo completamente entrenado, comenzando con la verificación
de predicciones de puntos de datos particulares. Tal vez haya algunos ejemplos "obvios" que desee probar. Por
ejemplo, para un detector de objetos, una imagen de entrada con un bonito gato grande debe etiquetarse como
tal; para un analizador de opiniones, un fragmento de texto que claramente es una reseña negativa de un cliente
debe clasificarse como tal. Estas respuestas correctas para las entradas del modelo dadas son a lo que nos
referimos comovalores dorados. Si sigue ciegamente la mentalidad de las pruebas unitarias tradicionales, es fácil
caer en la trampa de probar modelos de aprendizaje automático entrenados con valores dorados. Después de
todo, queremos un detector de objetos bien entrenado para etiquetar siempre al gato en una imagen que
contiene un gato, ¿verdad? No exactamente. Las pruebas basadas en valores dorados pueden ser problemáticas
en un entorno de aprendizaje automático porque estamos usurpando nuestra división de datos de capacitación,
validación y evaluación.
Suponiendo que tuvo una muestra representativa para sus conjuntos de datos de validación y prueba, y estableció una métrica de destino

adecuada (precisión, recuperación, etc.), ¿por qué se requiere que un ejemplo sea más correcto que otro? El entrenamiento de un modelo de

aprendizaje automático se ocupa de la precisión en todos los conjuntos de validación y prueba. Las predicciones para ejemplos individuales

pueden variar con la selección de hiperparámetros y valores de peso iniciales. Si hay algunos ejemplos que deben clasificarse correctamente y

son fáciles de identificar, ¿por qué no detectarlos antes de pedirle al modelo de aprendizaje automático que los clasifique y, en su lugar, utilizar

un código que no sea de aprendizaje automático para manejarlos? Tales ejemplos se usan ocasionalmente en sistemas de procesamiento de

lenguaje natural, donde un subconjunto de entradas de consulta (como las que se encuentran con frecuencia y las fácilmente identificables) se

enrutan automáticamente a un módulo que no es de aprendizaje automático para su manejo, mientras que las consultas restantes son

manejadas por un modelo de aprendizaje automático. Ahorrará tiempo de cómputo y esa parte del código es más fácil de probar con las

pruebas unitarias tradicionales. Si bien agregar una capa de lógica empresarial antes (o después) del predictor de aprendizaje automático puede

parecer un trabajo adicional, le brinda los ganchos para controlar las anulaciones de las predicciones. También es un lugar donde puede

agregar monitoreo o registro, que probablemente querrá como Si bien agregar una capa de lógica empresarial antes (o después) del predictor

de aprendizaje automático puede parecer un trabajo adicional, le brinda los ganchos para controlar las anulaciones de las predicciones.

También es un lugar donde puede agregar monitoreo o registro, que probablemente querrá como Si bien agregar una capa de lógica

empresarial antes (o después) del predictor de aprendizaje automático puede parecer un trabajo adicional, le brinda los ganchos para controlar

las anulaciones de las predicciones. También es un lugar donde puede agregar monitoreo o registro, que probablemente querrá como
Prueba de modelos de TensorFlow.js 423

su herramienta se vuelve más ampliamente utilizada. Con ese preámbulo, exploremos los tres deseos
comunes de los valores dorados por separado.
Una motivación común de este tipo de prueba de valor dorado está en el servicio de una prueba
completa de extremo a extremo: dada una entrada de usuario sin procesar, ¿qué genera el
sistema? Se entrena el sistema de aprendizaje automático y se solicita una predicción a través del
flujo de código normal del usuario final, y se devuelve una respuesta al usuario. Esto es similar a
nuestra prueba de unidad en el listado 12.1, pero el sistema de aprendizaje automático está en
contexto con el resto de la aplicación. Podríamos escribir una prueba similar a la lista 12.1 que no
se preocupa por el valor real de la predicción y, de hecho, sería una prueba más estable. Sin
embargo, es muy tentador combinarlo con un par de ejemplo/predicción que tenga sentido y se
entienda fácilmente cuando los desarrolladores revisen la prueba.
Aquí es cuando surge el problema: necesitamos un ejemplo cuya predicción se conozca y se garantice que es
correcta o, de lo contrario, la prueba de extremo a extremo falla. Entonces, agregamos una prueba a menor
escala que prueba esa predicción a través de un subconjunto de la canalización cubierta por la prueba de
extremo a extremo. Ahora, si la prueba de un extremo a otro falla y la prueba más pequeña pasa, hemos aislado
el error a las interacciones entre el modelo central de aprendizaje automático y otras partes de la canalización
(como la ingesta de datos o el posprocesamiento). Si ambos fallan al unísono, sabemos que nuestro invariante de
ejemplo/predicción está roto. En este caso, es más una herramienta de diagnóstico, pero el resultado probable
de la falla emparejada es elegir un nuevo ejemplo para codificar, no volver a entrenar el modelo por completo.

La siguiente fuente más común es alguna forma de requisito comercial. Algún conjunto
identificable de ejemplos debe ser más preciso que el resto. Como se mencionó anteriormente,
este es el escenario perfecto para agregar una capa de lógica empresarial anterior o posterior al
modelo para manejar estas predicciones. Sin embargo, puedes experimentar conponderación de
ejemplo, en el que algunos ejemplos cuentan más que otros al calcular las métricas de calidad
general. No garantizará la corrección, pero sesgará el modelo para que sea correcto. Si una capa de
lógica de negocios es difícil porque no puede preidentificar fácilmente las propiedades de la
entrada que desencadenan el caso especial, es posible que deba explorar un segundo modelo que
se use exclusivamente para determinar si se necesita anular. En este caso, está utilizando un
conjunto de modelos y su lógica comercial combina las predicciones de dos capas para realizar la
acción correcta.
El último caso aquí es cuando tiene un informe de error con un ejemplo proporcionado por el
usuario que dio un resultado incorrecto. Si es incorrecto por razones comerciales, volvemos al caso
inmediatamente anterior. Si está mal solo porque cae en el porcentaje de falla de la curva de
rendimiento del modelo, no hay mucho que debamos hacer. Está dentro del rendimiento aceptado
del algoritmo entrenado; Se espera que todos los modelos cometan algunos errores. Puede
agregar el par ejemplo/predicción correcta a sus conjuntos de entrenamiento/prueba/evaluación
según corresponda para generar un mejor modelo en el futuro, pero no es apropiado usar los
valores dorados para las pruebas unitarias.
Una excepción a esto es si mantiene el modelo constante: tiene los pesos y la arquitectura
del modelo verificados en el control de versiones y no los está regenerando en las pruebas.
Entonces puede ser apropiado usar valores dorados para probar los resultados de un sistema
de inferencia que usa el modelo como su núcleo, como ni el modelo ni los ejemplos.
424 CPASADO12Probar, optimizar e implementar modelos

están sujetos a cambios. Dicho sistema de inferencia contiene partes distintas del modelo, como
partes que preprocesan los datos de entrada antes de enviarlos al modelo y otras que toman los
resultados del modelo y los transforman en formas más adecuadas para su uso por los sistemas
posteriores. Dichas pruebas unitarias aseguran la corrección de dicha lógica de pre y
posprocesamiento.
Otro uso legítimo de los valores dorados es la prueba unitaria externa: el seguimiento de la calidad de
un modelo (pero no como prueba unitaria) a medida que evoluciona. Ampliaremos esto cuando
discutamos el validador y el evaluador del modelo en la siguiente sección.

12.1.3 Consideraciones en torno a la formación continua


En muchos sistemas de aprendizaje automático, obtiene nuevos datos de entrenamiento a intervalos
bastante regulares (todas las semanas o todos los días). Tal vez pueda usar sus registros del día anterior
para generar datos de entrenamiento nuevos y más oportunos. En dichos sistemas, el modelo debe
volver a entrenarse con frecuencia, utilizando los últimos datos disponibles. En estos casos, existe la
creencia de que la antigüedad o la obsolescencia del modelo afectan a su potencia. A medida que pasa el
tiempo, las entradas del modelo se desplazan hacia una distribución diferente a la que se entrenó, por lo
que las características de calidad empeorarán. Como ejemplo, podría tener una herramienta de
recomendación de ropa que se entrenó en el invierno pero hace predicciones en el verano.
Dada la idea básica, a medida que comience a explorar sistemas que requieren capacitación continua,
tendrá una amplia variedad de componentes adicionales que crearán su canalización. Una discusión
completa de estos está fuera del alcance de este libro, pero TensorFlow Extended (TFX)1es una
infraestructura para buscar más ideas. Los componentes de canalización que enumera y que tienen la
mayor relevancia en un campo de pruebas son losvalidador de ejemplo,validador de modelos, yevaluador
de modelos. El diagrama de la figura 12.1 contiene recuadros que corresponden a estos componentes.
El validador de ejemplo se trata de probar los datos, un aspecto fácil de pasar por alto al probar un
sistema de aprendizaje automático. Hay un dicho famoso entre los profesionales del aprendizaje
automático: "basura que entra, basura que sale". La calidad de un modelo de aprendizaje automático
entrenado está limitada por la calidad de los datos que contiene. Los ejemplos con valores de
características no válidos o etiquetas incorrectas probablemente dañarán la precisión del modelo
entrenado cuando se implemente para su uso (es decir, si el trabajo de entrenamiento del modelo no falla
debido a los malos ejemplos primero). El validador de ejemplo se usa para garantizar que las propiedades
de los datos que se incluyen en el entrenamiento y la evaluación del modelo siempre cumplan ciertos
requisitos: que tenga suficientes datos, que su distribución parezca válida y que no tenga valores atípicos
extraños. Por ejemplo, si tiene un conjunto de datos médicos, la altura del cuerpo (en centímetros) debe
ser un número positivo no mayor a 280; la edad del paciente debe ser un número positivo entre 0 y 130;
la temperatura oral (en grados Celsius) debe ser un número positivo entre aproximadamente 30 y 45, y
así sucesivamente. Si ciertos ejemplos de datos contienen características que se encuentran fuera de
dichos rangos o tienen valores de marcador de posición como "Ninguno" o NaN, sabemos que algo está
mal con esos ejemplos y deben tratarse en consecuencia, en la mayoría de los casos, excluidos del
entrenamiento y la evaluación.

1
Denis Baylor et al., "TFX: una plataforma de aprendizaje automático a escala de producción basada en TensorFlow", KDD
2017, www.kdd.org/kdd2017/papers/view/tfx-a-tensorflow-based-production-scale-machine-learning-platform.
Traducido del inglés al español - www.onlinedoctranslator.com

Optimización del modelo 425

Por lo general, los errores aquí indican una falla en el proceso de recopilación de datos o que "el
mundo ha cambiado" de manera incompatible con las suposiciones que tenía al construir el
sistema. Normalmente, esto es más análogo a la supervisión y alerta que a las pruebas de
integración.
Un componente como un validador de ejemplo también es útil para detectarsesgo de servicio de entrenamiento, un
tipo de error particularmente desagradable que puede surgir en los sistemas de aprendizaje automático. Las dos causas
principales son 1) el entrenamiento y el servicio de datos que pertenecen a diferentes distribuciones y 2) el
preprocesamiento de datos que involucra rutas de código que se comportan de manera diferente durante el
entrenamiento y el servicio. Un validador de ejemplo implementado en los entornos de capacitación y servicio tiene el
potencial de detectar errores introducidos a través de cualquiera de las rutas.

El validador del modelo desempeña el papel de la persona que construye el modelo al decidir si
el modelo es "suficientemente bueno" para usarlo en el servicio. Lo configura con las métricas de
calidad que le interesan y luego "bendice" el modelo o lo rechaza. Una vez más, al igual que el
validador de ejemplo, se trata más de una interacción de estilo monitor y alerta. Por lo general,
también querrá registrar y graficar sus métricas de calidad a lo largo del tiempo (precisión, etc.)
para ver si tiene degradaciones sistemáticas a pequeña escala que podrían no activar una alerta
por sí mismas, pero que aún podrían ser útiles para diagnosticar tendencias a largo plazo y aislar
sus causas.
El evaluador de modelos es una especie de inmersión más profunda en las estadísticas de calidad del modelo,
cortando y dividiendo la calidad a lo largo de un eje definido por el usuario. A menudo, esto se usa para probar si
el modelo se está comportando de manera justa para diferentes poblaciones de usuarios: franjas de edad, franjas
educativas, geográficas, etc. Un ejemplo simple sería mirar los ejemplos de flores de iris que usamos en la
sección 3.3 y verificar si la precisión de nuestra clasificación es más o menos similar entre las tres especies de iris.
Si nuestros conjuntos de prueba o evaluación están inusualmente sesgados hacia una de las poblaciones, es
posible que siempre nos equivoquemos en la población más pequeña sin que se muestre como un problema de
precisión de nivel superior. Al igual que con el validador de modelos, las tendencias a lo largo del tiempo suelen
ser tan útiles como la medición individual en un punto en el tiempo.

12.2 Optimización del modelo


Una vez que haya creado, entrenado y probado minuciosamente su modelo, es hora de ponerlo en
práctica. Este proceso, llamadoimplementación del modelo, no es menos importante que los pasos
anteriores del desarrollo del modelo. Ya sea que el modelo se envíe al lado del cliente para la
inferencia o se ejecute en el backend para servir, siempre queremos que el modelo sea rápido y
eficiente. Específicamente, queremos que el modelo

- Sea pequeño en tamaño y, por lo tanto, rápido para cargar a través de la web o desde el disco

- Consuma la menor cantidad de tiempo, computación y memoria posible cuando supredecir() se


llama metodo

Esta sección describe las técnicas disponibles en TensorFlow.js para optimizar el tamaño y la velocidad de
inferencia de los modelos entrenados antes de que se lancen para su implementación.
426 CPASADO12Probar, optimizar e implementar modelos

El significado de la palabramejoramientoestá sobrecargado. En el contexto de esta sección,


mejoramientose refiere a mejoras que incluyen la reducción del tamaño del modelo y la
aceleración del cálculo. Esto no debe confundirse con las técnicas de optimización de parámetros
de peso, como el descenso de gradiente en el contexto del entrenamiento de modelos y los
optimizadores. Esta distinción a veces se denomina modelo.calidadversus modelorendimiento. El
rendimiento se refiere a cuánto tiempo y recursos consume el modelo para realizar su tarea. La
calidad se refiere a qué tan cerca están los resultados de un ideal.

12.2.1 Optimización del tamaño del modelo a través de la cuantificación del peso posterior al entrenamiento

La necesidad de tener archivos pequeños que se carguen rápidamente a través de Internet debería ser
muy clara para los desarrolladores web. Es especialmente importante si su sitio web se dirige a una base
de usuarios muy grande o a usuarios con conexiones a Internet lentas.2Además, si tu modelo está
almacenado en un dispositivo móvil (consulta la sección 12.3.4 para ver una discusión sobre la
implementación móvil con TensorFlow.js), el tamaño del modelo suele estar limitado por el espacio de
almacenamiento limitado. Como desafío para la implementación de modelos, las redes neuronales son
grandes y siguen creciendo. La capacidad (es decir, el poder predictivo) de las redes neuronales
profundas a menudo tiene el costo de un mayor número de capas y tamaños de capa más grandes. En el
momento de escribir este artículo, el reconocimiento de imágenes de última generación,3reconocimiento
de voz,4procesamiento natural del lenguaje,5y modelos generativos6suelen superar 1 GB en el tamaño de
sus pesos. Debido a la tensión entre la necesidad de que los modelos sean pequeños y potentes, un área
de investigación muy activa en el aprendizaje profundo es la optimización del tamaño del modelo, o cómo
diseñar una red neuronal con un tamaño lo más pequeño posible que aún pueda realizar su función.
tareas con una precisión cercana a la de una red neuronal más grande. Hay dos enfoques generales
disponibles. En el primer enfoque, los investigadores diseñan una red neuronal con el objetivo de
minimizar el tamaño del modelo desde el principio. En segundo lugar, existen técnicas a través de las
cuales las redes neuronales existentes pueden reducirse a un tamaño más pequeño.
MobileNetV2, que visitamos en los capítulos sobre convnets, es producido por la primera línea de
investigación.7Es un modelo de imagen pequeño y liviano adecuado para su implementación en

2
En marzo de 2019, Google lanzó un Doodle con una red neuronal que puede componer música al estilo de Johann
Sebastian Bach (http://mng.bz/MOQW). La red neuronal se ejecuta en el navegador, con la tecnología de TensorFlow.js. El
modelo se cuantifica como números enteros de 8 bits con el método descrito en esta sección, que reduce el tamaño del
modelo por cable varias veces, hasta unos 380 KB. Sin esta cuantificación, sería imposible servir el modelo a una
audiencia tan amplia como la de la página de inicio de Google (donde aparecen los Doodles de Google).

3
Kaiming He et al., "Deep Residual Learning for Image Recognition", presentado el 10 de diciembre de 2015,https://arxiv.
org/abs/1512.03385.
4
Johan Schalkwyk, "An All-Neural On-Device Speech Recognizer", Google AI Blog, 12 de marzo de 2019,http://mng. bz/ad67
.
5
Jacob Devlin et al., "BERT: Entrenamiento previo de transformadores bidireccionales profundos para la comprensión del lenguaje",
presentado el 11 de octubre de 2018,https://arxiv.org/abs/1810.04805.
6
Tero Karras, Samuli Laine y Timo Aila, ”A Style-Based Generator Architecture for Generative Adversarial Networks”,
presentado el 12 de diciembre de 2018,https://arxiv.org/abs/1812.04948.
7
Mark Sandler et al., "MobileNetV2: Residuales invertidos y cuellos de botella lineales", Conferencia IEEE sobre visión
artificial y reconocimiento de patrones (CVPR), 2018, págs. 4510–4520,http://mng.bz/NeP7.
Optimización del modelo 427

entornos con recursos restringidos, como navegadores web y dispositivos móviles. La precisión de MobileNetV2
es ligeramente peor en comparación con la de una imagen más grande preparada para las mismas tareas, como
ResNet50. Pero su tamaño (14 MB) es algunas veces más pequeño en comparación (ResNet50 tiene un tamaño
de aproximadamente 100 MB), lo que hace que la ligera reducción en la precisión sea una compensación valiosa.

Incluso con su reducción de tamaño incorporada, MobileNetV2 sigue siendo un poco demasiado grande para
la mayoría de las aplicaciones de JavaScript. Considere el hecho de que su tamaño (14 MB) es aproximadamente
ocho veces el tamaño de una página web promedio.8MobileNetV2 ofrece un parámetro de ancho que, si se
establece en un valor inferior a 1, reduce el tamaño de todas las capas convolucionales y, por lo tanto,
proporciona una mayor reducción del tamaño (y una mayor pérdida de precisión). Por ejemplo, la versión de
MobileNetV2 con su ancho establecido en 0,25 es aproximadamente una cuarta parte del tamaño del modelo
completo (3,5 MB). Pero incluso eso puede ser inaceptable para los sitios web de alto tráfico que son sensibles a
los aumentos en el peso de la página y el tiempo de carga.
¿Hay alguna manera de reducir aún más el tamaño de tales modelos? Por suerte, la respuesta es sí.
Esto nos lleva al segundo enfoque mencionado, la optimización del tamaño independiente del modelo.
Las técnicas de esta categoría son más genéricas porque no requieren cambios en la arquitectura del
modelo en sí y, por lo tanto, deberían ser aplicables a una amplia variedad de redes neuronales
profundas existentes. La técnica en la que nos centraremos específicamente aquí se llamacuantificación
del peso post-entrenamiento. La idea es simple: después de entrenar un modelo, almacene sus
parámetros de peso con una precisión numérica más baja. El cuadro de información 12.1 describe cómo
se hace esto para los lectores interesados en las matemáticas subyacentes.

ICAJA NFO12.1 Las matemáticas detrás de la cuantificación del peso después del entrenamiento
Los parámetros de peso de una red neuronal se representan como números de coma
flotante de 32 bits (float32) durante el entrenamiento. Esto es cierto no solo en TensorFlow.js
sino también en otros marcos de aprendizaje profundo como TensorFlow y PyTorch. Esta
representación relativamente costosa generalmente está bien porque el entrenamiento del
modelo generalmente ocurre en entornos con recursos ilimitados (por ejemplo, el entorno
de back-end de una estación de trabajo equipada con amplia memoria, CPU rápidas y GPU
CUDA). Sin embargo, los hallazgos empíricos indican que para muchos casos de uso de
inferencia, podemos reducir la precisión de los pesos sin causar una disminución sustancial
en la precisión. Para reducir la precisión de la representación, asignamos cada valor float32 a
un valor entero de 8 o 16 bits que representa la ubicación discretizada del valor dentro del
rango de todos los valores en el mismo peso.cuantización.

8
Según HTTP Archive, el peso promedio de la página (tamaño de transferencia total de HTML, CSS, JavaScript, imágenes y otros archivos
estáticos) es de aproximadamente 1828 KB para escritorio y 1682 KB para dispositivos móviles a partir de mayo de 2019:https://httparchivo.
org/informes/peso-de-la-pagina.
428 CPASADO12Probar, optimizar e implementar modelos

(continuado)
En TensorFlow.js, la cuantificación del peso se realiza peso por peso. Por ejemplo,
si una red neuronal consta de cuatro variables de peso (como los pesos y sesgos
de dos capas densas), cada uno de los pesos se cuantificará como un todo. La
ecuación que gobierna la cuantificación de un peso es

cuantificar-w- =piso--w–wmínimo- -wEscala-2B- (Ecuación 12.1)

En esta ecuación,Bes la cantidad de bits en los que se almacenará el resultado de la


cuantificación. Puede ser 8 o 16, según lo admita actualmente TensorFlow.js.wmínimoes el
valor mínimo de los parámetros del peso.wEscalaes el rango de los parámetros (la diferencia
entre el mínimo y el máximo). La ecuación es válida, por supuesto, sólo cuandowEscalaes
distinto de cero. En los casos especiales en quewEscalaes cero, es decir, cuando todos los
parámetros del peso tienen el mismo valor, cuantificar (w) devolverá 0 para todosw's.

Los dos valores auxiliareswmínimoywEscalase guardan junto con los valores de peso cuantificados para
respaldar la recuperación de los pesos (un proceso al que nos referimos comodescuantificación) durante
la carga del modelo. La ecuación que gobierna la descuantificación es la siguiente:

descuantificar-v- =v-2B-wEscala+wmínimo (Ecuación 12.2)

Esta ecuación es válida tanto siwEscalaes cero

La cuantificación posterior al entrenamiento proporciona una reducción considerable en el tamaño del modelo: la
cuantificación de 16 bits reduce el tamaño del modelo en aproximadamente un 50 %, la cuantificación de 8 bits
en un 75 %. Estos porcentajes son aproximados por dos razones. En primer lugar, se dedica una fracción del
tamaño del modelo a la topología del modelo, tal como se codifica en el archivo JSON. En segundo lugar, como se
indica en el cuadro de información, la cuantificación requiere el almacenamiento de dos valores de números
flotantes adicionales (wmínimoywEscala), junto con un nuevo valor entero (los bits de cuantificación). Sin embargo,
estos suelen ser menores en comparación con la reducción en el número de bits utilizados para representar los
parámetros de peso.
La cuantización es una transformación con pérdida. Parte de la información de los valores de peso
originales se pierde como resultado de la disminución de la precisión. Es similar a reducir la profundidad
de bits de una imagen en color de 24 bits a una de 8 bits (del tipo que puede haber visto en las consolas
de juegos de Nintendo de la década de 1980), cuyo efecto es fácilmente visible para los ojos humanos. La
figura 12.2 proporciona comparaciones intuitivas del grado de discretización al que conduce la
cuantificación de 16 y 8 bits. Como era de esperar, la cuantificación de 8 bits conduce a una
representación más detallada de los pesos originales. Con la cuantificación de 8 bits, solo hay 256 valores
posibles en todo el rango de parámetros de un peso, en comparación con los 65 536 valores posibles con
la cuantificación de 16 bits. Ambos son reducciones dramáticas en la precisión en comparación con la
representación flotante de 32 bits.
En la práctica, ¿realmente importa la pérdida de precisión en los parámetros de peso? Cuando se trata
del despliegue de una red neuronal, lo que importa es su precisión en los datos de prueba.
Optimización del modelo 429

UN. B. C.

Figura 12.2 Ejemplos de cuantificación de pesos de 16 y 8 bits. Se reduce el tamaño de una función de identidad original (y = x, panel
A) con cuantificación de 16 y 8 bits; los resultados se muestran en los paneles B y C, respectivamente. Para que los efectos de
cuantización sean visibles en la página, ampliamos una pequeña sección de la función de identidad en las cercanías deX=0.

Para responder a esta pregunta, compilamos una serie de modelos que cubren diferentes tipos de
tareas en el ejemplo de cuantificación de tfjs-examples. Puede ejecutar los experimentos de
cuantización allí y ver los efectos por sí mismo. Para comprobar el ejemplo, utilice

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-ejemplos/


cuantificación
discos compactos

hilo

El ejemplo contiene cuatro escenarios, cada uno de los cuales muestra una combinación única de un
conjunto de datos y el modelo aplicado en el conjunto de datos. El primer escenario consiste en predecir
los precios promedio de la vivienda en las regiones geográficas de California mediante el uso de
características numéricas como la edad media de las propiedades, el número total de habitaciones, etc. El
modelo es una red de cinco capas que incluye capas de abandono para mitigar el sobreajuste. Para
entrenar y guardar el original (modelo no cuantificado), use este comando:

caja de tren de hilo

El siguiente comando realiza una cuantificación de 16 y 8 bits en el modelo guardado y evalúa cómo los
dos niveles de cuantificación afectan la precisión del modelo en un conjunto de prueba (un subconjunto
de los datos que no se ven durante el entrenamiento del modelo):

hilo cuantizar-y-evaluar-vivienda

Este comando incluye muchas acciones para facilitar su uso. Sin embargo, el paso clave que realmente
cuantifica el modelo se puede ver en el script de shell en quantization/quantize_evaluate.sh. En el script,
puede ver el siguiente comando de shell que cuantifica un modelo en la rutaMODEL_JSON_PATHcon
cuantificación de 16 bits. Puede seguir el ejemplo de este comando para cuantificar sus propios modelos
guardados en TensorFlow.js. Si la bandera de opción
- - bytes_de_cuantificaciónse establece en1en su lugar, se realizará una cuantificación de 8 bits:

tensorflowjs_convertidor \
- - formato_entrada tfjs_layers_model \
430 CPASADO12Probar, optimizar e implementar modelos

- - formato_salida tfjs_layers_model \
- - quantization_bytes 2 \ "${MODEL_JSON_PATH}" "$
{MODEL_PATH_16BIT}"

El comando anterior muestra cómo realizar la cuantificación del peso en un modelo


entrenado en JavaScript.tensorflowjs_convertertambién admite la cuantificación del peso al
convertir modelos de Python a JavaScript, cuyos detalles se muestran en el cuadro de
información 12.2.

ICAJA NFO12.2 Cuantificación de peso y modelos de Python


En el capítulo 5, mostramos cómo los modelos de Keras (Python) se pueden convertir a un formato
que TensorFlow.js puede cargar y usar. Durante dicha conversión de Python a JavaScript, puede
aplicar la cuantificación de peso. Para hacer eso, use el mismo --cuantización_ bytesmarca como se
describe en el texto principal. Por ejemplo, para convertir un modelo en formato HDF5 (.h5)
guardado por Keras con cuantización de 16 bits, use el siguiente comando:

tensorflowjs_converter \
- - formato_entrada queras \
- - formato de salida tfjs_layers_model \
- - bytes_de_cuantificación 2 \
"${KERAS_MODEL_H5_PATH}" "${TFJS_MODEL_PATH}"

En este comando,KERAS_MODEL_H5_PATHes la ruta al modelo exportado por Keras,


mientras queTFJS_MODEL_PATHes la ruta en la que se generará el modelo convertido y
cuantificado en peso.

Los valores de precisión detallados que obtenga variarán ligeramente de una ejecución a otra debido a la
inicialización aleatoria de los pesos y la combinación aleatoria de lotes de datos durante el entrenamiento. Sin
embargo, la conclusión general siempre debe ser válida: como se muestra en la primera fila de la tabla 12.1, la
cuantificación de 16 bits en los pesos conduce a cambios minúsculos en el MAE de la predicción del precio de la
vivienda, mientras que la cuantificación de 8 bits conduce a un relativamente mayor ( pero todavía minúsculo en
términos absolutos) aumento en el MAE.

Tabla 12.1 Precisión de evaluación para cuatro modelos diferentes con cuantificación del peso posterior al entrenamiento

Pérdida de evaluación y precisión sin cuantificación y


diferentes niveles de cuantización
Conjunto de datos y modelo

Precisión completa de 32 bits


cuantización de 16 bits cuantización de 8 bits
(sin cuantificación)

Vivienda en California: MAEa= 0.311984 MAE = 0,311983 MAE = 0.312780


regresor MLP

MNIST: convnet Precisión = 0,9952 Precisión = 0,9952 Precisión = 0,9952

Moda-MNIST: convnet Precisión = 0,922 Precisión = 0,922 Precisión = 0,9211

Subconjunto ImageNet de 1000: Precisión de los 1 principales = 0,618 Precisión de los 1 principales = 0,624 Precisión de los 1 principales = 0,280

MobileNetV2 Precisión de los 5 principales = 0,788 Precisión de los 5 principales = 0,789 Precisión de los 5 principales = 0,490

un. La función de pérdida MAE se utiliza en el modelo de vivienda de California. Más bajo es mejor para MAE, a diferencia de la precisión.
Optimización del modelo 431

El segundo escenario en el ejemplo de cuantificación se basa en el conjunto de datos MNIST familiar y la


arquitectura de convnet profunda. De manera similar al experimento de alojamiento, puede entrenar el
modelo original y realizar una evaluación en versiones cuantificadas del mismo mediante los siguientes
comandos:

hilo tren-mnistyarn cuantizar-y-evaluar-mnist

Como muestra la segunda fila de la tabla 12.1, ni la cuantificación de 16 bits ni la de 8 bits conducen a
ningún cambio observable en la precisión de la prueba del modelo. Esto refleja el hecho de que el convnet
es un clasificador multiclase, por lo que pequeñas desviaciones en los valores de salida de su capa pueden
no alterar el resultado final de la clasificación, que se obtiene con unargMax()operación.
¿Este hallazgo es representativo de los clasificadores multiclase orientados a imágenes? Tenga en
cuenta que MNIST es un problema de clasificación relativamente fácil. Incluso un convnet simple como el
que se usa en este ejemplo logra una precisión casi perfecta. ¿Cómo afecta la cuantización a las
precisiones cuando nos enfrentamos a un problema más difícil de clasificación de imágenes? Para
responder a esta pregunta, mire los otros dos escenarios en el ejemplo de cuantización.
Fashion-MNIST, que encontró en la sección sobre codificadores automáticos variacionales en el
capítulo 10, es un problema más difícil que MNIST. Al usar los siguientes comandos, puede entrenar un
modelo en el conjunto de datos Fashion-MNIST y examinar cómo la cuantificación de 16 y 8 bits afecta la
precisión de la prueba:

hilo tren-moda-mnist
hilo cuantificar-y-evaluar-moda-mnist

El resultado, que se muestra en la tercera fila de la tabla 12.1, indica que hay una pequeña disminución en
la precisión de la prueba (del 92,2 % al 92,1 %) causada por la cuantificación de 8 bits de los pesos, aunque
la cuantificación de 16 bits aún lleva la delantera. a ningún cambio observable.
Un problema de clasificación de imágenes aún más difícil es el problema de clasificación de
ImageNet, que involucra 1000 clases de salida. En este caso, descargamos un MobileNetV2
preentrenado en lugar de entrenar uno desde cero, como lo hacemos en los otros tres escenarios
de este ejemplo. El modelo preentrenado se evalúa en una muestra de 1000 imágenes del conjunto
de datos de ImageNet, en sus formas no cuantificadas y cuantificadas. Optamos por no evaluar
todo el conjunto de datos de ImageNet porque el conjunto de datos en sí es enorme (con millones
de imágenes) y la conclusión que sacaríamos de eso no sería muy diferente.

Para evaluar la precisión del modelo en el problema de ImageNet de una manera más integral,
calculamos las precisiones de los 1 primeros y los 5 primeros. La precisión Top-1 es la proporción de
predicciones correctas cuando solo se considera la salida logit única más alta del modelo, mientras que la
precisión Top-5 cuenta una predicción como correcta si cualquiera de los cinco logit más altos incluye la
etiqueta correcta. Este es un enfoque estándar para evaluar la precisión de los modelos en ImageNet
porque, debido a la gran cantidad de etiquetas de clase, algunas de las cuales están muy cerca unas de
otras, los modelos a menudo muestran la etiqueta correcta no en el logit superior, sino en uno de los
primeros. -5 logits. Para ver el experimento MobileNetV2 + ImageNet en acción, utilice

hilo cuantificar y evaluar-MobileNetV2


432 CPASADO12Probar, optimizar e implementar modelos

A diferencia de los tres escenarios anteriores, este experimento muestra un impacto sustancial de 8 bits
en la precisión de la prueba (consulte la cuarta fila de la tabla 12.1). Las precisiones de los primeros 1 y 5
primeros de MobileNet cuantificado de 8 bits están muy por debajo del modelo original, lo que hace que
la cuantificación de 8 bits sea una opción de optimización de tamaño inaceptable para MobileNet. Sin
embargo, MobileNet cuantificado de 16 bits todavía muestra precisiones comparables al modelo no
cuantificado.9Podemos ver que el efecto de la cuantización en la precisión depende del modelo y los
datos. Para algunos modelos y tareas (como nuestro convnet MNIST), ni la cuantificación de 16 bits ni la
de 8 bits conduce a una reducción observable en la precisión de la prueba. En estos casos, deberíamos
usar el modelo cuantificado de 8 bits durante la implementación para disfrutar del tiempo de descarga
reducido. Para algunos modelos, como nuestra convnet Fashion-MNIST y nuestro modelo de regresión
del precio de la vivienda, la cuantificación de 16 bits no produce un deterioro observado en la precisión,
pero la cuantificación de 8 bits conduce a un ligero empeoramiento de la precisión. En tales casos, utilice
su criterio para determinar si la reducción adicional del 25 % en el tamaño del modelo compensa la
disminución de la precisión. Finalmente, para algunos tipos de modelos y tareas (como nuestra
clasificación MobileNetV2 de imágenes ImageNet), La cuantificación de 8 bits provoca una gran
disminución de la precisión, lo que probablemente sea inaceptable en la mayoría de los casos. Para tales
problemas, debe seguir con el modelo original o la versión cuantificada de 16 bits.

Los casos en el ejemplo de cuantización son problemas de stock que pueden ser algo
simplistas. El problema que tiene entre manos puede ser más complejo y muy diferente
a esos casos. El mensaje final es que si cuantificar su modelo antes de implementarlo y
con qué profundidad de bits debe cuantificarlo son preguntas empíricas y solo pueden
responderse caso por caso. Debe probar la cuantificación y probar los modelos
resultantes en datos de prueba reales antes de tomar una decisión. El ejercicio 1 al final
de este capítulo le permite probar el MNIST ACGAN que entrenamos en el capítulo 10 y
decidir si la cuantificación de 16 u 8 bits es la decisión correcta para un modelo
generativo de este tipo.
WOCHO CUANTIZACIÓN Y COMPRESIÓN GZIP
Un beneficio adicional de la cuantificación de 8 bits que debe tenerse en cuenta es la
reducción adicional del tamaño del modelo por cable que proporciona bajo técnicas de
compresión de datos como gzip. gzip se usa ampliamente para entregar archivos
grandes a través de la web. Siempre debe habilitar gzip cuando entregue archivos
modelo de TensorFlow.js en la web. Los pesos float32 no cuantificados de una red
neuronal generalmente no son muy susceptibles a dicha compresión debido a la
variación similar al ruido en los valores de los parámetros, que contiene pocos patrones
repetitivos. Nuestra observación es que gzip normalmente no puede obtener más de un
10-20 % de reducción de tamaño de los pesos no cuantificados para los modelos. Lo
mismo ocurre con los modelos con cuantificación de peso de 16 bits. Sin embargo, una
vez que los pesos de un modelo se someten a una cuantificación de 8 bits,

9
De hecho, podemos ver pequeñasaumentaen precisión, que son atribuibles a la fluctuación aleatoria en el conjunto de prueba
relativamente pequeño que consta de solo 1,000 ejemplos.
Optimización del modelo 433

Esto se debe a la pequeña cantidad de contenedores disponibles con la precisión drásticamente


reducida (solo 256), lo que hace que muchos valores (como los que están alrededor de 0) caigan en el
mismo contenedor y, por lo tanto, genera más patrones repetitivos en el binario del peso. representación.
Esta es una razón adicional para preferir la cuantificación de 8 bits en los casos en que no provoque un
deterioro inaceptable de la precisión de la prueba.

Tabla 12.2 Las relaciones de compresión gzip de los artefactos del modelo bajo diferentes niveles de cuantificación

relación de compresión gzipa

Conjunto de datos y modelo


Precisión completa de 32 bits
cuantización de 16 bits cuantización de 8 bits
(sin cuantificación)

California-vivienda: 1.121 1.161 1.388


regresor MLP

MNIST: convnet 1.082 1.037 1.184

Moda-MNIST: convnet 1.078 1.048 1.229

Subconjunto ImageNet de 1000: 1.085 1.063 1.271


MobileNetV2

un. (tamaño total del archivo model.json y de peso)/(tamaño de la bola de alquitrán comprimida con gzip)

En resumen, con la cuantificación del peso posterior al entrenamiento, podemos reducir sustancialmente el tamaño de los modelos de TensorFlow.js

transferidos por cable y almacenados en el disco, especialmente con la ayuda de técnicas de compresión de datos como gzip. Este beneficio de relaciones de

compresión mejoradas no requiere ningún cambio de código por parte del desarrollador, ya que el navegador realiza la descompresión de forma transparente

cuando descarga los archivos del modelo. Sin embargo, no cambia la cantidad de cómputo involucrado en la ejecución de las llamadas de inferencia del

modelo. Tampoco cambia la cantidad de consumo de memoria de CPU o GPU para tales llamadas. Esto se debe a que los pesos se descuantifican después de

cargarlos (consulte la ecuación 12.2 en el cuadro de información 12.1). En cuanto a las operaciones que se ejecutan y los tipos de datos y formas de los

tensores generados por las operaciones, no hay diferencia entre un modelo no cuantificado y un modelo cuantificado. Sin embargo, para la implementación

de modelos, una preocupación igualmente importante es cómo hacer que un modelo se ejecute lo más rápido posible, así como hacer que consuma la menor

cantidad de memoria posible cuando se ejecuta, porque eso mejora la experiencia del usuario y reduce el consumo de energía. ¿Hay formas de hacer que un

modelo TensorFlow.js existente se ejecute más rápido cuando se implementa, sin pérdida de precisión de predicción y además de la optimización del tamaño

del modelo? Por suerte, la respuesta es sí. En la siguiente sección, nos centraremos en las técnicas de optimización de velocidad de inferencia que proporciona

TensorFlow.js. porque eso mejora la experiencia del usuario y reduce el consumo de energía. ¿Hay formas de hacer que un modelo TensorFlow.js existente se

ejecute más rápido cuando se implementa, sin pérdida de precisión de predicción y además de la optimización del tamaño del modelo? Por suerte, la

respuesta es sí. En la siguiente sección, nos centraremos en las técnicas de optimización de velocidad de inferencia que proporciona TensorFlow.js. porque eso

mejora la experiencia del usuario y reduce el consumo de energía. ¿Hay formas de hacer que un modelo TensorFlow.js existente se ejecute más rápido cuando

se implementa, sin pérdida de precisión de predicción y además de la optimización del tamaño del modelo? Por suerte, la respuesta es sí. En la siguiente

sección, nos centraremos en las técnicas de optimización de velocidad de inferencia que proporciona TensorFlow.js.
434 CPASADO12Probar, optimizar e implementar modelos

12.2.2 Optimización de la velocidad de inferencia mediante la conversión de GraphModel

Esta sección está organizada de la siguiente manera. Primero presentaremos los pasos involucrados en la
optimización de la velocidad de inferencia de un modelo TensorFlow.js usando elModelo gráfico
conversión. A continuación, enumeraremos medidas de rendimiento detalladas que cuantifican la
ganancia de velocidad proporcionada por este enfoque. Finalmente, explicaremos cómo elModelo gráfico
enfoque de conversión funciona bajo el capó.
Suponga que tiene un modelo TensorFlow.js guardado en la ruta my/layers-model; puede
usar el siguiente comando para convertirlo en untf.GraphModel:

tensorflowjs_converter \
- - formato_entrada tfjs_layers_model \
- - formato de salida tfjs_graph_model \
mi/capas-modelo mi/grafo-
modelo

Este comando crea un archivo model.json en el directorio de salida my/graph-model (el


directorio se creará si no existe), junto con varios archivos binarios de peso. Superficialmente,
este conjunto de archivos puede parecer idéntico en formato a los archivos en el directorio
de entrada que contiene el serializado.tf.LayersModel.Sin embargo, los archivos de salida
codifican un tipo diferente de modelo llamadotf.GraphModel (homónimo de este método de
optimización). Para cargar el modelo convertido en el navegador o Node.js, use el método
TensorFlow.jstf.loadGraphModel()en lugar de lo familiar
tf.loadLayersModel().Una vez eltf.GraphModelel objeto está cargado, puede
realizar inferencias exactamente de la misma manera que untf.LayersModelinvocando el
objetopredecir()método. Por ejemplo,
const model = await tf.loadGraphModel('file://./my/graph-model/model.json');
const ys = modelo.predecir(xs);
O use una URL http:// o https:// si Realice la inferencia utilizando los

carga el modelo en el navegador. datos de entrada 'xs'.

La velocidad de inferencia mejorada viene con dos limitaciones:

- Al momento de escribir este artículo, la última versión de TensorFlow.js (1.1.2) no


soportar capas recurrentes tales comotf.capas.simpleRNN(), tf.capas.gru(),
ytf.capas.lstm() (véase el capítulo 9) paraModelo gráficoconversión.
-el cargadotf.GraphModelel objeto no tieneencajar()método y, por lo tanto, no admite
capacitación adicional (por ejemplo, transferencia de aprendizaje).

La tabla 12.3 compara la velocidad de inferencia de los dos tipos de modelos con y sin Modelo
gráficoconversión. Ya queModelo gráficola conversión aún no admite capas recurrentes, solo se
presentan los resultados de un MLP y un convnet (MobileNetV2). Para cubrir diferentes entornos de
implementación, la tabla presenta los resultados tanto del navegador web como del tfjs-node que
se ejecutan en el entorno de back-end. De esta tabla, podemos ver queModelo gráficola conversión
invariablemente acelera la inferencia. Sin embargo, la proporción de la aceleración depende del
tipo de modelo y el entorno de implementación. Para el entorno de implementación del navegador
(WebGL),Modelo gráficola conversión lleva a una aceleración del 20 al 30 %, mientras que la
aceleración es más drástica (70 al 90 %) si el entorno de implementación
Optimización del modelo 435

es Node.js. A continuación, discutiremos por quéModelo gráficola conversión acelera la inferencia,


así como la razón por la que acelera la inferencia más para Node.js que para el entorno del
navegador.

Tabla 12.3 Comparación de la velocidad de inferencia de dos tipos de modelos (un MLP y MobileNetV2) con y sin Modelo
gráficooptimización de conversión y en diferentes entornos de implementacióna

predecir () tiempo (ms; cuanto más bajo, mejor)


(Promedio de más de 30 llamadas predict() precedidas por 20 llamadas de calentamiento)
Modelo
nombre y
Navegador WebGL tfjs-nodo (solo CPU) tfjs-nodo-gpu
topología

CapasModelo Modelo gráfico CapasModelo Modelo gráfico CapasModelo Modelo gráfico

MLPB 13 10 (1,3x) 18 10 (1,8x) 3 1.6 (1.9x)

Móvil- 68 57 (1,2x) 187 111 (1,7x) 66 39 (1,7x)


NetV2
(ancho =
1.0)

un. El código con el que se obtuvieron estos resultados está disponible enhttps://github.com/tensorflow/tfjs/tree/master/tfjs/integration_tests/.

B. El MLP consta de capas densas con recuentos de unidades: 4000, 1000, 5000 y 1. Las primeras tres capas tienen activación relu; el último
tiene activación lineal.

HAYGRAMORAFAMETROLA CONVERSIÓN ODEL ACELERA LA INFERENCIA DEL MODELO


CómoModelo gráfico¿La conversión aumenta la velocidad de inferencia de los modelos
TensorFlow.js? Se logra aprovechando el análisis anticipado de TensorFlow (Python) del
gráfico de cálculo del modelo con una granularidad fina. El análisis del gráfico de cómputo es
seguido por modificaciones al gráfico que reducen la cantidad de cómputo mientras se
preserva la corrección numérica del resultado de salida del gráfico. No se deje intimidar por
términos comoanálisis anticipadoygranularidad fina. Los explicaremos en un momento.
Para dar un ejemplo concreto del tipo de modificación de gráfico del que estamos hablando,
consideremos cómo funciona una capa de BatchNormalization en untf.LayersModely un
tf.GraphModel.Recuerde que BatchNormalization es un tipo de capa que mejora la convergencia y
reduce el sobreajuste durante el entrenamiento. Está disponible en la API de TensorFlow.js como
tf.layers.batchNormalización()y es utilizado por modelos preentrenados populares como
MobileNetV2. Cuando una capa BatchNormalization se ejecuta como parte de untf.LayersModel, el
cálculo sigue de cerca la definición matemática de normalización por lotes:

producción= -X–significar- - -sqrt-variable- +épsilon- -gama+beta


(Ecuación 12.3)

Se necesitan seis operaciones (u ops) para generar la salida a partir de la entrada


(X),en el orden aproximado de

1 cuadrado,convariablecomo entrada

2 agregar,conépsilony el resultado del paso 1 como entradas


436 CPASADO12Probar, optimizar e implementar modelos

3 sub,conXy medios como entradas


4 división,con los resultados de los pasos 2 y 3 como entradas

5 mul,congamay el resultado del paso 4 como entradas

6 agregar,conbetay el resultado del paso 5 como entradas

Con base en reglas aritméticas simples, se puede ver que la ecuación 12.3 se puede simplificar
significativamente, siempre que los valores demedia, var, épsilon, gamma,ybetason constantes (no
cambian con la entrada o con cuántas veces se ha invocado la capa). Después de entrenar un
modelo que comprende una capa BatchNormalization, todas estas variables se vuelven constantes.
Esto es exactamente lo queModelo gráficola conversión sí lo hace: “dobla” las constantes y
simplifica la aritmética, lo que conduce a la siguiente ecuación matemáticamente equivalente:

producción=X*k+B (Ecuación 12.4)

los valores dekyBse calculan duranteModelo gráficoconversión, no durante la


inferencia:

k=gama- -sqrt(var)+épsilon- (Ecuación 12.5)

B= –significar- -sqrt-variable- +épsilon- -gama+beta (Ecuación 12.6)

Por lo tanto, las ecuaciones 12.5 y 12.6 nonofactor en la cantidad de cálculo durante la
inferencia; sólo la ecuación 12.4 lo hace. Al contrastar las ecuaciones 12.3 y 12.4, puede ver
que el plegado constante y la simplificación aritmética reducen el número de operaciones de
seis a dos (unMulop entreXyky unagregarop entreBy el resultado de esoMuloperación), lo que
conduce a una aceleración considerable de la ejecución de esta capa. Pero ¿por qué
tf.LayersModelno realizar esta optimización? Es porque necesita admitir el entrenamiento de
la capa BatchNormalization, durante la cual los valores de media, var, gamma,ybetase
actualizan en cada paso del entrenamiento.Modelo gráficoLa conversión aprovecha el hecho
de que estos valores actualizados ya no son necesarios una vez que se completa el
entrenamiento del modelo.
El tipo de optimización que se ve en el ejemplo de BatchNormalization solo es posible si se
cumplen dos requisitos. En primer lugar, el cálculo debe representarse en un nivel suficientemente
granularidad fina—es decir, al nivel de operaciones matemáticas básicas como agregarymul,en
lugar de la granularidad capa por capa más gruesa en la que reside la API de capas de
TensorFlow.js. En segundo lugar, todo el cálculo se conoce de antemano, antes de las llamadas a
los datos del modelo.predecir()se ejecuta el método.Modelo gráficola conversión pasa por
TensorFlow (Python), que tiene acceso a una representación gráfica del modelo que cumple con
ambos criterios.
Además de la optimización aritmética y de plegado constante discutida anteriormente,
Modelo gráficoconversión es capaz de realizar otro tipo de optimización llamado fusión
operativa. Tome el tipo de capa densa de uso frecuente (tf.capas.denso()),por ejemplo. Una
capa densa implica tres operaciones: una multiplicación de matrices (matMul)entre la entrada
Xy el núcleoW, una adición de radiodifusión entre el resultado de la
Optimización del modelo 437

MatMuly el sesgo (B), y la función de activación relu por elementos (figura 12.3, panel A). La optimización
op fusion reemplaza las tres operaciones separadas con una sola operación que lleva a cabo todos los
pasos equivalentes (figura 12.3, panel B). Este reemplazo puede parecer trivial, pero conduce a un
cómputo más rápido debido a 1) la reducción de los gastos generales de las operaciones de lanzamiento
(sí, el lanzamiento de una operación siempre implica una cierta cantidad de gastos generales,
independientemente del backend de cómputo) y 2) más oportunidad de realizar trucos inteligentes para
la optimización de la velocidad dentro de la implementación de la propia operación fusionada.

UN.sin op fusión B.con op fusión


B B
W W

matMul+Relú
MatMul

fusionado
Relú
agregar

X y X y

Figura 12.3 Ilustración esquemática de las operaciones internas en una capa densa,
con (panel A) y sin (panel B) fusión op

¿En qué se diferencia la optimización de la fusión operacional del plegado constante y la simplificación
aritmética que acabamos de ver? Op fusion requiere que el op fusionado especial (Fundido matMul+relu,en
este caso) estar definido y disponible para el backend de cómputo que se está utilizando, mientras que el
plegado constante no lo está. Estas operaciones fusionadas especiales pueden estar disponibles solo para
ciertos backends de cómputo y entornos de implementación. Esta es la razón por la que vimos una mayor
cantidad de aceleración de la inferencia en el entorno Node.js que en el navegador (consulte la tabla
12.3). El backend de cómputo de Node.js, que usa libtensorflow escrito en C++ y CUDA, está equipado con
un conjunto más completo de operaciones que el backend WebGL de TensorFlow.js en el navegador.

Además del plegado constante, la simplificación aritmética y la fusión de operaciones, el sistema


Grappler de optimización de gráficos de TensorFlow (Python) es capaz de realizar otros tipos de
optimizaciones, algunas de las cuales pueden ser relevantes para la forma en que se optimizan los
modelos de TensorFlow.js a través deModelo gráficoconversión. Sin embargo, no los cubriremos debido a
los límites de espacio. Si está interesado en obtener más información sobre este tema, puede leer las
diapositivas informativas de Rasmus Larsen y Tatiana Shpeisman que se enumeran al final de este
capítulo.
En resumen,Modelo gráficoLa conversión es una técnica proporcionada porconvertidor
tensorflowjs_.Utiliza la capacidad de optimización de gráficos anticipada de TensorFlow (Python)
para simplificar los gráficos de cálculo y reducir la cantidad de cálculo necesario para la inferencia
del modelo. Si bien la cantidad detallada de aceleración de inferencia varía según el tipo de modelo
y el backend de cómputo, generalmente proporciona una tasa de aceleración del 20 % o más y, por
lo tanto, es un paso recomendable para realizar en sus modelos TensorFlow.js antes de su
implementación.
438 CPASADO12Probar, optimizar e implementar modelos

ICAJA NFO12.3 Cómo medir correctamente el tiempo de inferencia de un modelo


TensorFlow.js
Ambas cosastf.LayersModelytf.GraphModelproporcionar el unificadopredecir()método para apoyar
la inferencia. Este método toma uno o más tensores como entrada y devuelve uno o más tensores
como resultado de la inferencia. Sin embargo, es importante tener en cuenta que en el contexto de
la inferencia basada en WebGL en el navegador web, elpredecir()método solamentehorarios
operaciones a ejecutar en la GPU; no espera la finalización de su ejecución. Como resultado, si
usted cronometra ingenuamente unpredecir()llamada de la siguiente manera, el resultado de la
medición del tiempo será incorrecto:

console.time('inferencia TFjs');
const outputTensor = model.predict(inputTensor); ¡Forma incorrecta de medir el

consola.timeEnd('TFjsinferencia'); tiempo de inferencia!

Cuándopredecir()regresa, es posible que las operaciones programadas no hayan terminado


de ejecutarse. Por lo tanto, el ejemplo anterior conducirá a una medición de tiempo más
corta que el tiempo real que lleva completar la inferencia. Para asegurarse de que las
operaciones se completan antesconsola.timeEnd()se llama, debe llamar a uno de los
siguientes métodos del objeto tensor devuelto:formación()odatos().Ambos métodos
descargan los valores de textura que contienen los elementos del tensor de salida de la GPU
a la CPU. Para ello, deben esperar a que finalice el cálculo del tensor de salida. Entonces, la
forma correcta de medir el tiempo es la siguiente:

console.time('inferencia TFjs');
const outputTensor = model.predict(inputTensor); esperar
salidaTensor.array();
La llamada a array() no regresará hasta que se
console.timeEnd('inferencia TFjs');
complete el cálculo programado de
outputTensor, lo que garantiza la corrección de
la medición del tiempo de inferencia.

Otra cosa importante a tener en cuenta es que, como todos los demás programas de JavaScript, el
tiempo de ejecución de la inferencia de un modelo de TensorFlow.js es variable. Para obtener una
estimación confiable del tiempo de inferencia, el código del fragmento anterior debe colocarse en
unporbucle para que la medición se pueda realizar varias veces (por ejemplo, 50 veces), y el tiempo
promedio se puede calcular en función de las mediciones individuales acumuladas. Las primeras
ejecuciones suelen ser más lentas que las posteriores debido a la necesidad de compilar nuevos
programas de sombreado WebGL y configurar estados iniciales. Por lo tanto, el código de medición
del rendimiento a menudo omite las primeras ejecuciones (como las primeras cinco), a las que se
hace referencia comoquemadoocalentamientocarreras.

Si está interesado en una comprensión más profunda de estas técnicas de evaluación comparativa
del desempeño, realice el ejercicio 3 al final de este capítulo.
Implementación de modelos de TensorFlow.js en varias plataformas y entornos 439

12.3 Implementación de modelos de TensorFlow.js en varias


plataformas y entornos
Ha optimizado su modelo, es rápido y liviano, y todas sus pruebas son verdes. ¡Eres
bueno para ir! ¡Hurra! Pero antes de abrir ese champán, hay un poco más de trabajo por
hacer.
Es hora de poner su modelo en su aplicación y mostrarlo frente a su base de usuarios. En esta sección,
cubriremos algunas plataformas de implementación. La implementación en la web y la implementación
en un servicio de Node.js son rutas bien conocidas, pero también cubriremos algunos escenarios de
implementación más exóticos, como la implementación en una extensión de navegador o una aplicación
de hardware integrada de placa única. Señalaremos ejemplos simples y discutiremos consideraciones
especiales importantes para las plataformas.

12.3.1 Consideraciones adicionales al implementar en la web


Comencemos revisando el escenario de implementación más común para los modelos de TensorFlow.js,
implementando en la web como parte de una página web. En este escenario, nuestro modelo entrenado,
y posiblemente optimizado, se carga a través de JavaScript desde alguna ubicación de alojamiento, y
luego el modelo hace predicciones usando el motor de JavaScript dentro del navegador del usuario. Un
buen ejemplo de este patrón es el ejemplo de clasificación de imágenes de MobileNet del capítulo 5. El
ejemplo también está disponible para descargar desde tfjs-examples/mobilenet. Como recordatorio, el
código relevante para cargar un modelo y hacer una predicción se puede resumir de la siguiente manera:

constante MOBILENET_MODEL_PATH =
'https://storage.googleapis.com/tfjsmodels/tfjs/
mobilenet_v1_0.25_224/model.json';
const mobilenet = esperar tf.loadLayersModel(MOBILENET_MODEL_PATH); const
respuesta = mobilenet.predict(userQueryAsTensor);

Este modelo está alojado desde un depósito de Google Cloud Platform (GCP). Para aplicaciones
estáticas de poco tráfico como esta, es fácil alojar el modelo de forma estática junto con el resto del
contenido del sitio. Las aplicaciones más grandes y de mayor tráfico pueden optar por alojar el
modelo a través de una red de entrega de contenido (CDN) junto con otros activos pesados. Un
error de desarrollo común es olvidarse de tener en cuenta el intercambio de recursos de origen
cruzado (CORS) al configurar un depósito en GCP, Amazon S3 u otros servicios en la nube. Si CORS
está configurado incorrectamente, el modelo no se cargará y debería recibir un mensaje de error
relacionado con CORS en la consola. Esto es algo a tener en cuenta si su aplicación web funciona
bien localmente pero falla cuando se envía a su plataforma de distribución.
Después de que el navegador del usuario cargue HTML y JavaScript, el intérprete de JavaScript emitirá la
llamada para cargar nuestro modelo. El proceso de carga de un modelo pequeño lleva unos cientos de
milisegundos en un navegador moderno con una buena conexión a Internet, pero después de la carga inicial, el
modelo se puede cargar mucho más rápido desde la memoria caché del navegador. El formato de serialización
garantiza que el modelo se fragmente en partes lo suficientemente pequeñas como para admitir el límite de
caché del navegador estándar.
440 CPASADO12Probar, optimizar e implementar modelos

Una buena propiedad de la implementación web es que la predicción ocurre directamente


dentro del navegador. Los datos que se pasan al modelo nunca se envían por cable, lo que es
bueno para la latencia y excelente para la privacidad. Imagine un escenario de predicción de
entrada de texto en el que el modelo predice la siguiente palabra para la escritura asistida, algo
que vemos todo el tiempo, por ejemplo, en Gmail. Si necesitamos enviar el texto escrito a
servidores en la nube y esperar una respuesta de esos servidores remotos, la predicción se
retrasará y las predicciones de entrada serán mucho menos útiles. Además, algunos usuarios
podrían considerar que enviar sus pulsaciones de teclas incompletas a una computadora remota es
una invasión de su privacidad. Hacer predicciones localmente en su propio navegador es mucho
más seguro y sensible a la privacidad.
Una desventaja de hacer predicciones dentro del navegador es la seguridad del modelo. Enviar
el modelo al usuario facilita que el usuario conserve el modelo y lo utilice para otros fines.
TensorFlow.js actualmente (a partir de 2019) no tiene una solución para la seguridad del modelo en
el navegador. Algunos otros escenarios de implementación dificultan que el usuario use el modelo
para propósitos que el desarrollador no pretendía. La ruta de distribución con la mayor seguridad
del modelo es mantener el modelo en los servidores que usted controla y atender las solicitudes de
predicción desde allí. Por supuesto, esto tiene un costo de latencia y privacidad de datos. Equilibrar
estas preocupaciones es una decisión de producto.

12.3.2 Implementación en servicios en la nube

Muchos sistemas de producción existentes brindan predicción entrenada en aprendizaje


automático como servicio, como Google Cloud Vision AI (https://cloud.google.com/vision) o
Servicios cognitivos de Microsoft (https://azure.microsoft.com/en-us/services/cognitive-
services). El usuario final de dicho servicio realiza solicitudes HTTP que contienen los valores
de entrada para la predicción, como una imagen para una tarea de detección de objetos, y la
respuesta codifica la salida de la predicción, como las etiquetas y posiciones de los objetos en
la imagen. .
A partir de 2019, existen dos rutas para servir un modelo TensorFlow.js desde un servidor. La
primera ruta tiene el servidor ejecutando Node.js y realizando la predicción usando el tiempo de
ejecución nativo de JavaScript. Debido a que TensorFlow.js es tan nuevo, no conocemos casos de
uso de producción que hayan elegido este enfoque, pero las pruebas de concepto son fáciles de
construir.
La segunda ruta es convertir el modelo de TensorFlow.js a un formato que se pueda servir
desde una tecnología de servidor existente conocida, como el sistema TensorFlow Serving
estándar. De la documentación enwww.tensorflow.org/tfx/guide/serving:

TensorFlow Serving es un sistema de servicio flexible y de alto rendimiento para modelos de aprendizaje
automático, diseñado para entornos de producción. TensorFlow Serving facilita la implementación de
nuevos algoritmos y experimentos, manteniendo la misma arquitectura de servidor y API. TensorFlow
Serving proporciona una integración lista para usar con los modelos de TensorFlow, pero se puede
ampliar fácilmente para servir a otros tipos de modelos y datos.

Los modelos de TensorFlow.js que hemos serializado hasta ahora se han almacenado en un formato
específico de JavaScript. TensorFlow Serving espera que los modelos se empaqueten en TensorFlow
Implementación de modelos de TensorFlow.js en varias plataformas y entornos 441

formato estándar de modelo guardado. Afortunadamente, el proyecto tfjs-converter facilita la


conversión al formato necesario.
En el capítulo 5 (aprendizaje de transferencia) mostramos cómo los modelos guardados creados con la
implementación de Python de TensorFlow podrían usarse en TensorFlow.js. Para hacer lo contrario, primero
instala el paquete pip de tensorflowjs:

pip instalar tensorflowjs

A continuación, debe ejecutar el convertidor binario, especificando la entrada:

tensorflowjs_convertidor \
- - input_format=tfjs_layers_model \
- - formato_salida=keras_modelo_guardado / \
ruta/a/su/js/modelo.json \ /ruta/a/su/nuevo/
modelo-guardado

Esto creará un nuevo directorio de modelos guardados, que contendrá la topología y los
pesos requeridos en un formato que TensorFlow Serving entienda. Luego, debería poder
seguir las instrucciones para compilar el servidor TensorFlow Serving y realizar solicitudes de
predicción de gRPC en el modelo en ejecución. También existen soluciones administradas.
Por ejemplo, Google Cloud Machine Learning Engine proporciona una ruta para que cargue
su modelo guardado en Cloud Storage y luego configure el servicio como un servicio, sin
necesidad de mantener el servidor o la máquina. Puede obtener más información de la
documentación enhttps://cloud.google.com/ml-engine/docs/tensorflow/deploying-models.
La ventaja de servir su modelo desde la nube es que tiene el control total del modelo. Es fácil
realizar telemetría sobre qué tipo de consultas se están realizando y detectar problemas
rápidamente. Si se descubre que hay algún problema imprevisto con un modelo, se puede eliminar
o actualizar rápidamente, y hay poco riesgo de que se produzcan otras copias en máquinas fuera
de su control. La desventaja es la latencia adicional y las preocupaciones sobre la privacidad de los
datos, como se mencionó. También existe el costo adicional, tanto en desembolso monetario como
en costos de mantenimiento, al operar un servicio en la nube, ya que usted tiene el control de la
configuración del sistema.

12.3.3 Implementación en una extensión de navegador, como Chrome Extension

Algunas aplicaciones del lado del cliente pueden requerir que su aplicación pueda funcionar en
muchos sitios web diferentes. Los marcos de extensión del navegador están disponibles para todos
los principales navegadores de escritorio, incluidos Chrome, Safari y FireFox, entre otros. Estos
marcos permiten a los desarrolladores crear experiencias que modifican o mejoran la experiencia
de navegación agregando JavaScript nuevo y manipulando el DOM de los sitios web.

Dado que la extensión funciona sobre JavaScript y HTML dentro del motor de ejecución del navegador,
lo que puede hacer con TensorFlow.js en una extensión de navegador es similar a lo que es posible en
una implementación de página web estándar. La historia de seguridad del modelo y la historia de
privacidad de datos son idénticas a la implementación de la página web. Al realizar la predicción
directamente dentro del navegador, los datos de los usuarios están relativamente seguros. La historia de
la seguridad del modelo también es similar a la de la implementación web.
442 CPASADO12Probar, optimizar e implementar modelos

Como ejemplo de lo que es posible usando una extensión de navegador, vea el ejemplo de
extensión de cromo dentro de tfjs-examples. Esta extensión carga un modelo MobileNetV2 y
lo aplica a imágenes en la web, seleccionadas por el usuario. Instalar y usar la extensión es
un poco diferente de los otros ejemplos que hemos visto, ya que es una extensión, no un sitio
web alojado. Este ejemplo requiere el navegador Chrome.10
Primero, debe descargar y compilar la extensión, de forma similar a como podría compilar uno
de los otros ejemplos:

git clonar https://github.com/tensorflow/tfjs-examples.git tfjs-examples/


chrome-extension
discos compactos

hilo
hilo construir

Una vez que la extensión haya terminado de construirse, es posible cargar la extensión desempaquetada
en Chrome. Para hacerlo, debe navegar a chrome://extensiones, habilitar el modo de desarrollador y
luego hacer clic en Cargar sin empaquetar, como se muestra en la figura 12.4. Esto abrirá un cuadro de
diálogo de selección de archivos, donde debe seleccionar el directorio dist creado en el directorio de
extensión de cromo. Ese es el directorio que contiene manifest.json.
Una vez instalada la extensión, debería poder clasificar las imágenes en el navegador. Para
hacerlo, navegue a algún sitio con imágenes, como la página de búsqueda de imágenes de Google
para el términoTigreusado aquí. Luego haga clic derecho en la imagen que desea clasificar. Debería
ver una opción de menú para Clasificar imagen con TensorFlow.js. Al hacer clic en esa opción de
menú, la extensión debe ejecutar el modelo MobileNet en la imagen y luego agregar texto sobre la
imagen, indicando la predicción (consulte la figura 12.5).

Figura 12.4 Carga de la extensión TensorFlow.js MobileNet Chrome en modo desarrollador

10Las versiones más recientes de Microsoft Edge también ofrecen cierto soporte para la carga de extensiones entre navegadores.
Implementación de modelos de TensorFlow.js en varias plataformas y entornos 443

Figura 12.5 La extensión TensorFlow.js MobileNet Chrome ayuda a clasificar imágenes en una página web.

Para eliminar la extensión, haga clic en Eliminar en la página Extensiones (consulte la figura 12.4), o utilice la opción de

menú Eliminar de Chrome al hacer clic con el botón derecho en el icono de la extensión en la parte superior derecha.

Tenga en cuenta que el modelo que se ejecuta en la extensión del navegador tiene acceso a la misma
aceleración de hardware que el modelo que se ejecuta en la página web y, de hecho, utiliza gran parte del
mismo código. El modelo se carga con una llamada atf.loadGraphModel(...)usando una URL adecuada, y las
predicciones se hacen usando la mismamodelo.predecir(...)API que hemos visto. Migrar tecnología o una
prueba de concepto desde la implementación de una página web a una extensión de navegador es
relativamente fácil.

12.3.4 Implementación de modelos TensorFlow.js en aplicaciones móviles basadas en JavaScript

Para muchos productos, el navegador de escritorio no proporciona suficiente alcance, y el


navegador móvil no proporciona la experiencia de producto personalizada animada sin
problemas que los clientes esperan. Los equipos que trabajan en este tipo de proyectos a
menudo se enfrentan al dilema de cómo administrar el código base de su aplicación web
junto con los repositorios (normalmente) de aplicaciones nativas de Android (Java o Kotlin) e
iOS (Objective C o Swift). Si bien grupos muy grandes pueden soportar tal desembolso,
muchos desarrolladores eligen cada vez más reutilizar gran parte de su código en estas
implementaciones aprovechando los marcos de desarrollo híbridos multiplataforma.
444 CPASADO12Probar, optimizar e implementar modelos

Los marcos de aplicaciones multiplataforma, como


React Native, Ionic, Flutter y Progressive Web-Apps, le
permiten escribir la mayor parte de una aplicación una
vez en un lenguaje común y luego compilar esa
funcionalidad central para crear experiencias nativas con
la apariencia, la sensación, la y el rendimiento que los
usuarios esperan. El tiempo de ejecución/lenguaje
multiplataforma maneja gran parte de la lógica y el
diseño del negocio, y se conecta a los enlaces de la
plataforma nativa para las imágenes y la sensación de
rendimiento estandarizados. Cómo seleccionar el marco
de desarrollo de aplicaciones híbridas correcto es el tema
de innumerables blogs y videos en la web, por lo que no
revisaremos esa discusión aquí, sino que nos
centraremos en un solo marco popular, React Native. La
Figura 12.6 ilustra una aplicación React Native mínima
que ejecuta un modelo MobileNet. Observe la falta de
cualquier barra superior del navegador. Aunque esta
sencilla aplicación no tiene elementos de interfaz de
usuario, si lo hiciera, vería que coinciden con la
apariencia nativa de Android. La misma aplicación creada
para iOS coincidiríaesoselementos.
Afortunadamente, el tiempo de ejecución de JavaScript
Figura 12.6 Una captura de pantalla de una muestra
dentro de React Native es compatible con TensorFlow.js de
aplicación nativa de Android creada con React Native.
forma nativa sin ningún trabajo especial. El paquete tfjs- Aquí, estamos ejecutando un modelo TensorFlow.js
react-native todavía se encuentra en versión alfa (a partir de MobileNet dentro de la aplicación nativa.

diciembre de 2019), pero brinda compatibilidad con GPU con


WebGL a través de expo-gl. El código de usuario se parece a
lo siguiente:

importar * como tf desde '@tensorflow/tfjs'; importar


'@tensorflow/tfjs-react-native';

El paquete también proporciona una API especial para ayudar a cargar y guardar activos de
modelo dentro de la aplicación móvil.

Listado 12.2 Cargando y guardando un modelo dentro de una aplicación móvil construida con React-Native

importar * como tf desde '@tensorflow/tfjs';


importar {asyncStorageIO} desde '@tensorflow/tfjs-react-native';

tren asíncrono Guardar y cargar () {


const modelo = esperar tren(); esperar
model.save(asyncStorageIO( Guarda el modelo en AsyncStorage, un sistema simple de
'modelo-de-prueba-personalizado')) almacenamiento de clave-valor global para la aplicación.

modelo.predecir(tf.tensor2d([5], [1, 1])).imprimir();


Implementación de modelos de TensorFlow.js en varias plataformas y entornos 445

const modelo cargado =


esperar tf.loadLayersModel(asyncStorageIO( Carga el modelo
'prueba-del-modelo-personalizado')); de AsyncStorage
modelo cargado.predict(tf.tensor2d([5], [1, 1])).print();
}

Si bien el desarrollo de aplicaciones nativas a través de React Native aún requiere aprender algunas herramientas
nuevas, como Android Studio para Android y XCode para iOS, la curva de aprendizaje es menos profunda que
sumergirse directamente en el desarrollo nativo. El hecho de que estos marcos de desarrollo de aplicaciones
híbridas sean compatibles con TensorFlow.js significa que la lógica de aprendizaje automático puede residir en
una única base de código en lugar de requerir que desarrollemos, mantengamos y probemos una versión
separada para cada superficie de hardware, una clara victoria para los desarrolladores que desean ¡Admite la
experiencia de la aplicación nativa! Pero, ¿qué pasa con la experiencia de escritorio nativa?

12.3.5 Implementación de modelos de TensorFlow.js en aplicaciones de


escritorio multiplataforma basadas en JavaScript

Los marcos de JavaScript como Electron.js permiten que las aplicaciones de escritorio se escriban
de una manera multiplataforma que recuerda a las aplicaciones móviles multiplataforma escritas
en React Native. Con tales marcos, necesita escribir su código solo una vez, y puede implementarse
y ejecutarse en los principales sistemas operativos de escritorio, incluidos macOS, Windows y las
principales distribuciones de Linux. Esto simplifica enormemente el flujo de trabajo de desarrollo
tradicional de mantener bases de código separadas para sistemas operativos de escritorio en gran
parte incompatibles. Tome Electron.js, el marco líder en esta categoría, por ejemplo. Utiliza Node.js
como la máquina virtual que sustenta el proceso principal de la aplicación; para la parte de la GUI
de la aplicación, utiliza Chromium, un navegador web completo pero liviano que comparte gran
parte de su código con Google Chrome.
TensorFlow.js es compatible con Electron.js, como lo demuestra el ejemplo simple en el repositorio
tfjs-examples. Este ejemplo, que se encuentra en el directorio electron, ilustra cómo implementar un
modelo TensorFlow.js para la inferencia en una aplicación de escritorio basada en Electron.js. La
aplicación permite a los usuarios buscar en el sistema de archivos archivos de imagen que coincidan
visualmente con una o más palabras clave (ver la captura de pantalla en la figura 12.7). Este proceso de
búsqueda implica aplicar un modelo TensorFlow.js MobileNet para inferencia en un directorio de
imágenes.
A pesar de su simplicidad, esta aplicación de ejemplo ilustra una consideración importante al
implementar modelos de TensorFlow.js en Electron.js: la elección del backend de cómputo. Una
aplicación Electron.js se ejecuta en un proceso de back-end basado en Node.js, así como en un
proceso de front-end basado en Chromium. TensorFlow.js puede ejecutarse en cualquiera de esos
entornos. Como resultado, el mismo modelo puede ejecutarse en el proceso de back-end similar a
un nodo de la aplicación o en el proceso de front-end similar a un navegador. En el caso de la
implementación de backend, se usa el paquete @tensorflow/tfjs-node, mientras que el paquete
@tensorflow/tfjs se usa para el caso de frontend (figura 12.8). Una casilla de verificación en la GUI
de la aplicación de ejemplo le permite cambiar entre los modos de inferencia backend y frontend
(figura 12.7), aunque en una aplicación real impulsada por Electron.js y TensorFlow.js, usted
446 CPASADO12Probar, optimizar e implementar modelos

Figura 12.7 Una captura de pantalla de la aplicación de escritorio basada en Electron.js de


ejemplo que utiliza un modelo TensorFlow.js, de tfjs-examples/electron

normalmente decidiría un entorno para su modelo de antemano. A continuación, discutiremos


brevemente los pros y los contras de las opciones.
Como muestra la figura 12.8, las diferentes opciones del backend de cómputo hacen que el
cómputo de aprendizaje profundo se realice en un hardware de cómputo diferente. La
implementación de back-end basada en @tensorflow/tfjs-node asigna la carga de trabajo a la CPU,
aprovechando la biblioteca libtensorflow multiproceso y habilitada para SIMD. Esta opción de
implementación de modelo basada en Node.js suele ser más rápida que la opción de front-end y
puede adaptarse a modelos más grandes debido al hecho de que el entorno de back-end no tiene
restricciones de recursos. Sin embargo, su principal inconveniente es el gran tamaño del paquete,
que es el resultado del gran tamaño de libtensorflow (para tfjs-node, aproximadamente 50 MB con
compresión).
La implementación de front-end envía cargas de trabajo de aprendizaje profundo a WebGL. Para
modelos de tamaño pequeño a mediano, y en casos donde la latencia de la inferencia no es una
preocupación importante, esta es una opción aceptable. Esta opción conduce a un tamaño de paquete
más pequeño y funciona de forma inmediata para una amplia gama de GPU, gracias a la amplia
compatibilidad con WebGL.
Como también ilustra la figura 12.8, la elección del backend de cómputo es una preocupación
en gran medida separada del código JavaScript que carga y ejecuta su modelo. La misma API
funciona para las tres opciones. Esto se demuestra claramente en la aplicación de ejemplo, donde
el mismo módulo (clasificador de imagenen electron/image_classifier.js) sirve al
Implementación de modelos de TensorFlow.js en varias plataformas y entornos 447

Navegador de cromo

Electron.js Proceso principal proceso de renderizado


capa de aplicación (back-end) (Cromo)

TensorFlow.js TensorFlow.js compartido


capa de API codigo de usuario

TensorFlow.js nodo tfjs TensorFlow.js


capa de fondo libtensorflow back-end de WebGL

WebGL

Computar hardware
capa

UPC GPU

Figura 12.8 La arquitectura de una aplicación de escritorio basada en Electron.js que utiliza TensorFlow.js para el
aprendizaje profundo acelerado. Se pueden invocar diferentes backends informáticos de TensorFlow.js, ya sea desde el
proceso de backend principal o desde el proceso de representación en el navegador. Diferentes backends de cómputo
hacen que los modelos se ejecuten en diferentes hardware subyacentes. Independientemente de la elección del backend
informático, el código que carga, define y ejecuta modelos de aprendizaje profundo en TensorFlow.js es prácticamente el
mismo. Las puntas de flecha en este diagrama indican la invocación de funciones de biblioteca y otras rutinas invocables.

tarea de inferencia en los entornos backend y frontend. También debemos señalar que, aunque el
ejemplo de tfjs-examples/electron solo muestra inferencias, ciertamente puede usar TensorFlow.js para
otros flujos de trabajo de aprendizaje profundo, como la creación y capacitación de modelos (por
ejemplo, transferencia de aprendizaje) en aplicaciones de Electron.js. igualmente bueno.

12.3.6 Implementación de modelos de TensorFlow.js en WeChat y otros sistemas


de complementos de aplicaciones móviles basados en JavaScript

Hay algunos lugares donde la principal plataforma de distribución de aplicaciones móviles no es la


Play Store de Android ni la App Store de Apple, sino una pequeña cantidad de "superaplicaciones
móviles" que permiten extensiones de terceros dentro de su propia experiencia curada por ellos
mismos.
Algunas de estas súper aplicaciones móviles provienen de gigantes tecnológicos chinos, en particular WeChat
de Tencent, Alipay de Alibaba y Baidu. Estos usan JavaScript como su tecnología principal para permitir la creación
de extensiones de terceros, lo que convierte a TensorFlow.js en una opción natural para implementar el
aprendizaje automático en su plataforma. Sin embargo, el conjunto de API disponibles dentro de estos sistemas
de complementos de aplicaciones móviles no es el mismo que el conjunto disponible en JavaScript nativo, por lo
que se requieren algunos conocimientos y trabajo adicionales para implementar allí.
448 CPASADO12Probar, optimizar e implementar modelos

Usemos WeChat como ejemplo. WeChat es la aplicación de redes sociales más utilizada en China, con
más de mil millones de usuarios activos mensuales. En 2017, WeChat lanzó Mini Program, una plataforma
para que los desarrolladores de aplicaciones creen miniprogramas JavaScript dentro del sistema WeChat.
Los usuarios pueden compartir e instalar estos miniprogramas dentro de la aplicación WeChat sobre la
marcha, y ha sido un gran éxito. Para el segundo trimestre de 2018, WeChat tenía más de 1 millón de
miniprogramas y más de 600 millones de usuarios activos diarios de miniprogramas. También hay más de
1,5 millones de desarrolladores que desarrollan aplicaciones en esta plataforma, en parte debido a la
popularidad de JavaScript.
Las API del miniprograma de WeChat están diseñadas para proporcionar a los desarrolladores un fácil
acceso a los sensores de los dispositivos móviles (la cámara, el micrófono, el acelerómetro, el giroscopio,
el GPS, etc.). Sin embargo, la API nativa proporciona una funcionalidad de aprendizaje automático muy
limitada integrada en la plataforma. TensorFlow.js ofrece varias ventajas como solución de aprendizaje
automático para miniprogramas. Anteriormente, si los desarrolladores querían integrar el aprendizaje
automático en sus aplicaciones, tenían que trabajar fuera del entorno de desarrollo de miniprogramas
con una pila de aprendizaje automático del lado del servidor o basada en la nube. Hacerlo hace que la
barrera sea alta para la gran cantidad de desarrolladores de miniprogramas para construir y usar el
aprendizaje automático. Levantar una infraestructura de servicio externa está fuera del alcance de las
posibilidades para la mayoría de los desarrolladores de miniprogramas. Con TensorFlow.js, El desarrollo
del aprendizaje automático ocurre justo dentro del entorno nativo. Además, dado que es una solución del
lado del cliente, ayuda a reducir el tráfico de red y mejora la latencia, y aprovecha la aceleración de GPU
usando WebGL.
El equipo detrás de TensorFlow.js ha creado un miniprograma de WeChat que puede usar para
habilitar TensorFlow.js para su miniprograma (verhttps://github.com/tensorflow/tfjswechat). El
repositorio también contiene un miniprograma de ejemplo que usa PoseNet para anotar las posiciones y
posturas de las personas detectadas por la cámara del dispositivo móvil. Utiliza TensorFlow.js acelerado
por una API WebGL recientemente agregada de WeChat. Sin acceso a la GPU, el modelo se ejecutaría con
demasiada lentitud para ser útil para la mayoría de las aplicaciones. Con este complemento, un
miniprograma de WeChat puede tener el mismo rendimiento de ejecución del modelo que una aplicación
de JavaScript que se ejecuta dentro de los navegadores móviles. De hecho, hemos observado que la API
del sensor de WeChat normalmentesuperala contraparte en el navegador.
A fines de 2019, el desarrollo de experiencias de aprendizaje automático para complementos de súper
aplicaciones sigue siendo un territorio muy nuevo. Obtener un alto rendimiento puede requerir algo de ayuda de
los mantenedores de la plataforma. Aún así, es la mejor manera de implementar su aplicación frente a los cientos
de millones de personas para quienes la aplicación súper móvilesLa Internet.

12.3.7 Implementación de modelos de TensorFlow.js en computadoras de placa única

Para muchos desarrolladores web, implementar en una computadora de placa única sin periféricos suena
muy técnico y extraño. Sin embargo, gracias al éxito de Raspberry Pi, nunca ha sido tan fácil desarrollar y
construir dispositivos de hardware simples. Las computadoras de placa única brindan una plataforma
para implementar inteligencia de manera económica sin depender de conexiones de red a servidores en
la nube o computadoras voluminosas y costosas. Las computadoras de placa única se pueden usar para
respaldar aplicaciones de seguridad, moderar el tráfico de Internet, controlar el riego: el cielo es el límite.
Implementación de modelos de TensorFlow.js en varias plataformas y entornos 449

Muchas de estas computadoras de placa única brindan pines de entrada y salida de propósito general (GPIO)
para facilitar la conexión a sistemas de control físicos e incluyen una instalación completa de Linux para permitir
que los educadores, desarrolladores y piratas informáticos desarrollen una amplia gama de interactivos.
dispositivos. JavaScript se ha convertido rápidamente en un lenguaje popular para construir en este tipo de
dispositivos. Los desarrolladores pueden usar bibliotecas de nodos como rpi-gpio para interactuar
electrónicamente al nivel más bajo, todo en JavaScript.
Para ayudar a estos usuarios, TensorFlow.js actualmente tiene dos tiempos de ejecución en estos
dispositivos ARM integrados: tfjs-node (CPU11) y tfjs-headless-nodegl (GPU). Toda la biblioteca
TensorFlow.js se ejecuta en estos dispositivos a través de esos dos backends. Los desarrolladores pueden
ejecutar la inferencia utilizando modelos listos para usar o entrenar los suyos propios, ¡todo en el
hardware del dispositivo!
El lanzamiento de dispositivos recientes como NVIDIA Jetson Nano y Raspberry Pi 4 trae un
sistema en chip (SoC) con una pila de gráficos moderna. La GPU en estos dispositivos se puede
aprovechar mediante el código WebGL subyacente que se usa en el núcleo de TensorFlow.js. El
paquete WebGL sin interfaz (tfjs-backend-nodegl) permite a los usuarios ejecutar TensorFlow.js en
Node.js puramente acelerado por la GPU en estos dispositivos (consulte la figura 12.9). Al delegar la
ejecución de TensorFlow.js a la GPU, los desarrolladores pueden seguir utilizando la CPU para
controlar otras partes de sus dispositivos.

Figura 12.9 TensorFLow.js ejecutando MobileNet usando WebGL sin interfaz gráfica en una Raspberry Pi 4

Si está buscando utilizar la CPU con aceleración ARM NEON, debe usar el paquete tfjs-node en
11
estos dispositivos. Este paquete incluye soporte para las arquitecturas ARM32 y ARM64.
450 CPASADO12Probar, optimizar e implementar modelos

La seguridad del modelo y la seguridad de los datos son muy sólidas para la implementación de computadoras
de placa única. El cálculo y la actuación se manejan directamente en el dispositivo, lo que significa que los datos
no necesitan ir a un dispositivo fuera del control del propietario. El cifrado se puede utilizar para proteger el
modelo incluso si el dispositivo físico está comprometido.
La implementación en computadoras de placa única sigue siendo un área muy nueva para JavaScript
en general y TensorFlow.js en particular, pero abre una amplia gama de aplicaciones para las que otras
áreas de implementación no son adecuadas.

12.3.8 Resumen de implementaciones

En esta sección, hemos cubierto varias formas diferentes de poner su sistema de aprendizaje
automático TensorFlow.js frente a la base de usuarios (la tabla 12.4 las resume). ¡Esperamos haber
despertado su imaginación y haberlo ayudado a soñar con aplicaciones radicales de la tecnología!
El ecosistema de JavaScript es vasto y amplio, y en el futuro, los sistemas habilitados para el
aprendizaje automático se ejecutarán en áreas con las que ni siquiera podríamos soñar hoy.

Tabla 12.4 Entornos de destino en los que se pueden implementar los modelos de TensorFlow.js y el acelerador de
hardware que cada entorno puede usar

Despliegue Soporte de acelerador de hardware

Navegador WebGL

Servidor Node.js CPU con soporte multiproceso y SIMD; GPU


compatible con CUDA

Plugin para el navegador WebGL

Aplicación de escritorio multiplataforma (como Electron) WebGL, CPU con subprocesos múltiples y compatibilidad con SIMD, o
GPU compatible con CUDA

Aplicación móvil multiplataforma (como React WebGL


Native)

Complemento de aplicación móvil (como WeChat) WebGL móvil

Ordenador de placa única (como Raspberry Pi) GPU o ARM NEON

Materiales para lectura adicional


-Denis Baylor et al., "TFX: una plataforma de aprendizaje automático a escala de
producción basada en TensorFlow", KDD 2017,www.kdd.org/kdd2017/papers/view/tfx-
atensorflow-based-production-scale-machine-learning-platform.
-Raghuraman Krishnamoorthi, "Cuantización de redes convolucionales profundas para
una inferencia eficiente: un documento técnico", junio de 2018,https://arxiv.org/pdf/
1806.08342.pdf.
- Rasmus Munk Larsen y Tatiana Shpeisman, "Optimización de gráficos de
TensorFlow"https://ai.google/research/pubs/pub48051.
Ejercicios 451

Ejercicios
1 En el capítulo 10, entrenamos una GAN de clase auxiliar (ACGAN) en el conjunto de datos
MNIST para generar imágenes de dígitos MNIST falsas por clase. Específicamente, el
ejemplo que usamos está en el directorio mnist-acgan del repositorio tfjs-examples. La
parte del generador del modelo entrenado tiene un tamaño total de aproximadamente 10
MB, la mayor parte del cual está ocupado por los pesos almacenados como flotantes de 32
bits. Es tentador realizar una cuantificación del peso posterior al entrenamiento en este
modelo para acelerar la carga de la página. Sin embargo, antes de hacerlo, debemos
asegurarnos de que dicha cuantificación no produzca un deterioro significativo en la calidad
de las imágenes generadas. Pruebe la cuantificación de 16 y 8 bits y determine si una o
ambas son una opción aceptable. Utilizar eltensorflowjs_converterflujo de trabajo descrito en
la sección 12.2.1. ¿Qué criterios utilizará para evaluar la calidad de las imágenes MNIST
generadas en este caso?
2 Los modelos de Tensorflow que se ejecutan como extensiones de Chrome tienen la ventaja de
poder controlar el propio Chrome. En el ejemplo de comandos de voz del capítulo 4, mostramos
cómo usar un modelo convolucional para reconocer palabras habladas. La API de extensión de
Chrome le brinda la capacidad de consultar y cambiar pestañas. Intente incorporar el modelo de
comandos de voz en una extensión y ajústelo para que reconozca las frases "siguiente pestaña" y
"pestaña anterior". Utilice los resultados del clasificador para controlar el enfoque de la pestaña
del navegador.
3 El cuadro de información 12.3 describe la forma correcta de medir el tiempo que un modelo de
TensorFlow.jspredecir()tomas de llamada (llamada de inferencia) y los puntos de precaución que
implica. En este ejercicio, cargue un modelo MobileNetV2 en TensorFlow.js (consulte el ejemplo de
detección de objetos simples en la sección 5.2 si necesita un recordatorio de cómo hacerlo) y
cronometre supredecir()llamada:
a Como primer paso, genere un tensor de imagen de valor aleatorio de forma [1, 224,
224, 3]y la inferencia del modelo sobre él siguiendo los pasos establecidos en el
cuadro de información 12.3. Compare el resultado del tiempo con y sin el
formación()o datos()llame al tensor de salida. ¿Cuál es más corto? ¿Cuál es la medida
de tiempo correcta?
B Cuando se realiza la medición correcta 50 veces en un bucle, trace los números de
tiempo individuales utilizando el gráfico de líneas tfjs-vis (capítulo 7) y obtenga una
apreciación intuitiva de la variabilidad. ¿Puedes ver claramente que las primeras
medidas son significativamente diferentes del resto? Dada esta observación, analice la
importancia de realizar pruebas preliminares o de calentamiento durante la evaluación
comparativa del rendimiento.
C A diferencia de las tareas a y b, reemplace el tensor de entrada generado
aleatoriamente con un tensor de imagen real (como uno obtenido de unimagen
elemento usando tf.browser.fromPixels()),y luego repita las medidas en el paso b. ¿El
contenido del tensor de entrada afecta las mediciones de tiempo de alguna manera
significativa?
452 CPASADO12Probar, optimizar e implementar modelos

D En lugar de ejecutar la inferencia en un solo ejemplo (tamaño del lote = 1), intente
aumentar el tamaño del lote a 2, 3, 4 y así sucesivamente hasta llegar a un número
relativamente grande, como 32. ¿La relación entre el tiempo de inferencia promedio y el
tamaño del lote aumenta monótonamente? ¿Uno lineal?

Resumen
- Una buena disciplina de ingeniería en torno a las pruebas es tan importante para el código de
aprendizaje automático como para el código que no es de aprendizaje automático. Sin embargo,
evite la tentación de concentrarse fuertemente en ejemplos "especiales" o hacer afirmaciones
sobre predicciones de modelos "dorados". En su lugar, confíe en probar las propiedades
fundamentales de su modelo, como sus especificaciones de entrada y salida. Además, recuerde
que todo el código de preprocesamiento de datos antes de su sistema de aprendizaje automático
es solo código "normal" y debe probarse en consecuencia.
- Optimizar la velocidad de descarga e inferencia es un factor importante para el éxito de la
implementación del lado del cliente de los modelos de TensorFlow.js. Utilizando la función de
cuantificación del peso posterior al entrenamiento deltensorflowjs_converterbinario, puede reducir
el tamaño total de un modelo, en algunos casos sin pérdida observable de precisión de inferencia.
La función de conversión de modelo gráfico deconvertidor tensorflowjs_ ayuda a acelerar la
inferencia de modelos a través de transformaciones de gráficos como op fusion. Le
recomendamos encarecidamente que pruebe y emplee ambas técnicas de optimización de
modelos cuando implemente sus modelos de TensorFlow.js en producción.
- Un modelo entrenado y optimizado no es el final de la historia para su aplicación de aprendizaje
automático. Debe encontrar alguna manera de integrarlo con un producto real. La forma más
común de implementar las aplicaciones TensorFlow.js es dentro de las páginas web, pero esta es
solo una de una amplia variedad de escenarios de implementación, cada uno con sus propias
fortalezas. Los modelos TensorFlow.js pueden ejecutarse como extensiones de navegador, dentro
de aplicaciones móviles nativas, como aplicaciones de escritorio nativas e incluso en hardware de
placa única como Raspberry Pi.
Resumen, conclusiones,
y más allá

Este capítulo cubre


- Una mirada retrospectiva a los conceptos e ideas de alto nivel sobre la IA y el
aprendizaje profundo

- Una descripción general rápida de los diferentes tipos de algoritmos de


aprendizaje profundo que hemos visitado en este libro, cuándo son útiles
y cómo implementarlos en TensorFlow.js

- Modelos preentrenados del ecosistema de TensorFlow.js


- Limitaciones del aprendizaje profundo en su estado actual; y una
predicción educada de las tendencias en el aprendizaje profundo que
veremos en los próximos años

- Orientación sobre cómo avanzar aún más en su conocimiento de aprendizaje


profundo y mantenerse actualizado con el campo de rápido movimiento

Este es el capítulo final de este libro. Los capítulos anteriores han sido un gran recorrido por el
panorama actual del aprendizaje profundo, habilitado por los vehículos de TensorFlow.js y su
propio trabajo arduo. A lo largo de este viaje, es de esperar que haya adquirido bastantes
conceptos y habilidades nuevos. Es hora de dar un paso atrás y mirar el panorama general.

453
454 CPASADO13Resumen, conclusiones y más allá

de nuevo, además de refrescar algunos de los conceptos más importantes que ha aprendido. Este
último capítulo resumirá y revisará los conceptos básicos mientras expande sus horizontes más allá
de las nociones relativamente básicas que ha aprendido hasta ahora. Queremos asegurarnos de
que se dé cuenta de esto y esté debidamente equipado para dar los próximos pasos del viaje por
su cuenta.
Comenzaremos con una vista panorámica de lo que debe sacar de este libro. Esto debería refrescar tu
memoria con respecto a algunos de los conceptos que has aprendido. A continuación, presentaremos una
descripción general de algunas limitaciones clave del aprendizaje profundo. Para usar una herramienta
correctamente, no solo debe saber lo quelatahacer sino también lo quehipocresíahacer. El capítulo
finaliza con una lista de recursos y estrategias para ampliar sus conocimientos y habilidades sobre el
aprendizaje profundo y la IA en el ecosistema de JavaScript y mantenerse actualizado con los nuevos
desarrollos.

13.1 Conceptos clave en revisión


Esta sección sintetiza brevemente los puntos clave de este libro. Comenzaremos con el panorama
general del campo de la IA y terminaremos explicando por qué la combinación del aprendizaje
profundo y Java-Script presenta oportunidades únicas y emocionantes.

13.1.1 Diversos enfoques de la IA


En primer lugar, el aprendizaje profundo no es sinónimo de IA ni de aprendizaje automático. Inteligencia
artificiales un campo amplio con una larga historia. En general, se puede definir como "todos los intentos
de automatizar el proceso cognitivo", en otras palabras, la automatización del pensamiento. Esto puede
abarcar desde tareas muy básicas, como una hoja de cálculo de Excel, hasta esfuerzos muy avanzados,
como un robot humanoide que puede caminar y hablar.
Aprendizaje automáticoes uno de los muchos subcampos de la IA. Su objetivo es desarrollar
automáticamente programas (llamadosmodelos) puramente de la exposición a datos de entrenamiento.
Este proceso de convertir datos en un programa (el modelo) se llamaaprendiendo. Aunque el aprendizaje
automático ha existido durante mucho tiempo (al menos varias décadas), solo comenzó a despegar en
aplicaciones prácticas en la década de 1990.
Aprendizaje profundoes una de las muchas formas de aprendizaje automático. En el aprendizaje profundo,
los modelos consisten en muchos pasos de transformación de la representación, aplicados uno tras otro (de ahí
el adjetivo "profundo"). Estas operaciones se estructuran en módulos llamadoscapas. Los modelos de aprendizaje
profundo suelen ser pilas de muchas capas o, de manera más general, gráficos de muchas capas. Estas capas
están parametrizadas porpesos, valores numéricos que ayudan a transformar la entrada de una capa en su salida
y se actualizan durante el proceso de entrenamiento. El "conocimiento" aprendido por un modelo durante el
entrenamiento se materializa en sus pesos. El proceso de entrenamiento consiste principalmente en encontrar
un buen conjunto de valores para estos pesos.
Aunque el aprendizaje profundo es solo uno entre muchos enfoques del aprendizaje
automático, ha demostrado ser un gran éxito en comparación con otros enfoques. Repasemos
rápidamente las razones detrás del éxito del aprendizaje profundo.
Conceptos clave en revisión 455

13.1.2 Qué hace que el aprendizaje profundo se destaque entre los subcampos del aprendizaje automático

En el lapso de solo unos pocos años, el aprendizaje profundo ha logrado avances tremendos en múltiples
tareas que históricamente se consideraban extremadamente difíciles para las computadoras,
especialmente en el área de la percepción de la máquina, es decir, extraer información útil de imágenes,
audio, video, y modalidades similares de datos de percepción con una precisión suficientemente alta.
Dados suficientes datos de entrenamiento (en particular,etiquetado datos de entrenamiento), ahora es
posible extraer de los datos de percepción casi cualquier cosa que los humanos puedan extraer, a veces
con una precisión que supera la de los humanos. Por lo tanto, a veces se dice que el aprendizaje profundo
ha "resuelto la percepción" en gran medida, aunque esto es cierto solo para una definición bastante
estrecha de percepción (consulte la sección 13.2.5 para conocer las limitaciones del aprendizaje
profundo).
Debido a su éxito técnico sin precedentes, el aprendizaje profundo ha generado por sí
solo el tercero y, con mucho, el más grande hasta ahora.verano de IA, también conocido
como el revolución del aprendizaje profundo, que es un período de intenso interés, inversión
y exageración en el campo de la IA. Si este período terminará en un futuro cercano y qué
sucederá después, son temas de especulación y debate. Pero una cosa es cierta: en marcado
contraste con los veranos de IA anteriores, el aprendizaje profundo ha brindado un enorme
valor a una gran cantidad de empresas de tecnología, lo que permite la clasificación de
imágenes a nivel humano, la detección de objetos, el reconocimiento de voz, los asistentes
inteligentes, el procesamiento del lenguaje natural y la traducción automática. , sistemas de
recomendación, autos sin conductor y más. La exageración puede retroceder (con razón),
pero el impacto tecnológico sostenido y el valor económico del aprendizaje profundo
permanecerán. En ese sentido, el aprendizaje profundo podría ser análogo a Internet: puede
estar demasiado publicitado durante algunos años, lo que genera expectativas irrazonables y
una inversión excesiva.
Somos particularmente optimistas sobre el aprendizaje profundo porque, incluso si no
lográramos más avances académicos en la próxima década, poner las técnicas de aprendizaje
profundo existentes en cada problema práctico aplicable aún sería un cambio de juego para
muchas industrias (publicidad en línea, finanzas, automatización industrial y tecnologías de
asistencia para personas con discapacidades, solo por mencionar algunas). El aprendizaje profundo
es nada menos que una revolución, y el progreso se está produciendo actualmente a un ritmo
increíblemente rápido debido a una inversión exponencial en recursos y personal. Desde donde
estamos, el futuro parece brillante, aunque las expectativas a corto plazo pueden ser demasiado
optimistas; implementar el aprendizaje profundo en todo su potencial llevará más de una década.

13.1.3 Cómo pensar en el aprendizaje profundo a un alto nivel


Uno de los aspectos más sorprendentes del aprendizaje profundo es lo simple que es, dado lo bien
que funciona y cómo las técnicas de aprendizaje automático más complicadas que existían antes
no funcionaban tan bien. Hace diez años, nadie esperaba que pudiéramos lograr resultados tan
sorprendentes en problemas de percepción de máquinas usando solo modelos paramétricos.
456 CPASADO13Resumen, conclusiones y más allá

entrenado con descenso de gradiente. Ahora resulta que todo lo que necesita son modelos
paramétricos suficientemente grandes entrenados con descenso de gradiente y suficientes
ejemplos etiquetados. Como dijo una vez Richard Feynman sobre su comprensión del universo: "No
es complicado, es solo mucho".1
En el aprendizaje profundo, todo se representa como una serie de números; en otras palabras,
unvector. Un vector puede ser visto como unpuntoen unespacio geométrico. Las entradas del
modelo (datos tabulares, imágenes, texto, etc.) primero se vectorizan o se convierten en un
conjunto de puntos en el espacio vectorial de entrada. De manera similar, los objetivos (etiquetas)
también se vectorizan y se convierten en su correspondiente conjunto de puntos en el espacio
vectorial objetivo. Luego, cada capa en una red neuronal profunda realiza una transformación
geométrica simple en los datos que la atraviesan. Juntas, la cadena de capas en la red neuronal
forma una transformación geométrica compleja, hecha de una serie de transformaciones
geométricas simples. Esta transformación compleja intenta mapear los puntos en el espacio
vectorial de entrada a aquellos en el espacio vectorial de destino. Esta transformación está
parametrizada por los pesos de las capas, que se actualizan iterativamente en función de lo buena
que es la transformación actualmente.diferenciable:esto es lo que hace posible el descenso de
gradiente.

13.1.4 Tecnologías facilitadoras clave del aprendizaje profundo

La revolución del aprendizaje profundo que se está desarrollando actualmente no comenzó de la noche a la
mañana. En cambio, como cualquier otra revolución, es el producto de una acumulación de varios factores
habilitadores, lentamente al principio y luego acelerando repentinamente una vez que se alcanza la masa crítica.
En el caso del aprendizaje profundo, podemos señalar los siguientes factores clave:

- Innovaciones algorítmicas incrementales, distribuidas por primera vez en dos décadas2y luego se aceleró
a medida que se dedicó más esfuerzo de investigación al aprendizaje profundo después de 2012.3
- La disponibilidad de grandes cantidades de datos etiquetados, que abarcan muchas modalidades
de datos, incluidos los perceptuales (imágenes, audio y video), numéricos y de texto, lo que
permite entrenar modelos grandes con cantidades suficientes de datos. Este es un subproducto
del auge de Internet para el consumidor, impulsado por la popularización de los dispositivos
móviles, así como la ley de Moore aplicada a los medios de almacenamiento.
- La disponibilidad de hardware de computación rápido y altamente paralelizado a bajo costo,
especialmente las GPU producidas por NVIDIA: primero GPU para juegos reutilizadas para
computación paralela y luego chips diseñados desde cero para aprendizaje profundo.
- Una pila compleja de software de código abierto que hace que este poder computacional
esté disponible para muchos desarrolladores y estudiantes humanos, mientras oculta la
enorme cantidad de complejidad subyacente: el lenguaje CUDA, el navegador web

1
Richard Feynman, entrevista, “El mundo desde otro punto de vista”, Yorkshire Television, 1972.
2
Comenzando con la invención de la retropropagación de Rumelhart, Hinton y Williams, las capas convolucionales
de LeCun y Bengio y las redes recurrentes de Graves y Schmidthuber.
3
Por ejemplo, métodos mejorados de inicialización de peso, nuevas funciones de activación, abandono, normalización de lotes,
conexiones residuales.
Conceptos clave en revisión 457

Lenguajes de sombreado WebGL y marcos como TensorFlow.js, TensorFlow y Keras,


que realizan una diferenciación automática y proporcionan componentes básicos de
alto nivel fáciles de usar, como capas, funciones de pérdida y optimizadores. El
aprendizaje profundo está pasando de ser un dominio exclusivo de especialistas
(investigadores, estudiantes graduados en IA e ingenieros con formación académica) a
una herramienta para cada programador. TensorFlow.js es un marco ejemplar en este
frente. Reúne dos ecosistemas ricos y vibrantes: el ecosistema multiplataforma de
JavaScript y el ecosistema de aprendizaje profundo de rápido avance.

Una manifestación del amplio y profundo impacto de la revolución del aprendizaje profundo es su
fusión con pilas tecnológicas diferentes de aquella en la que se originó (el ecosistema C++ y Python
y el campo de la computación numérica). Su polinización cruzada con el ecosistema de JavaScript, el
tema principal del libro, es un excelente ejemplo de ello. En la siguiente sección, revisaremos las
razones clave por las que llevar el aprendizaje profundo al mundo de JavaScript abre nuevas y
emocionantes oportunidades y posibilidades.

13.1.5 Aplicaciones y oportunidades desbloqueadas por el aprendizaje profundo en JavaScript

El objetivo principal de entrenar un modelo de aprendizaje profundo es ponerlo a


disposición de los usuarios. Para muchos tipos de modalidades de entrada, como
imágenes de la cámara web, sonidos del micrófono y entrada de texto y gestos por
parte del usuario, los datos se generan y están disponibles directamente en el
cliente. JavaScript es quizás el lenguaje y ecosistema más maduro y ubicuo para la
programación del lado del cliente. El mismo código escrito en JavaScript se puede
implementar como páginas web e interfaces de usuario en una amplia gama de
dispositivos y plataformas. La API WebGL del navegador web permite el cálculo
paralelo multiplataforma en una variedad de GPU, que es aprovechado por
TensorFlow.js. Estos factores hacen de JavaScript una opción atractiva para la
implementación de modelos de aprendizaje profundo. TensorFlow.

Además de la facilidad de implementación, también hay una serie de ventajas adicionales para
servir y ajustar modelos de aprendizaje profundo mediante JavaScript:

- En comparación con la inferencia del lado del servidor, la inferencia del lado del cliente renuncia a la latencia de

la transferencia de datos bidireccional, lo que beneficia la disponibilidad y conduce a una experiencia de usuario

más fluida.

-Al realizar cálculos en el perímetro mediante la aceleración de la GPU en el dispositivo, el aprendizaje

profundo del lado del cliente elimina la necesidad de administrar los recursos de la GPU del lado del
servidor, lo que reduce significativamente la complejidad y los costos de mantenimiento de su pila de
tecnología.
- En virtud de mantener los datos y los resultados de la inferencia en el cliente, se protege la
privacidad de los datos del usuario. Esto es importante para dominios como el cuidado de la salud
y la moda.
458 CPASADO13Resumen, conclusiones y más allá

- La naturaleza visual e interactiva del navegador y otros entornos de interfaz de usuario


basados en JavaScript brinda oportunidades únicas para la visualización, la comprensión
asistida y la enseñanza de las redes neuronales.
-TensorFlow.js admite no solo la inferencia sino también el entrenamiento. Esto abre la puerta al aprendizaje

de transferencia y el ajuste fino del lado del cliente, lo que conduce a una mejor personalización de los
modelos de aprendizaje automático.
- En el navegador web, JavaScript proporciona una API independiente de la plataforma para
acceder a los sensores del dispositivo, como cámaras web y micrófonos, lo que acelera el
desarrollo de aplicaciones multiplataforma que utilizan entradas de estos sensores.

Además de su eminencia del lado del cliente, JavaScript extiende su destreza al lado del
servidor. En particular, Node.js es un marco muy popular para aplicaciones del lado del
servidor en JavaScript. Con la versión Node.js de TensorFlow.js (tfjs-node), puede entrenar y
servir modelos de aprendizaje profundo fuera del navegador web y, por lo tanto, sin
restricciones de recursos. Esto aprovecha el vasto ecosistema de Node.js y simplifica la pila
tecnológica para los miembros de la comunidad. Todo esto se puede lograr utilizando
esencialmente el mismo código TensorFlow.js que escribe para el lado del cliente, lo que lo
acerca a la visión de "escribir una vez, ejecutar en todas partes", como se ha demostrado en
varios ejemplos a lo largo del libro.

13.2 Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en
TensorFlow.js
Con la descripción histórica fuera del camino, ahora visitemos los aspectos técnicos de
TensorFlow.js. En esta sección, revisaremos el flujo de trabajo general que debe seguir al abordar
un problema de aprendizaje automático y destacamos algunas de las consideraciones más
importantes y las trampas más comunes. Luego repasaremos los diversos bloques de construcción
de redes neuronales (capas) que hemos cubierto en el libro. Además, examinaremos los modelos
previamente entrenados en el ecosistema TensorFlow.js, que puede usar para acelerar su ciclo de
desarrollo. Para concluir esta sección, presentaremos la gama de problemas de aprendizaje
automático que puede abordar potencialmente mediante el uso de estos componentes básicos, lo
que lo estimulará a imaginar cómo las redes neuronales profundas escritas en TensorFlow.js
pueden ayudarlo a abordar sus propios problemas de aprendizaje automático. .

13.2.1 El flujo de trabajo universal del aprendizaje profundo supervisado

El aprendizaje profundo es una herramienta poderosa. Pero quizás algo sorprendente, la parte
más difícil y que consume más tiempo del flujo de trabajo de aprendizaje automático es a menudo
todo lo que viene antes de diseñar y entrenar tales modelos (y para los modelos implementados en
producción, lo que viene después también). Estos pasos difíciles incluyen comprender el dominio
del problema lo suficientemente bien como para poder determinar qué tipo de datos se necesitan,
qué tipo de predicciones se pueden hacer con una precisión razonable y un poder de
generalización, cómo encaja el modelo de aprendizaje automático en la solución general que
aborda un problema práctico, y cómo medir el grado en que el modelo tiene éxito en hacer su
trabajo. Aunque estos son requisitos previos para cualquier aplicación exitosa de
Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en TensorFlow.js 459

aprendizaje automático, no son algo que una biblioteca de software como TensorFlow.js pueda
automatizar para usted. Como recordatorio, lo que sigue es un breve resumen del flujo de trabajo típico
de aprendizaje supervisado:

1 Determine si el aprendizaje automático es el enfoque correcto. Primero, considere si el


aprendizaje automático es el enfoque correcto para su problema y continúe con los siguientes
pasos solo si la respuesta es afirmativa. En algunos casos, un enfoque que no sea de aprendizaje
automático funcionará igual de bien o incluso mejor, a un costo menor.
2 Definir el problema de aprendizaje automático. Determine qué tipo de datos están disponibles y
qué está tratando de predecir usando los datos.
3 Comprueba si tus datos son suficientes. Determine si la cantidad de datos que ya tiene es suficiente para
el entrenamiento del modelo. Es posible que deba recopilar más datos o contratar personas para
etiquetar manualmente un conjunto de datos sin etiquetar.

4 Identifique una forma de medir de forma fiable el éxito de un modelo entrenado en su objetivo.
Para tareas simples, esto puede ser solo precisión de predicción, pero en muchos casos, requerirá
métricas específicas de dominio más sofisticadas.
5 Preparar el proceso de evaluación. Diseñe el proceso de validación que utilizará para evaluar sus
modelos. En particular, debe dividir sus datos en tres conjuntos homogéneos pero que no se
superpongan: un conjunto de entrenamiento, un conjunto de validación y un conjunto de prueba. Las
etiquetas de los conjuntos de validación y prueba no deben filtrarse en los datos de entrenamiento. Por
ejemplo, con la predicción temporal, los datos de validación y prueba deben provenir de intervalos de
tiempo posteriores a los datos de entrenamiento. Su código de preprocesamiento de datos debe estar
cubierto por pruebas para protegerse contra errores (sección 12.1).

6 Vectorizar los datos. Convierta sus datos en tensores, onorte-matrices dimensionales: la lengua
franca de los modelos de aprendizaje automático en marcos como TensorFlow.js y TensorFlow. A
menudo, es necesario preprocesar los datos tensionados para que sean más adecuados para sus
modelos (por ejemplo, a través de la normalización). Batir la línea de base del sentido común.
7 Desarrolle un modelo que supere una línea de base que no sea de aprendizaje automático (como
predecir el promedio de la población para un problema de regresión o predecir el último punto de
datos en un problema de predicción de series temporales), demostrando así que el aprendizaje
automático realmente puede agregar valor a su solución. Este puede no ser siempre el caso (vea
el paso 1).
8 Desarrollar un modelo con capacidad suficiente. Refina gradualmente la arquitectura de tu
modelo ajustando los hiperparámetros y agregando regularización. Realice cambios en función de
la precisión de la predicción solo en el conjunto de validación, no en el conjunto de entrenamiento
ni en el conjunto de prueba. Recuerde que debe hacer que su modelo se sobreajuste al problema
(lograr una mejor precisión de predicción en el conjunto de entrenamiento que en el conjunto de
validación), identificando así una capacidad de modelo que es mayor que la que necesita. Solo
entonces debería comenzar a utilizar la regularización y otros enfoques para reducir el
sobreajuste.
9 Ajustar hiperparámetros. Tenga cuidado con el sobreajuste del conjunto de validación al ajustar los
hiperparámetros. Debido a que los hiperparámetros se determinan en función del rendimiento
460 CPASADO13Resumen, conclusiones y más allá

en el conjunto de validación, sus valores estarán demasiado especializados para el conjunto de


validación y, por lo tanto, es posible que no se generalicen bien a otros datos. El propósito del
conjunto de prueba es obtener una estimación imparcial de la precisión del modelo después del
ajuste de hiperparámetros. Por lo tanto, no debe usar el conjunto de prueba al ajustar los
10 hiperparámetros. Validar y evaluar el modelo entrenado. Como discutimos en la sección 12.1,
pruebe su modelo con un conjunto de datos de evaluación actualizado y decida si la precisión de
la predicción cumple con un criterio predeterminado para servir a los usuarios reales. Además,
realice un análisis más profundo de la calidad del modelo en diferentes segmentos (subconjuntos)
de los datos, con el objetivo de detectar cualquier comportamiento injusto (como precisiones muy
diferentes en diferentes segmentos de los datos) o sesgos no deseados.4Continúe con el paso
final solo si el modelo pasa estos criterios de evaluación.
11 Optimizar e implementar el modelo. Realice la optimización del modelo para reducir su tamaño y
aumentar su velocidad de inferencia. Luego implemente el modelo en el entorno de servicio,
como una página web, una aplicación móvil o un punto final de servicio HTTP (sección 12.3).

Esta receta es para el aprendizaje supervisado, que se encuentra en muchos problemas prácticos. Otros
tipos de flujos de trabajo de aprendizaje automático que se tratan en este libro incluyen el aprendizaje
por transferencia (supervisado), el RL (aprendizaje por refuerzo) y el aprendizaje profundo generativo. El
aprendizaje de transferencia supervisado (capítulo 5) comparte el mismo flujo de trabajo que el
aprendizaje supervisado sin transferencia, con la ligera diferencia de que el diseño del modelo y los pasos
de entrenamiento se basan en un modelo previamente entrenado y pueden requerir una cantidad menor
de datos de entrenamiento que entrenar un modelo desde cero. El aprendizaje profundo generativo tiene
un tipo de objetivo diferente del aprendizaje supervisado, es decir, crear ejemplos falsos que parezcan lo
más reales posible. En la práctica, existen técnicas que convierten el entrenamiento de modelos
generativos en aprendizaje supervisado, como vimos en los ejemplos VAE y GAN del capítulo 9. RL, por su
parte, implica una formulación de problema fundamentalmente diferente y, en consecuencia, un flujo de
trabajo radicalmente diferente, uno en el que los jugadores principales son el entorno, el agente, las
acciones, la estructura de recompensas y los tipos de algoritmo o modelo empleados para resolver el
problema. El Capítulo 11 proporcionó una descripción general rápida de los conceptos y algoritmos
básicos en RL.

13.2.2 Revisión de modelos y tipos de capas en TensorFlow.js: una referencia rápida


Todas las numerosas redes neuronales cubiertas en este libro se pueden dividir en tres familias:
redes densamente conectadas (también denominadas MLP o perceptrones multicapa), convnets
(redes convolucionales) y redes recurrentes. Estas son las tres familias básicas de redes con las que
todo profesional del aprendizaje profundo debería estar familiarizado. Cada tipo de red es
adecuado para un tipo específico de entrada: una arquitectura de red (MLP, convolucional o
recurrente) codifica suposiciones sobre la estructura de los datos de entrada, un espacio de
hipótesis dentro del cual la búsqueda de un buen modelo a través de

4La equidad en el aprendizaje automático es un campo de estudio incipiente; ver el siguiente enlace para más discusión
http://mng.bz/eD4Q.
Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en TensorFlow.js 461

Se produce retropropagación y ajuste de hiperparámetros. Que una arquitectura dada funcione


para un problema dado depende completamente de qué tan bien coincida la estructura de los
datos con la suposición de la arquitectura de la red.
Estos diferentes tipos de redes se pueden combinar fácilmente al estilo LEGO para formar redes
más complejas y multimodales. En cierto modo, las capas de aprendizaje profundo son ladrillos
LEGO para el procesamiento de información diferenciable. Brindamos una descripción general
rápida del mapeo entre la modalidad de los datos de entrada y la arquitectura de red adecuada:

- Datos vectoriales (sin orden temporal o serial): MLP (capas densas) Datos de
- imagen (blanco y negro, escala de grises o color): conexiones 2D Datos de audio
- como espectrogramas: conexiones 2D o RNN
- Datos de texto: convnets 1D o RNN Datos de
- series temporales: convnets 1D o RNN
- Datos volumétricos (como imágenes médicas en 3D): convenciones 3D
- Datos de video (secuencia de imágenes): ya sea convnets 3D (si necesita capturar efectos de
movimiento) o una combinación de un convnet 2D cuadro por cuadro para la extracción de
características seguido de un RNN o un convnet 1D para procesar la secuencia de características
resultante

Ahora profundicemos un poco más en cada una de las tres principales familias de arquitectura, las
tareas en las que son buenos y cómo usarlas a través de TensorFlow.js.

DREDES CONECTADAS ENSELY Y PERCEPTRONES MULTICAPA


Los términosredes densamente conectadasyperceptrón multicapason en gran parte intercambiables, con
la advertencia de que una red densamente conectada puede contener tan solo una capa, mientras que un
MLP debe constar de al menos una capa oculta y una capa de salida. Usaremos el términoMLPpara
referirse a todos los modelos construidos principalmente con capas densas en aras de la brevedad. Estas
redes están especializadas para datos vectoriales no ordenados (por ejemplo, las características
numéricas en el problema de detección de sitios web de phishing y el problema de predicción de precios
de viviendas). Cada capa densa intenta modelar la relación entre todos los pares posibles de entidades de
entrada y las activaciones de salida de la capa. Esto se logra mediante una multiplicación matricial entre el
núcleo de la capa densa y el vector de entrada (seguido de la suma con un vector de polarización y una
función de activación). El hecho de que cada activación de salida se vea afectada por cada característica de
entrada es la razón por la cual dichas capas y las redes construidas sobre ellas se conocen como
densamente conectado(o referido comototalmente conectadopor algunos autores). Esto contrasta con
otros tipos de arquitectura (convnets y RNN) en las que un elemento de salida depende solo de un
subconjunto de los elementos de los datos de entrada.

Los MLP se usan más comúnmente para datos categóricos (por ejemplo, donde las características de
entrada son una lista de atributos, como en el problema de detección de sitios web de phishing). También
se utilizan a menudo como las etapas finales de salida de la mayoría de las redes neuronales para
clasificación y regresión, que pueden contener capas convolucionales o recurrentes como extractores de
características que alimentan entradas de características a tales MLP. Por ejemplo, las convnets 2D que
462 CPASADO13Resumen, conclusiones y más allá

Todas las cubiertas en los capítulos 4 y 5 terminan con una o dos capas densas, al igual que las redes
recurrentes que visitamos en el capítulo 9.
Repasemos brevemente cómo seleccionar la activación de la capa de salida de un MLP para
diferentes tipos de tareas en el aprendizaje supervisado. Para realizar la clasificación binaria, la
capa densa final de su MLP debe tener exactamente una unidad y usar la activación sigmoidea. los
binariaCrosentropíala función de pérdida debe utilizarse como función de pérdida durante el
entrenamiento de dicho MLP de clasificador binario. Los ejemplos en tus datos de entrenamiento
deben tener etiquetas binarias (etiquetas de 0 o 1). Específicamente, el código TensorFlow.js parece

importar * como tf desde '@tensorflow/tfjs';

const modelo = tf.secuencial();


model.add(tf.layers.dense({unidades: 32, activación: 'relu', inputShape:
[númCaracterísticas de entrada]}));
model.add(tf.layers.dense({unidades: 32, activación: 'relu'}));
model.add(tf.layers.dense({unidades: 1: activación: 'sigmoide'}));
model.compile({pérdida: 'binaryCrossentropy', optimizador: 'adam'});

Para realizar una clasificación multiclase de una sola etiqueta (donde cada ejemplo tiene
exactamente una clase entre varias clases candidatas), termine su pila de capas con una capa
densa que contenga una activación softmax y una cantidad de unidades igual a la cantidad
de clases. Si sus objetivos están codificados en caliente, utiliceCruzentropía categóricacomo la
función de pérdida; si son índices enteros, utiliceSparseCategorical Crossentropy.Por ejemplo,

const modelo = tf.secuencial();


model.add(tf.layers.dense({unidades: 32, activación: 'relu', inputShape:
[númCaracterísticas de entrada]});
model.add(tf.layers.dense({unidades: 32, activación: 'relu'});
model.add(tf.layers.dense({unidades: numClasses: activación: 'softmax'});
model.compile( {pérdida: 'categoricalCrossentropy', optimizador: 'adam'});

Para realizar una clasificación multiclase multietiqueta (donde cada ejemplo puede tener varias
clases correctas), termine su pila de capas con una capa densa que contenga una activación
sigmoidea y una cantidad de unidades igual a la cantidad de todas las clases candidatas. Utilizar
binariaCrosentropíapara la función de pérdida. Sus objetivos deben estar codificados en k-hot:

const modelo = tf.secuencial();


model.add(tf.layers.dense({unidades: 32, activación: 'relu', inputShape:
[númCaracterísticas de entrada]}));
model.add(tf.layers.dense({unidades: 32, activación: 'relu'}));
modelo.add(tf.layers.dense({unidades: numClasses: activación: 'sigmoide'}));
model.compile({pérdida: 'binaryCrossentropy', optimizador: 'adam'});

Para realizar una regresión hacia un vector de valores continuos, termine su pila de capas con una
capa densa con la cantidad de unidades igual a la cantidad de valores que está tratando de
predecir (a menudo solo un número, como el precio de la vivienda o una temperatura). valor) y la
activación lineal por defecto. Varias funciones de pérdida pueden ser adecuadas para
Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en TensorFlow.js 463

regresión. Los más utilizados sonerror medio cuadradoymedia absoluta-


Error:
const modelo = tf.secuencial();
model.add(tf.layers.dense({unidades: 32, activación: 'relu', inputShape:
[númCaracterísticas de entrada]}));
model.add(tf.layers.dense({unidades: 32, activación: 'relu'}));
model.add(tf.layers.dense({units: numClasses})); model.compile({pérdida:
'meanSquaredError', optimizador: 'adam'});

CREDES ONVOLUCIONALES
Las capas convolucionales observan los patrones espaciales locales aplicando la misma transformación
geométrica a diferentes ubicaciones espaciales (parches) en un tensor de entrada. Esto da como resultado
representaciones que son invariantes a la traducción, lo que hace que las capas convolucionales sean
altamente eficientes y modulares. Esta idea es aplicable a espacios de cualquier dimensionalidad: 1D
(secuencias), 2D (imágenes o representaciones similares de cantidades que no son imágenes, como
espectrogramas de sonido), 3D (volúmenes), etc. Puedes usar eltf.layers.conv1d capa para procesar
secuencias, la capa conv2d para procesar imágenes y la capa conv3d para procesar volúmenes.

Las convnets consisten en pilas de capas convolucionales y de agrupación. Las capas de agrupación le
permiten reducir la muestra espacial de los datos, lo cual es necesario para mantener los mapas de
características en un tamaño razonable a medida que crece la cantidad de características y para permitir que las
capas posteriores "vean" una mayor extensión espacial de las imágenes de entrada de la convnet. Convnets a
menudo terminan con una capa plana o una capa de agrupación global, lo que convierte los mapas de
características espaciales en vectores, que a su vez pueden procesarse mediante una pila de capas densas (un
MLP) para lograr resultados de clasificación o regresión.
Es muy probable que la convolución regular pronto sea reemplazada en su mayor parte (o
completamente) por una alternativa equivalente pero más rápida y eficiente: convolución separable
en profundidad (tf.capas.separablesConv2dcapas). Cuando está construyendo una red desde cero, se
recomienda encarecidamente utilizar convolución separable en profundidad. La capa
separableConv2d se puede usar como un reemplazo directo paratf.layers . conv2d,dando como
resultado una red más pequeña y más rápida que se desempeña igual o mejor en su tarea. A
continuación se muestra una red típica de clasificación de imágenes (clasificación multiclase de
etiqueta única, en este caso). Su topología contiene patrones repetitivos de grupos de capas de
agrupación de convolución:

const modelo = tf.secuencial();


modelo.add(tf.layers.separableConv2d({
filtros: 32, kernelSize: 3, activación: 'relu', inputShape: [alto,
ancho, canales]})); modelo.add(tf.layers.separableConv2d({

filtros: 64, kernelSize: 3, activación: 'relu'}));


modelo.add(tf.layers.maxPooling2d({poolSize: 2}));

modelo.add(tf.layers.separableConv2d({
filtros: 64, kernelSize: 3, activación: 'relu'}));
modelo.add(tf.layers.separableConv2d({
filtros: 128, kernelSize: 3, activación: 'relu'}));
464 CPASADO13Resumen, conclusiones y más allá

modelo.add(tf.layers.maxPooling2d({poolSize: 2}));

modelo.add(tf.layers.separableConv2d({
filtros: 64, kernelSize: 3, activación: 'relu'}));
modelo.add(tf.layers.separableConv2d({
filtros: 128, kernelSize: 3, activación: 'relu'}));
modelo.add(tf.layers.globalAveragePooling2d()); model.add(tf.layers.dense({unidades: 32,
activación: 'relu'})); model.add(tf.layers.dense({unidades: numClasses, activación: 'softmax'}));

model.compile({pérdida: 'categoricalCrossentropy', optimizador: 'adam'});

RREDES ACTUALES
Los RNN funcionan procesando secuencias de entradas una marca de tiempo a la vez y manteniendo un
estado en todo momento. Un estado suele ser un vector o un conjunto de vectores (un punto en un
espacio geométrico). Los RNN se deben usar preferentemente sobre los convnets 1D en el caso de
secuencias en las que los patrones de interés no son temporalmente invariantes (por ejemplo, datos de
series temporales en los que el pasado reciente es más importante que el pasado lejano).
Hay tres tipos de capas RNN disponibles en TensorFlow.js: simpleRNN, GRU y LSTM. Para
la mayoría de los propósitos prácticos, debe usar GRU o LSTM. LSTM es el más poderoso de
los dos, pero también es computacionalmente más costoso. Puede pensar en GRU como una
alternativa más simple y económica a LSTM.
Para apilar varias capas RNN una encima de la otra, cada capa, excepto la última, debe
configurarse para devolver la secuencia completa de sus salidas (cada paso de tiempo de
entrada corresponderá a un paso de tiempo de salida). Si no se requiere el apilamiento de
capas RNN, normalmente la capa RNN necesita devolver solo la última salida, que en sí
misma contiene información sobre la secuencia completa.
El siguiente es un ejemplo del uso de una sola capa RNN junto con una capa densa para realizar
la clasificación binaria de una secuencia vectorial:

const modelo = tf.secuencial();


modelo.add(tf.layers.lstm({
unidades: 32,
inputShape: [numTimesteps, numFeatures] }));

model.add(tf.layers.dense({unidades: 1, activación: 'sigmoide'})); model.compile({pérdida:


'binaryCrossentropy', optimizador: 'rmsprop'});

El siguiente es un modelo con una pila de capas RNN para la clasificación multiclase de una sola etiqueta de una
secuencia vectorial:

const modelo = tf.secuencial();


modelo.add(tf.layers.lstm({
unidades: 32,
returnSequences: verdadero,
inputShape: [numTimesteps, numFeatures] }));

model.add(tf.layers.lstm({unidades: 32, returnSequences: true}));


modelo.add(tf.layers.lstm({unidades: 32}));
model.add(tf.layers.dense({unidades: numClasses, activación: 'softmax'}));
model.compile({pérdida: 'categoricalCrossentropy', optimizador: 'rmsprop'});
Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en TensorFlow.js 465

LAYUDAS Y REGULARIZADORES QUE AYUDAN A MITIGAR EL SOBREAJUSTE Y MEJORAR LA CONVERGENCIA


Aparte de los tipos de capas principales antes mencionados, algunos otros tipos de capas son
aplicables a una amplia gama de modelos y tipos de problemas y ayudan en el proceso de
capacitación. Sin estas capas, las precisiones de última generación en muchas tareas de
aprendizaje automático no serían tan altas como lo son hoy. Por ejemplo, las capas de abandono y
normalización por lotes a menudo se insertan en MLP, convnets y RNN para ayudar a que el
modelo converja más rápido durante el entrenamiento y para reducir el sobreajuste. El siguiente
ejemplo muestra una regresión MLP con capas de abandono incluidas:

const modelo = tf.secuencial();


modelo.add(tf.layers.dense({
unidades: 32,
activación: 'relú',
forma de entrada: [numCaracterísticas]
}));
model.add(tf.layers.dropout({tasa: model.add(tf.layers.dense({unidades:
0,25})); 64,
activación: 'relu'})); model.add(tf.layers.dropout({tasa: 0,25}));

model.add(tf.layers.dense({unidades: 64, activación: 'relu'}));


modelo.add(tf.layers.dropout({rate: 0.25}));
modelo.add(tf.layers.dense({
unidades: numClasses,
activación: 'categoricalCrossentropy' }));

model.compile({pérdida: 'categoricalCrossentropy', optimizador: 'rmsprop'});

13.2.3 Uso de modelos previamente entrenados de TensorFlow.js

Cuando el problema de aprendizaje automático que pretende resolver es específico de su


aplicación o conjunto de datos, crear y entrenar un modelo desde cero es el camino correcto, y
TensorFlow.js le permite hacerlo. Sin embargo, en algunos casos, el problema al que se enfrenta es
genérico para el que existen modelos preentrenados que coinciden exactamente con sus requisitos
o pueden satisfacer sus necesidades con solo pequeños ajustes. Hay disponible una colección de
modelos preentrenados de TensorFlow.js y de desarrolladores externos que se basan en ellos.
Dichos modelos proporcionan API limpias y fáciles de usar. También están muy bien
empaquetados como paquetes npm en los que puede confiar convenientemente en sus
aplicaciones de JavaScript (incluidas las aplicaciones web y los proyectos de Node.js).
El uso de tales modelos previamente entrenados en casos de uso apropiados puede acelerar
sustancialmente su desarrollo. Dado que es imposible enumerar todos los modelos preentrenados
basados en TensorFlow.js que existen, examinaremos solo los más populares que conocemos. Los
paquetes con el prefijo de nombre @tensorflow-models/ son propios y los mantiene el equipo de
TensorFlow.js, mientras que el resto es obra de desarrolladores externos.
@tensorflow-models/mobilenet es un modelo ligero de clasificación de imágenes. Produce las
puntuaciones de probabilidad para las 1000 clases de ImageNet dada una imagen de entrada. Es útil para
etiquetar imágenes en páginas web y para detectar contenidos específicos del flujo de entrada de la
cámara web, así como para tareas de transferencia de aprendizaje que involucran entradas de imágenes.
Si bien @tensorflow-models/mobilenet se ocupa de las clases de imágenes genéricas, hay
466 CPASADO13Resumen, conclusiones y más allá

son paquetes de terceros para una clasificación de imágenes más específica del dominio. Por
ejemplo, nsfwjsclasifica las imágenes en aquellas que contienen contenido pornográfico y otro
inapropiado versus contenido seguro, lo cual es útil para el control parental, la navegación segura y
aplicaciones similares.
Como discutimos en el capítulo 5, la detección de objetos difiere de la clasificación de imágenes
en que genera no soloquéobjetos que contiene una imagen sino tambiéndondeestán en el sistema
de coordenadas de la imagen.@tensorflow-models/coco-ssdes un modelo de detección de objetos
capaz de detectar 90 clases de objetos. Para cada imagen de entrada, puede detectar varios
objetos de destino con cuadros delimitadores potencialmente superpuestos, si existen (figura 13.1,
panel A).

A B C

D
mi
texto Identidad Insulto obsceno grave sexual amenaza toxicidad
ataque toxicidad explícito

Somos tipos en cpmputers, imbécil. Eres falso cierto falso falso falso falso cierto
asombrosamente estúpido.

Por favor deje de. Si continúa destrozando Wikipedia, falso falso falso falso falso falso falso
como lo hizo con Kmart, se le bloqueará la edición.

Respeto su punto de vista, y cuando esta discusión se falso falso falso falso falso falso falso
originó el 8 de abril tendía a estar de acuerdo con usted.

Figura 13.1 Capturas de pantalla de varios modelos de paquetes npm previamente entrenados creados con TensorFlow.js. Panel A:
@tensorflow-models/coco-ssd es un detector de objetos multiobjetivo. Panel B: face-api.js es para la detección de puntos clave
faciales y faciales en tiempo real (reproducido dehttps://github.com/justadudewhohacks/face-api.jscon permiso de Vincent Mühler).
Panel C: handtrack.js rastrea la ubicación de una o ambas manos en tiempo real (reproducido de https://github.com/victordibia/
handtrack.js/con permiso de Víctor Dibia). Panel D: @tensorflowmodels/posenet detecta puntos clave del esqueleto del cuerpo
humano mediante la entrada de imágenes en tiempo real. Panel E: @tensorflow-models/toxicity detecta y etiqueta siete tipos de
contenido inapropiado en cualquier entrada de texto en inglés.

Para las aplicaciones web, ciertos tipos de objetos son de especial interés debido a su potencial
para permitir interacciones entre humanos y computadoras novedosas y divertidas. Estos incluyen
el rostro humano, las manos y todo el cuerpo. Para cada uno de los tres, existen modelos de
terceros especializados basados en TensorFlow.js. para la cara,cara-api.js ymanos libresambos
admiten el seguimiento facial en tiempo real y la detección de puntos de referencia faciales (como
los ojos o la boca; figura 13.1, panel B). para las manos,pista de mano.js puede rastrear la ubicación
de una o ambas manos en tiempo real (figura 13.1, panel C). Para
Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en TensorFlow.js 467

todo el cuerpo,@tensorflow-modelos/posenetpermite la detección de alta precisión en


tiempo real de puntos clave del esqueleto (como hombros, codos, caderas y rodillas; figura
13.1, panel D).
Para la modalidad de entrada de audio,@tensorflow-models/speech-commandsofrece un
modelo preentrenado que detecta 18 palabras en inglés en tiempo real, utilizando directamente la
API WebAudio del navegador. Aunque esto no es tan poderoso como el reconocimiento de voz
continuo de gran vocabulario, permite una variedad de interacciones de usuario basadas en la voz
en el navegador.
También hay modelos preentrenados para la entrada de texto. Por ejemplo, el modelo de
@tensorflow-modelos/toxicidaddetermina qué tan tóxicos son los textos de entrada en inglés en varias
dimensiones (por ejemplo, amenazantes, insultantes u obscenos), lo cual es útil para la moderación de
contenido asistido (figura 13.1, panel E). El modelo de toxicidad se basa en un modelo de procesamiento
de lenguaje natural más genérico llamado@tensorflow-models/universal-sentence-encoder, que mapea
cualquier oración en inglés dada en un vector que luego se puede usar para una amplia gama de tareas
de procesamiento de lenguaje natural, como clasificación de intenciones, clasificación de temas, análisis
de sentimientos y respuesta a preguntas.
Debe enfatizarse que algunos de los modelos mencionados no solo admiten la
inferencia simple, sino que también pueden formar la base para el aprendizaje por
transferencia o el aprendizaje automático posterior, lo que le permite aplicar el
poder de estos modelos previamente entrenados a los datos específicos de su
dominio sin un modelo extenso. -Proceso de construcción o formación. Esto se
debe en parte a la componibilidad de capas y modelos similar a LEGO. Por ejemplo,
la salida del codificador de oraciones universal está destinada principalmente a ser
utilizada por un modelo posterior. El modelo de comandos de voz tiene soporte
integrado para recopilar muestras de voz para nuevas clases de palabras y entrenar
un nuevo clasificador basado en las muestras, lo cual es útil para aplicaciones de
comandos de voz que requieren un vocabulario personalizado o una adaptación de
voz específica del usuario. Además, las salidas de modelos como PoseNet y face-api.

Además de los modelos orientados a la modalidad de entrada mencionados anteriormente, también


existen modelos preentrenados de terceros basados en TensorFlow.js orientados a la creatividad
artística. Por ejemplo,ml5.jsincluye un modelo para la transferencia rápida de estilos entre imágenes y un
modelo que puede dibujar bocetos automáticamente.@magenta/músicapresenta un modelo que puede
transcribir música de piano ("audio a partitura") y MusicRNN, un "modelo de lenguaje para melodías" que
puede "escribir" melodías basadas en algunas notas iniciales, junto con otros modelos intrigantes
previamente entrenados.
La colección de modelos preentrenados es grande y sigue creciendo. La comunidad de JavaScript y la
comunidad de aprendizaje profundo tienen una cultura abierta y un espíritu de compartir. A medida que
avanza en su viaje de aprendizaje profundo, puede encontrar nuevas ideas interesantes que son
potencialmente útiles para otros desarrolladores, momento en el que se le anima a entrenar, empaquetar
y cargar sus modelos en npm en forma de modelos preentrenados.
468 CPASADO13Resumen, conclusiones y más allá

modelos que hemos mencionado, seguido de la interacción con los usuarios y la realización de mejoras
iterativas en su paquete. Entonces realmente se convertirá en un miembro contribuyente de la
comunidad de aprendizaje profundo de JavaScript.

13.2.4 El espacio de posibilidades


Con todas estas capas y módulos preentrenados como bloques de construcción, ¿qué modelos
útiles y divertidos puedes construir? Recuerde, construir modelos de aprendizaje profundo es como
jugar con ladrillos LEGO: las capas y los módulos se pueden conectar para asignar esencialmente
cualquier cosa a cualquier cosa, siempre que las entradas y salidas se representen como tensores,
y las capas tengan formas de tensores de entrada y salida compatibles. . La pila de capas resultante
que es el modelo realiza una transformación geométrica diferenciable, que puede aprender la
relación de mapeo entre la entrada y la salida siempre que la relación no sea demasiado compleja
dada la capacidad del modelo. En este paradigma, el espacio de posibilidades es infinito. Esta
sección ofrece algunos ejemplos para inspirarlo a pensar más allá de las tareas básicas de
clasificación y regresión que hemos enfatizado en este libro.
Hemos ordenado las sugerencias por modalidades de entrada y salida. Tenga en cuenta que algunos
de ellos amplían los límites de lo que es posible. Aunque un modelo podría entrenarse en cualquiera de
las tareas, dado que hay una cantidad suficiente de datos de entrenamiento disponibles, en algunos
casos, tal modelo probablemente no generalizaría bien lejos de sus datos de entrenamiento:

- Mapeo de vector a vector


– Atención médica predictiva—Asignación de registros médicos de pacientes a resultados de
tratamiento previstos

– Segmentación por comportamiento—Asignación de un conjunto de atributos del sitio web al


comportamiento de un espectador potencial en el sitio web (incluidas las visitas a la página, los clics y
otras interacciones)
– control de calidad del producto—Asignar un conjunto de atributos relacionados con un
producto fabricado a predicciones sobre qué tan bien funcionará el producto en el
mercado (ventas y ganancias en diferentes áreas del mercado)
- Mapeo de imagen a vector
– Imagen médica IA—Asignación de imágenes médicas (como radiografías) a resultados de diagnóstico

– Dirección automática del vehículo—Asignación de imágenes de cámaras a señales de control del


vehículo, como acciones de dirección del volante

– ayudante de dieta—Asignación de imágenes de alimentos y platos a efectos de salud previstos (por


ejemplo, recuentos de calorías o advertencias de alergia)

– recomendación de productos cosméticos—Asignación de imágenes de selfies a productos


cosméticos recomendados

- Asignación de datos de series temporales a vectores

– Interfaces cerebro-computadora—Asignación de señales de electroencefalograma (EEG) a las


intenciones del usuario
Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en TensorFlow.js 469

– Segmentación por comportamiento—Asignación del historial pasado de compras de


productos (como compras de películas o libros) a las probabilidades de comprar otros
productos en el futuro
– Predicción de terremotos y réplicas—Asignación de secuencias de datos de
instrumentos sísmicos a las probabilidades previstas de terremotos y réplicas
subsiguientes
- Asignación de texto a vector

– Clasificador de correo electrónico—Asignación de contenido de correo electrónico a etiquetas genéricas o definidas por el

usuario (por ejemplo, relacionado con el trabajo, relacionado con la familia y spam)

– anotador de gramática—Asignación de muestras de escritura de los estudiantes a puntajes de calidad de escritura

– Clasificación médica basada en el habla—Asignar la descripción de la enfermedad de un


paciente al departamento médico al que se debe derivar al paciente
- Asignación de texto a texto

– Sugerencia de mensaje de respuesta—Asignación de correos electrónicos a un conjunto de posibles mensajes de

respuesta

– Respuesta a preguntas específicas del dominio—Asignación de preguntas de clientes a textos de respuesta


automatizados

– resumen—Asignación de un artículo extenso a un breve resumen


- Asignación de imágenes a texto
– Generación automática de texto alternativo—Dada una imagen, generar un breve fragmento
de texto que capture la esencia del contenido
– Ayudas a la movilidad para personas con discapacidad visual—Mapeo de imágenes del
entorno interior o exterior para orientación hablada y advertencias sobre posibles peligros de
movilidad (por ejemplo, ubicaciones de salidas y obstáculos)
- Asignación de imágenes a imágenes

– Superresolución de imagen—Asignación de imágenes de baja resolución a otras de mayor resolución

– Reconstrucción 3D basada en imágenes—Asignación de imágenes ordinarias a imágenes del


mismo objeto pero visto desde un ángulo diferente
- Asignación de datos de imágenes y series temporales a vectores

– Asistente multimodal del doctor—Mapeo de la imagen médica de un paciente (como una resonancia
magnética) y el historial de signos vitales (presión arterial, frecuencia cardíaca, etc.) para predicciones
de los resultados del tratamiento
- Mapeo de imagen y texto a texto
– Respuesta de preguntas basada en imágenes—Asignar una imagen y una pregunta relacionada con
ella (por ejemplo, una imagen de un automóvil usado y una pregunta sobre su marca y año) a una
respuesta
- Mapeo de imagen y vector a imagen
– Prueba virtual de ropa y productos cosméticos—Asignación de la selfie de un usuario y
una representación vectorial de un cosmético o prenda a una imagen del usuario
usando ese producto
470 CPASADO13Resumen, conclusiones y más allá

- Asignación de datos de series temporales y vectores a datos de series temporales

– Transferencia de estilo musical—Asignación de una partitura musical (como una pieza clásica
representada como una serie temporal de notas) y una descripción del estilo deseado (por
ejemplo, jazz) a una nueva partitura musical en el estilo deseado

Como habrá notado, las últimas cuatro categorías de esta lista involucran modalidades mixtas en los datos de
entrada. En este punto de nuestra historia tecnológica, donde la mayoría de las cosas en la vida se han
digitalizado y, por lo tanto, se pueden representar como tensores, lo que potencialmente puede lograr con el
aprendizaje profundo está limitado solo por su propia imaginación y la disponibilidad de datos de entrenamiento.
Aunque casi cualquier mapeo es posible, no todos los mapeos lo son. Discutiremos en la siguiente sección qué
aprendizaje profundono poderhacer todavía.

13.2.5 Limitaciones del aprendizaje profundo

El espacio de aplicaciones que se pueden implementar con el aprendizaje profundo es casi infinito.
Como resultado, es fácil sobrestimar el poder de las redes neuronales profundas y ser demasiado
optimista sobre los problemas que pueden resolver. Esta sección habla brevemente sobre algunas
de las limitaciones que todavía tienen.

norteLAS REDES EURAL NO VEN EL MUNDO COMO LOS HUMANOS


Un riesgo al que nos enfrentamos cuando tratamos de comprender el aprendizaje
profundo esantropomorfización—es decir, la tendencia a malinterpretar las redes
neuronales profundas como si imitaran la percepción y la cognición en los humanos.
Antropomorfizar redes neuronales profundas es demostrablemente erróneo en algunos
aspectos. Primero, cuando los humanos perciben un estímulo sensorial (como una
imagen con la cara de una niña o una imagen con un cepillo de dientes), no solo
perciben los patrones de brillo y color de la entrada, sino que también extraen los
conceptos más profundos e importantes representados por esos patrones superficiales
(por ejemplo, la cara de un individuo femenino joven o un producto de higiene dental, y
la relación entre ambos). Las redes neuronales profundas, por otro lado, no funcionan
de esta manera. Cuando ha entrenado un modelo de subtítulos de imágenes para
asignar imágenes a la salida de texto, es un error creer que el modelo entiende la
imagen en un sentido humano. En algunos casos,
En particular, la forma peculiar y no humana en la que las redes neuronales profundas procesan sus
entradas se destaca porejemplos adversarios, que son muestras diseñadas a propósito para engañar a un
modelo de aprendizaje automático para que cometa errores de clasificación. Como demostramos al
encontrar las imágenes de máxima activación para los filtros de convnet en la sección 7.2, es posible
hacer un ascenso de gradiente en el espacio de entrada para maximizar la activación de un filtro de
convnet. La idea se puede extender a las probabilidades de salida, por lo que podemos realizar un
ascenso de gradiente en el espacio de entrada para maximizar la probabilidad predicha del modelo para
cualquier clase de salida dada. Al tomar una fotografía de un panda y agregarle un "gradiente de gibón",
podemos hacer que un modelo clasifique erróneamente la imagen como un gibón (figura 13.3). Esto es a
pesar del hecho de que el gradiente de gibón es similar al ruido y de pequeña magnitud, por lo que la
imagen del adversario resultante parece indistinguible de la imagen original del panda para los humanos.
Descripción general rápida del flujo de trabajo y los algoritmos de aprendizaje profundo en TensorFlow.js 471

Figura 13.2 Fallo de un modelo


de subtítulos de imágenes
entrenado con aprendizaje profundo

“El niño está sosteniendo un bate de béisbol”.

f(x) f(x)

Panda ¡Gibón!
Gibón
gradiente de clase

Panda Ejemplo adversarial

Figura 13.3 Ejemplo adversarial: los cambios imperceptibles para los ojos humanos pueden alterar el
resultado de la clasificación de un convnet profundo. Vea más discusión sobre los ataques adversarios de
las redes neuronales profundas enhttp://mng.bz/pyGz.
472 CPASADO13Resumen, conclusiones y más allá

Entonces, las redes neuronales profundas para la visión por computadora no poseen una comprensión
real de las imágenes, al menos no en un sentido humano. Otra área en la que el aprendizaje humano
contrasta fuertemente con el aprendizaje profundo es cómo los dos tipos de aprendizaje se generalizan a
partir de un número limitado de ejemplos de capacitación. Las redes neuronales profundas pueden hacer
lo que se puede llamargeneralización local. La figura 13.4 ilustra un escenario en el que una red neuronal
profunda y un humano tienen la tarea de aprender el límite de una sola clase en un espacio paramétrico
2D utilizando solo una pequeña cantidad de (digamos, ocho) ejemplos de entrenamiento. El ser humano
se da cuenta de que la forma del límite de clase debe ser suave y la región debe estar conectada, y
rápidamente dibuja una sola curva cerrada como el límite "estimado". Una red neuronal, por otro lado,
adolece de una falta de abstracción y conocimiento previo. Por lo tanto, puede terminar con un límite
irregular ad hoc severamente sobreajustado a las pocas muestras de entrenamiento. El modelo
entrenado generalizará muy mal más allá de las muestras de entrenamiento. Agregar más muestras
puede ayudar a la red neuronal, pero no siempre es posible en la práctica. El principal problema es que la
red neuronal se crea desde cero, sólo para este problema en particular. A diferencia de un individuo
humano, no tiene ningún conocimiento previo en el que confiar y, por lo tanto, no sabe qué "esperar".5
Esta es la razón fundamental detrás de una limitación importante de los algoritmos de aprendizaje
profundo actuales: a saber, generalmente se requiere una gran cantidad de datos de entrenamiento
etiquetados por humanos para entrenar una red neuronal profunda con una precisión de generalización
decente.

El mismo conjunto de
puntos de datos
o experiencia

Figura 13.4 Generalización local en


modelos de aprendizaje profundo frente a
Generalización local: Generalización extrema:
poder de generalización de poder de generalización generalización extrema en inteligencia
aprendizaje automático de humanos humana

5
Hay esfuerzos de investigación para entrenar una sola red neuronal profunda en muchas tareas diferentes y aparentemente no
relacionadas para facilitar el intercambio de conocimientos entre dominios (ver, por ejemplo, Lukasz Kaiser et al., "One Model To
Learn Them All", presentado el 16 de junio de 2018). 2017,https://arxiv.org/abs/1706.05137). Pero tales modelos multitarea aún no
han recibido una amplia adopción.
Tendencias en el aprendizaje profundo 473

13.3 Tendencias en el aprendizaje profundo

Como hemos discutido, el aprendizaje profundo ha hecho un progreso increíble en los últimos
años, pero todavía tiene algunas limitaciones. Pero el campo no es estático; sigue avanzando a una
velocidad vertiginosa, por lo que es probable que algunas de las limitaciones se mejoren en un
futuro próximo. Esta sección contiene un conjunto de conjeturas informadas sobre los avances
importantes en el aprendizaje profundo que veremos en los próximos años:

- Primero, el aprendizaje no supervisado o semisupervisado podría experimentar avances significativos.


Esto tendrá un profundo impacto en todas las formas de aprendizaje profundo porque, aunque los
conjuntos de datos etiquetados son costosos de construir y difíciles de encontrar, hay una gran cantidad
de conjuntos de datos sin etiquetar en todo tipo de dominios comerciales. Si podemos inventar una
forma de utilizar una pequeña cantidad de datos etiquetados para guiar el aprendizaje a partir de una
gran cantidad de datos no etiquetados, se desbloquearán muchas aplicaciones nuevas para el
aprendizaje profundo.

- En segundo lugar, es posible que se siga mejorando el hardware para el aprendizaje profundo,
dando lugar a aceleradores de redes neuronales cada vez más potentes (como las futuras
generaciones de la Unidad de procesamiento de tensores).6). Esto permitirá a los investigadores
entrenar redes cada vez más poderosas con conjuntos de datos cada vez más grandes y, por lo
tanto, continuar impulsando la precisión de vanguardia en muchas tareas de aprendizaje
automático, como visión por computadora, reconocimiento de voz, procesamiento de lenguaje
natural y generativo. modelos
-Es probable que el diseño de la arquitectura del modelo y el ajuste de los hiperparámetros del
modelo se automaticen cada vez más. Ya estamos viendo una tendencia en esta área, como
lo demuestran tecnologías como AutoML.7y Google Visir.8
- El uso compartido y la reutilización de componentes de redes neuronales probablemente
seguirá creciendo. El aprendizaje de transferencia basado en modelos previamente
entrenados cobrará mayor impulso. Los modelos de aprendizaje profundo de última
generación son cada vez más poderosos y genéricos cada día. Están cada vez más
capacitados en conjuntos de datos cada vez más grandes, a veces con enormes cantidades
de poder de cómputo en aras de la búsqueda arquitectónica automatizada y el ajuste de
hiperparámetros (consulte la primera y la segunda predicción). Como consecuencia, se está
volviendo más sensato y económico reutilizar dichos modelos previamente entrenados, ya
sea para inferencia directa o transferencia de aprendizaje, que entrenarlos desde cero una y
otra vez. En cierto modo, esto hace que el campo del aprendizaje profundo sea más similar
a la ingeniería de software tradicional, en la que se depende de las bibliotecas de alta
calidad y se reutilizan con regularidad.

Norman P. Jouppi et al., "Análisis de rendimiento en el centro de datos de una unidad de procesamiento de tensores™", 2017,
6

https://arxiv.org/pdf/1704.04760.pdf.
7
Barret Zoph y Quoc V. Le, "Búsqueda de arquitectura neuronal con aprendizaje reforzado", presentado el 5 de noviembre
de 2016,https://arxiv.org/abs/1611.01578.
8
Daniel Golovin, "Google Vizier: un servicio para la optimización de caja negra", Proc. 23.ª Conferencia internacional ACM
SIGKDD sobre descubrimiento de conocimientos y minería de datos, 2017, págs. 1487–1495,http://mng.bz/O9yE.
474 CPASADO13Resumen, conclusiones y más allá

- El aprendizaje profundo puede implementarse en nuevas áreas de aplicación, mejorando muchas


soluciones existentes y abriendo nuevos casos de uso práctico. En nuestra opinión, las áreas potenciales
de aplicación son verdaderamente ilimitadas. Los campos que incluyen la agricultura, las finanzas, la
educación, el transporte, la atención médica, la moda, los deportes y el entretenimiento presentan
innumerables oportunidades que esperan ser exploradas por los profesionales del aprendizaje
profundo.

- A medida que el aprendizaje profundo penetre en más dominios de aplicaciones, es probable que haya
un mayor énfasis en el aprendizaje profundo en el borde porque los dispositivos del borde están más
cerca de donde están los usuarios. Como resultado, es probable que el campo invente arquitecturas de
redes neuronales más pequeñas y con mayor eficiencia energética que logren la misma precisión y
velocidad de predicción que los modelos más grandes existentes.

Todas estas predicciones afectarán el aprendizaje profundo en JavaScript, pero las últimas tres
predicciones son especialmente relevantes. Espere que modelos más potentes y eficientes estén
disponibles para TensorFlow.js en el futuro.

13.4 Consejos para una mayor exploración


Como palabras finales de despedida, queremos darle algunos consejos sobre cómo seguir aprendiendo y
actualizando sus conocimientos y habilidades después de haber pasado la última página de este libro. El
campo del aprendizaje profundo moderno, tal como lo conocemos hoy, tiene solo unos pocos años, a
pesar de una prehistoria larga y lenta que se remonta a décadas. Con un aumento exponencial de los
recursos financieros y el personal de investigación desde 2013, el campo en su conjunto ahora se mueve a
un ritmo frenético. Muchas de las cosas que aprendiste en este libro no serán relevantes por mucho
tiempo. Son las ideas centrales del aprendizaje profundo (aprender de los datos, reducir la ingeniería
manual de funciones, la transformación de la representación capa por capa) las que probablemente se
mantendrán durante más tiempo. Más importante, La base del conocimiento que desarrolló al leer este
libro lo preparará para aprender sobre nuevos desarrollos y tendencias en el campo del aprendizaje
profundo por su cuenta. Afortunadamente, el campo tiene una cultura abierta en la que la mayoría de los
avances de vanguardia (¡incluidos muchos conjuntos de datos!) se publican en forma de preprints
gratuitos y de acceso abierto, acompañados de publicaciones de blogs públicos y tweets. Aquí hay
algunos recursos principales con los que debe estar familiarizado.

13.4.1 Practique problemas de aprendizaje automático del mundo real en Kaggle

Una forma efectiva de adquirir experiencia en el mundo real en el aprendizaje automático (y


especialmente en el aprendizaje profundo) es probar suerte en las competencias en Kaggle (https://
kaggle.com). La única forma real de aprender el aprendizaje automático es a través de la codificación real,
la creación de modelos y el ajuste. Esa es la filosofía del libro, como se refleja en sus numerosos ejemplos
de código listos para que los estudie, modifique y piratee. Pero nada es tan efectivo para enseñarle cómo
hacer aprendizaje automático como construir sus modelos y sistemas de aprendizaje automático desde
cero, utilizando una biblioteca como TensorFlow.js. En Kaggle, puede encontrar una variedad de
conjuntos de datos y competencias de ciencia de datos que se renuevan constantemente, muchos de los
cuales involucran aprendizaje profundo.
Traducido del inglés al español - www.onlinedoctranslator.com

Consejos para una mayor exploración 475

Aunque la mayoría de los usuarios de Kaggle usan herramientas de Python (como TensorFlow y Keras) para
resolver las competencias, la mayoría de los conjuntos de datos en Kaggle son independientes del idioma. Por lo
tanto, es completamente factible resolver la mayoría de los problemas de Kaggle utilizando un marco de
aprendizaje profundo que no sea de Python como TensorFlow.js. Al participar en algunas competencias, tal vez
como parte de un equipo, se familiarizará con el lado práctico de algunas de las mejores prácticas avanzadas
descritas en este libro, especialmente el ajuste de hiperparámetros y evitar el sobreajuste del conjunto de
validación.

13.4.2 Lea acerca de los últimos desarrollos en arXiv


La investigación de aprendizaje profundo, a diferencia de otros campos académicos, se lleva a cabo casi
completamente al aire libre. Los documentos se hacen públicos y de libre acceso tan pronto como se
finalizan y pasan la revisión, y una gran cantidad de software relacionado es de código abierto. ArXiv (
https://arxiv.org)—pronunciado “archivo” (la X representa la letra griega chí)—es un servidor de
preimpresión de acceso abierto para trabajos de matemáticas, física e informática. Se ha convertido en la
forma de facto de publicar trabajos de vanguardia en el campo del aprendizaje automático y el
aprendizaje profundo y, por lo tanto, también es la forma de facto de mantenerse actualizado en el
campo. Esto permite que el campo se mueva extremadamente rápido: todos los nuevos descubrimientos
e inventos están disponibles instantáneamente para que todos los vean, critiquen y desarrollen.
Una desventaja importante de ArXiv es la gran cantidad de documentos nuevos que se publican todos
los días, lo que hace que sea imposible hojearlos todos. El hecho de que muchos de los artículos sobre
ArXiv no estén revisados por pares hace que sea difícil identificar cuáles son importantes y de alta
calidad. La comunidad ha creado herramientas para ayudar con estos desafíos. Por ejemplo, un sitio web
llamado ArXiv Sanity Preserver (arxiv-sanity.com) sirve como un motor de recomendación para nuevos
documentos de ArXiv y puede ayudarlo a realizar un seguimiento de los nuevos desarrollos en dominios
verticales específicos de aprendizaje profundo (como el procesamiento del lenguaje natural o la detección
de objetos). Además, puede usar Google Scholar para realizar un seguimiento de las publicaciones en sus
áreas de interés y de sus autores favoritos.

13.4.3 Explorar el ecosistema TensorFlow.js


TensorFlow.js tiene un ecosistema dinámico y en crecimiento de documentación, guías, tutoriales,
blogosfera y proyectos de código abierto:

- Su referencia principal para trabajar con TensorFlow.js es la documentación oficial en


línea enwww.tensorflow.org/js/. La documentación API detallada y actualizada está
disponible enhttps://js.tensorflow.org/api/latest/.
- Puedes hacer preguntas sobre TensorFlow.js en Stack Overflow usando la etiqueta
"tensorflow.js":https://stackoverflow.com/questions/tagged/tensorflow.js. Para una
- discusión general sobre la biblioteca, use el Grupo de Google:https://grupos.
google.com/a/tensorflow.org/forum/#!forum/tfjs.
- También puede seguir a los miembros del equipo de TensorFlow.js que tienen una
presencia activa en Twitter, incluidos
– https://twitter.com/sqcai
– https://twitter.com/nsthorat
476 CPASADO13Resumen, conclusiones y más allá

– https://twitter.com/dsmilkov
– https://twitter.com/tensorflow

Ultimas palabras

Este es el final deAprendizaje profundo con JavaScript! Esperamos que haya aprendido un par de cosas
sobre IA, aprendizaje profundo y cómo realizar algunas tareas básicas de aprendizaje profundo en Java-
Script usando TensorFlow.js. Como cualquier tema interesante y útil, aprender sobre IA y aprendizaje
profundo es un viaje de toda la vida. Lo mismo puede decirse de la aplicación de la IA y el aprendizaje
profundo a problemas prácticos. Esto es cierto tanto para profesionales como para aficionados. A pesar
de todo el progreso realizado en el aprendizaje profundo hasta el momento, la mayoría de las preguntas
fundamentales siguen sin respuesta, y la mayor parte del potencial práctico del aprendizaje profundo
apenas se ha aprovechado. ¡Siga aprendiendo, cuestionando, investigando, imaginando, pirateando,
construyendo y compartiendo! ¡Esperamos ver lo que construye usando aprendizaje profundo y
JavaScript!
Apéndice A
Instalación de tfjs-node-gpu
y sus dependencias
Para usar la versión acelerada por GPU de TensorFlow.js (tfjs-node-gpu) en Node.js,
debe tener CUDA y CuDNN instalados en su máquina. En primer lugar, la máquina
debe estar equipada con una GPU NVIDIA compatible con CUDA. Para verificar si la
GPU en su máquina cumple con ese requisito, visitehttps://desarrollador. nvidia.com/
cuda-gpus.
A continuación, enumeramos los pasos detallados de la instalación del controlador y la biblioteca
para Linux y Windows, ya que estos son los dos sistemas operativos en los que actualmente se admite
tfjs-node-gpu.

A.1 Instalación de tfjs-node-gpu en Linux


1 Suponemos que ha instalado Node.js y npm en su sistema y que las rutas a
node y npm están incluidas en la ruta de su sistema. Si no, vea https://
nodejs.org/en/download/ para instaladores descargables.
2Descargue el kit de herramientas CUDA dehttps://developer.nvidia.com/cuda-
descargas. Asegúrese de elegir la versión adecuada para la versión de tfjs-nodegpu
que pretende utilizar. Al momento de escribir este artículo, la última versión de
tfjsnode-gpu es 1.2.10, que funciona con CUDA Toolkit versión 10.0. Además,
asegúrese de seleccionar el sistema operativo (Linux), la arquitectura (por ejemplo,
x86_64 para máquinas con CPU Intel estándar), la distribución de Linux y la versión
de la distribución correctos. Tendrá la opción de descargar varios tipos de
instaladores. Aquí, asumimos que descarga el archivo "runfile (local)" (a diferencia
de, por ejemplo, el paquete .deb local) para usarlo en los pasos posteriores.

3 En su carpeta de descargas, haga ejecutable el archivo de ejecución recién descargado. Por


ejemplo,
chmod +x cuda_10.0.130_410.48_linux.run

477
478 AAPÉNDICEA Instalación de tfjs-node-gpu y sus dependencias

4 Utilizarsudopara ejecutar el archivo de ejecución. Tenga en cuenta que es posible que el proceso
de instalación de CUDA Toolkit deba instalar o actualizar el controlador NVIDIA si la versión del
controlador NVIDIA que ya está instalada en su máquina es demasiado antigua o si no se ha
instalado dicho controlador. Si este es el caso, debe detener el servidor X pasando al modelo de
solo shell. En las distribuciones de Ubuntu y Debian, puede ingresar al modelo de solo shell con la
tecla de método abreviado Ctrl-Alt-F1.
Siga las indicaciones en la pantalla para instalar la instalación de CUDA Toolkit,
seguido de un reinicio de la máquina. Si está en modo de solo shell, puede
reiniciar de nuevo al modo GUI normal.
5 Si el paso 3 se completó correctamente, elnvidia-smiEl comando ahora debería estar
disponible en su ruta. Puede usarlo para verificar el estado de sus GPU. Proporciona
información como el nombre, la lectura del sensor de temperatura, la velocidad del
ventilador, el procesador y el uso de la memoria de las GPU NVIDIA instaladas en su
máquina, además de la versión actual del controlador NVIDIA. Es una herramienta útil para
el monitoreo en tiempo real de su GPU cuando usa tfjs-node-gpu para entrenar redes
neuronales profundas. Un mensaje impreso típico denvidia-smise parece a lo siguiente
(tenga en cuenta que esta máquina tiene dos GPU NVIDIA):

+------------------------------------------------------------------------------------+
| NVIDIA-SMI 384.111 Versión del controlador: 384.111 |
|-----------------------------------------------+----------------- -----+----------------------+ | Nombre de la GPU
Persistencia-M| ID de bus Disp.A | Descorr. volátil ECC | Uso de
| Fan Temp Perf Pwr:Uso/Cap| |===============================+================== memoria | GPU-Util Cómputo M. |
=====+======================| |
0 cuádruple P1000 Apagado | 00000000:65:00.0 activado | N/A |
| 41% 53C P0 ¡ERRAR! / N / A | 620MiB / 4035MiB | 0% Por defecto |
+----------------------------------+-----------------------+-------------------------+
|1 Quadro M4000 Apagado | 00000000:B3:00.0 Desactivado N/A |
| 46% 30C P8 | 11W / 120W | 2MiB / 8121MiB | 0% Predeterminado |
+----------------------------------+-----------------------+------------------------+

+------------------------------------------------------------------------------------+|
Procesos: Memoria GPU |
| GPU PID Escribe Nombre del proceso Uso |
|================================================= ============================| | 0
3876 G /usr/lib/xorg/Xorg 283MiB |
+------------------------------------------------------------------------------------+

6 Agregue la ruta a los archivos de la biblioteca CUDA de 64 bits a suLD_LIBRARY_PATH


Variable ambiental. Suponiendo que está utilizando el shell bash, puede agregar la
siguiente línea a su archivo .bashrc:
exportar LD_LIBRARY_PATH="/usr/local/cuda/lib64:${RUTA}"

tfjs-node-gpu utiliza elLD_LIBRARY_PATHvariable de entorno para encontrar los


archivos de biblioteca dinámica necesarios al iniciar.
7Descargar CuDNN dehttps://developer.nvidia.com/cudnn. Por qué
¿Necesita CuDNN además de CUDA? Esto se debe a que CUDA es una biblioteca de computación
genérica con usos en campos distintos al aprendizaje profundo (por ejemplo, fluidez).
Instalación de tfjs-node-gpu en Linux 479

dinámica). CuDNN es la biblioteca de NVIDIA para operaciones aceleradas de redes neuronales


profundas construidas sobre CUDA.
NVIDIA puede requerir que cree una cuenta de inicio de sesión y responda algunas
preguntas de la encuesta para descargar CuDNN. Asegúrese de descargar la versión
de CuDNN que coincida con la versión de CUDA Toolkit instalada en los pasos
anteriores. Por ejemplo, CuDNN 7.6 va con CUDA Toolkit 10.0.
8 A diferencia de CUDA Toolkit, el CuDNN descargado no viene con un instalador
ejecutable. En cambio, es un tarball comprimido que contiene varios archivos de
biblioteca dinámica y encabezados C/C++. Estos archivos deben extraerse y copiarse
en las carpetas de destino correspondientes. Puede usar una secuencia de comandos
como la siguiente para lograr esto:
alquitrán xzvf cudnn-10.0-linux-x64-v7.6.4.38.tgz cuda/lib64/* /
c.p. usr/local/cuda/lib64 cuda/incluir/* /usr/local/cuda/
c.p. incluir
9 Ahora que se han instalado todos los controladores y bibliotecas necesarios, puede verificar
rápidamente CUDA y CuDNN importando tfjs-node-gpu en el nodo:
npm i @tensorflow/tfjs @tensorflow/tfjs-node-gpu nodo

Luego, en la interfaz de línea de comandos de Node.js,


> const tf = requerir('@tensorflow/tfjs');
> require('@tensorflow/tfjs-node-gpu');

Si todo salió bien, debería ver una serie de líneas de registro que confirman el
descubrimiento de una GPU (o varias GPU, según la configuración de su sistema)
lista para usar con tfjs-node-gpu:
2018-09-04 13:08:17.602543: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1405]
Dispositivo encontrado 0 con propiedades:

nombre: Quadro M4000 mayor: 5 menor: 2 memoryClockRate (GHz): 0.7725 pciBusID:


0000:b3:00.0
memoria total: 7,93 GiB memoria libre: 7,86 GiB
2018-09-04 13:08:17.602571: I
tensorflow/core/common_runtime/gpu/gpu_device.cc:1484] dispositivos Agregando visible
gpu: 0
2018-09-04 13:08:18.157029: I tensorflow/core/common_runtime/gpu/
gpu_device.cc:965] interconectar StreamExecutor con matriz de borde de Dispositivo
fuerza 1: 2018-09-04 13:08:18.157054: I

tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 2018-09-04 0
13:08:18.157061: Yo
tensorflow/core/common_runtime/gpu/gpu_device.cc:984] 2018-09-04 0: norte

13:08:18.157213: Yo
tensorflow/core/common_runtime/gpu/gpu_device.cc:1097] Dispositivo TensorFlow
Creado (/
trabajo:localhost/réplica:0/tarea:0/dispositivo:GPU:0 con 7584 MB de memoria) -> GPU física
(dispositivo: 0, nombre : Quadro M4000, ID de bus pci: 0000:b3:00.0, capacidad de cómputo: 5.2)
480 AAPÉNDICEA Instalación de tfjs-node-gpu y sus dependencias

10 Ahora ya está todo listo para utilizar todas las funciones de tfjs-node-gpu. Solo asegúrese de
incluir las siguientes dependencias en su paquete.json (o sus versiones posteriores):
...
"dependencias": {
"@tensorflow/tfjs": "^0.12.6", "@tensorflow/tfjs-
node": "^0.1.14", . . .

}
...

En su archivo .js principal, asegúrese de importar las dependencias básicas, incluidas


@tensorflow/tfjsy @tensorflow/tfjs-nodo-gpu.El primero te da la
API general de TensorFlow.js, mientras que este último conecta las operaciones de TensorFlow.js
a los kernels de computación de alto rendimiento implementados en CUDA y CuDNN:
const tf = require('@tensorflow/tfjs');
require('@tensorflow/tfjs-node-gpu');

A.2 Instalación de tfjs-node-gpu en Windows


1 Asegúrese de que su Windows cumpla con los requisitos del sistema de CUDA Toolkit.
Ciertas versiones de Windows y arquitecturas de máquinas de 32 bits no son
compatibles con CUDA Toolkit. Verhttps://docs.nvidia.com/cuda/cuda-installation-
guidemicrosoft-windows/index.html#system-requirementspara más detalles.
2 Suponemos que ha instalado Node.js y npm en su sistema y que las rutas de Node.js y
npm están disponibles en la variable de entorno de su sistema. Sendero.Si no, vea
https://nodejs.org/en/descargar/para instaladores descargables. Instale Microsoft
3 Visual Studio, ya que lo requiere la instalación de CUDA Toolkit. Consulte el mismo
vínculo que en el paso 1 para saber qué versión de Visual Studio instalar.

4 Descargue e instale el kit de herramientas CUDA para Windows. Al momento de escribir este
artículo, se requiere CUDA 10.0 para ejecutar tfjs-node-gpu (última versión: 1.2.10). Asegúrese de
seleccionar el instalador correcto para su versión de Windows. Los instaladores para Windows 7 y
Windows 10 están disponibles. El paso requiere privilegios de administrador. Descargar CuDNN.
5 Asegúrese de que la versión de CuDNN coincida con la versión de CUDA. Por ejemplo, CuDNN 7.6
coincide con CUDA Toolkit 10.0. NVIDIA puede solicitarle que cree un inicio de sesión para su sitio
web y responda algunas preguntas de la encuesta antes de poder descargar CuDNN.

6 A diferencia del instalador de CUDA Toolkit, el CuDNN que acaba de descargar es


un archivo zip. Extráigalo y verá tres carpetas dentro: cuda/bin, cuda/include y
cuda/lib/x64. Localice el directorio en el que está instalado su CUDA Toolkit (de
forma predeterminada, es algo así como C:/Program Files/NVIDIA CUDA Toolkit
10.0/cuda). Copie los archivos extraídos a las subcarpetas correspondientes con el
mismo nombre allí. Por ejemplo, los archivos en cuda/bin del archivo zip extraído
deben copiarse en C:/Program Files/NVIDIA CUDA Toolkit 10.0/cuda/bin. Este
paso también puede requerir privilegios de administrador.
Instalación de tfjs-node-gpu en Windows 481

7 Después de instalar CUDA Toolkit y CuDNN, reinicie su sistema Windows. Descubrimos que esto
es necesario para que todas las bibliotecas recién instaladas se carguen correctamente para el
uso de tfjs-node-gpu.
8 Instale el paquete npmherramientas de construcción de ventanas.Esto es necesario para la
instalación del paquete npm @tensorflow/tfjs-nodo-gpuen el siguiente paso:
npm install --add-python-to-path='true' --herramientas globales de compilación de Windows

9 Instale los paquetes @tensorflow/tfjsy @tensorflow/tfjs-nodo-gpucon


npm:
npm -i @tensorflow/tfjs @tensorflow/tfjs-node-gpu
10 Para verificar que la instalación se realizó correctamente, abra la línea de comando del nodo y ejecute

> const tf = require('@tensorflow/tfjs');


> require('@tensorflow/tfjs-node-gpu');

Ver que ambos comandos terminen sin errores. Después del segundo comando, debería ver
algunas líneas de registro en la consola impresas por la biblioteca compartida de GPU de
TensorFlow. Esas líneas enumerarán los detalles de las GPU habilitadas para CUDA que tfjsnode-
gpu ha reconocido y utilizará en programas de aprendizaje profundo posteriores.
apéndice B
Un tutorial rápido de tensores.
y operaciones en
TensorFlow.js
Este apéndice se centra en las partes de la API de TensorFlow.js que no sontf.Modelo.
Aunquemodelo tfproporciona un conjunto completo de métodos para entrenar y evaluar
modelos y usarlos para la inferencia, a menudo necesita usar métodos nomodelo tfpartes
de TensorFlow.js para trabajar conmodelo tfobjetos. Los casos más comunes son

- Convertir sus datos en tensores que se pueden alimentar amodelo tfobjetos Clasificar los
- datos de las predicciones hechas pormodelo tf,que están en formato de tensores, por lo
que pueden ser utilizados por otras partes de su programa

Como verá, introducir y sacar datos de los tensores no es difícil, pero hay algunos
patrones habituales y puntos de precaución que vale la pena señalar.

B.1 Convenciones de creación de tensores y ejes de tensores


Recuerda que untensores simplemente un contenedor de datos. Todo tensor tiene dos
propiedades fundamentales: tipo de dato (dtype) y forma.tipo de dcontrola qué tipos de valores
se almacenan dentro del tensor. Un tensor dado puede almacenar solo un tipo de valor. En el
momento de escribir este artículo (versión 0.13.5), los dtype admitidos son float32, int32 y bool.

losformaes una matriz de enteros que indica cuántos elementos hay en el tensor y
cómo están organizados. Puede pensarse como la “forma y tamaño” del contenedor
que es el tensor (ver figura B.1).
La longitud de la forma se conoce como el tensor.rango. Por ejemplo, un tensor 1D,
también conocido comovector, tiene rango 1. La forma de un tensor 1D es una matriz que
contiene un número, y ese número nos dice cuánto mide el tensor 1D. Rango creciente

482
Convenciones de creación de tensores y ejes de tensores 483

tensor2d
escalar tensor1d (rango-2)
(rango-0) (rango-1) Forma: [3, 3]
Forma: [] Forma: [3]

tensor3d
(rango-3)
Forma: [3, 3, 3]
tensor4d
(rango-4)
Forma: [3, 3, 3, 3]

Figura B.1 Ejemplos de tensores de rango 0, 1, 2, 3 y 4

por uno, obtenemos un tensor 2D, que se puede visualizar como una cuadrícula de números en un plano
2D (como una imagen en escala de grises). La forma de un tensor 2D tiene dos números, que nos dicen
qué tan alto y qué tan ancho es la cuadrícula. Aumentando aún más el rango en uno, obtenemos un
tensor 3D. Como se muestra en el ejemplo de la figura B.1, puede visualizar un tensor 3D como una
cuadrícula de números 3D. La forma de un tensor 3D consta de tres números enteros; nos dicen el
tamaño de la cuadrícula 3D a lo largo de las tres dimensiones. Entonces, ves el patrón. Los tensores de
rango 4 (tensores 4D) son más difíciles de visualizar directamente porque el mundo en el que vivimos
tiene solo tres dimensiones espaciales. Los tensores 4D se usan con frecuencia en muchos modelos,
como convnets profundos. TensorFlow.js admite tensores de hasta el rango 6. En la práctica, los tensores
de rango 5 se usan solo en algunos casos de nicho (por ejemplo, aquellos que involucran datos de video),

B.1.1 Escalar (tensor de rango 0)

Un escalar es un tensor cuya forma es una matriz vacía ([]). No tiene ejes y siempre contiene
exactamente un valor. Puede crear un nuevo escalar usando eltf.escalar()función. En la consola de
JavaScript (nuevamente, suponiendo que TensorFlow.js esté cargado y disponible en el t.f.símbolo),
haga lo siguiente:

> const miEscalar = tf.scalar(2018);1


> miEscalar.print();
Tensor
2018
> miEscalar.dtype;
"flotador32"
> miEscalar.forma;

1
Tenga en cuenta que por espacio y claridad, omitiremos las líneas de salida de la consola de JavaScript que resultan de las asignaciones, ya
que no son ilustrativas del problema en cuestión.
484 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

[]
> miEscalar.rango;
0

Hemos creado un tensor escalar que contiene solo el valor 2018. Su forma es la lista vacía, como se
esperaba. Tiene el tipo de d por defecto ("flotador32").Para forzar que el dtype sea un número
entero, proporcione 'int32'como un argumento adicional al llamartf.escalar():

> const myIntegerScalar = tf.scalar(2018, 'int32');


> myIntegerScalar.dtype; "int32"

Para recuperar los datos del tensor, podemos usar el método asíncronodatos().El método es
asíncrono porque, en general, el tensor puede estar alojado fuera de la memoria principal, como
en las GPU, como una textura WebGL. Recuperar el valor de dichos tensores implica operaciones
que no se garantiza que se resuelvan de inmediato, y no queremos que esas operaciones bloqueen
el hilo principal de JavaScript. Esta es la razón por la cual eldatos()el método es asíncrono. También
hay una función síncrona que recupera los valores de los tensores a través del sondeo:
sincronización de datos().Este método es conveniente pero bloquea el subproceso principal de Java-
Script, por lo que debe usarse con moderación (por ejemplo, durante la depuración). Prefiero el
asíncronodatos()siempre que sea posible:

> arr = esperar myScalar.data(); Matriz


flotante32 [2018]
> arr.longitud
1
> arr[0]
2018

Usarsincronización de datos():

> arr = myScalar.dataSync(); Matriz


flotante32 [2018]
> arr.longitud
1
> arr[0]
2018

Vemos que para tensores de tipo float32, eldatos()ysincronización de datos ()los métodos devuelven
los valores como JavaScriptMatriz flotante32primitivo. Esto puede ser un poco sorprendente si
esperaba un número simple y antiguo, pero tiene más sentido si se considera que es posible que
los tensores de otras formas deban devolver un contenedor de varios números. Para tipo int32
y tensores de tipo bool,datos()ysincronización de datos ()regresoInt32ArrayyUint8Array,
respectivamente.

Tenga en cuenta que aunque un escalar siempre contiene exactamente un elemento, lo


contrario no es cierto. Un tensor cuyo rango es mayor que 0 también puede tener exactamente un
elemento, siempre que el producto de los números en su forma sea 1. Por ejemplo, un tensor 2D
de forma [1, 1]tiene un solo elemento, pero tiene dos ejes.
Convenciones de creación de tensores y ejes de tensores 485

B.1.2 tensor1d (tensor de rango 1)

Un tensor 1D a veces se denomina tensor de rango 1 o vector. Un tensor 1D tiene exactamente un


eje y su forma es una matriz de longitud 1. El siguiente código creará un vector en la consola:

> const miVector = tf.tensor1d([-1.2, 0, 19, 78]);


> miVector.forma;
[4]
> miVector.rango;
1
> esperar myVector.data(); Matriz
Flotante32(4) [-1.2, 0, 19, 78]

Este tensor 1D tiene cuatro elementos y puede denominarse vector de 4 dimensiones. No confundas un
4Dvectorcon un 4Dtensor! Un vector 4D es un tensor 1D que tiene un eje y contiene exactamente cuatro
valores, mientras que un tensor 4D tiene cuatro ejes (y puede tener cualquier número de dimensiones a
lo largo de cada eje). La dimensionalidad puede indicar la cantidad de elementos a lo largo de un eje
específico (como en nuestro vector 4D) o la cantidad de ejes en un tensor (por ejemplo, un tensor 4D), lo
que a veces puede resultar confuso. Es técnicamente más correcto y menos ambiguo referirse a un tensor
de rango 4, pero la notación ambigua del tensor 4D es común independientemente. En la mayoría de los
casos, esto no debería ser un problema, ya que se puede eliminar la ambigüedad según el contexto.

Como en el caso de los tensores escalares, puede utilizar eldatos()ysincronización de datos ()métodos para
acceder a los valores de los elementos del tensor 1D; por ejemplo,

> esperar myVector.data()


Flotador32Array(4) [-1.2000000476837158, 0, 19, 78]

Alternativamente, puede utilizar la versión síncrona dedatos()-a saber,sincronización de datos ()—pero ten en cuenta que
sincronización de datos ()puede bloquear el subproceso de la interfaz de usuario y debe evitarse si es posible:

> miVector.dataSync()
Flotador32Array(4) [-1.2000000476837158, 0, 19, 78]

Para acceder al valor de un elemento específico del tensor 1D, simplemente puede
indexar en el TypedArray devuelto pordatos()odataSync();por ejemplo,
> [esperar myVector.data()][2] 19

B.1.3 tensor2d (tensor de rango 2)

Un tensor 2D tiene dos ejes. En algunos casos, un tensor 2D se denominamatriz, y sus dos ejes
pueden interpretarse como los índices de fila y columna de la matriz, respectivamente. Puede
interpretar visualmente una matriz como una cuadrícula rectangular de elementos (consulte el
tercer panel de la figura B.1). En TensorFlow.js,

> const miMatriz = tf.tensor2d([[1, 2, 3], [40, 50, 60]]);


> miMatriz.forma;
[2, 3]
> miMatriz.rango;
2
486 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

Las entradas del primer eje se denominanfilas, y las entradas del segundo eje son las
columnas. En el ejemplo anterior, [1, 2, 3]es la primera fila, y [1, 40]es la primera columna. Es
importante saber que al devolver los datos, utilizandodatos()o sincronización de datos(),los
datos vendrán como una matriz plana enfila mayorpedido. En otras palabras, los elementos
de la primera fila aparecerán en elMatriz flotante32primero, seguido de los elementos de la
segunda fila, y así sucesivamente:2

> esperar myMatrix.data(); Matriz flotante32 (6) [1,


2, 3, 40, 50, 60]

Anteriormente, mencionamos que eldatos()ysincronización de datos ()Los métodos, cuando son


seguidos por la indexación, se pueden usar para acceder al valor de cualquier elemento de
un tensor 1D. Cuando se usa en tensores 2D, la operación de indexación se vuelve tediosa
porque el TypedArray devuelto pordatos()ysincronización de datos ()aplanalos elementos del
tensor 2D. Por ejemplo, para determinar el elemento de TypedArray que corresponde al
elemento en la segunda fila y la segunda columna del tensor 2D, tendría que realizar
operaciones aritméticas como las siguientes:

> (esperar myMatrix.data())[1 * 3 + 1]; 50

Afortunadamente, TensorFlow.js proporciona otro conjunto de métodos para descargar valores de


tensores en estructuras de datos simples de JavaScript:formación()yarraySync().a diferencia de datos()y
sincronización de datos(),estos métodos devuelven matrices de JavaScript anidadas que conservan
correctamente el rango y la forma de los tensores originales. Por ejemplo,

> JSON.stringify(esperar myMatrix.array()) "[[1,2,3],


[40,50,60]]"

Para acceder a un elemento en la segunda fila y la segunda columna, simplemente podemos realizar la
indexación en la matriz anidada dos veces:

> (esperar myMatrix.array())[1][1] 50

Esto elimina la necesidad de realizar aritmética de índices y será especialmente conveniente para
tensores de mayor dimensión.arraySync()es la versión síncrona de formación().Me gustasincronización de
datos (), sincronización de matriz ()puede bloquear el subproceso de la interfaz de usuario y debe usarse con
precaución.
En eltf.tensor2d()llamada, proporcionamos una matriz de JavaScript anidada como
argumento. El argumento consta de filas de matrices anidadas dentro de otra matriz. Esta
estructura de anidamiento es utilizada portf.tensor2d()para inferir la forma del tensor 2D, es
decir, cuántas filas y cuántas columnas hay, respectivamente. Una forma alternativa de crear
el mismo tensor 2D contf.tensor2d()es proporcionar los elementos como una matriz de
JavaScript plana (no anidada) y acompañarla de un segundo argumento que especifica la
forma del tensor 2D:

2
Esto es diferente del orden de las columnas principales que se observa en otros marcos numéricos como MATLAB
y R.
Convenciones de creación de tensores y ejes de tensores 487

> const miMatriz = tf.tensor2d([1, 2, 3, 40, 50, 60], [2, 3]);


> miMatriz.forma;
[2, 3]
> miMatriz.rango;
2

En este enfoque, el producto de todos los números en elformaEl argumento debe coincidir con el
número de elementos en la matriz flotante, de lo contrario, se generará un error durante el
tf.tensor2d()llamada. Para tensores de rangos superiores a 2, también existen dos enfoques
análogos para la creación de tensores: usar una matriz anidada única como argumento o una
matriz plana acompañada de un argumento de forma. Verá ambos enfoques utilizados en
diferentes ejemplos a lo largo de este libro.

B.1.4 Tensores de rango 3 y dimensiones superiores

Si empaqueta varios tensores 2D en una nueva matriz, obtendrá un tensor 3D, que puede imaginar
como un cubo de elementos (el cuarto panel en la figura B.1). Los tensores de rango 3 se pueden
crear en TensorFlow.js siguiendo el mismo patrón que antes:

> const miRank3Tensor = tf.tensor3d([[[1, 2, 3],


[4, 5, 6]],
[[10, 20, 30],
[40, 50, 60]]]);
> myRank3Tensor.shape;
[2, 2, 3]
> myRank3Tensor.rango;
3

Otra forma de hacer lo mismo es proporcionar una matriz de valores plana (no anidada),
junto con una forma explícita:

> const otroRank3Tensor = tf.tensor3d( [1, 2, 3, 4, 5, 6, 7, 8,


9, 10, 11, 12], [2, 2, 3]);

lostf.tensor3d()La función en este ejemplo se puede reemplazar con la más genérica tf.tensor()
función. Esto le permite generar tensores de cualquier rango hasta 6. A continuación,
creamos un tensor de rango 3 y uno de rango 6:

> otroTensorRank3 = tf.tensor(


[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [2, 2, 3]);

> otraRank3Tensor.shape; [2, 2, 3]

> otroRank3Tensor.rank; 3

> tinyRank6Tensor = tf.tensor([13], [1, 1, 1, 1, 1, 1]);


> tinyRank6Tensor.forma; [1, 1,
1, 1, 1, 1]
> tinyRank6Tensor.rango; 6
488 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

B.1.5 La noción de lotes de datos


En la práctica, el primer eje (eje 0, porque la indexación comienza en 0) en todos los tensores con
los que se encontrará en el aprendizaje profundo casi siempre será eleje de lote(a veces llamado el
eje de muestrasodimensión del lote). Por lo tanto, un tensor real tomado por un modelo como
entrada tiene un rango que excede el rango de una característica de entrada individual en 1. Esto
es cierto en todos los modelos TensorFlow.js de este libro. El tamaño de la primera dimensión es
igual al número de ejemplos en el lote, conocido comotamaño del lote. Por ejemplo, en el ejemplo
de clasificación de la flor del iris en el capítulo 3 (listado 3.9), la característica de entrada de cada
ejemplo consta de cuatro números representados como un vector de longitud 4 (un tensor 1D de
forma [4]). Por lo tanto, la entrada al modelo de clasificación del iris es 2D y tiene una forma [nulo,
4], donde el primer valor nulo indica un tamaño de lote que se determinará en el tiempo de
ejecución del modelo (ver figura B.2). Esta convención de procesamiento por lotes también se
aplica a la salida de los modelos. Por ejemplo, el modelo de clasificación de iris genera una
codificación one-hot para los tres posibles tipos de iris para cada ejemplo de entrada individual,
que es un tensor de forma 1D [3].Sin embargo, la forma de salida real del modelo es 2D y tiene una
forma de [nulo, 3],donde la primera dimensión de valor nulo es el tamaño del lote a determinar.

tensor para
Tensor para individuo ejemplos por lotes
ejemplo (rango-2)
(rango-1)
Forma: [nulo, 3]
Forma: [3]
Ejemplo 1

Ejemplo 2

Ejemplonorte

Figura B.2 Formas de tensor para ejemplos individuales (izquierda) y ejemplos por lotes (derecha). El tensor
para ejemplos por lotes tiene un rango uno mayor que el tensor para un ejemplo individual y es el formato
aceptado por elpredecir(),encajar(), yevaluar()métodos demodelo tfobjetos. losnuloen la forma del
tensor para ejemplos por lotes indica que la primera dimensión del tensor tiene un tamaño indeterminado,
que puede ser cada entero positivo durante las llamadas reales a los métodos antes mencionados.

B.1.6 Ejemplos del mundo real de tensores

Hagamos los tensores más concretos con algunos ejemplos similares a los que encontrarás
en el libro. Los datos que manipulará casi siempre caerán en una de las siguientes categorías.
En la discusión anterior, seguimos la convención de procesamiento por lotes y siempre
incluimos el número de ejemplos en el lote (numEjemplos)como primer eje:
-Datos vectoriales—Tensores 2D con forma [numEjemplos, caracteristicas]
- Datos de serie temporal (secuencia)—Tensores 3D con forma [numEjemplos, intervalos de tiempo,
caracteristicas]
Convenciones de creación de tensores y ejes de tensores 489

-Imágenes—Tensores 4D con forma [numEjemplos, alto, ancho, canales]


-Video—Tensores 5D con forma [numEjemplos, marco, altura, ancho,
canales]
VDATOS ECTORALES
Este es el caso más común. En dicho conjunto de datos, cada muestra de datos individual se puede
codificar como un vector y, por lo tanto, un lote de datos se codificará como un tensor de rango 2, donde
el primer eje es el eje de muestras y el segundo eje es el eje de características.
Veamos dos ejemplos:
- Un conjunto de datos actuariales de personas, en el que consideramos la edad, el código postal y los ingresos de
cada persona. Cada persona se puede caracterizar como un vector de 3 valores y, por lo tanto, se puede
almacenar un conjunto de datos completo de 100,000 personas en un tensor 2D con forma

[100000, 3].
- Un conjunto de datos de documentos de texto, donde representamos cada documento por
el conteo de cuántas veces aparece cada palabra en él (por ejemplo, de un diccionario de
inglés de las 20,000 palabras más comunes). Cada documento se puede codificar como un
vector de 20.000 valores (un recuento por palabra en el diccionario) y, por lo tanto, se puede
almacenar un lote de 500 documentos en un tensor de forma [500, 20000].

TYO ME-DATOS DE SERIE O SECUENCIA


Siempre que el tiempo importe en sus datos (o la
noción de orden de secuencia), tiene sentido
almacenarlo en un tensor 3D con un eje de tiempo
explícito. Cada muestra se codifica como una
Características

secuencia de vectores (un tensor 2D) y, por lo tanto,


un lote de muestras se codificará como un tensor 3D
os
(consulte la figura B.3). pl
em
Ej
El eje de tiempo es casi siempre el segundo Pasos de tiempo

eje (eje de índice 1) por convención, como en los


siguientes ejemplos: Figura B.3 Un tensor de datos de series de tiempo 3D

- Un conjunto de datos de precios de acciones. Cada minuto almacenamos el precio actual de


la acción, el precio más alto en el último minuto y el precio más bajo en el último minuto.
Así, cada minuto se codifica como un vector de tres valores. Dado que hay 60 minutos en
una hora, una hora de negociación se codifica como un tensor de forma 2D [60, 3].Si
tenemos un conjunto de datos de 250 horas independientes de secuencias, la forma del
conjunto de datos será [250, 60, 3].
- Un conjunto de datos de tweets en el que codificamos cada tweet como una secuencia de 280
caracteres de un alfabeto de 128 caracteres únicos. En esta configuración, cada carácter se puede
codificar como un vector binario de tamaño 128 (todos ceros excepto una entrada 1 en el índice
correspondiente al carácter). Entonces cada uno puede ser considerado como un tensor de forma
de rango 2 [280, 128].Un conjunto de datos de 1 millón de tweets se puede almacenar en un
tensor de forma [1000000, 280, 128].
490 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

IDATOS DEL MAGO

Los datos de una imagen suelen tener tres dimensiones:


alto, ancho y profundidad de color. Aunque las imágenes en lo
r
Co ales
escala de grises tienen un solo canal de color, por n
ca
convención, los tensores de imagen siempre son de rango
3, con un canal de color unidimensional para imágenes en

Altura
escala de grises. Un lote de 128 imágenes en escala de
os
grises de tamaño 256 × 256 se almacenaría en un tensor de pl
j e m
E
forma [128, 256, 256, 1],y un lote de 128 imágenes en color
Ancho
se almacenaría en un tensor de forma [128, 256, 256, 3] (ver
figura B.4). Esto se llama la convención NHWC (consulte el Figura B.4 Un tensor de datos de imagen 4D

capítulo 4 para obtener más detalles).


Algunos marcos ponen la dimensión de los canales antes que la altura y el ancho, usando
la convención NCHW. No usamos esta convención en este libro, pero no se sorprenda al ver
un tensor de imagen de una forma como [128, 3, 256, 256]en otra parte.

VDATOS IDEOLÓGICOS

Los datos de video sin procesar son uno de los pocos tipos de datos comunes del mundo real para los que
necesitará tensores de rango 5. Un vídeo puede entenderse como una secuencia de fotogramas, siendo
cada fotograma una imagen en color. Dado que cada cuadro se puede almacenar en un tensor de rango 3
[alto, ancho, colorChannel],una secuencia de fotogramas se puede almacenar en un tensor 4D [cuadros,
alto, ancho, colorChannel],y así se almacenaría un lote de videos diferentes
en un tensor 5D de forma [muestras, marcos, alto, ancho, colorChannel].
Por ejemplo, un videoclip de YouTube de 60 segundos y 144 × 256 muestreado a 4 fotogramas
por segundo tendría 240 fotogramas. Un lote de cuatro clips de vídeo de este tipo se almacenaría
en un tensor de forma [4, 240, 144, 256, 3].¡Eso es un total de 106,168,320 valores! Si el dtipo del
tensor fuera 'flotar32',entonces cada valor se almacenaría en 32 bits, por lo que el tensor
representaría 405 MB. ¡Esta es una gran cantidad de datos! Los videos que encuentras en la vida
real son mucho más livianos porque no están almacenados en float32 y, por lo general, están
comprimidos en gran medida (como en el formato MPEG).

B.1.7 Creación de tensores a partir de búferes de tensores

Hemos mostrado cómo crear tensores a partir de matrices de JavaScript utilizando funciones como
tf.tensor2d()ytf.tensor().Para hacerlo, debe haber determinado los valores de todos los elementos y
establecerlos en las matrices de JavaScript de antemano. En algunos casos, sin embargo, es algo
tedioso crear una matriz JavaScript de este tipo desde cero. Por ejemplo, suponga que desea crear
una matriz de 5 × 5 en la que todos los elementos fuera de la diagonal sean cero y los elementos de
la diagonal formen una serie creciente que es igual al índice de fila o columna más 1:

[[1, 0, 0, 0, 0],
[0, 2, 0, 0, 0], [0, 0, 3,
0, 0], [0, 0, 0, 4, 0], [0,
0, 0, 0, 5]]
Convenciones de creación de tensores y ejes de tensores 491

Si tuviera que crear una matriz de JavaScript anidada para cumplir con este
requisito, el código sería similar al siguiente:
constante n = 5;
const matrizArray = []; para (sea i = 0; i <
5; ++i) {
const fila = [];
para (sea j = 0; j < 5; ++j) {
fila.push(j === i ? i + 1 : 0);
}
matrizArray.push(fila);
}

Finalmente, puede convertir la matriz de JavaScript anidadamatrizArrayen un tensor 2D:

> const matriz = tf.tensor2d(matrixArray);

Este código parece un poco tedioso. Se trata de dos anidadosporbucles ¿Hay alguna manera
de simplificarlo? La respuesta es sí: podemos usar eltf.tensorBuffer()método para crear un
TensorBuffer.ATensorBufferobjeto le permite especificar sus elementos por índices y cambiar
sus valores usando elcolocar()método. Esto es diferente de un objeto tensor en TensorFlow.js,
cuyos valores de elementos soninmutable. Cuando haya terminado de configurar los valores
de todos los elementos de unTensorBufferdesea establecer, elTensorBufferse puede convertir
convenientemente en un objeto tensor real a través de suaTensor()método. Por lo tanto, si
usamostf.tensorBuffer()para lograr la misma tarea de creación de tensores que el código
anterior, el nuevo código se verá como

const buffer = tf.tensorBuffer([5, 5]); para (sea i = 0; i


Especifica la forma del tensor al crear un
< 5; ++i) {
TensorBuffer. Un TensorBuffer tiene valores
buffer.set(i + 1, i, i);
todos cero después de la creación.
}
const matriz = buffer.toTensor(); El primer argumento son los valores deseados,
mientras que los argumentos restantes son los

Obtiene un objeto tensor real índices del elemento que se va a configurar.

del TensorBuffer

Por lo tanto, al usartf.tensorBuffer(),redujimos las líneas de código de 10 a 5.

B.1.8 Creación de tensores todo cero y todo uno

A menudo es deseable crear un tensor de una forma dada con todos los elementos iguales a cero.
Puedes usar eltf.ceros()función para lograrlo. Para llamar a la función, proporcione la forma
deseada como argumento de entrada; por ejemplo,

> const x = tf.ceros([2, 3, 3]);


> x.imprimir();
Tensor
[[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]]
492 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

El tensor creado tiene el dtype predeterminado (float32). Para crear tensores todos ceros de otros
dtypes, especifique el dtype como el segundo argumento paratf.ceros().
Una función relacionada estf.zerosLike(),que le permite crear un tensor todo cero de
la misma forma y tipo que un tensor existente. Por ejemplo,
> const y = tf.zerosLike(x);

es equivalente a

> const y = tf.ceros(x.forma, x.dtipo);

pero es más resumido.


Los métodos análogos le permiten crear tensores de los cuales todos los elementos son iguales a
una:tf.unos()ytf.oneLike().

B.1.9 Crear tensores de valores aleatorios


La creación de tensores con valores aleatorios es útil en muchos casos, como la inicialización de
pesos. Las funciones más utilizadas para crear tensores de valores aleatorios son
tf.randomNormal()ytf.uniforme aleatorio().Las dos funciones tienen una sintaxis similar pero
conducen a diferentes distribuciones en los valores de los elementos. Como su nombre indica,
tf.random-Normal()devuelve tensores en los que los valores de los elementos siguen una
distribución normal (gaussiana).3Si invoca la función con solo un argumento de forma, obtendrá un
tensor cuyos elementos siguen elunidaddistribución normal: una distribución normal con media =
0 y desviación estándar (DE) = 1. Por ejemplo,

> const x = tf.randomNormal([2, 3]);


> x.imprimir():
Tensor
[[-0.2772508, 0.63506, 0.3080665],
[0,7655841, 2,5264773, 1,142776]]

Si desea que la distribución normal tenga una media o SD no predeterminada, puede


proporcionarlos como segundo y tercer argumento de entrada, respectivamente. Por ejemplo, la
siguiente llamada crea un tensor en el que los elementos siguen una distribución normal de media
=-20 y DE = 0,6:

> const x = tf.randomNormal([2, 3], -20, 0.6);


> x.imprimir();
Tensor
[[-19.0392246, - 21.2259483, - 21.2892818],
[-20.6935596, - 20.3722878, - 20.1997948]]

tf.uniforme aleatorio()le permite crear tensores aleatorios con valores de elementos distribuidos
uniformemente. De forma predeterminada, la distribución uniforme es una unidad, es decir, con un límite
inferior de 0 y un límite superior de 1:

> const x = tf.uniforme aleatorio([3, 3]);


> x.imprimir();
Tensor

3Para los lectores familiarizados con las estadísticas, los valores de los elementos son independientes entre sí.
Operaciones básicas de tensor 493

[[0.8303654, 0.3996494, 0.3808384],


[0.0751046, 0.4425731, 0.2357403],
[0.4682371, 0.0980235, 0.7004037]]

Si desea que el valor del elemento siga una distribución uniforme no unitaria, puede
especificar los límites inferior y superior como segundo y tercer argumento paratf.aleatorio-
Uniform(),respectivamente. Por ejemplo,

> const x = tf.uniforme aleatorio([3, 3], -10, 10);

crea un tensor con valores distribuidos aleatoriamente en el [-10, 10)intervalo:

> x.imprimir();
Tensor
[[-7.4774652, -4.3274679, 5.5345411],
[-6.767087, -3.8834026, -3.2619202], [-8.0232048,
7.0986223, -1.3350322]]

tf.uniforme aleatorio()se puede utilizar para crear tensores de tipo int32 con valores aleatorios. Esto
es útil para los casos en los que desea generar etiquetas aleatorias. Por ejemplo, el siguiente
código crea un vector de longitud 10 en el que los valores se extraen aleatoriamente de los
números enteros del 0 al 100 (el intervalo [0, 100)):

> const x = tf.randomUniform([10], 0, 100, 'int32');


> x.imprimir();
Tensor
[92, 16, 65, 60, 62, 16, 77, 24, 2, 66]

Tenga en cuenta que el 'int32'argumento es la clave en este ejemplo. Sin él, el tensor que
obtenga contendrá valores float32 en lugar de int32.

B.2 Operaciones básicas de tensor


Los tensores no serían de mucha utilidad si no pudiéramos realizar operaciones sobre ellos.
Tensor-Flow.js admite una gran cantidad de operaciones de tensor. Puede ver una lista de
ellos, junto con su documentación, enhttps://js.tensorflow.org/api/latest. Describir cada uno
de ellos sería tedioso y redundante. Por lo tanto, destacaremos algunas de las operaciones
más utilizadas como ejemplos. Las operaciones de uso frecuente se pueden clasificar en dos
tipos: unarias y binarias. AunarioLa operación toma un tensor como entrada y devuelve un
nuevo tensor, mientras que unabinarioLa operación toma dos tensores como entradas y
devuelve un nuevo tensor.

B.2.1 operaciones unarias

Consideremos la operación de tomar el negativo de un tensor, es decir, usar el valor


negativo de cada elemento del tensor de entrada, y formar un nuevo tensor de la misma
forma y tipo. Esto se puede hacer contf.neg():
> const x = tf.tensor1d([-1, 3, 7]);
> const y = tf.neg(x);
> y.imprimir();
Tensor
[1, -3, -7]
494 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

FUNCCIONALAPIcontra.ENCADENAMIENTOAPI
En el ejemplo anterior, invocamos la funcióntf.neg()con el tensorXcomo argumento de
entrada. TensorFlow.js proporciona una forma más concisa de realizar la operación
matemáticamente equivalente: usar elneg()método, que es un método del objeto tensor en sí
mismo, en lugar de una función bajo elf.*espacio de nombres:

> const y = x.neg();

En este ejemplo simple, la cantidad de escritura ahorrada debido a la nueva API puede no
parecer tan impresionante. Sin embargo, en los casos en que sea necesario aplicar una serie
de operaciones una tras otra, la segunda API mostrará ventajas considerables sobre la
primera. Por ejemplo, considere un algoritmo hipotético en el que desea tomar el negativo
deX,calcular el recíproco (1 dividido por cada elemento), y aplicar el relufunción de activación
en él. Este es el código que se necesita para implementar el algoritmo en la primera API:

> const y = tf.relu(tf.reciprocal(tf.neg(x)));

Por el contrario, en la segunda API, el código de implementación parece

> const y = x.neg().reciprocal().relu();

La segunda implementación eclipsa a la primera en estos aspectos:


- Hay menos caracteres, menos escritura y, por lo tanto, una menor posibilidad de cometer
errores.
- No es necesario equilibrar los pares anidados de paréntesis de apertura y cierre (aunque la
mayoría de los editores de código modernos lo ayudarán a hacerlo).
- Más importante aún, el orden en que aparecen los métodos en el código coincide con
el orden en que ocurren las operaciones matemáticas subyacentes. (Observe que en la
primera implementación, el orden se invierte). Esto conduce a una mejor legibilidad
del código en la segunda implementación.

Nos referiremos a la primera API como lafuncionalAPI porque se basa en funciones de llamada
bajo elf.*espacio de nombres La segunda API será referida como laencadenamiento API, debido a
que las operaciones aparecen en una secuencia como una cadena (como puedes ver en el ejemplo
anterior). Se puede acceder a la mayoría de las operaciones en TensorFlow.js como la versión
funcional bajo elf.*espacio de nombres y la versión de encadenamiento como un método de
objetos tensor. Puede elegir entre las dos API en función de sus necesidades. A lo largo de este
libro, usamos ambas API en diferentes lugares, con preferencia por la API de encadenamiento para
los casos que involucran operaciones en serie.

miLEMENTO-SABIO VS.OPERACIONES DE REDUCCIÓN


Los ejemplos de operaciones unarias que mencionamos (tf.neg(), tf.recíproco(),y tf.relu())tienen
la propiedad común de que la operación ocurre en elementos individuales del tensor de
entrada de forma independiente. Como resultado, el tensor devuelto de dicha operación
conserva la forma del tensor de entrada. Sin embargo, otras operaciones unarias en
TensorFlow.js conducen a una forma de tensor más pequeña que la original. Que hace
Operaciones básicas de tensor 495

¿Significa "más pequeño" en el contexto de la forma del tensor? En algunos casos, significa un rango
inferior. Por ejemplo, una operación unaria puede devolver un tensor escalar (rango 0) dado un tensor 3D
(rango 3). En otros casos, significa que el tamaño de cierta dimensión es más pequeño que el original. Por
ejemplo, una operación unaria puede devolver un tensor de forma [3, 1] dada una entrada de forma [3,
20].Independientemente de cómo se encoja la forma, estas operaciones se conocen comooperaciones de
reducción.
tf.media()es una de las operaciones de reducción más utilizadas. aparece como elsignificar()
metodo de laTensorclass en la API de encadenamiento. Cuando se invoca sin argumentos
adicionales, calcula la media aritmética de todos los elementos del tensor de entrada,
independientemente de su forma, y devuelve un escalar. Su uso en la API de
encadenamiento parece

> const x = tf.tensor2d([[0, 10], [20, 30]]);


> x.media().print();
Tensor
15

A veces, requerimos que la media se calcule por separado sobre las filas del tensor 2D
(matriz) en lugar de sobre el tensor completo. Esto se puede lograr proporcionando un
argumento adicional a lasignificar()método:

> x.media(-1).imprimir();
Tensor
[5, 25]

El argumento -1indica que elsignificar()El método debe calcular las medias aritméticas a lo
largo de la última dimensión del tensor.4Esta dimensión se conoce como la dimensión de
reducción, ya que se "reducirá" en el tensor de salida, que se convierte en un tensor de rango
1. Una forma alternativa de especificar la dimensión de reducción es utilizar el índice real de
la dimensión:

> x.media(1).print();

Tenga en cuenta quesignificar()también admite múltiples dimensiones de reducción. Por


ejemplo, si tiene un tensor 3D de forma [10, 6, 3],y quiere que la media aritmética se calcule
sobre las dos últimas dimensiones, produciendo un tensor de forma 1D [10],Puedes llamar
significar()comox.media([-2, -1])ox.media([1, 2]).Dejamos esto como un ejercicio en el
final de este apéndice.
Otras operaciones unarias de reducción de uso frecuente incluyen

-tf.suma(),que es casi idéntico atf.media(),pero calcula la suma, en lugar de la


media aritmética, sobre los elementos.
- tf.norma(),que calcula la norma sobre los elementos. Hay diferentes tipos de normas.
Por ejemplo, una norma 1 es una suma de los valores absolutos de los elementos. Una
norma de 2 se calcula tomando la raíz cuadrada de la suma sobre los elementos
cuadrados. En otras palabras, es la longitud de un vector en un espacio euclidiano.

4Esto sigue la convención de indexación de Python.


496 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

tf.norma()se puede utilizar para calcular la varianza o la desviación estándar de una


lista de números.
-tf.min()ytf.max(),que calculan el valor mínimo y máximo sobre los elementos,
respectivamente.
- tf.argMax(),que devuelve el índice del elemento máximo sobre un eje de reducción.
Esta operación se usa con frecuencia para convertir la salida de probabilidad de un
modo de clasificación en el índice de la clase ganadora (por ejemplo, vea el problema
de clasificación de la flor de iris en la sección 3.3.2).tf.argMin()proporciona una
funcionalidad similar para encontrar el valor mínimo.

Mencionamos que las operaciones por elementos conservan la forma del tensor de entrada.
Pero la conversación no es verdadera. Algunas operaciones de preservación de la forma no
son elementos. por ejemplo, eltf.transponer()La operación puede realizar una transposición
de matriz, en la que el elemento en los índices [yo, j]en el tensor 2D de entrada se asigna a los
índices [Ji]en el tensor 2D de salida. Las formas de entrada y salida detf.transponer()será
idéntico si la entrada es una matriz cuadrada, pero esta no es una operación de elementos,
ya que el valor en [yo, j]del tensor de salida no depende únicamente del valor en [yo, j] en el
tensor de entrada, sino que depende de valores en otros índices.

B.2.2 Operaciones binarias

A diferencia de las operaciones unarias, una operación binaria requiere dos argumentos de
entrada.tf.añadir() es quizás la operación binaria más utilizada. Es quizás también el más simple, ya
que simplemente suma dos tensores. Por ejemplo,

> const x = tf.tensor2d([[0, 2], [4, 6]]);


> const y = tf.tensor2d([[10, 20], [30, 46]]);
> tf.añadir(x, y).imprimir();
Tensor
[[10, 22],
[34, 52]]

Las operaciones binarias similares incluyen

- tf.sub()para restar dos tensores


- tf.mul()para multiplicar dos tensores
- tf.matMul()para calcular el producto matricial entre dos tensores
-tf.lógicoY(), tf.lógicoO(),y tf.logicaXor()para realizar
Operaciones AND, OR y XOR en tensores de tipo bool, respectivamente.

Algunas operaciones binarias admitenradiodifusión, u operar en dos tensores de entrada de diferentes


formas y aplicar un elemento en la entrada de una forma más pequeña sobre múltiples elementos en la
otra entrada de acuerdo con una determinada regla. Ver cuadro de información 2.4 en el capítulo 2 para
una discusión detallada.
Operaciones básicas de tensor 497

B.2.3 Concatenación y corte de tensores


Las operaciones unarias y binarias son tensor-in-tensor-out (TITO), en el sentido de que
toman uno o más tensores como entrada y devuelven un tensor como salida. Algunas
operaciones de uso frecuente en TensorFlow.js no son TITO porque toman un tensor,
junto con otro argumento no tensor, como sus entradas.tf.concat()es quizás la función
más utilizada en esta categoría. Le permite concatenar múltiples tensores de forma
compatible en un solo tensor. La concatenación es posible solo si la forma de los
tensores satisface ciertas restricciones. Por ejemplo, es posible combinar a [5, 3]tensor y
un [4, 3]tensor a lo largo del primer eje para obtener un [9, 3]tensor, pero no es posible
combinar los tensores si sus formas son [5, 3]y [4, 2]!Dada la compatibilidad de formas,
puede utilizar eltf.concat()Función para concatenar tensores. Por ejemplo, el siguiente
código concatena un todo cero [2, 2]tensor con un solo [2, 2]tensor a lo largo del primer
eje, lo que da un [4, 2]tensor en el que la mitad "superior" es todo cero y la mitad
"inferior" es todo uno:
> const x = tf.ceros([2, 2]);
> const y = tf.unos([2, 2]);
> tf.concat([x, y]).print(); Tensor

[[0, 0],
[0, 0],
[1, 1],
[1, 1]]

Debido a que las formas de los dos tensores de entrada son idénticas, es posible concatenarlas de
manera diferente: es decir, a lo largo del segundo eje. El eje se puede especificar como el segundo
argumento de entrada paratf.concat().Esto nos dará un [2, 4]tensor en el que la mitad izquierda es todo
cero y la mitad derecha es todo uno:

> tf.concat([x, y], 1).print(); Tensor

[[0, 0, 1, 1],
[0, 0, 1, 1]]

Además de concatenar varios tensores en uno, a veces queremos realizar la operación


"inversa", recuperando una parte de un tensor. Por ejemplo, suponga que ha creado un
tensor 2D (matriz) de forma [3, 2],
> const x = tf.randomNormal([3, 2]);
> x.imprimir();
Tensor
[[1.2366893 , 0.6011682 ],
[-1.0172369, - 0.5025602],
[-0.6265425, - 0.0009868]]

y le gustaría obtener la segunda fila de la matriz. Para eso, puedes usar la versión de
encadenamiento detf.slice():
> x.segmento([1, 0], [1, 2]).print(); Tensor

[[-1.0172369, -0.5025602],]
498 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

El primer argumento arodaja()indica que la parte del tensor de entrada que queremos
comienza en el índice 1 a lo largo de la primera dimensión y el índice 0 de la segunda
dimensión. En otras palabras, debe comenzar desde la segunda fila y la primera columna, ya
que el tensor 2D que estamos tratando aquí es una matriz. El segundo argumento especifica
la forma de la salida deseada: [1, 2]o, en lenguaje matricial, 1 fila y 2 columnas.
Como puede verificar al mirar los valores impresos, hemos recuperado con éxito la segunda fila
de la matriz 3 × 2. La forma de la salida tiene el mismo rango que la entrada (2), pero el tamaño de
la primera dimensión es 1. En este caso, estamos recuperando la totalidad de la segunda
dimensión (todas las columnas) y un subconjunto de la primera dimensión (un subconjunto de las
filas). Este es un caso especial que nos permite lograr el mismo efecto con una sintaxis más simple:

> x.rebanada(1, 1).imprimir();


Tensor
[[-1.0172369, -0.5025602],]

En esta sintaxis más simple, solo necesitamos especificar el índice de inicio y el tamaño del
fragmento solicitado a lo largo de la primera dimensión. Si se pasa 2 en lugar de 1 como segundo
argumento de entrada, la salida contendrá la segunda y tercera fila de la matriz:

> x.rebanada(1, 2).imprimir();


Tensor
[[-1.0172369, - 0.5025602],
[-0.6265425, - 0.0009868]]

Como habrás adivinado, esta sintaxis más simple está relacionada con la convención de procesamiento
por lotes. Facilita la obtención de datos para ejemplos individuales de un tensor por lotes.
Pero, ¿y si queremos accedercolumnasde la matriz en lugar de filas? En este caso,
tendríamos que usar la sintaxis más compleja. Por ejemplo, supongamos que queremos la
segunda columna de la matriz. Se puede lograr por

> x.segmento([0, 1], [-1, 1]).print(); Tensor

[[0.6011682],
[-0.5025602],
[-0.0009868]]

Aquí, el primer argumento ([0, 1])es una matriz que representa los índices iniciales del segmento que
queremos. Es el primer índice a lo largo de la primera dimensión y el segundo índice a lo largo de la
segunda dimensión. En pocas palabras, queremos que nuestro segmento comience en la primera fila y la
segunda columna. El segundo argumento ([-1, 1])especifica el tamaño de la rebanada que queremos. El
primer número (–1) indica que queremos todos los índices a lo largo de la primera dimensión (queremos
que comiencen todas las filas), mientras que el segundo número (1) significa que queremos solo un índice
a lo largo de la segunda dimensión (queremos solo una columna). El resultado es la segunda columna de
la matriz.
Mirando la sintaxis derodaja(),es posible que te hayas dado cuenta de querodaja()no se limita a
recuperar solo filas o columnas. De hecho, es lo suficientemente flexible como para permitirle recuperar
cualquier "submatriz" del tensor 2D de entrada (cualquier área rectangular consecutiva dentro de la
matriz), si los índices iniciales y la matriz de tamaño se especifican correctamente. Más generalmente,
Gestión de memoria en TensorFlow.js: tf.dispose() y tf.tidy() 499

para tensores de cualquier rango mayor que 0,rodaja()le permite recuperar cualquier
subtensor consecutivo del mismo rango dentro del tensor de entrada. Te lo dejamos como
ejercicio al final de este apéndice.
Aparte detf.rebanada()ytf.concat(),otras dos operaciones de uso frecuente que dividen un
tensor en partes o combinan múltiples tensores en uno sontf.unstack() ytf.pila(). tf.unstack()
divide un tensor en múltiples "piezas" a lo largo de la primera dimensión. Cada una de esas
piezas tiene un tamaño de 1 a lo largo de la primera dimensión. Por ejemplo, podemos usar
la API de encadenamiento detf.unstack():

> const x = tf.tensor2d([[1, 2], [3, 4], [5, 6]]);


> x.imprimir();
Tensor
[[1, 2],
[3, 4],
[5, 6]]
> piezas const = x.unstack();
> consola.log(piezas.longitud); 3

> piezas[0].print();
Tensor
[1, 2]
> piezas[1].print();
Tensor
[3, 4]
> piezas[2].print();
Tensor
[5, 6]

Como puede notar, las "piezas" devueltas pordesapilar()tienen un rango uno menor que el del
tensor de entrada.
tf.pila()es el reverso detf.unstack().Como sugiere su nombre, "apila" varios tensores
con formas idénticas en un nuevo tensor. Siguiendo el fragmento de código del ejemplo
anterior, volvemos a juntar las piezas:
> tf.stack(piezas).print(); Tensor

[[1, 2],
[3, 4],
[5, 6]]

tf.unstack()es útil para obtener los datos correspondientes a ejemplos individuales


de un tensor por lotes;tf.pila()es útil para combinar los datos de ejemplos
individuales en un tensor por lotes.

B.3 Gestión de memoria en TensorFlow.js:


tf.dispose() y tf.tidy()
En TensorFlow.js, si trata directamente con objetos tensoriales, debe administrar la memoria
en ellos. En particular, un tensor debe desecharse después de su creación y uso, o seguirá
ocupando la memoria asignada para él. Si los tensores no dispuestos se vuelven demasiados
en número o demasiado grandes en su tamaño total, eventualmente causarán
500 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

la pestaña del navegador se quede sin memoria WebGL o que el proceso de Node.js se quede sin
memoria del sistema o GPU (dependiendo de si se está utilizando la versión de CPU o GPU de
tfjsnode). TensorFlow.js no realiza la recolección automática de elementos no utilizados de
tensores creados por el usuario.5Esto se debe a que JavaScript no admite la finalización de objetos.
TensorFlow.js proporciona dos funciones para la gestión de la memoria:tf.dispose()y
tf.ordenado().
Por ejemplo, considere un ejemplo en el que realiza inferencias repetidas en un
modelo TensorFlow.js usando unporlazo:
const modelo = esperar tf.loadLayersModel(
'https://storage.googleapis.com/tfjs-models/tfjs/iris_v1/model.json'); const x = tf.uniforme
aleatorio([1, 4]);
para (sea i = 0; i < 3; ++i) { Crea un maniquí Carga un preentrenado
const y = modelo.predecir(x); tensor de entrada modelo de la web
y.imprimir();
console.log(`# de tensores: ${tf.memory().numTensors}` );
Comprueba el número
} de actualmente
tensores asignados
La salida se verá como
Tensor
[[0.4286409, 0.4692867, 0.1020722],]
# de tensores: 14
Tensor
[[0.4286409, 0.4692867, 0.1020722],]
# de tensores: 15
Tensor
[[0.4286409, 0.4692867, 0.1020722],]
# de tensores: 16

Como puede ver en el registro de la consola, cada vez quemodelo.predecir()se llama, genera un tensor
adicional, que no se elimina después de que finaliza la iteración. Si elpor loop puede ejecutarse durante
suficientes iteraciones, eventualmente causará un error de falta de memoria. Esto se debe a que el tensor
de salidayno se desecha correctamente, lo que provoca una pérdida de memoria del tensor. Hay dos
formas de arreglar esta pérdida de memoria.
En el primer acercamiento, puede llamartf.dispose()en el tensor de salida cuando ya no es
necesario:

para (sea i = 0; i < 3; ++i) {


const y = modelo.predecir(x); Elimina la salida
y.imprimir(); tensor después de su uso
tf.dispose(y);
console.log(`# de tensores: ${tf.memory().numTensors}` );
}

En el segundo enfoque, puede envolver el cuerpo delporbucle contf.ordenado():

5
Sin embargo, los tensores creados dentro de las funciones y los métodos de objetos de TensorFlow.js son administrados
por la propia biblioteca, por lo que no debe preocuparse por envolver las llamadas a dichas funciones o métodos en
tf.ordenado().Ejemplos de tales funciones incluyentf.confusionMatrix(), tf.Modelo.predecir(),ytf.Modelo.ajuste().
Gestión de memoria en TensorFlow.js: tf.dispose() y tf.tidy() 501

para (sea i = 0; i < 3; ++i) {


tf.tidy() elimina automáticamente todos los tensores
tf.ordenado(() => {
creados dentro de la función que se le pasa, excepto
const y = modelo.predecir(x);
los tensores que devuelve la función.
y.imprimir();
console.log(`# de tensores: ${tf.memory().numTensors} ); });

Con cualquier enfoque, debería ver que la cantidad de tensores asignados se vuelve
constante durante las iteraciones, lo que indica que ya no hay pérdida de memoria de tensor.
¿Qué enfoque debería preferir? En general, debe utilizartf.ordenado() (el segundo enfoque),
porque elimina la necesidad de llevar un registro de qué tensores desechar.tf.ordenado()es
una función inteligente que elimina todos los tensores creados dentro de la función anónima
que se le pasa como argumento (excepto aquellos que son devueltos por la función, más
adelante), incluso para los tensores que no están vinculados a ningún objeto de JavaScript.
Por ejemplo, supongamos que modificamos ligeramente el código de inferencia anterior
para obtener el índice de la clase ganadora usandoargMax():

const modelo = esperar tf.loadLayersModel(


'https://storage.googleapis.com/tfjs-models/tfjs/iris_v1/model.json'); const x = tf.uniforme
aleatorio([1, 4]);
para (sea i = 0; i < 3; ++i) {
const índiceganador =
modelo.predecir(x).argMax().dataSync()[0]; console.log(`índice
ganador: ${winningIndex}`); console.log(`# de tensores: $
{tf.memory().numTensors}` );
}

Cuando se ejecuta este código, verá que en lugar de filtrar un tensor por iteración, filtra
dos:
índice ganador: 0
# de tensores: 15
índice ganador: 0
# de tensores: 17
índice ganador: 0
# de tensores: 19

¿Por qué se filtran dos tensores por iteración? Bueno, la línea

const índiceganador =
modelo.predecir(x).argMax().dataSync()[0];

genera dos nuevos tensores. La primera es la salida demodelo.predecir(),y el segundo es el valor de


retorno deargMax().Ninguno de los tensores está vinculado a ningún objeto de JavaScript. Se
utilizan inmediatamente después de la creación. Los dos tensores están "perdidos" en el sentido de
que no hay objetos de JavaScript que pueda usar para referirse a ellos. Por eso, tf.dispose()no se
puede utilizar para limpiar los dos tensores. Sin embargo,tf.ordenado()todavía se puede usar para
corregir la pérdida de memoria, ya que realiza la contabilidad de los nuevos tensores,
independientemente de si están vinculados a objetos de JavaScript:

const modelo = esperar tf.loadLayersModel(


'https://storage.googleapis.com/tfjs-models/tfjs/iris_v1/model.json');
502 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

const x = tf.uniforme aleatorio([1, 4]); para (sea i


= 0; i < 3; ++i) {
tf.ordenado(() => {
const índiceganador = modelo.predecir(x).argMax().dataSync()[0];
console.log(`índice ganador: ${winningIndex}`);
console.log(`# de tensores: ${tf.memory().numTensors} ); });

tf.tidy() elimina automáticamente los tensores creados en el cuerpo


}
de una función anónima que se le pasa como argumento, incluso
cuando esos tensores no están vinculados a objetos de JavaScript.

Los usos de ejemplo detf.ordenado()operar en funciones que no devuelven ningún tensor. Si


la función devuelve tensores, no desea que se eliminen porque deben usarse después. Esta
situación se encuentra con frecuencia cuando escribe operaciones de tensor personalizadas
mediante las operaciones de tensor básicas proporcionadas por TensorFlow.js. Por ejemplo,
supongamos que queremos escribir una función que calcule el valor normalizado del tensor
de entrada, es decir, un tensor con la media restada y la desviación estándar escalada a 1:

función normalizar(x) {
const media = x.media(); const sd =
x.norma(2); devuelve
x.sub(media).div(sd);
}

¿Cuál es el problema con esta implementación?6En términos de gestión de memoria, filtra un


total de tres tensores: 1) la media, 2) la SD y 3) una más sutil: el valor de retorno de lasub()
llamada. Para arreglar la pérdida de memoria, envolvemos el cuerpo de la función
ción contf.ordenado():

función normalizar(x) {
volver tf.ordenado(() => {
const media = x.media(); const
sd = x.norma(2); regreso
x.sub(media).div(sd);
});
}

Aquí,tf.ordenado()hace tres cosas por nosotros:

- Elimina automáticamente los tensores que se crean en la función anónima pero


que no devuelve, incluidas las tres filtraciones mencionadas. Lo hemos visto en
los ejemplos anteriores.
- Detecta que la salida deldiv()La llamada es devuelta por la función anónima y, por
lo tanto, la reenviará a su propio valor de retorno.
- Mientras tanto, evitará deshacerse de ese tensor en particular, por lo que puede usarse
fuera deltf.ordenado()llamada.

6
Hay otros problemas con esta implementación. Por ejemplo, no realiza comprobaciones de cordura en el tensor de entrada para
asegurarse de que tiene al menos dos elementos para que SD no sea cero, lo que conduciría a una división por cero y resultados
infinitos. Pero esos problemas no están directamente relacionados con la discusión aquí.
Cálculo de gradientes 503

Como podemos ver,tf.ordenado()es una función inteligente y potente para la gestión de la memoria.
Se usa ampliamente en la propia base de código de TensorFlow.js. También lo verá muchas veces a
lo largo de los ejemplos de este libro. Sin embargo, tiene la siguiente limitación importante: la
función anónima pasada atf.ordenado()como debe ser el argumentonoser asíncrono. Si tiene algún
código asíncrono que requiere administración de memoria, debe usartf.dispose()y realice un
seguimiento de los tensores que se eliminarán manualmente en su lugar. En tales casos, puede
utilizartf.memory().numTensorpara comprobar el número de tensores filtrados. Una buena práctica
es escribir pruebas unitarias que afirmen la ausencia de fugas de memoria.

B.4 Cálculo de gradientes


Esta sección es para lectores interesados en realizar cálculos de gradientes y derivados en
TensorFlow.js. Para la mayoría de los modelos de aprendizaje profundo de este libro, el
cálculo de derivadas y gradientes se realiza bajo el capó pormodelo.fit()y modelo.fitDataset().
Sin embargo, para ciertos tipos de problemas, como encontrar imágenes de activación
máxima para filtros de convolución en el capítulo 7 y RL en el capítulo 11, es necesario
calcular derivadas y gradientes explícitamente. TensorFlow.js proporciona API para admitir
tales casos de uso. Comencemos desde el escenario más simple, a saber, una función que
toma un solo tensor de entrada y devuelve un solo tensor de salida:

const f = x => tf.atan(x);

Para calcular la derivada de la función (F)con respecto a la entrada (X), usamos el


tf.grado()función:
const df = tf.grad(f);

Tenga en cuenta quetf.grado()no le da el valor del derivado de inmediato. En su


lugar, le da unafunciónque es la derivada de la función original (F).Puede invocar
esa función (f)con un valor concreto deX,y ahí es cuando obtienes el valor dedf/dx.
Por ejemplo,
const x = tf.tensor([-4, -2, 0, 2, 4]); df(x).imprimir();

lo que le da una salida que refleja correctamente la derivada de laun bronceado()función en valores
de x de –4, –2, 0, 2 y 4 (consulte la figura B.5):

Tensor
[0,0588235, 0,2, 1, 0,2, 0,0588235]

tf.grado()se limita a una función con un solo tensor de entrada. ¿Qué pasa si tienes una
función con múltiples entradas? Consideremos un ejemplo deh(x, y),que es simplemente el
producto de dos tensores:

const h = (x, y) => x.mul(y);

tf.graduados() (con la “s” en el nombre) genera una función que devuelve la derivada parcial
de la función de entrada con respecto a todos los argumentos:
504 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

const dh = tf.grads(h);
const dhValues = dh([tf.tensor1d([1, 2]), tf.tensor1d([-1, -2])]); dhValores[0].print();

dhValores[1].print();

que da los resultados


Tensor
[-1, -2]
Tensor
[1, 2]

un bronceado

1.5

0.5

– 0.5

–1
Figura B.5 Un gráfico de
– 1.5 la funciónatán(x)
–6 –4 –2 0 2 4 6
atán (x)

Estos resultados son correctos porque la derivada parcial deX*ycon respecto aXesyy que
con respecto ayesX.
Las funciones generadas portf.grado()ytf.graduados()darte solo las derivadas, no el
valor de retorno de la función original. En el ejemplo deh(X,y), ¿qué sucede si queremos
obtener no solo las derivadas sino también el valor deh? Para eso, puedes usar el
tf.valueAndGrads()función:
const vdh = tf.valueAndGrads(h);
const out = vdh([tf.tensor1d([1, 2]), tf.tensor1d([-1, -2])]);

La salida (fuera)es un objeto con dos campos:valor,cual es el valor dehdados los valores de entrada,
ygraduados,que tiene el mismo formato que el valor de retorno de la función generada por
tf.graduados()—a saber, una matriz de tensores de derivadas parciales:

out.value.print();
out.grads[0].print();
out.grads[1].print();

Tensor
[-1, -4]
Tensor
[-1, -2]
Tensor
[1, 2]
Ejercicios 505

Las API discutidas tienen que ver con el cálculo de las derivadas de funciones con respecto a
sus argumentos explícitos. Sin embargo, un escenario común en el aprendizaje profundo
involucra funciones que usan pesos en su cálculo. Estos pesos se representan como
tf.Variableobjetos y sonnopasa explícitamente a las funciones como argumentos. Para tales
funciones, a menudo necesitamos calcular sus derivadas con respecto a los pesos durante el
entrenamiento. Este flujo de trabajo es servido por eltf.variableGrads()función, que realiza un
seguimiento de las variables entrenables a las que accede la función que se está
diferenciando y calcula automáticamente las derivadas con respecto a ellas. Considere el
siguiente ejemplo:

const entrenable = verdadero;


const a = tf.variable(tf.tensor1d([3, 4]), entrenable, 'a'); const b = F(a,B) =a*X 2 +B*X.
tf.variable(tf.tensor1d([5, 6]), entrenable, 'b'); const x = tf.tensor1d([1, 2]); Se llama al método sum()
porque tf.variableGrads()
requiere que la función se
const f = () => a.mul(x.square()).add(b.mul(x)).sum(); const {valor, diferencie para devolver un
graduados} = tf.variableGrads(f); escalar.

losvalorcampo detf.variableGrads()'La salida es el valor de retorno deFdados los


valores actuales deun, b,yX.losgraduadoscampo es un objeto de JavaScript que lleva
las derivadas con respecto a las dos variables (ayB)bajo los nombres de clave
correspondientes. Por ejemplo, la derivada def(a, b)con respecto aaesx2,y la
derivada de f(a, b)con respecto aBesX,
graduados.a.print();
graduados.b.print();

que da correctamente

Tensor
[1, 4]
Tensor
[1, 2]

Ejercicios
1 Utilizartf.tensorBuffer()para crear un "tensor 4D de identidad" que satisfaga las
siguientes propiedades. Su forma debe ser [5, 5, 5, 5].Debe tener valores 0 en
todas partes, excepto en los elementos cuyos índices son cuatro números
idénticos (por ejemplo, [2, 2, 2, 2]),que debe tener el valor 1.
2 Crear un tensor 3D de forma [2, 4, 5]utilizandotf.uniforme aleatorio()y el valor
predeterminado [0, 1)intervalo. Utilizandotf.suma(),escriba una línea de código para
realizar una suma reducida sobre la segunda y la tercera dimensión. Examine la salida.
Debe tener una forma de [2].¿Cuáles espera que sean los valores de los elementos,
aproximadamente? ¿El resultado coincide con sus expectativas?
(Pista: ¿cuál es el valor esperado de un número distribuido aleatoriamente en la [0,
1)¿intervalo? ¿Cuál es el valor esperado de la suma de dos de esos valores, dada la
independencia estadística?)
506 AAPÉNDICEB Un tutorial rápido de tensores y operaciones en TensorFlow.js

3 Utilizartf.uniforme aleatorio()para crear una matriz de 4 × 4 (un tensor 2D de forma [4,


4]).Obtenga la submatriz 2 × 2 ubicada en el centro usandotf.rebanada().
4 Utilizartf.unos(), tf.mul(),ytf.concat()para crear un tensor 3D de este tipo: su forma debe
ser [5, 4, 3].El primer corte a lo largo del primer eje (el tensor de forma [1, 4, 3])debe
tener valores de elemento que sean todos 1; el segundo corte a lo largo del primer eje
debe tener valores de elementos que sean todos 2; Etcétera.
a Puntos extra: el tensor tiene muchos elementos, por lo que es difícil probar su corrección
simplemente mirando el texto de salida deimprimir().¿Cómo se puede escribir una prueba unitaria
para comprobar su corrección? (Sugerencia: utilicedatos(), sincronización de datos(),oformación-
sincronizar()).

5 Escriba una función de JavaScript que realice las siguientes operaciones en dos tensores 2D
de entrada (matrices) de formas idénticas. Primero, suma las dos matrices. En segundo
lugar, la matriz resultante se divide por 2, elemento por elemento. En tercer lugar, se
transpone la matriz. La función devuelve el resultado de la operación de transposición.
a ¿Qué funciones de TensorFlow.js usas para escribir esta función?
B ¿Puede implementar la función dos veces, una usando la API funcional y otra
usando la API de encadenamiento? ¿Qué implementación parece más limpia y
legible?
C ¿Qué pasos implican la transmisión?
D ¿Cómo se asegura de que esta función no pierda memoria?
mi ¿Puede escribir una prueba unitaria (usando la biblioteca Jasmine enhttps://jasmine.github. yo/) para
afirmar sobre la ausencia de pérdida de memoria?
glosario
Función de activación La función en la última etapa de una capa de red neuronal. por ejemplo, un
La función de unidad lineal rectificada (relu) se puede aplicar al resultado de la multiplicación de la
matriz para generar el resultado final de una capa densa. Una función de activación puede ser
lineal o no lineal. Las funciones de activación no lineal se pueden utilizar para aumentar el poder (o
capacidad) de representación de una red neuronal. Los ejemplos de activaciones no lineales
incluyen sigmoide, tangente hiperbólica (tanh) y el relu mencionado anteriormente.

Área bajo la curva (AUC)Un solo número utilizado para cuantificar la forma de una curva ROC. Está
definida como la integral definida bajo la curva ROC, desde la tasa de falsos positivos de 0 a 1. Ver
curva ROC.

Eje En el contexto de TensorFlow.js, cuando hablamos de untensor, un eje (pluralhachas) es una de las claves
independientes que indexan en el tensor. Por ejemplo, un tensor de rango 3 tiene tres ejes; un elemento
de un tensor de rango 3 se identifica mediante tres números enteros que corresponden a los tres ejes.
También conocido como undimensión.

retropropagaciónEl algoritmo que rastrea desde el valor de pérdida de un diferenciable


modelo de aprendizaje automático a los gradientes en los parámetros de peso. Se basa en la regla de la
cadena de diferenciación y constituye la base del entrenamiento para la mayoría de las redes neuronales
de este libro.

Retropropagación a través del tiempo (BPTT)Una forma especial de retropropagación en la que los pasos
no están sobre las operaciones para las sucesivas capas de un modelo, sino sobre las operaciones
para los sucesivos pasos de tiempo. Subyace en el entrenamiento de redes neuronales recurrentes
(RNN).

Saldo (conjunto de datos) Una cualidad de un conjunto de datos con etiquetas categóricas. Cuanto más igual sea el número

bras de ejemplos de diferentes categorías, más equilibrado es un conjunto de datos.

LoteDurante el entrenamiento de redes neuronales, a menudo se agregan múltiples ejemplos de entrada.


gated para formar un solo tensor, que se utiliza para calcular los gradientes y las
actualizaciones de los pesos de la red. Tal agregación se llamalote. El número de
ejemplos en el lote se llamatamaño del lote.

507
508 GRAMOPERDIDA

Ecuación de Bellman En el aprendizaje por refuerzo, una ecuación recursiva que cuantifica el valor de
un par estado-acción como la suma de dos términos: 1) la recompensa que se espera que el agente obtenga
inmediatamente después de la acción y 2) la mejor recompensa esperada que el agente puede obtener en el
siguiente estado, descontada por un factor. El segundo término supone una selección óptima de acción en el
siguiente estado. Constituye la base de los algoritmos de aprendizaje por refuerzo, como el Q-learning
profundo.

clasificación binaria Una tarea de clasificación en la que el objetivo es la respuesta a una pregunta de sí/no,
como si una determinada imagen de rayos X indica neumonía o si una transacción con tarjeta de
crédito es legítima o fraudulenta.

Radiodifusión TensorFlow permite operaciones por pares entre tensores con diferentes pero com-
formas compatibles. Por ejemplo, es posible agregar un tensor de forma [5]a un tensor de forma [13, 5].En
efecto, el tensor más pequeño se repetirá 13 veces para calcular la salida. Los detalles de las reglas sobre
cuándo se permite la transmisión se encuentran en el cuadro de información 2.4 en el capítulo 2.

Capacidad El rango de relaciones de entrada-salida que un modelo de aprendizaje automático es capaz de


aprendiendo. Por ejemplo, una red neuronal con una capa oculta con una función de activación no
lineal tiene una mayor capacidad que un modelo de regresión lineal.

Mapa de activación de clasesUn algoritmo que puede visualizar la importancia relativa de diferentes partes
de una imagen de entrada para la salida de clasificación de una red neuronal
convolucional. Se basa en calcular el gradiente de la puntuación de probabilidad final de la
clase ganadora con respecto a la salida de la última capa convolucional interna de la red.
Se trata en detalle en la sección 7.2.3.
Visión por computador El estudio de cómo las computadoras pueden entender imágenes y videos. es una importacion -

parte hormiga del aprendizaje automático. En el contexto del aprendizaje automático, las tareas comunes de visión
artificial incluyen el reconocimiento de imágenes, la segmentación, los subtítulos y la detección de objetos.

Matriz de confusión Una matriz cuadrada (un tensor 2D) de la forma [numClasses, numClasses].En
clasificación multiclase, se utiliza una matriz de confusión para cuantificar cuántas veces los ejemplos
de una clase de verdad dada se clasifican como cada una de las clases posibles. El elemento en los
índices [yo, j]es el número de ejemplos de veces de la clase verdaderaIse clasifican como clasej.Los
elementos en la línea diagonal corresponden a resultados de clasificación correctos.

Plegado constanteUn tipo de optimización de gráfico de cálculo en el que un subgrafo que contiene
solo los nodos constantes predeterminados y las operaciones deterministas entre ellos se
reducen a un solo nodo constante. losModelo gráficoLa técnica de conversión en Tensor-Flow.js
aprovecha el plegado constante.
Núcleo convolucionalEn operaciones de convolución, un tensor que opera sobre el tensor de entrada para
generar el tensor de salida. Tome los tensores de imagen, por ejemplo: el kernel suele ser más
pequeño en sus dimensiones de alto y ancho en comparación con la imagen de entrada. Se "desliza"
sobre las dimensiones de alto y ancho de la imagen de entrada y se somete a un producto de puntos
(multiplicar y sumar) en cada posición de deslizamiento. Para una capa convolucional de TensorFlow.js
(como conv2d), el kernel es su peso clave.
GRAMOPERDIDA 509

Aumento de datos El proceso de generar más datos de entrenamiento a partir de muestras de entrenamiento existentes
ejemplos (x, y) mediante la creación de mutaciones de las muestras de entrenamiento a través de una familia
de transformaciones programáticas que producen entradas válidas x' sin cambiar el objetivo. Esto ayuda a
exponer el modelo a más aspectos de los datos y, por lo tanto, a generalizar mejor sin que el ingeniero tenga
que generar manualmente la invariancia para este tipo de transformaciones en el modelo.

Aprendizaje profundo El estudio y la aplicación de redes neuronales profundas (es decir, el uso de un gran número

tipo de transformaciones representacionales sucesivas para resolver problemas de aprendizaje automático).

red neuronal profunda Una red neuronal con un gran número (entre dos y mil)
arenas) de capas.

Dimensión En el contexto de un tensor, sinónimo deeje. Vereje.


producto punto Verproducto Interno.

incrustación En el aprendizaje profundo, una representación de una determinada pieza de datos en unnorte-vec dimensional-

por espacio (nortesiendo un entero positivo). En otras palabras, es una representación de un dato como un conjunto
ordenado de longitud.nortematriz de números de punto flotante. Se pueden crear representaciones incrustadas
para muchos tipos de datos: imágenes, sonidos, palabras y elementos de un conjunto cerrado. Una incrustación
generalmente proviene de una capa intermedia de una red neuronal entrenada.

aprendizaje conjunto La práctica de entrenar una serie de modelos individuales de aprendizaje automático
y usarlos juntos para inferir sobre el mismo problema. Aunque cada modelo individual puede no ser
muy preciso, el modelo de conjunto puede tener una precisión mucho mayor. Los modelos de
conjunto a menudo son utilizados por las entradas ganadoras de concursos de ciencia de datos, como
los concursos de Kaggle.

ÉpocaAl entrenar un modelo, una pasada completa a través de los datos de entrenamiento.

Política codiciosa de EpsilonEn el aprendizaje por refuerzo, un método de selección de acciones que parametriza
el equilibrio entre el comportamiento exploratorio aleatorio y el comportamiento óptimo por parte del
agente. El valor de epsilon está limitado entre 0 y 1. Cuanto mayor sea, más probable es que el agente
seleccione acciones aleatorias.

EjemploEn el contexto del aprendizaje automático, una instancia individual de datos de entrada (por ejemplo,
ejemplo, una imagen del tamaño apropiado para un modelo de visión por computadora), para el cual un modelo de
aprendizaje automático generará una predicción de salida (como una etiqueta para la imagen).

RasgoUn aspecto de los datos de entrada para un modelo de aprendizaje automático. Una característica puede estar en cualquier

de las siguientes formas:

- Un número (por ejemplo, el monto monetario de una transacción con tarjeta de crédito) Una
- cadena de un conjunto abierto (nombre de la transacción)
- Una pieza de información categórica (como el nombre de la marca de la tarjeta de crédito) Una matriz de

- números unidimensional o multidimensional (por ejemplo, una imagen en escala de grises de la firma del
cliente de la tarjeta de crédito representada como una matriz 2D)

- Otros tipos de información (por ejemplo, fecha y hora)

Un ejemplo de entrada puede constar de una o varias características.


510 GRAMOPERDIDA

Ingeniería de característicasEl proceso de transformar las características originales de los datos de entrada en una representación

resentimiento más susceptible de resolver el problema de aprendizaje automático. Antes del aprendizaje
profundo, la ingeniería de funciones la realizaban ingenieros con conocimientos específicos del dominio a
través de prueba y error. A menudo era un proceso laborioso y frágil, sin ninguna garantía de encontrar la
solución óptima. El aprendizaje profundo ha automatizado en gran medida la ingeniería de funciones.

Sintonia FINA En el aprendizaje por transferencia, una fase del entrenamiento modelo durante la cual los pesos en algunos

se permite actualizar las capas del modelo base. Por lo general, sigue una fase inicial de entrenamiento del
modelo durante la cual todos los pesos en el modelo base se congelan para evitar que los grandes
gradientes iniciales perturben demasiado los pesos preentrenados. Cuando se usa correctamente, el ajuste
fino puede aumentar la capacidad del modelo de transferencia de aprendizaje, logrando así una precisión
superior y consumiendo significativamente menos recursos informáticos que entrenar un modelo completo
desde cero.

Red antagónica generativa (GAN)Un tipo de modelo generativo de aprendizaje automático que implica
dos partes llamadasdiscriminadoy elgenerador. El discriminador está entrenado para distinguir ejemplos
reales de un conjunto de entrenamiento de los falsos, mientras que el generador está entrenado para
generar ejemplos que hacen que el discriminador genere puntajes de realismo altos (es decir, "engañar" al
discriminador para que "piense" que el discriminador es falso). los ejemplos son reales). Después de un
entrenamiento adecuado, el generador es capaz de generar ejemplos falsos muy realistas.

Valor dorado En el contexto de probar un sistema de aprendizaje automático, el resultado correcto es un modelo

debe generar para una entrada dada. Un ejemplo es la etiqueta "clásica" para una red neuronal que
clasifica las grabaciones de audio en géneros musicales cuando se le entrega una grabación de la
Quinta Sinfonía de Beethoven.

Descenso de gradiente El proceso de minimizar el valor numérico de salida de un sistema por iteración.
cambiando los parámetros del sistema de manera activa a lo largo de la dirección de los gradientes (es
decir, derivadas de los parámetros con respecto al valor de salida). Es la forma principal en la que se
entrenan las redes neuronales. En el contexto del entrenamiento de redes neuronales, el sistema está
formado por la red neuronal y una función de pérdida seleccionada por el ingeniero. Los parámetros
del sistema son los pesos de las capas de la red neuronal. El proceso de iteración ocurre lote por lote
sobre los datos de entrenamiento.

Unidad de procesamiento de gráficos (GPU)Chips de computación en paralelo equipados con un número mucho mayor

(cientos o miles) de núcleos que las CPU típicas. Las GPU se diseñaron originalmente para acelerar el
cálculo y la representación de gráficos 2D y 3D. Pero también resultaron ser útiles para el tipo de
computación paralela involucrada en la ejecución de redes neuronales profundas. Las GPU son un
factor importante que contribuye a la revolución del aprendizaje profundo y continúan
desempeñando un papel fundamental en la investigación y las aplicaciones del aprendizaje profundo
en la actualidad. TensorFlow.js aprovecha el poder de cómputo paralelo de las GPU a través de dos
conductos: 1) la API WebGL del navegador web y 2) el enlace a los núcleos TensorFlow CUDA en
Node.js.
GRAMOPERDIDA 511

Modelo gráfico En TensorFlow.js, un modelo convertido de TensorFlow (Python) y cargado en


JavaScript.Modelo gráficotiene el potencial de someterse a optimizaciones de rendimiento internas de TensorFlow,
como la optimización aritmética de Grappler y la fusión de operaciones (consulte la sección 12.2.2 para obtener más
detalles).

capa oculta Una red neuronal que consiste en una capa cuya salida no está expuesta como una salida.
puesto de la red, pero en cambio es consumido solo por otras capas de la red. Por ejemplo, en
una red neuronal definida como un modelo secuencial de TensorFlow.js, todas las capas excepto
la última son capas ocultas.
Optimización de hiperparámetros A veces también llamadoajuste de hiperparámetros; el proceso de búsqueda-
ing para el conjunto de hiperparámetros que da la menor pérdida de validación en una determinada tarea de
aprendizaje automático.

Hiperparámetros Parámetros ajustables del modelo y del optimizador que no se pueden ajustar con
retropropagación Por lo general, la tasa de aprendizaje y la estructura del modelo son hiperparámetros de ejemplo
comunes. Los hiperparámetros pueden ajustarse mediante la búsqueda en cuadrícula o algoritmos de ajuste de
hiperparámetros más sofisticados.

Espacio de hipótesisEn el contexto del aprendizaje automático, el conjunto de posibles soluciones a un


problema de aprendizaje automático. El proceso de formación implica buscar una buena solución en
dicho espacio. El espacio de hipótesis está determinado por el tipo y la arquitectura del modelo de
aprendizaje automático elegido para resolver el problema.

ImageNet Un conjunto de datos público a gran escala de imágenes en color etiquetadas. Es un conjunto de entrenamiento importante.

y punto de referencia para redes neuronales profundas orientadas a la visión por computadora. ImageNet
fue fundamental para marcar el comienzo de la revolución del aprendizaje profundo.

imputación Una técnica para completar los valores faltantes de un conjunto de datos. Por ejemplo, si tuviéramos un

conjunto de datos de autos, y a algunos autos les faltaba su característica de "peso", simplemente podríamos
adivinar el peso promedio para esas características. También son posibles técnicas de imputación más
sofisticadas.

Comienzo Un tipo de red neuronal convolucional profunda que presenta una gran cantidad de capas y
una estructura de red compleja.

Independientes e idénticamente distribuidos (IID)Una propiedad estadística de las muestras de datos. si asumimos
que los datos se muestrean de una distribución subyacente, entonces las muestras se distribuyen de
manera idéntica si cada muestra proviene de la misma distribución. Las muestras son independientes
si conocer el valor de una muestra no le brinda información adicional sobre la siguiente muestra.

Una muestra de tiradas de dados es un ejemplo de una colección de muestras de IID. Si se ordenan las tiradas de
dados, las muestras se distribuyen idénticamente pero no son independientes. Los datos de entrenamiento deben
ser IID, o es probable que haya convergencia u otros problemas durante el entrenamiento.

Inferencia Usar un modelo de aprendizaje automático en datos de entrada para generar una salida. es el ultimo

Compañero propósito de entrenar el modelo.


512 GRAMOPERDIDA

Producto InternoTambién conocido comoproducto punto;una operación matemática sobre dos vectores de equivalente

forma, dando un solo valor escalar. Para calcular el producto interior entre vectoresa yB,
resumir todoa[yo] * b[yo]para todos los valores válidos deI.En términos geométricos, el
producto interno de dos vectores es igual al producto de sus magnitudes y el coseno del
ángulo entre ellos.
Keras Una biblioteca popular para el aprendizaje profundo. Hoy en día, es la biblioteca de aprendizaje profundo
más utilizada en las competiciones de Kaggle. François Chollet, actualmente ingeniero de software en
Google, es su autor original. Keras es una biblioteca de Python. La API de alto nivel de TensorFlow.js, que es
el enfoque principal de este libro, está modelada y es compatible con Keras.

Etiqueta La respuesta deseada para un ejemplo de entrada dada la tarea en cuestión. Una etiqueta puede ser una
respuesta booleana (sí/no), un número, una cadena de texto, una categoría entre varias categorías posibles, una
secuencia de números o tipos de datos más complejos. En el aprendizaje automático supervisado, un modelo tiene
como objetivo generar resultados que coincidan estrechamente con las etiquetas.

Capa En el contexto de las redes neuronales, una transformación de la representación de datos. Se


comporta como una función matemática: dada una entrada, emite una salida. Una capa puede tener
estado capturado por sus pesos. Los pesos se pueden alterar durante el entrenamiento de la red
neuronal.

CapasModelo Un modelo creado con la API de alto nivel similar a Keras de TensorFlow.js. también puede ser
cargado desde un modelo convertido de Keras (Python). ACapasModeloapoya la inferencia
(con supredecir()método) y formación (con suencajar()yajusteDataset()métodos).
Tasa de aprendizajeDurante el descenso de pendiente, los pesos del modelo se modifican para reducir la pérdida. el exacto

el cambio en los pesos es una función no solo del gradiente de la pérdida sino también de un
parámetro. En el algoritmo de descenso de gradiente estándar, la actualización del peso se calcula
multiplicando el gradiente por la tasa de aprendizaje, que suele ser una pequeña constante positiva.
La tasa de aprendizaje predeterminada para el 'sgd'El optimizador en tensorflow.js es 0.01.

Mínimo local Al optimizar los parámetros de un modelo, una configuración de los parámetros para
que cualquier cambio suficientemente pequeño en los parámetros siempre aumenta la pérdida.
Similar a una canica en el fondo de un cuenco, no hay un pequeño movimiento que sea aún más
bajo. Un mínimo local se distingue de unmínimo mundialen que un mínimo local es el punto
más bajo en la vecindad local, pero el mínimo global es el punto más bajo en general.
registro En aprendizaje automático, un valor de probabilidad no normalizado. A diferencia de las probabilidades, los
logits no se limitan al intervalo [0, 1] ni se requiere que sumen 1. Por lo tanto, una capa de red neuronal
puede generarlos más fácilmente. Un conjunto de logits se puede normalizar a valores de probabilidad a
través de una operación llamadasoftmax.

Aprendizaje automático Un subcampo de la inteligencia artificial (IA) que automatiza el descubrimiento de reglas

para resolver problemas complejos mediante el uso de datos etiquetados con las respuestas deseadas. Se
diferencia de la programación clásica en que no implica la elaboración manual de las reglas.

Proceso de decisión de Markov (MDP) En el aprendizaje por refuerzo, un proceso de decisión en el que el actual
el estado de alquiler y la acción seleccionada por el agente determinan completamente el próximo estado que
GRAMOPERDIDA 513

el agente terminará con y la recompensa que el agente recibirá en el paso. Es una simplificación
importante que permite aprender algoritmos como Q-learning.
Modelo En el aprendizaje automático y el aprendizaje profundo, un objeto que transforma los datos de entrada
(como una imagen) en la salida deseada (como una etiqueta de texto para la imagen) a través de una serie de
operaciones matemáticas sucesivas. Un modelo tiene parámetros (llamadospesos) que se pueden ajustar
durante el entrenamiento.

Adaptación del modeloEl proceso de entrenar un modelo previamente entrenado o una parte de él para hacer
el modelo logra una mayor precisión durante la inferencia de los datos de entrada de un usuario
específico o un caso de uso específico. Es un tipo de transferencia de aprendizaje, en el que los tipos
de características de entrada y el tipo de destino no difieren del modelo original.

Despliegue del modeloEl proceso de empaquetar un modelo entrenado en el lugar donde se puede usar
por hacer predicciones. Al igual que "empujar a producción" para otras pilas de software, la implementación es la
forma en que los usuarios pueden llegar a usar modelos "de verdad".

MobileNet Una red neuronal convolucional profunda preentrenada. Por lo general, se entrena en el
El conjunto de datos de clasificación de imágenes de ImageNet se puede utilizar para el aprendizaje por
transferencia. Entre las redes neuronales convolucionales preentrenadas similares, tiene un tamaño relativamente
pequeño e implica menos cómputo para realizar la inferencia y, por lo tanto, es más adecuada para ejecutarse en un
entorno con recursos restringidos, como el navegador web, con TensorFlow.js.

Clasificación multiclase Un problema de clasificación en el que el objetivo puede tomar más de dos
etiquetas discretas. Algunos ejemplos son qué tipo de animal contiene una imagen o en qué lenguaje (natural) se
encuentra una página web dado su contenido.

Codificación multi-caliente Una forma de representar las palabras en una oración (o, en general, los elementos en un

secuencia) como un vector estableciendo los elementos que corresponden a las palabras en 1 y
dejando el resto en 0. Esto puede verse como una generalización decodificación one-hot. Descarta la
información relativa al orden de las palabras.

Perceptrón multicapa (MLP) Una red neuronal que consta de topología feedforward y al menos
una capa oculta.
Procesamiento natural del lenguaje El subcampo de las ciencias de la computación que estudia cómo usar la computadora
ers para procesar y comprender el lenguaje natural, sobre todo el texto y el habla. El aprendizaje profundo
encuentra muchas aplicaciones en el procesamiento del lenguaje natural.

red neuronal Una categoría de modelos de aprendizaje automático inspirados en la organización en capas
visto en los sistemas neuronales biológicos. Las capas de una red neuronal realizan transformaciones
separables de varios pasos de la representación de datos.

no linealidadUna relación de entrada-salida que no cumple con la definición de linealidad (lineal


combinaciones de entradas conducen a una combinación lineal de las salidas, hasta una diferencia de
término constante). En las redes neuronales, las relaciones no lineales (como las activaciones sigmoideas y
relu en capas) y la conexión en cascada de múltiples relaciones de este tipo pueden aumentar la capacidad
de las redes neuronales.
514 GRAMOPERDIDA

Detección de objetosUna tarea de visión artificial que consiste en detectar ciertas clases de objetos y
su ubicación en una imagen.

Codificación one-hotEl esquema de codificación de datos categóricos como un vector de longitud.norteconsistente

de todos los ceros excepto en el índice que corresponde a la clase real.


Fusión operativa Una técnica de optimización de gráfico de cálculo en la que múltiples operaciones (o
ops) se sustituyen por un solo equivalente op. Op fusion reduce la sobrecarga de despacho de operaciones y
puede generar más oportunidades para una mayor optimización de la memoria y el rendimiento dentro de la
operación.

Fuera de vocabulario (OOV)En el contexto del aprendizaje profundo, cuando unvocabulariose utiliza en un conjunto de

elementos discretos, el vocabulario a veces no incluye todos los elementos posibles. Cuando se
encuentra un elemento fuera del vocabulario, se asigna a un índice especial llamado fuera del
vocabulario, que luego se puede asignar a un elemento especial en la codificación one-hot o
representación incrustada. Vervocabulario.

sobreajuste Cuando un modelo se ajusta a los datos de entrenamiento de tal manera que el modelo tiene suficiente

capacidad de memorizar los datos de entrenamiento, vemos que la pérdida de entrenamiento continúa bajando, pero la

pérdida de prueba o validación comienza a aumentar. Los modelos con esta propiedad comienzan a perder su capacidad de

generalizar y funcionan bien solo en las muestras exactas de los datos de entrenamiento. Decimos que los modelos en esta

circunstancia están sobreajustados.

Gradientes de política Un tipo de algoritmo de aprendizaje por refuerzo que calcula y utiliza el
gradientes de ciertas medidas (como logits) de acciones seleccionadas con respecto a los pesos de una
red de políticas para hacer que la red de políticas seleccione gradualmente mejores acciones.

PrecisiónUna métrica de un clasificador binario, definida como la proporción de los ejemplos etiquetados por el
clasificador como positivo que en realidad son positivos. Verrecordar.

pseudo ejemplos Ejemplos adicionales basados en mutaciones válidas conocidas del entrenamiento de entrada

ejemplos, utilizados para complementar los datos de entrenamiento. Por ejemplo, podríamos tomar los
dígitos MNIST y aplicar pequeñas rotaciones y sesgos. Estas transformaciones no cambian la etiqueta de la
imagen.

Q-redEn el aprendizaje por refuerzo, una red neuronal que predice los valores Q de todos los posibles
posibles acciones dada la observación del estado actual. El algoritmo Q-learning consiste en
entrenar una red Q utilizando datos de la experiencia del agente.
valor Q En el aprendizaje por refuerzo, la recompensa acumulada futura total esperada por tomar una
acción en un estado dado. Por lo tanto, un valor Q es una función de acción y estado. Guía la
selección de acciones en Q-learning.
Inicialización aleatoria Antes de que se ajuste un modelo, el proceso de asignar un valor inicial a los pesos
como punto de partida. Hay mucha literatura sobre qué, exactamente, son buenas distribuciones para
elegir para los valores iniciales según el tipo de capa, el tamaño y la tarea.

Recordar Una métrica de un clasificador binario, definida como la proporción de los ejemplos reales que el
clasificador etiqueta como positivos. Verprecisión.
GRAMOPERDIDA 515

Regresión Un tipo de problema de aprendizaje donde el resultado deseado (o etiqueta) es un número o una lista

de numeros Hacer predicciones que estén numéricamente más cerca del resultado esperado es
mejor.

regularización En el aprendizaje automático, el proceso de imponer varias modificaciones a la pérdida


función o el proceso de entrenamiento para contrarrestar el sobreajuste. Hay varias
formas de realizar la regularización, las más utilizadas son la regularización de pesos
L1 y L2.
Aprendizaje por refuerzo (RL)Un tipo de aprendizaje automático que implica aprender decisiones óptimas
que maximizan una métrica llamadarecompensaa través de la interacción con un entorno. El Capítulo 11 de
este libro cubre los conceptos básicos de RL y cómo resolver problemas simples de RL utilizando técnicas de
aprendizaje profundo.

ResNet Corto parared residual; una popular red convolucional ampliamente utilizada en computación
visión, con conexiones residuales, es decir, conexiones que saltan capas.
curva ROCUna forma de visualizar la compensación entre la tasa positiva verdadera (recordación) y la tasa falsa
tasa positiva (tasa de falsas alarmas) de un clasificador binario. El nombre de la curva (lacurva de
características de funcionamiento del receptor) se originó en los primeros días de la tecnología de radar. Ver
área bajo la curva(AUC).

EspectrogramaUna representación 2D similar a una imagen de señales de tiempo 1D como sonidos. un espectro-
gramo tiene dos dimensiones: tiempo y frecuencia. Cada elemento representa la intensidad o potencia que
contiene el sonido en un rango de frecuencia determinado en un momento determinado.

Aprendizaje supervisado El paradigma de entrenar un modelo de aprendizaje automático usando exámenes etiquetados

por favor Los parámetros internos del modelo se modifican de manera que se minimiza la
diferencia entre la salida del modelo para los ejemplos y las etiquetas reales correspondientes.
tensor simbólicoEn TensorFlow.js, un objeto delTensor simbólicoclase que es una especificación
para la forma y el tipo de datos (dtype) de un tensor. A diferencia de un tensor, unTensor simbólicoobjeto no
está asociado con valores concretos. En su lugar, se utiliza como marcador de posición para la entrada o
salida de una capa o un modelo.

Tensor Una estructura de datos para contener elementos de datos, generalmente números. Los tensores se pueden pensar

de comonortecuadrículas dimensionales, donde cada posición en la cuadrícula contiene exactamente un


elemento. El número de dimensiones y el tamaño de cada dimensión se llama el tensorforma. Por ejemplo,
una matriz de 3 × 4 es un tensor con forma [3, 4].Un vector de longitud 10 es un tensor 1D con forma [10].
Cada instancia de tensor contiene solo un tipo de elemento. Los tensores están diseñados de esta manera
porque permiten implementaciones convenientes y altamente eficientes de operaciones comunes necesarias
para el aprendizaje profundo: por ejemplo, productos de matriz de puntos.

TensorTablero Una herramienta de monitoreo y visualización para TensorFlow. Permite a los usuarios visualizar

estructura del modelo y rendimiento del entrenamiento en el navegador. TensorFlow.js puede escribir registros de
entrenamiento en un formato de datos compatible con TensorBoard.
516 GRAMOPERDIDA

TensorFlow Una biblioteca Python de código abierto para el aprendizaje automático acelerado, con un enfoque en

redes neuronales profundas. Fue lanzado por el equipo Brain de Google en noviembre de 2015. Su API
forma un modelo para el de TensorFlow.js.

CapacitaciónEl proceso de alterar los parámetros internos (pesos) de un modelo de aprendizaje automático para
hacer que los resultados del modelo coincidan más con las respuestas deseadas.

Datos de entrenamiento Los datos que se utilizan para entrenar un modelo de aprendizaje automático. Los datos de entrenamiento consisten

de ejemplos individuales. Cada ejemplo es información estructurada (por ejemplo,


imágenes, audio o texto) junto con la respuesta esperada (la etiqueta).
Transferencia de aprendizajeLa práctica de tomar un modelo de aprendizaje automático previamente entrenado para uno

tarea, volver a entrenarlo con una cantidad relativamente pequeña de datos (en comparación con el conjunto de datos de

entrenamiento original) para una nueva tarea y usarlo para inferir en la nueva tarea.

subequipamientoCuando un modelo está entrenado para muy pocos pasos de optimización, o un modelo tiene un

suficiente poder representacional (capacidad) para aprender los patrones en los datos de entrenamiento, lo que da
como resultado un modelo que no alcanza un nivel de calidad decente, decimos que el modelo es inadaptado.

Aprendizaje sin supervisiónEl paradigma del aprendizaje automático que utiliza datos sin etiquetar. Está
opuesto al aprendizaje supervisado, que utiliza datos etiquetados. Los ejemplos de aprendizaje no
supervisado incluyen el agrupamiento (descubrir distintos subconjuntos de ejemplos en el conjunto de datos)
y la detección de anomalías (determinar si un ejemplo dado es lo suficientemente diferente de los ejemplos
en el conjunto de entrenamiento).

Datos de validación Datos que se separan de los datos de entrenamiento para el ajuste de hiperparámetros,
como la tasa de aprendizaje o el número de unidades en una capa densa. Los datos de validación nos permiten ajustar

nuestro algoritmo de aprendizaje, posiblemente ejecutando el entrenamiento muchas veces. Dado que los datos de

validación también están separados de los datos de prueba, aún podemos confiar en el resultado de los datos de prueba para

obtener una estimación imparcial de cómo funcionará nuestro modelo con datos nuevos e invisibles.

Problema del gradiente de fugaUn problema clásico en el entrenamiento de redes neuronales profundas en el que la gra-

Los componentes del parámetro de peso se hacen cada vez más pequeños a medida que aumenta el
número de capas y, como resultado, los parámetros de peso se alejan cada vez más de la función de
pérdida. En el aprendizaje profundo moderno, este problema se mitiga mediante funciones de
activación mejoradas, inicialización adecuada de pesos y otros trucos.

VectorizaciónEl proceso de convertir una pieza de datos no numéricos en una representación como un
matriz de números (como un vector). Por ejemplo, la vectorización de texto implica convertir
caracteres, palabras u oraciones en vectores.

Visera En tfjs-vis (una biblioteca de visualización estrechamente integrada con TensorFlow.js), una región contraíble que
se puede crear con una sola llamada de función en el costado de la página web para contener superficies para
visualización. Se pueden crear varias pestañas dentro de una visera para organizar las superficies. Consulte la
sección 8.1 para obtener más detalles.

Vocabulario En el contexto del aprendizaje profundo, un conjunto de elementos discretos y únicos que pueden usarse como

la entrada o salida de una red neuronal. Por lo general, cada elemento del vocabulario puede
GRAMOPERDIDA 517

asignarse a un índice entero, que luego se puede convertir en una representación one-hot o basada
en incrustaciones.

Peso Un parámetro ajustable de una capa de red neuronal. Cambiar los pesos cambia el
detalles numéricos de cómo la entrada se transforma en la salida. El entrenamiento de una red neuronal
consiste principalmente en actualizar los valores de peso de forma sistemática.

Cuantificación de peso Una técnica para reducir el tamaño serializado y en el cable de un modelo. Eso
implica almacenar los parámetros de peso del modelo con una precisión numérica más baja.

incrustación de palabras Una forma de vectorizar palabras en redes neuronales relacionadas con texto. una palabra es

mapeado en un tensor 1D (o vector) a través de un proceso de búsqueda de incrustación. A diferencia de la


codificación one-hot, la incrustación de palabras implica vectores no dispersos en los que los valores de los
elementos son números que varían continuamente en lugar de 0 y 1.
índice
numéricos algoritmos 17–18
Alipay 447
conexiones 1D 312–320, 461 tensores todo uno 491–492
construcción sobre conjuntos de datos 315–318 tensores todo cero 491–492
para inferencia en páginas web 319–320 ejecución AlphaZero 5
sobre conjuntos de datos 315–318 relleno de Amazon Echo 32, 145 Amazon
secuencia 314 Web Services 19, 26 AMD 16
truncamiento de secuencias 314
tensores 1D 128 Y operador 496
convolución 2D 122 antropomorfización 470 API (interfaz
Tensores 2D 27, 82, 274, 485–486, 489 del programa de aplicación)
Vector 2D 52 funcional frente a encadenamiento
Convenciones 3D 461 494 en JavaScript 391–392
Tensores 3D 27, 128, 275, 487, 489 añadir () método 401
Tensores 4D 122, 485, 490 método apply() 178–179, 268 área bajo la
tensores 5D 489 curva.VerFunción AUC argMax() 111, 136,
331, 431, 501 Función Array.forEach() 209
A Array.from() función 137
método array() 486
precisión 96–99 matrices 203–205
ACGAN (GAN clasificador auxiliar) 357 arraySync() método 486
resumen 360–362 artefactos 142
entrenamiento 363–366 inteligencia artificial.VerAI
funciones de activación 17, 82 redes neuronales artificiales 12
activaciones repositorio arXiv 475
de convnets, internos ascenso.Vermétodo asincrónico de
extrayendo 264 ascenso de gradiente 484
visualizando 262–264 función atan() 503
de imágenes 265–268 mecanismos de atencion 321
softmax 109–111 arquitectura de codificador-decodificador y función 324–
AdaDelta 95 327 de 326–327
Optimizadores ADAM 18, 94–95, 354 tareas de secuencia a secuencia con 321–331
AdaMax 95 codificador-decodificador basado en la atención
método add() 57, 121, 167 modelos 327–331
aleatoriedad ajustable 342–345 formular 321–324
ejemplos contradictorios 470 modelos de codificador-decodificador basados en la atención

IA (inteligencia artificial) 3, 6–18, 454 327–331

519
520 ÍNDICE

AUC (área bajo la curva) 98, 100–101, C


103–104, 290, 419
datos de audio llamada () método 350
accediendo con tf.data.microphone 228–230 activadores de devolución de llamada 71

convnets en 144–149 CAM (mapa de activación de clases) 269–270, 272


representación de sonidos como imágenes 145– Algoritmos CAM 270–271
149 espectrogramas 145–149 elementos del lienzo 135–137
aumento de datos 242–244 Aplicación Canvas Friends 32
capacidad de los modelos 84–86
autocompletado 321
método capture() 168, 226
codificadores automáticos 345–349
ejemplos de cart-pole 376–389
Ver tambiénVAE (codificadores automáticos
redes de políticas 378–381 Algoritmo REINFORCE
variacionales) AutoML 473
381–389 ejemplos de aprendizaje por refuerzo
clasificador auxiliar GAN.VerEjes 376–378 redes de políticas de capacitación 381–389
ACGAN 69
funciones lineales en cascada 85
B funciones no lineales en cascada 84
entropía cruzada categórica 111–113
retropropagación 56–59, 82, 160, 265 datos categóricos 107–109
retropropagación a través del tiempo.Ver categoricalCrossentropy 112, 131, 148, 289, 462 CDN
BPTT Baidu 447 (red de entrega de contenido) 439
regla de la cadena 57
gráficos de barras 251–252
encadenamiento API 494
función barchart() 251
modelo base 153 charSetSize 339
chatbots 321
lote eje 488
puntos de control 142
dimensión del lote 488
mapa de activación de clases (CAM) 269–270, 272
tamaño de lote 52, 488 Capa BatchNormalization 435
codificadores automáticos clásicos 345, 347–349
parámetro de tamaño de lote 131, 358, 397, 403
clasificación 154
Ecuación de Bellman 395, 399, 404–406, 410 puntos binario
de referencia 17 definido 92–95
función de pérdida para modelos
bias 44, 50, 74, 88, 241, 460 103–106 para 92–106
biasAdd (suma de sesgo) 94 precisión 96–99
clasificación binaria 92, 102 entropía cruzada binaria 103–106 calidad de
clasificadores binarios medición de clasificadores binarios 96–99 precisión
definido 92–95 96–99
calidad de medición de 96–99 recordar 96–99
entropía cruzada binaria 103–106 Curvas ROC 96–103
objeto grande binario.Ver resultados de 269–270

Operaciones binarias BLOB 496 Ver tambiénclasificación


multiclase Clinic Doctor app 32
Función binaria Crossentropy 103–106, 462
servicios en la nube 440–441
intervalos 252
Red CNN-LSTM 19
BLOB (objeto grande binario) 418
COCO (objeto común en contexto) 187, 194 listas
tensores de tipo bool 484, 496
de códigos 39–40
BPTT (propagación hacia atrás a través del tiempo) 298–299,
CodePen 40, 61, 247, 255
302 Colaboración 241
ejes de difusión 69 ColumnConfigs 204, 224
navegadores método columnNames() 204, 222 Objeto común
extensiones, implementando modelos TensorFlow.js en contexto (COCO) 187, 194 formas de salida
a 441–443 compatibles 155–161
cargando modelos en búfer 142– compilar () método 45, 157, 181, 191, 196, 300,
144 Tamaño 212 330
función buildLinearRegressionModel() 280, 290 Compute la arquitectura de dispositivos unificados.VerCUDA
ÍNDICE 521

función computeOutputShape() 350 entropía cruzada


método concat() 331 binario 103–106
. método concatenar (conjunto de datos) categórico 111–113
211 tensores de concatenación 497–499 Intercambio de recursos de origen cruzado (CORS) 439
config.validationData 72 marcos de aplicaciones multiplataforma 444
matriz de confusión 96, 113–114 Formato CSV 205–206, 220–223
interacciones de consola 39–40 método CSVSubclase de conjunto de datos 222
console.timeEnd() 438 red de entrega de CUDA (Arquitectura de dispositivo unificado de cómputo)
contenido (CDN) 439 capacitación 17, 19, 21, 29, 137–138, 322, 349, 427, 437
continua 424–425 Juego de herramientas CUDA 477, 479–
capas conv1d 316 480 CuDNN 21, 478–480
capas conv2d 121–126, 161, 298, 313 Devolución de llamada personalizada 277

capas conv2d-maxPooling2d 121 capa Función de pérdida personalizada 191


conv2dTranspose 362, 366 filtros
convnet 261
ConvNetJS 31
D
convnets (redes neuronales convolucionales) 4, 15,
biblioteca d3.js 26
117–151, 461, 463
datos 17, 201–245
resultados de clasificación 269–270
acceso 63–64, 220–230
capa conv2d 122–125
Datos de formato CSV 220–223 en el
capas densas 128–130
conjunto de datos 209
mejorado 138–140
con tf.data.microphone 228–230 con
extracción de activaciones internas de
tf.data.webcam 225–228 datos de
264 capas planas 128–130
audio 144–149
para predicciones 134–137
tensores de imagen de elementos canvas 135–137
aumento 242–244
tensores de imagen de elementos HTML img
lotes de 488
135–137 categórico 107–109
tensores de imagen de TypedArrays 134 tensores de
creando 40–43
imagen de elementos de video 135–137
fallas en 230-242
tf.browser.fromPixels() 135–137 corregir 235–242
maxPooling2d capa 126–127 detectar 235–242
detección de objetos a través del aprendizaje de transferencia en
formato 40–43
185-195 datos de imagen 490

sobre datos de audio 144–149 cantidades limitadas de 256–259


representación de sonidos como imágenes 145– aprendizaje automático y 201–202
149 espectrogramas 145–149 gestión con tf.data 202–214
reconocimiento de palabras habladas 144–149 acceder a datos en Dataset 209 crear
representación de sonidos como imágenes 145– tf.data.Dataset 203–207 manipular tfjs-
149 espectrogramas 145–149 data Datasets 210–214 tf.data.Dataset
motivos repetidos de convolución 127 objetos 203
motivos repetidos de agrupación 127 falta 236–238
imágenes representativas 118–119 normalizando 66–69
tensores 118–119 trampas en 240-242
formación 130–133 representando 9–12
modelos de entrenamiento con Node.js 137–144 datos de secuencia 489
dependencias para tfjs-node 137–142 teoría de 231-234
importaciones para tfjs-node 137–142 cargar entrenar modelos con model.fitDataset 214–219 datos
modelos en navegadores 142–144 guardar vectoriales 489
modelos de Node.js 142–144 visualizar datos de vídeo 490
activaciones internas de 262–264 Ver también visualizando 247–259
convnets 1D convolución, motivos repetitivos de privacidad de datos 20
127 kernel convolucional 121 propiedad de tipo de datos (dtype) 166, 482, 484, 490
archivo data.js 62
CORS (intercambio de recursos de origen cruzado) 439 método data() 76, 484–486
522 ÍNDICE

sesgo del conjunto de datos 231 en computadoras de placa única 448–


función dataset.columnNames() 222 función 450 en WeChat 447–448
dataset.forEachAsync() 205, 209 función resumen 450
dataset.map 213, 243 a extensiones de navegador 441–443
Método Dataset.skip() 222 Método a servicios en la nube 440–441 a web
Dataset.take() 222 Función 439–440
Dataset.toArray() 209, 222 conjuntos de descuantificación 428
datos 60–61 función determineMeanAndStddev 67–69 Clase
construcción de convnets 1D en 315–318 de dígitos 364
para MNIST 119 función div() 502
rodante 399–401 curva dosis-respuesta 248 producto punto 74,
ejecutando convnets 1D en 315–318 123, 317 descargas, prediciendo la duración
método dataSync() 66, 136, 484–486
de con
árboles de decisión 14
TensorFlow.js 38–49
decodificador 346
listados de códigos 39–40
decoderCombinedContext 330
interacciones de la consola 39–40
aprendizaje profundo 13, 454
creación de datos 40–43
ventajas de 16-18
definición de modelos 43–46
avances algorítmicos 17–18 puntos
ajustar modelos a datos de entrenamiento 46–48
de referencia 17
dar formato a datos 40–43
datos 17
ferretería 16–17 predicciones con modelos entrenados 48
generativo 334–370 DQN (redes Q profundas)
descripción general de 396–399
limitaciones de 470–472
supervisado 458–460 formación 399–410
tendencias en 473–474 equilibrar la exploración y la explotación
redes neuronales profundas 13 401–402
aprendizaje de refuerzo profundo 371–414 Ecuación de Bellman 404–406 Algoritmos
ejemplos de cart-pole 376–389 ávidos de épsilon 401–402 Extracción de
ejemplos de 373–376, 389–392 valores Q predichos 402–404 Extracción de
gradientes de políticas 376–389 valores Q objetivo 404–406 Intuición detrás
redes de políticas 376–389 Q- 399
learning 389–410 función de pérdida para retropropagación del valor Q
DQN 396–399 406–410
Proceso de decisión de Markov 392–396 función de pérdida para la predicción del valor Q 406–410
Valores Q 392–396 memoria de reproducción 399–401
capacitación DQN 399–410 conjuntos de datos móviles para 399–
redes de valor 389–410 401 capas de exclusión 140–142
tasa de aprendizaje por defecto 54 abandonoRate parámetro 280
transformaciones de capa densa 12 propiedad dtype (tipo de datos) 166, 482, 484, 490
capas densas 44, 50, 80, 90, 128–130, 294–296
redes densamente conectadas 461–463
dependencias para tfjs-node 137–142
mi
reduciendo el sobreajuste con capas de abandono
140–142 Electron.js 26, 445
Entrenamiento mejorado convnet para MNIST en
operaciones por elementos 494–496
tfjs-nodo 138–140 Proyector integrado 319
despliegue incrustaciones de vectores 312, 319
modelos 417–452 incrustaciones 169, 173
TensorFlow.js modelos 439–450 transferir el aprendizaje basado en incrustaciones
en escritorio multiplataforma basado en JavaScript de 168–173 palabras 310–312
aplicaciones 445–447 archivo embedding_test.js 420
en el complemento de aplicación móvil basado en JavaScript matrices vacías 483
sistemas 447–448 arquitectura de codificador-decodificador 324–327
en aplicaciones móviles basadas en JavaScript modelos de codificador-decodificador 327–331
443–445 encoderLast 329
ÍNDICE 523

codificadores 346 generar MNIST ACGAN 366–368


codificación 306–308 capacitar ACGAN 363–366
Ver tambiénconvnet mejorado capacitación MNIST ACGAN 366–368
de codificación one-hot 138–140 descripción general 357–360
epsilon vector 350
entrenamiento 366
algoritmos ávidos de épsilon 401–402, 409
unidades recurrentes cerradas 302–305
método de evaluación () 46, 144, 488
función de recopilación () 268
función exp 110
función expandDims() 135 GCP (Google Cloud Platform) 439 entrada-salida
explotación 380, 401–402 de propósito general (GPIO) 449 redes
exploración 380, 401–402 antagónicas generativas.VerFunción de
generador de GAN 206–207
F espacio geométrico 456
método getLayer() 164
cara-api.js 467 función getWeights() 76, 88
Facetas 241 GitHub 25, 30
falsos negativos (FN) 96 acceder a proyectos del 61 al 63
tasa de falsos positivos (FPR) 98–102, 104, 115 ejecutar proyectos del 61 al 63
falsos positivos (FP) 96
capa globalMaxPool1d 316
Conjunto de datos Fashion-MNIST 348, 355, 432
Inicialización de Glorot 18
Transformada rápida de Fourier (FFT) 229 Ingeniería
Inicializador glorotNormal 89
de características 16
caracteristicas 41
GloVe (vectores globales) 319
FFT (transformada rápida de Fourier) 229 GNMT (traducción automática neuronal de Google)
parámetros de filtros 122–123, 125, 127, 332 5, 327
granularidad fina 436 valores dorados 422–424
perfeccionamiento en el aprendizaje por transferencia 174, 177–184 Nube de Google 19, 26
modelos individuales para 177–178 Google Cloud Platform (GCP) 439
descongelación de capa pasante 180–184 Google Cloud Vision AI 440 Google
método fit() 46, 48, 131, 144, 156–158, 161, 170, Home 145
191, 195, 276, 314, 354, 380, 399, 488
E/S de Google 30
función fitCallbacks() 277
Traducción automática neuronal de Google (GNMT)
método fitDataset() 156, 219, 276–277, 354, 380,
5, 327
399
aplanar capas 128–130 Visir de Google 473
Float32Array 134, 137, 484, 486 Proyecto Magenta 31 de Google
tensores tipo float32 484 GPIO (entrada-salida de propósito general) 449
Marco de aleteo 444 - - indicador gpu 186, 299, 322, 338, 349, 366, 408
FN (falsos negativos) 96 función forEach() GPU (unidades de procesamiento de gráficos) 15–16, 19, 22,
209, 227 . método forEachAsync(f) 210 30, 52, 75, 138, 142, 148, 261
función forEachAsync() 205, 207, 209 ascenso de gradiente 266–268 descenso
formateo de datos 40–43 de gradiente 50, 53–59, 266
retropropagación 56–59
FPR (tasa de falsos positivos) 98–102, 104, 115
optimizando 50
FP (falsos positivos) 96
gradientes 52, 90, 503–505
función de las capas de
Ver tambiéngradientes de política
congelación 155–161.Verfunción
de pérdida funcional API 494 unidades de procesamiento de gráficos (GPU) 15–16, 19, 22,
30, 52, 75, 138, 142, 148, 261
Conversión de GraphModel 437
GRAMO
aceleración de la inferencia de modelos con 435–437

GAN (redes antagónicas generativas) 5, 345, optimización de la velocidad de inferencia con 434–437
366, 460 gráficos 27
generar imágenes con 356–368 GRU (unidades recurrentes cerradas) 302–305,
ACGAN 360–362 464 compresión gzip 432–433
524 ÍNDICE

H fase de inferencia 9, 141 tubería de


destilación de información 264 producto
mapas de calor 254–255 interno 74
canal de altura-anchura (HWC) 118, 128 capas funciones de entrada 59–74
ocultas 80, 82, 87, 89, 94 aprendizaje de acceder a los datos 63–64
representación jerárquica 13 tensores de acceder a proyectos desde GitHub 61–63
dimensiones superiores 487 histogramas 252– normalización de datos 66–69
253 conjuntos de datos 60–61

HSV (valor de saturación de tono) 10 elementos definición de problemas 65–66 regresión


HTML img 135–137 valor de saturación de tono lineal en datos 70–74 ejecución de proyectos
(HSV) 10 HWC (canal de altura-anchura) 118, 128 desde GitHub 61–63 espacio de entrada 267–
hiperparámetros 72, 89–92, 289–290, 340, 459 268
hipótesis espacio 8 instalando tfjs-node-gpu 477–481
en Linux 477–480
en Windows 480–481
I acceso instantáneo 21
aceleración WebGL instantánea 20
Int32Array 484
IID (independiente e idénticamente distribuido)
tensores de tipo int32 484
conjuntos de datos 232, 234
activaciones internas
datos de imagen 490
extrayendo 264
- - indicador de imagen 272
visualizando 262–264
tensores de imagen

de los elementos canvas 135–137 de pesas internas 75–76


interpretando
los elementos HTML img 135–137 de
advertencias sobre 77
TypedArrays 134
modelos 74–77
de elementos de video 135–137
ImageNet 17, 117, 154, 431
extraer pesos internos de los modelos
75–76
imágenes
extraer significado de los pesos aprendidos
activación 265–268
74–75
generar con GAN 356–368
no linealidad y 87–89
ACGAN 360–362
iónico 26, 444
generar MNIST ACGAN 366–368
conjunto de datos de flor de iris 107
descripción general de GAN 357–360
capacitación de ACGAN 363–366
capacitación de MNIST ACGAN 366–368 j
que representan 118–119
representar sonidos como 145–149 transformaciones lenguaje JavaScript
de imagen a imagen 125–126 Expresión de función API 391–392
asíncrona invocada inmediatamente aplicaciones de escritorio multiplataforma 445–447
patrón 46 ecosistema de 25–26
valores inmutables 491 sistemas de complementos de aplicaciones

importaciones, para tfjs-node 137–142 móviles 447–448 aplicaciones móviles 443–445

reduciendo el sobreajuste con capas de abandono Jupyter 241

140–142
Entrenamiento mejorado convnet para MNIST en k
tfjs-nodo 138–140
imputación 237 Kaggle 17, 241, 255, 474–475
Inicio 154 Biblioteca Keras 25, 27, 29–31, 165, 430, 475
formas de salida incompatibles 161–173 Métodos del kernel 14
independientes e idénticamente distribuidas (IID) kernelInitializer campo 90
conjuntos de datos 232, 234 kernelRegularizer parámetro 280
inferencia kernels 44, 74, 88, 282
en páginas web 319–320 kernelSize parámetro 122, 125, 127, 148, 313,
velocidad 434–437 332, 362
Traducido del inglés al español - www.onlinedoctranslator.com

ÍNDICE 525

KL (Kullbach-Liebler) divergencia 353 kNN LSTM (Memoria a Largo Corto Plazo), generando
(k-vecino más cercano) 172–174, 184 texto con 335–345
Kullbach-Liebler (KL) divergencia 353 aleatoriedad ajustable en 342–345
ejemplo de 337–342
predictores del siguiente carácter 335–
L
337 Comando lstm 317
Regularizador L2 282 argumento lstmLayerSize 339
datos de entrenamiento etiquetados 455

etiquetas 8 METRO

activación de última capa 289


vector latente 346 aprendizaje automático 6–26, 454
aprendizaje de representación en capas 13 datos y 201–202
enfoque de congelación de capas 160 capas aprendizaje profundo con Node.js 24–25
44, 454, 465 ecosistema de JavaScript 25–26
conv2d 122–125 representación de texto en 306–308
denso 128–130, 294–296 programación tradicional frente a 7–12 flujo
abandono 140–142 de trabajo universal de 287–290 traducción

aplanar 128–130 automática 321

congelación 155–161 MAE (error absoluto medio) 47, 282


maxPooling2d 126–127 función map() 226
apilamiento 86–87
. método mapAsync() 211 MAR
descongelación 180–184
(perdido al azar) 236
Proceso de decisión de Markov (MDP) 392–396, 410
Capas API 30
Math.seedrandom() función 420
atributo de capas 164
matMul (multiplicación de matrices) 94
LD_LIBRARY_PATH variable 478
matriz.Vermatriz de confusión max()
activación de LeakyReLU 366
función 404
pesos aprendidos 74–75
maxPooling2d capas 121, 126–127, 316 MCAR
lecunnormal 89
(Missing Completely at Random) 237 MDP
biblioteca libtensorflow 446
(proceso de decisión de Markov) 392–396, 410
gráficos de líneas 248–250
error absoluto medio (MAE) 47, 282
regresión lineal, en TensorFlow.js 37–78
método medio() 495
descenso de gradiente 50–59
función meanAbsoluteError 45, 47, 65, 194, 279,
interpretar modelos 74–77
462
model.fit() 50–59
función meanSquaredError 70, 74, 106, 189,
predicción de la duración de la descarga con 38–49
194, 289, 406–407, 463
con funciones de entrada múltiple 59–74 escalado
memoria, administración en Tensorflow.js 499–503
lineal 85 MetaCar 32
función linechart() 248–250, 272 Servicios de aprendizaje automático de Microsoft
Sistema operativo Linux, instalación 440 Microsoft Visual Studio 480
tfjs-nodo-gpu 477–480 minimizar () método 354
Red de labios 19
Desaparecido al azar (MAR) 236
función loadLayersModel() 155–156 Falta completamente al azar (MCAR) 237
localidad 125 Falta no al azar (MNAR) 237 ML5.js 32
- - logDir flag 299, 367 logreg
(regresión logística) 14 borde de MLP (perceptrón multicapa) 80, 121, 170, 264,
pérdida 56 280–281, 284, 295–296, 301, 309, 317, 332,
función de pérdida 45, 289 352, 460–461, 463, 465
para retropropagación de valores Q 406–410 MNAR (No falta al azar) 237 MNIST
para clasificación binaria 103–106
para clasificación multiclase 111–113 para conjunto de datos para 119

predicción de valores Q 406–410 superficie generar ACGAN 366–368 entrenar ACGAN


de pérdida 50 366–368 entrenar convnet mejorado para
latencia de inferencia reducida 19 138–140
526 ÍNDICE

MobileNet 5, 19, 153–154, 162, 166, 168, 170, prueba 417–452


172–173, 186, 191, 193, 196, 432, 445 entrenado, predicciones con 48
MobileNetV2 426, 431 entrenamientos
adaptación del modelo 154 visualización de 273–287 con
paso de compilación del modelo 45 model.fitDataset 214–219 con
implementación del modelo 425 Node.js 137–144
modelo de rendimiento 426 visualización después del entrenamiento 260–271
modelo calidad 426 con salidas de modelos base 161–173
función model.compile() 52, 70, 90, 131, 157 transferencia de aprendizaje sobre incrustaciones 168–173
función model.evaluate() 73, 132, 141–142 cámara web transferencia de aprendizaje 164–166
función model.fit() 50–59, 71, 73, 88, 100, 131, Momentum 95
142, 420, 503 Mongo DB 26
retropropagación 56–59 formato MPEG 490
optimización del descenso de gradiente 50 MSE (error cuadrático medio) 65, 81, 86–87, 103,
model.fitDataset 209, 214–219, 223, 244, 503 función 106, 283, 345, 419
model.predict() 136, 141, 325, 500–501 función función mul() 196, 404 clasificación
model.save() 143 multiclase 92, 106–114
model.summary() método 80–81, 165, 280 activando softmax 109–111 entropía
modelos cruzada categórica 111–113 matriz de
capacidad de 84–86 confusión 113–114
definición 43–46 análisis detallado de la función de
despliegue 417–452 pérdida 113–114 para 111–113
modelos de codificador-decodificador 327–331 codificación one-hot de datos categóricos 107–109
extracción de pesos internos de 75–76 ajuste a codificación multi-hot 306–308
datos de entrenamiento 46–48 perceptrón multicapa (MLP) 80, 121, 170, 264,
para clasificación 92–106 280–281, 284, 295–296, 301, 309, 317, 332,
precisión 96–99 352, 460–461, 463, 465
clasificaciones binarias 92–95 entropía cruzada perceptrones multicapa 461–463
binaria 103–106 función de pérdida para multiplicar capa 362
clasificaciones binarias composición musical 321
103–106 MúsicaRNN 467
medir la calidad de los clasificadores binarios 96–99
precisión 96–99 norte
recordar 96–99
Curvas ROC 96–103 Clasificador Naive Bayes 14 NaN
para afinar el aprendizaje por transferencia 177–178 a (no es un número) 55, 235
partir de tensores simbólicos 166–167 Formato NCHW 118
velocidad de inferencia de 435–437 matrices n-dimensionales 288
interpretación 74–77 valores negativos 384
advertencias sobre 77 redes neuronales 6, 12–18, 470–472
extraer significado de los pesos aprendidos construyendo intuición para la no linealidad en 82–89
74–75 no linealidad y capacidad del modelo 84–86 no
no linealidad y 87–89 linealidad e interpretabilidad del modelo
cargando en los navegadores 142– 87–89
144 de orden secuencial apilar capas sin no linealidad 86–87
en GRU 302–305 historia de 15–16
en RNN 296–305 siguiente () función 276
en RNN simples 299–302 con predictores de siguiente carácter 335–
capas densas 294–296 337 Formato NHWC 118, 120, 122, 490
optimizando 417–425, 437–452 Plataforma Node.js 24–25
velocidad de inferencia con GraphModel guardando modelos de 142–144
conversión 434–437 entrenando modelos con 137–144
tamaño a través del peso post-entrenamiento dependencias para tfjs-node 137–142
cuantización 426–433 importaciones para tfjs-node 137–142
guardar desde Node.js 142–144 cargando modelos en navegadores 142–144
ÍNDICE 527

media sin defecto 492 paralelización 301


no linealidad 79–116 compartir parámetros 125, 298
en la salida 92–106 Variable de ruta 480
intuición para 82–89 PCA (análisis de componentes principales) 319
capacidad del modelo y 84–86 perceptrones, multicapa 461–463 Conjunto de datos
interpretabilidad del modelo y 87–89 del sitio web de phishing 92
descripción general 80–92 pip install comando tensorboard 300
apilar capas sin 86–87 plotly.js 26, 232
archivo normalization.js 62 POJO (objeto de JavaScript simple y antiguo) 248
normalización de datos 66–69 gradientes de política 383–389
not-a-number (NaN) 55, 235 gestión de ejemplos de cart-pole 376–378 redes de políticas
paquetes npm 62 distinción numérica/ 378–381 algoritmo REINFORCE 381–389 ejemplos
categórica 240 Controlador NVIDIA 478 de aprendizaje por refuerzo 376–378 redes de
políticas de capacitación 381–389
NVIDIA Jetson Nano 449
redes de políticas 373, 376–389
Comando nvidia-smi 478
ejemplos de carretas 376–378 Algoritmo
REINFORCE 381–389 ejemplos de aprendizaje por
O refuerzo 376–378 entrenamiento 381–389

detección de objetos puesta en común 127


detección de objetos simples 187–195 PoseNet 21, 467
a través del aprendizaje de transferencia en convnets 185–195 cuantificación del peso posterior al entrenamiento
objetos en tf.data. Conjunto de datos 203 427 precisión 96–99, 104
Cuaderno observable 241 método predict() 48, 80, 144, 170, 172, 178, 264,
observación 373, 375, 400 268, 368, 425, 436, 438, 451, 488
onBatchBegin 71 prediciendo
enBatchEnd 71, 278 conexiones para 134–137
codificación one-hot 107–110, 112, 120, 306–308 tensores de imagen de elementos canvas 135–137
vectorización one-hot 307 tensores de imagen de HTML img
onEpochBegin 71, 100, 115 elementos 135–137
onEpochEnd 71, 219, 278 tensores de imagen de TypedArrays 134 tensores de
onEpochStart 219 imagen de elementos de video 135–137
DQN en línea 406 tf.browser.fromPixels() 135–137
enTrainBegin 71 duración de las descargas con TensorFlow.js
onTrainEnd 71, 113 38–49
op fusión 436 listados de códigos 39–40

esquemas de optimización 18 interacciones de la consola 39–40


configuración del optimizador 289 creación de datos 40–43
optimizadores 45 definición de modelos 43–46
ajustar modelos a datos de entrenamiento 46–48
O operador 496
dar formato a datos 40–43
valores atípicos 235–236
siguientes personajes 335–337 con
secuencia de salida 321
modelos entrenados 48 modelos
formas de salida
previamente entrenados
compatibles 155–161
aprovechamiento de TensorFlow.js 465–468
incompatibles 161–173
reutilización 153–184
sobreajuste 280–287, 459
creando modelos con salidas desde la base
reductor con capas de deserción 140–142
modelos 161–173
capas de congelación 155–161
PAGS transferir aprendizaje en salida compatible
formas 155–161
archivo paquete.json 62 transferir aprendizaje en salida incompatible
secuencias de relleno 314 formas 161–173
biblioteca papaparse 64 transferir el aprendizaje a través del ajuste fino
operaciones paralelizables 22 174–184
528 ÍNDICE

redes neuronales preentrenadas, reutilización 152–197 relu (unidad lineal rectificada) 17, 82, 84 .
detección de objetos a través del aprendizaje de transferencia en repetir (contar) método 211
convenciones 185–195 memoria de reproducción 399–401
reutilización de modelos previamente entrenados 153–184 representando
transferencia de aprendizaje 153–184 datos 9–12
análisis de componentes principales (PCA) imágenes 118–119
319 función print() 108 sonidos como imágenes 145–149 texto en
prob (probabilidad) 105, 112 aprendizaje automático 306–308 Solicitar
Progressive WebApps 444 parámetros 224
Project Magenta 31 SolicitudInfo 220, 224
proyectos ResNet 154
acceder desde GitHub 61–63 Repensar DB 26
ejecutar desde GitHub 61–63 propiedad returnSequence 338
pseudoejemplos 242 descuento de recompensas 382
PyPI 25 recompensas 373–375, 400
PyTorch 21, 427 RGB (rojo-verde-azul) 7, 10, 20, 118, 123, 266 RL
(aprendizaje por refuerzo) 5, 372, 410, 460 RMS
q (raíz cuadrática media) 95
RMS Prop 18, 95, 289
Q-redes 373, 389 RNN (redes neuronales recurrentes) 5, 28, 276,
cuantización 428–429 294–305, 317, 321, 326, 461, 465 modelado
Valores Q 392, 394–396, 399, 401, 405–406 de orden secuencial en 296–305 modelado de
función de pérdida para retropropagación de 406–410 orden secuencial en GRU 302–305 modelado de
función de pérdida para predicción de 406–410 orden secuencial en RNN simples
predicho 402–404 299–302
objetivo 404–406 modelado de orden secuencial con capas densas
294–296
R Curvas ROC (características de funcionamiento del receptor)
curvas) 96–103
bosques aleatorios 15, 17 inicialización diagramas RNN rodados 297
aleatoria 50 tensores de valores aleatorios conjuntos de datos rodantes 399–
492–493 aleatoriedad en el texto generado 401 raíz cuadrada media (RMS) 95
342–345 rango 482–483 orden principal de filas 486

tensor de rango 0 483–484


tensor de rango 1 485
S
tensor de rango 2 485–487
función sample() 342, 401
tensor de rango 3 487
variable sampledLabels 370
Raspberry Pi 4 449 React Native 26,
sampleLen 339
34, 444–445 recuperación 96–99,
- - indicador sampleLen 339
104
eje de muestras 488
función rect() 151
unidad lineal rectificada (relu) 17, 82, 84 muestras dimensión 67
redes recurrentes 464 muestrasSoFar 214
redes neuronales recurrentes.VerRNN rojo-verde- método save() 142, 166 escalar
azul (RGB) 7, 10, 20, 118, 123, 266 operaciones de (tensor de rango 0) 483–484
reducción 494–496 tensor escalar 190
regresión 43, 154 diferencias de escala 240
regularización 290 diagramas de dispersión 250

regularizadores 465 función scatterplot() 250


algoritmo REINFORCE 381–389 aprendizaje por etiquetas <script> 41, 247
refuerzo, ejemplos de 376–378, SD (desviación estándar) 492
389–392 análisis de sentimiento 308–310
formular 373–376 valor centinela 238
API de JavaScript 391–392 datos de secuencia 489
ÍNDICE 529

secuenciaLongitud 311 paso() método 103, 391


secuencias 292–333 descenso de gradiente estocástico (sgd) 45, 71, 94–95
relleno 314 normalización de transmisión 213
RNN 294–305 ventana de transmisión 233
truncando 314 EstiloGAN 356
tareas de secuencia a secuencia método sub() 502
formular 321–324 comando sudo 478
con mecanismos de atención 321–331 suma () función 404
modelos de codificador-decodificador basados en la atención método summary() 86, 143, 157, 196
327–331 aprendizaje supervisado 9
arquitectura codificador-decodificador y orden patrones de superficie 23
secuencial 324–327, modelado SVM (máquinas de vectores de soporte) 14, 17
en GRU 302–305 IA simbólica 7
en RNN 296–305 tensores simbólicos 166–167
en RNN simples 299–302 con SymbolicTensor 166–167, 177
capas densas 294–296 costo del escenas sintetizadas 186–187
servidor 19
Función Set.forEach() 209
Método set() 491 T
sgd (descenso de gradiente estocástico) 45, 71, 94–95
abordar binario 14
optimizador sgd 45
. método take() 211
aprendizaje superficial 14
objetivo 41
propiedad de forma 482
objetivos DQN 406
. método shuffle(bufferSize, seed?) 212
targetQs 403, 405, 407
matriz shuffledTargets 107
función sigmoidea 82, 110
Vecino estocástico distribuido en t (t-SNE)
SIMD (instrucción única, datos múltiples) 22
empotrar 271, 319
valores de temperatura 342
detección de objetos simples 186–195
tampones tensoriales 490–491
simplesRNNs 296–302, 304, 326, 329, 464 modelos
individuales para ajuste fino en la transferencia
Tensor clase 495
aprendizaje 177–178 tensor1d (tensor de rango 1) 485
computadoras de placa única 448–450 Detección tensor2d (tensor de rango 2) 485–487
de disparo único (SSD) 193–194 SISD (datos TensorBoard URL 367
únicos de instrucción única) 22 sesgo 239–240 TensorBuffer 491
@tensorflow/tfjs 480
. método skip(count) 211 TensorFlow Developer Summit 30 TensorFlow
función slice() 331, 498 Playground 23–24, 29 TensorFlow.js 4, 19, 23, 427,
tensores de corte 497–499 475, 480, 483, 487,
SoC (sistema en chip) 449 494, 502
función softmax 109–111 ventajas de 27–33
sonidos redes convolucionales 463 redes
reconociendo 144–149 densamente conectadas 461–463
representación como imágenes 145– ecosistema de 475
149 sparseCategoricalCrossentropy 462 historia de 27-30
espectrogramas 145–149 Keras 27–30
aplicaciones de comandos de voz 174–176 TensorFlow 27–30
palabras habladas, reconocimiento 144–149 capas 465
representación de sonidos como imágenes 145– aprovechando modelos previamente entrenados de 465–
149 espectrogramas 145–149 468 regresión lineal en 37–78
error al cuadrado 66 descenso de gradiente 50–59
SSD (detección de disparo único) 193–194 interpretar modelos 74–77
<ST> (símbolo de inicio de secuencia) 325 model.fit() 50–59
capas de apilamiento 86–87 con funciones de entrada múltiple 59–
desviación estándar (SD) 492 74 gestión de memoria en 499–503
transformación estándar 66 perceptrones multicapa 461–463
530 ÍNDICE

TensorFlow.js(continuado) tensor2d (tensor de rango 2) 485–487


predecir la duración de la descarga con 38–49 operaciones unarias 493–496
listados de códigos 39–40 operaciones por elementos vs. reducción
interacciones de la consola 39–40 operaciones 494–496
creación de datos 40–43 API funcional frente a API de encadenamiento
definición de modelos 43–46 494 tensors.trainTarget 71
predicción de duración 38 testData constante 41
ajustar modelos a datos de entrenamiento 46–48 pruebas
dar formato a datos 40–43 modelos 417–452
predicciones con modelos entrenados 48 TensorFlow.js modelos 418–425
referencia rápida 460–465 formación continua 424–425
redes recurrentes 464 pruebas unitarias 418–422
regularizadores 465 con valores dorados 422–424
bibliotecas similares frente a texto
31 modelos de TensorFlow.js aleatoriedad ajustable en 342–345
despliegue 439–450 aprendizaje profundo para 292–333
Consideraciones al implementar en la web generación con LSTM 335–345
439–440 ejemplo de 337–342
en escritorio multiplataforma basado en JavaScript predictores del siguiente carácter 335–337
aplicaciones 445–447 modelos para 305–320
en el complemento de aplicación móvil basado en JavaScript representación en aprendizaje automático 306–308
sistemas 447–448 RNN 294–305
en aplicaciones móviles basadas en JavaScript resumen de texto 321
443–445 vectorización de texto 306
en computadoras de placa única 448– espacio de nombres tf 47,
450 en WeChat 447–448 494 símbolo tf 41, 483
resumen 450 método tf.abs() 47
a extensiones de navegador 441–443 método tf.add() 496
a servicios en la nube 440–441 método tf.argMax() 496
pruebas 418–425 método tf.argMin() 496
formación continua 424–425 función tf.browser.fromPixels() 135–137, 151,
pruebas unitarias 418–422 451
con valores dorados 422–424 tf.callbacks.earlyStopping() método 286
@tensorflow/tfjs-node-gpu 480 tf.concat() función 368, 497, 499 tf.data
tensorflowjs_converter 437, 451
tensor-in-tensor-out (TITO) 497 acceder a datos en Dataset 209
tensores 27, 118–119, 288, 482–499 crear tf.data.Dataset 203–207
tensores todo uno 491–492 de matrices 203–205
tensores todo cero 491–492 desde archivos CSV 205–206 desde la función de
operaciones binarias 496 generador 206–207 administrar datos con 202–
concatenando 497–499 214 manipular tfjs-data Conjuntos de datos 210–
lotes de datos 488 214 tf.data.Objetos de conjunto de datos 203
ejemplos de 488–490
datos de imagen 490 función tf.data.array() 203–206 función
datos de secuencia 489 tf.data.csv() 204, 221, 223–224
datos vectoriales 489 tf.data.Dataset 203–207
datos de vídeo 490 de matrices 203–205
de búferes de tensor 490–491 tensores de archivos CSV 205–206 de
de dimensiones superiores 487 Conjunto generador función 206–207
de datos MNIST 119 manipulación 210–214
tensores de valores aleatorios 492–493 objetos en 203
Tensores de rango 3 487 método tf.data.generator() 204, 206–208, 244,
escalar (tensor de rango 0) 483–484 276
rebanado 497–499 tf.datos.micrófono 228–230
tensor1d (tensor de rango 1) 485 tf.datos.cámara web 225–228
ÍNDICE 531

tf.dispose() 499–503 método tf.square() 68


función tf.div() 342 método tf.stack() 499
tf.grad() función 268, 503–504 tf.grads() función método tf.sub() 68, 496
503–504 tf.image.resizeBilinear() función 135, método tf.sum() 495
151 tf.image.resizeNearestNeighbor() función tf.tensor() función 487
135, tf.tensor1d(shuffledTargets).toInt() función 107
151 tf.tensor2d() método 42, 63, 486–487, 490
Método tf.io.browserHTTPRequest() 144 tf.tensor3d() método 487
Función tf.layer.embedding() 310 Objeto tf.tensor4d() método 134
tf.LayerModel 353 tf.tensorBuffer() función 491
tf.layers.batchNormalization() método 286, 435 tf.tidy() 499–503
tf.layers.conv1d() función 312, 463 función tf.tidy() 103
tf.layers.dropout() método 285 Función tf.train.adam() 354 Método
tf.layers.flatten capa 294 tf.transpose() 496 Método tf.unstack()
tf.layers.gru() método 434 tf.layers.lstm() método 499 Función tf.valueAndGrads() 504
434 tf.layers.separableConv2d capas 463 Función tf.variableGrads() 407, 505
tf.layers.simpleRNN() función 296, 434 Función tf.zeros() 491
tf.LayersModel.fit() función 299–300
tf.LayersModel .fitDataset() función 300 Función tf.zerosLike() 492 Método
tf.loadGraphModel() método 434 tfjs.converters.save_keras_model() 166 Función
tf.loadLayersModel() método 143–144, 155, 166, tfjs.vis.linechart() 272
tfjs-nodo 3, 458
175, 399, 434–435 dependencias para 137–142
función tf.log() 342 importaciones para 137–142
método tf.logicalAnd() 496 Entrenamiento mejorado convnet para MNIST en
método tf.logicalOr() 496 138–140
método tf.logicaXor() 496 tfjs-node-gpu, instalando 477–481
método tf.matMul() 496 paquete tfjs-react-native 444 tfjs-vis
método tf.max() 496 247–259
método tf.mean() 495 gráficos de barras 251–252
Función tf.metric.meanSquaredError() 191 Espacio de conceptos básicos de 247–255

nombres tf.metrics 112 mapas de calor 254–255


tf.min() método 496 histogramas 252–253
tf.Model objeto 315, 330, 482 gráficos de líneas 248–250
tf.Model.fit() método 56, 60, 81, 265 diagramas de dispersión 250

tf.model() función 167, 178, 264, 268 tfvis.render espacio de nombres 247–248, 250–
tf.mul() método 496 251 tfvis.render.barchart() función 255
función tf.multinomial() 342–343, 345, 380, 401 tfvis.render.heathmap() función 255
función tf.neg() 493–494 tfvis.render.heatmap() función 254
Función tf.nextFrame() 227 tfvis.render.histogram() función 253, 255 tfvis
Método tf.norm() 495 Método .render.linechart() 249, 255, 257
función tf.oneHot() 107, 403 Función tfvis.render.scatterplot() 255 Función
método tf.ones() 492 tfvis.show.fitCallbacks() 277, 279 Función
Método tf.onesLike() 492 Función tfvis.show.layer() 283
tf.randomNormal() 492 Función Función tfvis.show.modelSummary() 280
tf.randomUniform() 492–493 Función Método tfvis.visor().surface() 277
tf.reciprocal() 494 TFX (TensorFlow Extended) 424
tf.regularizers.l1() método 285 entonces() método 76
tf.regularizers.l1l2() método 285 función de umbral 103
tf.relu() función 494 dimensión del tiempo 298
método tf.scalar() 484 capa distribuida en el tiempo 330 datos de
Función tf.secuencial() 167 series temporales (secuencia) 488 TITO
Función tf.sigmoid() 380 (tensor-in-tensor-out) 497 TN (negativos
Función tf.slice() 497, 499 verdaderos) 96
Método tf.sqrt() 68 . método toArray() 207, 210, 227
532 ÍNDICE

método toTensor() 491 flujo de trabajo universal


TPR (tasa de verdaderos positivos) 100–102, 115 de aprendizaje automático 287–290
TP (verdaderos positivos) 96 de aprendizaje profundo supervisado 458–460
atributo entrenable 180–181 propiedad diagrama RNN desenrollado 297
entrenable 156–157, 191, 195 constante
trainData 41 V
argumento trainData.labels 131
argumento trainData.xs 130 VAE (codificadores automáticos variacionales) 5, 345–356, 460
modelos entrenados 48 codificadores automáticos clásicos y 345–
entrenamiento 50 349 ejemplos de 349–356
datos de entrenamiento 8, 72 datos de validación 72
indicador de entrenamiento 268 ValidationData 157, 219
bucle de entrenamiento 50 ValidationSplit parámetro 131, 182
fase de entrenamiento 140 Value Networks 389–410
subconjunto de entrenamiento 182 DQN 396–399
sesgo de entrenamiento-servicio 425 Proceso de decisión de Markov 392–396
división de prueba de tren 233 Valores Q 392–396
transferir el aprendizaje 152–197 ejemplos de aprendizaje por refuerzo 389–392
crear modelos con salidas de modelos base entrenamiento DQN 399–410
161-173 equilibrar la exploración y la explotación
ajuste fino en 174–184 401–402
modelos individuales para 177–178 Ecuación de Bellman 404–406 Algoritmos
descongelación de capa pasante 180–184 capas ávidos de épsilon 401–402 Extracción de
de congelación 155–161 valores Q predichos 402–404 Extracción de
en aplicaciones de comandos de voz 174–176 en valores Q objetivo 404–406 Intuición detrás
formas de salida compatibles 155–161 399
en convnets, detección de objetos hasta 185–195 en función de pérdida para retropropagación del valor Q
incrustaciones 168–173 406–410
en formas de salida incompatibles 161–173 reutilización de función de pérdida para la predicción del valor Q 406–410

modelos previamente entrenados 153–184 aprendizaje de memoria de reproducción 399–401

transferencia de cámara web 164–166 modelo de conjuntos de datos rodantes para entrenar DQN 399–
transferencia 153
401 problema de gradiente de fuga 302
codificadores automáticos variacionales.VerDatos
verdaderos negativos (TN) 96
vectoriales VAE 489
secuencias truncadas 314
vectorización de datos 288, 459
tSNE (vecino estocástico distribuido en t)
vega.js 26
incrustación) 271
VGG16 modelo 261–262, 266, 269
parámetros ajustables 50
datos de video 225–228, 490
TypedArrays 134, 485–486
elementos de vídeo 135–137
TypeScript 166
superficie de la visera 277

visualizante
tu datos 246–272
efectos de la regularización del peso 283–287 activaciones
Ganchos de interfaz de usuario 62
internas de convnet 262–264 interpretaciones de los
Uint8Array 484 resultados de clasificación de convnet
operaciones unarias 493–496 269–270
operaciones por elementos vs. reducción entrenamiento modelo 273–287
operaciones 494–496 contramedidas 278–287
API funcional frente a API de encadenamiento 494 sobreajuste 278–287
datos desequilibrados 240 subequipamiento 278–287
ajuste insuficiente 48, 278–280, 286 modelos 246–272
simetría indeseable 294 modelos después del entrenamiento 260–271
descongelación de capas 180–184 activación de imágenes 265–
prueba unitaria 419–422 268 Algoritmos CAM 270–271
unidades parámetro 44, 133 vocabulario 307, 309
ÍNDICE 533

W X
ver comando 108 Código X 445
páginas web, convnets 1D para inferencia en 319–320 web, xEtiqueta 250, 254
implementación de modelos TensorFlow.js en 439–440 Operador XOR 496
WebAudio API 19, 145, 149, 467 Etiquetas xTick 254
aprendizaje de transferencia de cámara web
164–166 WebGL 23, 30–31, 33, 103, 166, 444
Y
WeChat 447–448
cuantificación de peso
comando de reloj de hilo y hilo 80, 120
compresión gzip y entrenamiento
comando de tren de hilo 148, 185, 322, 338, 354, 366 tren
posterior 432–433 426–433
de hilo --comando de ayuda 340
regularización de peso
comando de reloj de hilo 149, 186, 192, 261, 319,
reducción del sobreajuste con 282–287
338, 355, 368, 376, 389, 408
visualización de los efectos de 283–287
eje y 259
esquemas de inicialización de peso 18
yEtiqueta 250, 254
pesos 12, 44, 74, 454
YOLO (Solo miras una vez) 193–194
función de ventana 85
YOLO2 19
paquete de herramientas de creación de ventanas
yTickLabels 254
481 Sistema operativo Windows, instalación
tfjs-node-gpu en 480–481
incrustaciones de palabras 310–312 Z
predicción de palabras 321
instalación cero 21
normalización de puntuación z
66 vector z 346
Continuación del interior de la portada

entrenamiento modelo

Cómo funciona la retropropagación Elegir optimizador


Secta. 2.2 Cuadro de información 3.1

Visualización del proceso de formación Cómo lidiar con el ajuste insuficiente y el sobreajustado

Navegador: secc. 7.1.1 Secta. 8.2


Node.js: cuadro de información 9.1 Tabla 8.1

Visualización y comprensión de modelos entrenados


Secta. 7.2

Guardar, cargar y convertir modelos

tipo de tarea API / comando Referencia

Guardar un modelo en JavaScript tf.LayersModel.save() Secta. 4.3.2

Cargando un modelo en JavaScript tf.loadLayersModel()

Convertir un modelo de Keras para JavaScript tensorflowjs_converter Cuadro de información 5.1

Cargando modelos convertidos de TensorFlow tf.loadGraphModel() Secta. 12.2

Preparación de modelos para la producción.

Probar un modelo y el código que lo rodea Cuantificación del peso: reducción del tamaño del modelo
Secta. 12.1 Secta. 12.2.1

Optimización de la velocidad del modelo con Grappler


Secta. 12.2.2

Implementación de modelos en producción

Entorno de destino Referencia Entorno de destino Referencia

Navegador Varios, como la secta. 4.3.2 Escritorio (Electron.js) Secta. 12.3.5

Secta. 12.3.6
Secta. 12.3.3
Plataformas de complementos de aplicaciones
extensión del navegador
(como WeChat)
Ordenadores de placa única Secta. 12.3.7
servicio en la nube Secta. 12.3.2
(como Raspberry Pi)

Móvil (ReactNative) Secta. 12.3.4


APRENDIZAJE AUTOMÁTICO/JAVASCRIPT

Aprendizaje profundo con JavaScript


Caí- Bileschi Nielsen
-
- Chollet
Ver primera página

R
Ejecutar aplicaciones de aprendizaje profundo en el navegador o en
backends basados en nodos abre posibilidades interesantes para las


aplicaciones web inteligentes. Con la biblioteca TensorFlow.js, crea y
Este libro debe servir como
entrena modelos de aprendizaje profundo con JavaScript. TensorFlow.js, que
fuente autorizada para los
ofrece escalabilidad, modularidad y capacidad de respuesta de calidad de
producción sin concesiones, realmente destaca por su portabilidad. Sus modelos
lectores que deseen aprender
ML y usan JavaScript como

se ejecutan en cualquier lugar donde se ejecute JavaScript, lo que empuja a ML
más arriba en la pila de aplicaciones. su lenguaje principal.

EnAprendizaje profundo con JavaScript, aprenderá a usar Tensor-Flow.js para


— Del prólogo de
Nikhil Thorat y Daniel Smilkov
crear modelos de aprendizaje profundo que se ejecutan directamente en el
TensorFlow.js
navegador. Este libro de ritmo rápido, escrito por ingenieros de Google, es práctico,


atractivo y fácil de seguir. A través de diversos ejemplos que incluyen análisis de
Repleto de una gran cantidad de
texto, procesamiento de voz, reconocimiento de imágenes e IA de juegos de
información sobre profundidad
autoaprendizaje, dominará todos los conceptos básicos del aprendizaje profundo y
explorará conceptos avanzados, como volver a entrenar modelos existentes para el aprendizaje, este libro
aprendizaje por transferencia y la generación de imágenes. eminentemente legible presenta un
caso muy sólido para usar JavaScript
Qué hay adentro para el aprendizaje automático. ”
- Procesamiento de imágenes y lenguaje en el navegador — Jorge Tomás
- Ajuste de modelos ML con datos del lado del cliente I+D, Asociados de Manhattan

Creación de texto e imágenes con deep learning generativo


-

- Ejemplos de código fuente para probar y modificar Este libro es su guía a través
del mundo del aprendizaje
Para programadores de JavaScript interesados en el aprendizaje profundo. profundo, conducido por el
mejor en su campo. Se
Shanqing Cai,stanley bileschi, yEric D. Nielsenson ingenieros de
sorprenderá de todo lo
software con experiencia en el equipo de Google Brain y fueron
que es posible hacer en
cruciales para el desarrollo de la API de alto nivel de TensorFlow.js.


un navegador hoy en día.
Este libro está basado en parte en el clásico,Aprendizaje profundo
con Pythonde François Chollet. — Edin KapiC,iSolución s

Para descargar su libro electrónico gratuito en formato PDF, ePub y Kindle,


los propietarios de este libro deben visitar

manning.com/books/aprendizaje-profundo-con-javascript
ISBN: 978-1-61729-617-8

MANEJO $49.99 / Puede $65.99[INCLUYE EL LIBRO ELECTRÓNICO]

También podría gustarte