Está en la página 1de 50

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

com

CAPÍTULO 11

Entrenamiento de redes neuronales profundas

Con los libros electrónicos Early Release, obtiene libros en su forma más
temprana, el contenido sin editar y sin editar del autor mientras escribe,
para que pueda aprovechar estas tecnologías mucho antes del
lanzamiento oficial de estos títulos. El siguiente será el Capítulo 11 en la
versión final del libro.

EnCapítulo 10presentamos redes neuronales artificiales y entrenamos nuestras primeras redes


neuronales profundas. Pero eran redes muy poco profundas, con solo unas pocas capas
ocultas. ¿Qué sucede si necesita abordar un problema muy complejo, como detectar cientos de
tipos de objetos en imágenes de alta resolución? Es posible que deba entrenar un DNN mucho
más profundo, tal vez con 10 capas o mucho más, cada una con cientos de neuronas,
conectadas por cientos de miles de conexiones. Esto no sería un paseo por el parque:

• En primer lugar, se enfrentaría a la complicadaGradientes que se desvanecenproblema (o el relacionado


gradientes explosivosproblema) que afecta las redes neuronales profundas y hace que las capas
inferiores sean muy difíciles de entrenar.

• En segundo lugar, es posible que no tenga suficientes datos de entrenamiento para una red tan grande o que sea
demasiado costoso etiquetarlos.

• Tercero, el entrenamiento puede ser extremadamente lento.

• Cuarto, un modelo con millones de parámetros correría el grave riesgo de sobreajustar el conjunto de
entrenamiento, especialmente si no hay suficientes instancias de entrenamiento o si son demasiado
ruidosas.

En este capítulo, analizaremos cada uno de estos problemas y presentaremos técnicas para resolverlos.
Comenzaremos explicando el problema de los gradientes que se desvanecen y explorando algunas de las
soluciones más populares para este problema. A continuación, veremos el aprendizaje de transferencia y la
capacitación previa no supervisada, que pueden ayudarlo a abordar situaciones complejas.

325
tareas incluso cuando tiene pocos datos etiquetados. Luego, analizaremos varios optimizadores que pueden
acelerar enormemente el entrenamiento de modelos grandes en comparación con el descenso de gradiente
simple. Finalmente, veremos algunas técnicas de regularización populares para redes neuronales grandes.

Con estas herramientas podrás entrenar redes muy profundas: ¡bienvenido a Deep Learning!

Problemas de gradientes de desaparición/explosión

Como discutimos enCapítulo 10, el algoritmo de retropropagación funciona yendo de la capa de


salida a la capa de entrada, propagando el gradiente de error en el camino. Una vez que el
algoritmo ha calculado el gradiente de la función de costo con respecto a cada parámetro en la
red, usa estos gradientes para actualizar cada parámetro con un paso de descenso de
gradiente.

Desafortunadamente, los gradientes a menudo se vuelven más y más pequeños a medida que el algoritmo
avanza hacia las capas inferiores. Como resultado, la actualización de Gradient Descent deja los pesos de
conexión de la capa inferior prácticamente sin cambios y el entrenamiento nunca converge en una buena
solución. Esto se llama elGradientes que se desvanecenproblema. En algunos casos, puede suceder lo
contrario: los gradientes pueden crecer más y más, por lo que muchas capas obtienen actualizaciones de
peso increíblemente grandes y el algoritmo diverge. Este es elgradientes explosivosproblema, que se
encuentra principalmente en redes neuronales recurrentes (ver???). De manera más general, las redes
neuronales profundas sufren gradientes inestables; diferentes capas pueden aprender a velocidades muy
diferentes.

Aunque este comportamiento desafortunado se ha observado empíricamente durante bastante


tiempo (fue una de las razones por las que las redes neuronales profundas se abandonaron en
su mayoría durante mucho tiempo), solo alrededor de 2010 se logró un progreso significativo
en su comprensión. un papel titulado“Comprender la dificultad de entrenar redes neuronales
de alimentación directa profunda”por Xavier Glorot y Yoshua Bengio1encontró algunos
sospechosos, incluida la combinación de la popular función logística de activación sigmoidea y
la técnica de inicialización de peso que era más popular en ese momento, a saber, la
inicialización aleatoria utilizando una distribución normal con una media de 0 y una desviación
estándar de 1 En resumen, demostraron que con esta función de activación y este esquema de
inicialización, la varianza de las salidas de cada capa es mucho mayor que la varianza de sus
entradas. Avanzando en la red, la varianza sigue aumentando después de cada capa hasta que
la función de activación se satura en las capas superiores. En realidad, esto empeora por el
hecho de que la función logística tiene una media de 0,5, no 0 (la función tangente hiperbólica
tiene una media de 0 y se comporta ligeramente mejor que la función logística en redes
profundas).

1 "Comprender la dificultad de entrenar redes neuronales de alimentación directa profunda", X. Glorot, Y Bengio (2010).

326 | Capítulo 11: Entrenamiento de redes neuronales profundas


En cuanto a la función de activación logística (verFigura 11-1), puede ver que cuando las entradas se
vuelven grandes (negativas o positivas), la función se satura en 0 o 1, con una derivada
extremadamente cercana a 0. Por lo tanto, cuando se activa la retropropagación, prácticamente no
tiene gradiente para propagarse a través de la red, y el pequeño gradiente que existe sigue
diluyéndose a medida que la retropropagación avanza a través de las capas superiores, por lo que en
realidad no queda nada para las capas inferiores.

Figura 11-1. Saturación de la función de activación logística

Inicialización de Glorot y He
En su artículo, Glorot y Bengio proponen una forma de aliviar significativamente este problema.
Necesitamos que la señal fluya correctamente en ambas direcciones: en la dirección de avance
cuando se hacen predicciones, y en la dirección de retroceso cuando se propagan gradientes
hacia atrás. No queremos que la señal se apague, ni queremos que explote y se sature. Para
que la señal fluya correctamente, los autores argumentan que necesitamos que la varianza de
las salidas de cada capa sea igual a la varianza de sus entradas,2y también necesitamos que los
gradientes tengan la misma variación antes y después de fluir a través de una capa en la
dirección inversa (consulte el documento si está interesado en los detalles matemáticos). En
realidad, no es posible garantizar ambos a menos que la capa tenga el mismo número de
entradas y neuronas (estos números se denominanfan-inydesplieguede la capa), pero
propusieron un buen compromiso que ha demostrado funcionar muy bien en la práctica: los
pesos de conexión de cada capa deben inicializarse aleatoriamente como

2 He aquí una analogía: si coloca la perilla de un amplificador de micrófono demasiado cerca de cero, las personas no escucharán su voz, pero

si lo configura demasiado cerca del máximo, su voz se saturará y la gente no entenderá lo que está diciendo.
Ahora imagine una cadena de tales amplificadores: todos deben configurarse correctamente para que su voz se
escuche alta y clara al final de la cadena. Tu voz tiene que salir de cada amplificador con la misma amplitud con la
que entró.

Problemas de gradientes que desaparecen o explotan | 327


descrito enEcuación 11-1, dóndeadmiradorpromedio=admiradoren+admiradorafuera/2. Esta inicialización

la estrategia se llamaInicialización de Xavier(después del nombre del autor) oInicialización de Glorot(


después de su apellido).

Ecuación 11-1. Inicialización de Glorot (cuando se usa la función de activación logística)

1
Distribución normal con media 0 y varianzaσ2=
fanavg

3
O una distribución uniforme entre −ry +r, conr=
fanavg

Si solo reemplazasadmiradorpromedioconadmiradorenenEcuación 11-1, obtienes una estrategia de


inicialización que en realidad ya propuso Yann LeCun en la década de 1990, llamadaInicialización de
LeCun, que incluso fue recomendado en el libro de 1998Redes neuronales: trucos del oficiopor
Genevieve Orr y Klaus-Robert Müller (Springer). Es equivalente a la inicialización de Glorot cuando
admiradoren=admiradorafuera. Los investigadores tardaron más de una década en darse cuenta de lo
importante que es realmente este truco. El uso de la inicialización de Glorot puede acelerar
considerablemente el entrenamiento, y es uno de los trucos que llevaron al éxito actual de Deep
Learning.

Algunodocumentos3han proporcionado estrategias similares para diferentes funciones de


activación. Estas estrategias difieren solo por la escala de la varianza y si usanadmiradorpromedio o
admiradoren, como se muestra enTabla 11-1(para la distribución uniforme, simplemente calculer
=3σ2). La estrategia de inicialización para la función de activación de ReLU (y sus variantes,
incluida la activación de ELU descrita en breve) a veces se denominaEl inicializacion(después del
apellido de su autor). La función de activación de SELU se explicará más adelante en este
capítulo. Debe usarse con inicialización LeCun (preferiblemente con una distribución normal,
como veremos).

Tabla 11-1. Parámetros de inicialización para cada tipo de función de activación

Inicialización Funciones de activación σ² (Normal)

gloria Ninguno, Tanh, Logística, Softmax 1 /admiradorpromedio

Él ReLU y variantes 2 /admiradoren

lecun SELU 1 /admiradoren

De forma predeterminada, Keras utiliza la inicialización de Glorot con una distribución


uniforme. Puede cambiar esto a la inicialización He configurando
kernel_initializer="él_uniforme"oker nel_initializer="él_normal"al crear una capa, así:

3 Como "Profundizando en los rectificadores: superando el rendimiento a nivel humano en la clasificación de ImageNet", K.
Él et al. (2015).

328 | Capítulo 11: Entrenamiento de redes neuronales profundas


queras.capas.Denso(10,activación="relu",kernel_initializer="él_normal")

Si desea la inicialización de He con una distribución uniforme, pero basada enadmiradorpromediomás


bien queadmiradoren, puedes usar elEscalado de varianzainicializador así:

he_avg_init=queras.inicializadores.Escalado de varianza(escala=2.,modo='fan_avg',
distribución='uniforme')
queras.capas.Denso(10,activación="sigmoideo",kernel_initializer=he_avg_init)

Funciones de activación no saturadas


Una de las ideas del artículo de 2010 de Glorot y Bengio fue que los problemas de los gradientes de
desaparición/explosión se debían en parte a una mala elección de la función de activación. Hasta
entonces, la mayoría de la gente había asumido que si la Madre Naturaleza había optado por utilizar
funciones de activación aproximadamente sigmoideas en las neuronas biológicas, debían ser una
excelente elección. Pero resulta que otras funciones de activación se comportan mucho mejor en
redes neuronales profundas, en particular la función de activación ReLU, principalmente porque no se
satura para valores positivos (y también porque es bastante rápida de calcular).

Desafortunadamente, la función de activación de ReLU no es perfecta. Sufre de un problema conocido


comoReLU moribundos: durante el entrenamiento, algunas neuronas mueren efectivamente, lo que
significa que dejan de generar cualquier salida que no sea 0. En algunos casos, puede encontrar que la
mitad de las neuronas de su red están muertas, especialmente si usó una gran tasa de aprendizaje.
Una neurona muere cuando sus pesos se modifican de tal manera que la suma ponderada de sus
entradas es negativa para todas las instancias del conjunto de entrenamiento. Cuando esto sucede,
sigue emitiendo 0 y el descenso del gradiente ya no lo afecta, ya que el gradiente de la función ReLU
es 0 cuando su entrada es negativa.4

Para resolver este problema, es posible que desee utilizar una variante de la función ReLU, como la
ReLU con fugas. Esta función se define como LeakyReLUα(z) = máx(az,z) (ver Figura 11-2). El
hiperparámetroαdefine cuánto “pierde” la función: es la pendiente de la función paraz<0, y
normalmente se establece en 0,01. Esta pequeña pendiente asegura que los ReLU con fugas nunca
mueran; pueden entrar en un coma prolongado, pero tienen la oportunidad de despertarse
eventualmente. Apapel de 20155comparó varias variantes de la función de activación de ReLU y una de
sus conclusiones fue que las variantes con fugas siempre superaron la estricta función de activación
de ReLU. De hecho, establecerα= 0.2 (gran fuga) parecía dar como resultado un mejor rendimiento
queα= 0,01 (pequeña fuga). También evaluaron la ReLU aleatorio con fugas(RReLU), dondeαse elige al
azar en un rango determinado durante el entrenamiento y se fija en un valor promedio durante la
prueba. También se desempeñó bastante bien y pareció actuar como un regularizador (reduciendo el
riesgo de sobreajustar el conjunto de entrenamiento).

4 A menos que sea parte de la primera capa oculta, una neurona muerta a veces puede volver a la vida: descenso de gradiente
de hecho, puede modificar las neuronas en las capas inferiores de tal manera que la suma ponderada de las entradas de la neurona muerta

sea positiva nuevamente.

5 “Evaluación empírica de activaciones rectificadas en red de convolución”, B. Xu et al. (2015).

Problemas de gradientes que desaparecen o explotan | 329


Finalmente, también evaluaron laReLU paramétrico con fugas(PRELU), dondeαestá autorizado para ser
aprendido durante el entrenamiento (en lugar de ser un hiperparámetro, se convierte en un parámetro que
se puede modificar mediante retropropagación como cualquier otro parámetro). Se informó que esto superó
ampliamente a ReLU en conjuntos de datos de imágenes grandes, pero en conjuntos de datos más pequeños
se corre el riesgo de sobreajustar el conjunto de entrenamiento.

Figura 11-2. ReLU con fugas

Por último, pero no menos importante, unpapel de 2015por Djork-Arné Clevert et al.6propuso una
nueva función de activación llamadaunidad lineal exponencial(ELU) que superó a todas las variantes
de ReLU en sus experimentos: el tiempo de entrenamiento se redujo y la red neuronal se desempeñó
mejor en el conjunto de prueba. se representa enFigura 11-3, yEcuación 11-2 muestra su definición.

Ecuación 11-2. Función de activación ELU

αExpz−1 siz<0 z
ELUαz=
siz≥ 0

6 “Aprendizaje de red profundo rápido y preciso mediante unidades lineales exponenciales (ELU)”, D. Clevert, T. Unterthiner,
S. Hochreiter (2015).

330 | Capítulo 11: Entrenamiento de redes neuronales profundas


Figura 11-3. Función de activación ELU

Se parece mucho a la función ReLU, con algunas diferencias importantes:

• Primero toma valores negativos cuandoz<0, lo que permite que la unidad tenga una salida
promedio más cercana a 0. Esto ayuda a aliviar el problema de los gradientes que se desvanecen,
como se discutió anteriormente. El hiperparámetroαdefine el valor al que se aproxima la función
ELU cuandozes un gran número negativo. Por lo general, se establece en 1, pero puede
modificarlo como cualquier otro hiperparámetro si lo desea.

• Segundo, tiene un gradiente distinto de cero paraz<0, lo que evita el problema de las neuronas muertas.

• Tercero, siαes igual a 1, entonces la función es suave en todas partes, incluso alrededorz=0,
lo que ayuda a acelerar el descenso de gradiente, ya que no rebota tanto a la izquierda
como a la derecha dez=0.

El principal inconveniente de la función de activación ELU es que es más lenta de calcular que
ReLU y sus variantes (debido al uso de la función exponencial), pero durante el entrenamiento
esto se compensa con una tasa de convergencia más rápida. Sin embargo, en el momento de la
prueba, una red ELU será más lenta que una red ReLU.

Además, en unpapel de 20177por Günter Klambauer et al., llamado "Redes neuronales


autonormalizantes", los autores demostraron que si construye una red neuronal compuesta
exclusivamente por una pila de capas densas, y si todas las capas ocultas usan la función de activación
SELU (que es solo una versión escalada de la función de activación ELU, como su nombre sugiere),
entonces la redauto-normalizarse: la salida de cada capa tenderá a conservar la media 0 y la
desviación estándar 1 durante el entrenamiento, lo que resuelve el problema de los gradientes de
desaparición/explosión. Como resultado, esta función de activación a menudo supera

7 “Redes neuronales autonormalizantes”, G. Klambauer, T. Unterthiner y A. Mayr (2017).

Problemas de gradientes que desaparecen o explotan | 331


forma otras funciones de activación muy significativamente para tales redes neuronales (especialmente las
profundas). Sin embargo, existen algunas condiciones para que ocurra la autonormalización:

• Las características de entrada deben estar estandarizadas (media 0 y desviación estándar 1).

• Los pesos de cada capa oculta también deben inicializarse utilizando la inicialización normal de LeCun.
zación En Keras, esto significa establecerkernel_initializer="lecun_normal".
• La arquitectura de la red debe ser secuencial. Desafortunadamente, si intenta usar
SELU en arquitecturas no secuenciales, como redes recurrentes (ver???) o redes con
omitir conexiones(es decir, conexiones que saltan capas, como en redes anchas y
profundas), no se garantizará la autonormalización, por lo que SELU no
necesariamente superará otras funciones de activación.
• El papel solo garantiza la autonormalización si todas las capas son densas. Sin embargo, en la
práctica, la función de activación SELU parece funcionar muy bien también con redes neuronales
convolucionales (vercapitulo 14).

Entonces, ¿qué función de activación debería usar para las capas ocultas
de sus redes neuronales profundas? Aunque su kilometraje variará, en
general SELU> ELU> ReLU con fugas (y sus variantes)> ReLU> tanh
> logística. Si la arquitectura de la red evita que se autonormalice,
entonces ELU puede funcionar mejor que SELU (ya que SELU no es fluido
enz=0). Si le importa mucho la latencia del tiempo de ejecución, es posible
que prefiera ReLU con fugas. Si no desea modificar otro hiperparámetro,
puede usar el valor predeterminadoαvalores utilizados por Keras (p. ej.,
0,3 para ReLU con fugas). Si tiene tiempo libre y poder de cómputo,
puede usar la validación cruzada para evaluar otras funciones de
activación, en particular RReLU si su red está sobreajustada, o PReLU si
tiene un gran conjunto de entrenamiento.

Para usar la función de activación de ReLU con fugas, debe crear unaLeakyReLUinstancia como esta:

gotera_relu=queras.capas.LeakyReLU(alfa=0.2) capa=queras
.capas.Denso(10,activación=gotera_relu,
kernel_initializer="él_normal")

Para PReLU, simplemente reemplaceLeakyRelu (alfa = 0.2)conPRELU().Actualmente no existe una


implementación oficial de RReLU en Keras, pero puede implementar la suya con bastante
facilidad (vea los ejercicios al final deCapítulo 12).

Para la activación de SELU, simplemente configureactivación="selu"y


kernel_initial izer="lecun_normal"al crear una capa:
capa=queras.capas.Denso(10,activación="selu",
kernel_initializer="lecun_normal")

332 | Capítulo 11: Entrenamiento de redes neuronales profundas


Normalización por lotes

Aunque el uso de la inicialización de He junto con ELU (o cualquier variante de ReLU) puede reducir
significativamente los problemas de gradientes de desaparición/explosión al comienzo del
entrenamiento, no garantiza que no vuelvan a aparecer durante el entrenamiento.

en unpapel de 2015,8Sergey Ioffe y Christian Szegedy propusieron una técnica llamada Normalización
por lotes(BN) para abordar los problemas de gradientes de desaparición/explosión. La técnica consiste
en agregar una operación en el modelo justo antes o después de la función de activación de cada capa
oculta, simplemente centrando en cero y normalizando cada entrada, luego escalando y cambiando el
resultado usando dos nuevos vectores de parámetros por capa: uno para escalar, el otro para
cambiar. En otras palabras, esta operación permite que el modelo aprenda la escala y la media
óptimas de cada una de las entradas de la capa. En muchos casos, si agrega una capa BN como la
primera capa de su red neuronal, no necesita estandarizar su conjunto de entrenamiento (por
ejemplo, usando unescalador estándar):la capa BN lo hará por usted (bueno, aproximadamente, ya
que solo mira un lote a la vez, y también puede cambiar la escala y cambiar cada función de entrada).

Para centrar en cero y normalizar las entradas, el algoritmo necesita estimar la media
y la desviación estándar de cada entrada. Lo hace evaluando la media y la desviación
estándar de cada entrada sobre el mini lote actual (de ahí el nombre de
“normalización de lotes”). Toda la operación se resume enEcuación 11-3.

Ecuación 11-3. Algoritmo de normalización por lotes

1
megabyte

1. mB= ∑Xi
B yo=1
metro

B
metro

1 2
2. σB2 = ∑ X i − mB
metroB yo=1

Xi−m B
3. Xi =
σB2 + �

4. zi =γ⊗Xi+β

• mBes el vector de medios de entrada, evaluado sobre todo el mini-loteB(contiene una


media por entrada).

8 “Normalización por lotes: Aceleración del entrenamiento profundo de redes mediante la reducción del cambio interno de covariables”, S. Ioffe

y C. Szegedy (2015).

Problemas de gradientes que desaparecen o explotan | 333


• σBes el vector de desviaciones estándar de entrada, también evaluado sobre todo el
minilote (contiene una desviación estándar por entrada).

• metroBes el número de instancias en el mini lote.


• X(i)es el vector de entradas normalizadas y centradas en cero, por ejemploi.

• γes el vector de parámetros de escala de salida para la capa (contiene un parámetro de escala
por entrada).

• ⊗ representa la multiplicación por elementos (cada entrada se multiplica por su parámetro de


escala de salida correspondiente).

• βes el vector de parámetro de desplazamiento (desplazamiento) de salida para la capa (contiene un parámetro de

desplazamiento por entrada). Cada entrada se compensa con su parámetro de desplazamiento correspondiente.

• ϵes un número pequeño para evitar la división por cero (típicamente 10–5). Esto se llama un
término de suavizado.

• z(i)es la salida de la operación BN: es una versión reescalada y desplazada de las


entradas.

Entonces, durante el entrenamiento, BN simplemente estandariza sus entradas, luego las vuelve a
escalar y las compensa. ¡Bueno! ¿Qué pasa en el momento de la prueba? Bueno, no es tan simple. De
hecho, es posible que necesitemos hacer predicciones para instancias individuales en lugar de lotes de
instancias: en este caso, no tendremos forma de calcular la media y la desviación estándar de cada
entrada. Además, incluso si tenemos un lote de instancias, puede ser demasiado pequeño, o las
instancias pueden no ser independientes e idénticamente distribuidas (IID), por lo que calcular
estadísticas sobre las instancias del lote no sería confiable (durante el entrenamiento, los lotes no
debe ser demasiado pequeño, si es posible más de 30 instancias, y todas las instancias deben ser IID,
como vimos enCapítulo 4). Una solución podría ser esperar hasta el final del entrenamiento, luego
ejecutar todo el conjunto de entrenamiento a través de la red neuronal y calcular la media y la
desviación estándar de cada entrada de la capa BN. Estas medias de entrada y desviaciones estándar
"finales" se pueden usar en lugar de las medias y desviaciones estándar de entrada por lotes al hacer
predicciones. Sin embargo, a menudo se prefiere estimar estas estadísticas finales durante el
entrenamiento utilizando un promedio móvil de las medias de entrada y las desviaciones estándar de
la capa. En resumen, se aprenden cuatro vectores de parámetros en cada capa normalizada por lotes:
γ(el vector de escala de salida) yβ(el vector de compensación de salida) se aprenden a través de la
retropropagación regular, ym(el vector medio de entrada final), yσ(el vector de desviación estándar de
entrada final) se estiman utilizando un promedio móvil exponencial. Tenga en cuenta quemyσse
estiman durante el entrenamiento, pero no se usan en absoluto durante el entrenamiento, solo
después del entrenamiento (para reemplazar las medias de entrada por lotes y las desviaciones
estándar en Ecuación 11-3).

Los autores demostraron que esta técnica mejoró considerablemente todas las redes neuronales
profundas con las que experimentaron, lo que llevó a una gran mejora en la tarea de clasificación de
ImageNet (ImageNet es una gran base de datos de imágenes clasificadas en muchas clases y
comúnmente utilizada para evaluar sistemas de visión por computadora). la desaparición-

334 | Capítulo 11: Entrenamiento de redes neuronales profundas


El problema de los gradientes de saturación se redujo considerablemente, hasta el punto
de que podían utilizar funciones de activación saturantes como la tanh e incluso la función
de activación logística. Las redes también eran mucho menos sensibles a la inicialización
del peso. Pudieron utilizar tasas de aprendizaje mucho mayores, lo que aceleró
significativamente el proceso de aprendizaje. Específicamente, señalan que “aplicada a un
modelo de clasificación de imágenes de última generación, la normalización por lotes
logra la misma precisión con 14 veces menos pasos de entrenamiento y supera al modelo
original por un margen significativo. […] Utilizando un conjunto de redes normalizadas por
lotes, mejoramos el mejor resultado publicado en la clasificación de ImageNet:
alcanzamos un 4,9 % de error de validación entre los 5 primeros (y un 4,8 % de error de
prueba), superando la precisión de los evaluadores humanos”. Finalmente, como un
regalo que sigue dando,

Sin embargo, la normalización por lotes agrega algo de complejidad al modelo (aunque puede
eliminar la necesidad de normalizar los datos de entrada, como discutimos anteriormente). Además,
existe una penalización de tiempo de ejecución: la red neuronal hace predicciones más lentas debido a
los cálculos adicionales requeridos en cada capa. Entonces, si necesita que las predicciones sean
ultrarrápidas, es posible que desee verificar qué tan bien funciona la inicialización simple de ELU + He
antes de jugar con la normalización por lotes.

Puede encontrar que el entrenamiento es bastante lento, porque cada época toma
mucho más tiempo cuando usa la normalización por lotes. Sin embargo, esto suele
verse contrarrestado por el hecho de que la convergencia es mucho más rápida con
BN, por lo que se necesitarán menos épocas para alcanzar el mismo rendimiento.
Considerándolo todo,tiempo de paredgeneralmente será más pequeño (este es el
tiempo medido por el reloj en su pared).

Implementación de la normalización por lotes con Keras

Como ocurre con la mayoría de las cosas con Keras, implementar la normalización por lotes es
bastante simple. Solo agrega unNormalización por lotescapa antes o después de la función de
activación de cada capa oculta y, opcionalmente, agregue una capa BN, así como la primera capa en
su modelo. Por ejemplo, este modelo aplica BN después de cada capa oculta y como la primera capa
del modelo (después de aplanar las imágenes de entrada):

modelo=queras.modelos.Secuencial([
queras.capas.Aplanar(entrada_forma=[28,28]),
queras.capas.Normalización por lotes(),
queras.capas.Denso(300,activación="elú",kernel_initializer="él_normal"), queras.
capas.Normalización por lotes(),
queras.capas.Denso(100,activación="elú",kernel_initializer="él_normal"), queras.
capas.Normalización por lotes(), queras.capas.Denso(10,activación="softmax")

])

Problemas de gradientes que desaparecen o explotan | 335


¡Eso es todo! En este pequeño ejemplo con solo dos capas ocultas, es poco probable que la
Normalización por lotes tenga un impacto muy positivo, pero para redes más profundas puede
marcar una gran diferencia.

Hagamos un poco de zoom. Si muestra el resumen del modelo, puede ver que cada capa
BN agrega 4 parámetros por entrada:γ,β,myσ(por ejemplo, la primera capa BN agrega
3136 parámetros, que es 4 veces 784). Los dos últimos parámetros,myσ, son los
promedios móviles, no se ven afectados por la retropropagación, por lo que Keras los
llama "No entrenables".9(si cuenta el número total de parámetros BN, 3136 + 1200 + 400, y
lo divide por dos, obtiene 2368, que es el número total de parámetros no entrenables en
este modelo).

> > > modelo.resumen()


Modelo: "secuencial_3"
________________________________________________________________ Capa (tipo)
Forma de salida Parámetro #

==================================================
=============== flatten_3 (Aplanar)
(Ninguno, 784) 0
_________________________________________________________________
lote_normalización_v2 (Batc (Ninguno, 784) 3136
________________________________________________________________ denso_50
(Denso) (Ninguno, 300) 235500
_________________________________________________________________
lote_normalización_v2_1 (Ba (Ninguno, 300) 1200
________________________________________________________________ denso_51
(Denso) (Ninguno, 100) 30100
_________________________________________________________________
lote_normalización_v2_2 (Ba (Ninguno, 100) 400
________________________________________________________________ denso_52
(Denso) (Ninguno, 10) 1010
================================================== ===============
Parámetros totales: 271,346
Parámetros entrenables: 268,978
Parámetros no entrenables: 2,368

Veamos los parámetros de la primera capa BN. Dos son entrenables (por backprop), y dos
no lo son:

> > > [(variable.nombre,variable.entrenable)porvariableenmodelo.capas[1].Variables]


[('batch_normalization_v2/gamma:0', Verdadero),
('batch_normalization_v2/beta:0', True),
('batch_normalization_v2/moving_mean:0', False),
('batch_normalization_v2/moving_variance:0', False)]

Ahora, cuando crea una capa BN en Keras, también crea dos operaciones que serán llamadas
por Keras en cada iteración durante el entrenamiento. Estas operaciones actualizarán el

9 Sin embargo, se estiman durante el entrenamiento, en función de los datos de entrenamiento, por lo que podría decirse quesonentrenable. En

Keras, "No entrenable" realmente significa "sin tocar por retropropagación".

336 | Capítulo 11: Entrenamiento de redes neuronales profundas


medias móviles. Dado que usamos el backend de TensorFlow, estas operaciones son
operaciones de TensorFlow (hablaremos de las operaciones de TF enCapítulo 12).

> > > modelo.capas[1].actualizaciones


[<tf.Operación 'cond_2/Identidad' tipo=Identidad>,
<tf.Operación 'cond_3/Identidad' tipo=Identidad>]

Los autores del artículo de BN argumentaron a favor de agregar las capas de BN antes de las
funciones de activación, en lugar de después (como acabamos de hacer). Existe cierto debate sobre
esto, ya que parece depender de la tarea. Así que esa es una cosa más con la que puede experimentar
para ver qué opción funciona mejor en su conjunto de datos. Para agregar las capas BN antes de las
funciones de activación, debemos eliminar la función de activación de las capas ocultas y agregarlas
como capas separadas después de las capas BN. Además, dado que una capa de normalización por
lotes incluye un parámetro de compensación por entrada, puede eliminar el término de sesgo de la
capa anterior (simplemente paseuse_bias=Falsoal crearlo):

modelo=queras.modelos.Secuencial([
queras.capas.Aplanar(entrada_forma=[28,28]),
queras.capas.Normalización por lotes(),
queras.capas.Denso(300,kernel_initializer="él_normal",use_bias=Falso), queras.
capas.Normalización por lotes(), queras.capas.Activación("elú"),

queras.capas.Denso(100,kernel_initializer="él_normal",use_bias=Falso), queras.
capas.Activación("elú"), queras.capas.Normalización por lotes(), queras.capas.
Denso(10,activación="softmax")

])

losNormalización por lotesLa clase tiene bastantes hiperparámetros que puede modificar. Por lo
general, los valores predeterminados estarán bien, pero es posible que de vez en cuando necesite
modificar elimpulso. Este hiperparámetro se utiliza al actualizar las medias móviles exponenciales:
dado un nuevo valorv(es decir, un nuevo vector de medias de entrada o desviaciones estándar
calculadas sobre el lote actual), el promedio móvil�se actualiza usando la siguiente ecuación:

v v× impulso +v× 1 − impulso

Un buen valor de impulso suele ser cercano a 1, por ejemplo, 0,9, 0,99 o 0,999 (quiere más 9
para conjuntos de datos más grandes y minilotes más pequeños).

Otro hiperparámetro importante eseje:determina qué eje debe normalizarse. El valor predeterminado
es -1, lo que significa que, de manera predeterminada, normalizará el último eje (usando las medias y
las desviaciones estándar calculadas en elotroejes). Por ejemplo, cuando el lote de entrada es 2D (es
decir, la forma del lote es [tamaño del lote, características]), esto significa que cada característica de
entrada se normalizará en función de la media y la desviación estándar calculadas en todas las
instancias del lote. Por ejemplo, la primera capa BN en el ejemplo de código anterior normalizará (y
cambiará de escala y desplazará) de forma independiente cada una de las 784 características de
entrada. Sin embargo, si movemos la primera capa BN antes de laAplanar

Problemas de gradientes que desaparecen o explotan | 337


capa, entonces los lotes de entrada serán 3D, con forma [tamaño de lote, altura, ancho], por lo
tanto, la capa BN calculará 28 medias y 28 desviaciones estándar (una por columna de píxeles,
calculada en todas las instancias en el lote, y todas las filas en la columna), y normalizará todos
los píxeles en una columna dada usando la misma media y desviación estándar. También habrá
solo 28 parámetros de escala y 28 parámetros de cambio. Si, en cambio, aún desea tratar cada
uno de los 784 píxeles de forma independiente, debe configurar
eje=[1, 2].
Tenga en cuenta que la capa BN no realiza el mismo cálculo durante el entrenamiento y después del
entrenamiento: utiliza estadísticas por lotes durante el entrenamiento y las estadísticas "finales" después del
entrenamiento (es decir, el valor final de los promedios móviles). Echemos un vistazo al código fuente de esta
clase para ver cómo se maneja esto:

claseNormalización por lotes(Capa):


[...]
definitivamentellamar(uno mismo,entradas,capacitación=Ninguna):

sicapacitaciónesNinguna:
capacitación=queras.back-end.fase_de_aprendizaje()
[...]

losllamar()El método es el que realmente realiza los cálculos y, como puede ver, tiene un extra
capacitaciónargumento: si esNingunavuelve a caerkeras.backend.fase_de_aprendizaje(),que
regresa1durante el entrenamiento (eladaptar()el método lo asegura). De lo contrario, vuelve0.Si
alguna vez necesita escribir una capa personalizada y debe comportarse de manera diferente
durante el entrenamiento y la prueba, simplemente use el mismo patrón (hablaremos sobre las
capas personalizadas enCapítulo 12).

La normalización por lotes se ha convertido en una de las capas más utilizadas en las redes
neuronales profundas, hasta el punto de que a menudo se omite en los diagramas, ya que se supone
que se agrega BN después de cada capa. Sin embargo, una muy recientepapel10por Hongyi Zhang et
al. bien puede cambiar esto: los autores muestran que mediante el uso de una nueva técnica de
inicialización de peso de actualización fija (fixup), logran entrenar una red neuronal muy profunda
(¡10,000 capas!) sin BN, logrando un estado del arte. rendimiento en tareas complejas de clasificación
de imágenes.

Recorte de degradado

Otra técnica popular para disminuir el problema de los gradientes explosivos es simplemente
recortar los gradientes durante la retropropagación para que nunca excedan algún umbral. Se
llamaRecorte de degradado.11Esta técnica se usa con más frecuencia en las neu‐

10 “Inicialización de corrección: aprendizaje residual sin normalización”, Hongyi Zhang, Yann N. Dauphin, Tengyu
Mamá (2019).

11 “Sobre la dificultad de entrenar redes neuronales recurrentes”, R. Pascanu et al. (2013).

338 | Capítulo 11: Entrenamiento de redes neuronales profundas


redes ral, ya que la normalización por lotes es difícil de usar en RNN, como veremos en???.
Para otros tipos de redes, BN suele ser suficiente.

En Keras, implementar Gradient Clipping es solo una cuestión de configurar elvalor de clipo
clipnormargumento al crear un optimizador. Por ejemplo:

optimizador=queras.optimizadores.USD(valor de clip=1.0)
modelo.compilar(pérdida="mse",optimizador=optimizador)

Esto recortará todos los componentes del vector de gradiente a un valor entre -1.0 y 1.0. Esto
significa que todas las derivadas parciales de la pérdida (con respecto a todos y cada uno de los
parámetros entrenables) se recortarán entre –1,0 y 1,0. El umbral es un hiperparámetro que
puede ajustar. Tenga en cuenta que puede cambiar la orientación del vector de gradiente: por
ejemplo, si el vector de gradiente original es [0.9, 100.0], apunta principalmente en la dirección
del segundo eje, pero una vez que lo recorta por valor, obtiene [0.9, 1.0], que apunta
aproximadamente en la diagonal entre los dos ejes. En la práctica, sin embargo, este enfoque
funciona bien. Si desea asegurarse de que el recorte de degradado no cambie la dirección del
vector de degradado, debe recortar por norma configurandoclipnorm
en vez devalor de clip.Esto recortará todo el degradado si es ℓ2norma es mayor que el
umbral que eligió. Por ejemplo, si establececlipnorm=1.0,entonces el vector [0.9, 100.0] se
recortará a [0.00899964, 0.9999595], preservando su orientación, pero casi eliminando el
primer componente. Si observa que los gradientes explotan durante el entrenamiento
(puede realizar un seguimiento del tamaño de los gradientes con TensorBoard), puede
probar el recorte por valor y el recorte por norma, con un umbral diferente, y ver qué
opción funciona mejor en el conjunto de validación. .

Reutilización de capas previamente entrenadas

Por lo general, no es una buena idea entrenar un DNN muy grande desde cero: en su lugar, siempre debe
intentar encontrar una red neuronal existente que realice una tarea similar a la que está tratando de abordar
(hablaremos sobre cómo encontrarlos encapitulo 14), luego simplemente reutilice las capas inferiores de esta
red: esto se llamatransferir el aprendizaje. No solo acelerará considerablemente el entrenamiento, sino que
también requerirá muchos menos datos de entrenamiento.

Por ejemplo, suponga que tiene acceso a un DNN que fue entrenado para clasificar imágenes
en 100 categorías diferentes, incluidos animales, plantas, vehículos y objetos cotidianos. Ahora
desea entrenar un DNN para clasificar tipos específicos de vehículos. Estas tareas son muy
similares, incluso se superponen parcialmente, por lo que debe intentar reutilizar partes de la
primera red (verFigura 11-4).

Reutilización de capas previamente entrenadas | 339


Figura 11-4. Reutilización de capas previamente entrenadas

Si las imágenes de entrada de su nueva tarea no tienen el mismo tamaño que las
utilizadas en la tarea original, normalmente tendrá que agregar un paso de
preprocesamiento para cambiarlas al tamaño esperado por el modelo original. En
términos más generales, el aprendizaje por transferencia funcionará mejor cuando
las entradas tengan características similares de bajo nivel.

La capa de salida del modelo original generalmente debe reemplazarse, ya que lo más probable es
que no sea útil para la nueva tarea y es posible que ni siquiera tenga la cantidad correcta de salidas
para la nueva tarea.

De manera similar, es menos probable que las capas ocultas superiores del modelo original sean tan útiles
como las capas inferiores, ya que las características de alto nivel que son más útiles para la nueva tarea
pueden diferir significativamente de las que fueron más útiles para la tarea original. . Desea encontrar el
número correcto de capas para reutilizar.

Cuanto más similares sean las tareas, más capas querrás reutilizar (empezando
por las capas inferiores). Para tareas muy similares, puede intentar mantener
todas las capas ocultas y simplemente reemplazar la capa de salida.

Intente congelar todas las capas reutilizadas primero (es decir, haga que sus pesos no se puedan entrenar, de
modo que el descenso de gradiente no los modifique), luego entrene su modelo y vea cómo funciona. Luego
intente descongelar una o dos de las capas ocultas superiores para permitir que la retropropagación las
ajuste y vea si el rendimiento mejora. Cuantos más datos de entrenamiento tengas, más

340 | Capítulo 11: Entrenamiento de redes neuronales profundas


capas que puedes descongelar. También es útil reducir la tasa de aprendizaje cuando
descongelas capas reutilizadas: esto evitará arruinar sus pesos ajustados.

Si aún no puede obtener un buen rendimiento y tiene pocos datos de entrenamiento, intente eliminar
las capas ocultas superiores y congele todas las capas ocultas restantes nuevamente. Puede iterar
hasta encontrar el número correcto de capas para reutilizar. Si tiene muchos datos de entrenamiento,
puede intentar reemplazar las capas ocultas superiores en lugar de eliminarlas, e incluso agregar más
capas ocultas.

Transferir el aprendizaje con Keras

Veamos un ejemplo. Supongamos que el conjunto de datos MNIST de moda solo contiene 8 clases,
por ejemplo, todas las clases excepto sandalias y camisas. Alguien construyó y entrenó un modelo de
Keras en ese conjunto y obtuvo un rendimiento razonablemente bueno (>90 % de precisión).
Llamemos a este modelo A. Ahora quiere abordar una tarea diferente: tiene imágenes de sandalias y
camisas, y quiere entrenar un clasificador binario (positivo=camisas, negativo=sandalias). Sin
embargo, su conjunto de datos es bastante pequeño, solo tiene 200 imágenes etiquetadas. Cuando
entrena un nuevo modelo para esta tarea (llamémoslo modelo B), con la misma arquitectura que el
modelo A, funciona razonablemente bien (97,2 % de precisión), pero como es una tarea mucho más
fácil (solo hay 2 clases), esperabas más. Mientras toma su café de la mañana, se da cuenta de que su
tarea es bastante similar a la tarea A, entonces, ¿quizás el aprendizaje por transferencia pueda
ayudar? ¡Vamos a averiguar!

Primero, necesita cargar el modelo A y crear un nuevo modelo basado en las capas del modelo A.
Reutilicemos todas las capas excepto la capa de salida:

modelo_A=queras.modelos.cargar_modelo("mi_modelo_A.h5")
modelo_B_en_A=queras.modelos.Secuencial(modelo_A.capas[:-1])
modelo_B_en_A.agregar(queras.capas.Denso(1,activación="sigmoideo"))

Tenga en cuenta quemodelo_Aymodelo_B_en_Aahora comparte algunas capas. cuando entrenas


modelo_B_en_A,también afectarámodelo_A.Si quieres evitar eso, necesitas clonar
modelo_Aantes de reutilizar sus capas. Para hacer esto, debe clonar la arquitectura del
modelo A, luego copiar sus pesos (ya quemodelo_clon()no clona los pesos):

modelo_A_clon=queras.modelos.clon_modelo(modelo_A)
modelo_A_clon.establecer_pesos(modelo_A.obtener_pesos())

Ahora podríamos simplemente entrenarmodelo_B_en_Apara la tarea B, pero dado que la nueva capa de salida
se inicializó aleatoriamente, cometerá grandes errores, al menos durante las primeras épocas, por lo que
habrá grandes gradientes de error que pueden arruinar los pesos reutilizados. Para evitar esto, un enfoque
es congelar las capas reutilizadas durante las primeras épocas, dando a la nueva capa algo de tiempo para
aprender pesos razonables. Para hacer esto, simplemente establezca la configuración de cada capa.entrenar
capazatribuir aFalsoy compilar el modelo:

porcapaenmodelo_B_en_A.capas[:-1]:
capa.entrenable=Falso

Reutilización de capas previamente entrenadas | 341


modelo_B_en_A.compilar(pérdida="binary_crossentropy",optimizador="sgd",
métrica=["precisión"])

Siempre debe compilar su modelo después de congelar o descongelar


capas.

A continuación, podemos entrenar el modelo durante algunas épocas, luego descongelar las capas
reutilizadas (lo que requiere volver a compilar el modelo) y continuar entrenando para ajustar las capas
reutilizadas para la tarea B. Después de descongelar las capas reutilizadas, suele ser una buena idea para
reducir la tasa de aprendizaje, una vez más para evitar dañar los pesos reutilizados:

historia=modelo_B_en_A.adaptar(Tren_X_B,y_tren_B,épocas=4,
datos_de_validación=(X_válido_B,y_válido_B))

porcapaenmodelo_B_en_A.capas[:-1]:
capa.entrenable=Verdadero

optimizador=queras.optimizadores.USD(yo=1e-4)#el lr predeterminado es 1e-3


modelo_B_en_A.compilar(pérdida="binary_crossentropy",optimizador=optimizador,
métrica=["precisión"])
historia=modelo_B_en_A.adaptar(Tren_X_B,y_tren_B,épocas=dieciséis,
datos_de_validación=(X_válido_B,y_válido_B))

Entonces, ¿cuál es el veredicto final? Bueno, la precisión de la prueba de este modelo es del 99,25 %, lo que
significa que el aprendizaje por transferencia redujo la tasa de error del 2,8 % a casi el 0,7 %. ¡Eso es un factor
de 4!

> > > modelo_B_en_A.evaluar(X_prueba_B,y_prueba_B)


[0.06887910133600235, 0.9925]

¿Estás convencido? Bueno, no deberías estarlo: ¡hice trampa! :) Probé muchas configuraciones
hasta que encontré una que demostró una gran mejora. Si intenta cambiar las clases o la
semilla aleatoria, verá que la mejora generalmente cae, o incluso desaparece o se invierte. Lo
que hice se llama "torturar los datos hasta que confiesen". Cuando un artículo parece
demasiado positivo, debe sospechar: tal vez la nueva y llamativa técnica no ayude mucho (de
hecho, incluso puede degradar el rendimiento), pero los autores probaron muchas variantes y
solo informaron los mejores resultados (lo que puede deberse a para esquilar la suerte), sin
mencionar cuántos fracasos encontraron en el camino. La mayoría de las veces, esto no es
malicioso en absoluto, pero es parte de la razón por la cual tantos resultados en Science nunca
se pueden reproducir.

Entonces, ¿por qué hice trampa? Bueno, resulta que el aprendizaje por transferencia no funciona muy
bien con redes pequeñas y densas: funciona mejor con redes neuronales convolucionales profundas,
por lo que revisaremos el aprendizaje por transferencia encapitulo 14, utilizando las mismas técnicas
(¡y esta vez no habrá trampas, lo prometo!).

342 | Capítulo 11: Entrenamiento de redes neuronales profundas


Preentrenamiento no supervisado

Suponga que desea abordar una tarea compleja para la que no tiene muchos datos de entrenamiento
etiquetados, pero lamentablemente no puede encontrar un modelo entrenado en una tarea similar. ¡No
pierdas toda esperanza! Primero, por supuesto, debe intentar recopilar más datos de entrenamiento
etiquetados, pero si esto es demasiado difícil o demasiado costoso, es posible que aún pueda realizar
preentrenamiento no supervisado(verFigura 11-5). A menudo es bastante barato recopilar ejemplos de
capacitación sin etiquetar, pero bastante costoso etiquetarlos. Si puede recopilar una gran cantidad de datos
de entrenamiento sin etiquetar, puede intentar entrenar las capas una por una, comenzando con la capa más
baja y luego subiendo, usando un algoritmo detector de características no supervisado comoMáquinas
Boltzmann restringidas(RBM; ver???) o codificadores automáticos (ver???). Cada capa se entrena en la salida
de las capas previamente entrenadas (todas las capas excepto la que se está entrenando se congelan). Una
vez que todas las capas hayan sido entrenadas de esta manera, puede agregar la capa de salida para su tarea
y ajustar la red final usando el aprendizaje supervisado (es decir, con los ejemplos de entrenamiento
etiquetados). En este punto, puede descongelar todas las capas preentrenadas, o solo algunas de las
superiores.

Figura 11-5. Preentrenamiento sin supervisión

Este es un proceso bastante largo y tedioso, pero a menudo funciona bien; de hecho, es esta técnica la
que usaron Geoffrey Hinton y su equipo en 2006 y que condujo al resurgimiento de las redes
neuronales y al éxito del aprendizaje profundo. Hasta 2010, el preentrenamiento no supervisado
(típicamente usando RBM) era la norma para las redes profundas, y fue solo después de que se alivió
el problema de los gradientes que se desvanecían que se volvió mucho más común.

Reutilización de capas previamente entrenadas | 343


mon para entrenar DNN utilizando puramente el aprendizaje supervisado. Sin embargo, el preentrenamiento no supervisado

(hoy en día normalmente se usan codificadores automáticos en lugar de RBM) sigue siendo una buena opción cuando tiene una

tarea compleja que resolver, no hay un modelo similar que pueda reutilizar y hay pocos datos de entrenamiento etiquetados

pero muchos datos de entrenamiento sin etiquetar.

Preentrenamiento en una tarea auxiliar

Si no tiene muchos datos de entrenamiento etiquetados, una última opción es entrenar una primera red
neuronal en una tarea auxiliar para la que pueda obtener o generar fácilmente datos de entrenamiento
etiquetados, luego reutilice las capas inferiores de esa red para su tarea real. Las capas inferiores de la
primera red neuronal aprenderán detectores de características que probablemente serán reutilizables por la
segunda red neuronal.

Por ejemplo, si desea crear un sistema para reconocer caras, es posible que solo tenga unas pocas
imágenes de cada individuo, lo que claramente no es suficiente para entrenar a un buen clasificador.
No sería práctico reunir cientos de fotografías de cada persona. Sin embargo, podría recopilar muchas
imágenes de personas aleatorias en la web y entrenar una primera red neuronal para detectar si dos
imágenes diferentes muestran o no a la misma persona. Tal red aprendería buenos detectores de
características para rostros, por lo que reutilizar sus capas inferiores le permitiría entrenar un buen
clasificador de rostros usando pocos datos de entrenamiento.

Paraprocesamiento natural del lenguaje(NLP), puede descargar fácilmente millones de documentos


de texto y generar automáticamente datos etiquetados a partir de ellos. Por ejemplo, podría
enmascarar aleatoriamente algunas palabras y entrenar un modelo para predecir cuáles son las
palabras que faltan (por ejemplo, debería predecir que la palabra que falta en la oración "¿Qué ___
estás diciendo?" es probablemente "son" o "eran" ). Si puede entrenar un modelo para que alcance un
buen desempeño en esta tarea, entonces ya sabrá mucho sobre el lenguaje y ciertamente puede
reutilizarlo para su tarea real y ajustarlo en sus datos etiquetados (lo haremos). discutir más tareas
previas al entrenamiento en???).

Aprendizaje autosupervisadoes cuando genera automáticamente las etiquetas a


partir de los datos mismos, luego entrena un modelo en el conjunto de datos
"etiquetado" resultante utilizando técnicas de aprendizaje supervisado. Dado que
este enfoque no requiere ningún tipo de etiquetado humano, es mejor clasificarlo
como una forma de aprendizaje no supervisado.

Optimizadores más rápidos

Entrenar una red neuronal profunda muy grande puede ser terriblemente lento. Hasta ahora hemos visto
cuatro formas de acelerar el entrenamiento (y llegar a una mejor solución): aplicar una buena estrategia de
inicialización para los pesos de conexión, usar una buena función de activación, usar Batch Normalization y
reutilizar partes de una red preentrenada (posiblemente construido sobre una tarea auxiliar o usando
aprendizaje no supervisado). Otro gran impulso de velocidad proviene del uso de un optimizador más rápido
que el optimizador de descenso de gradiente normal. En esta sección

344 | Capítulo 11: Entrenamiento de redes neuronales profundas


presentaremos los más populares: optimización Momentum, Nesterov Accelerated
Gradient, AdaGrad, RMSProp, y finalmente optimización Adam y Nadam.

Optimización de impulso
Imagine una bola de bolos rodando por una pendiente suave sobre una superficie lisa:
comenzará lentamente, pero rápidamente tomará impulso hasta que finalmente alcance la
velocidad terminal (si hay algo de fricción o resistencia del aire). Esta es la idea muy simple
detrás optimización de impulso,propuesto por Boris Polyak en 1964.12Por el contrario, el
Descenso de gradiente regular simplemente dará pequeños pasos regulares por la pendiente,
por lo que llevará mucho más tiempo llegar al fondo.

Recuerde que Gradient Descent simplemente actualiza los pesosθrestando directamente el


gradiente de la función de costoj(θ) con respecto a los pesos (∇θj(θ)) multiplicado por la tasa de
aprendizajeη. la ecuacion es:θ←θ–η∇θj(θ). No le importa cuáles fueron los gradientes anteriores.
Si el gradiente local es pequeño, va muy lentamente.

La optimización del momento se preocupa mucho por cuáles eran los gradientes
anteriores: en cada iteración, resta el gradiente local delvector de impulsometro(
multiplicado por la tasa de aprendizajeη), y actualiza los pesos simplemente agregando
este vector de impulso (verEcuación 11-4). En otras palabras, la pendiente se usa para la
aceleración, no para la velocidad. Para simular algún tipo de mecanismo de fricción y
evitar que el impulso crezca demasiado, el algoritmo introduce un nuevo hiperparámetro
β, llamado simplemente elimpulso, que debe configurarse entre 0 (alta fricción) y 1 (sin
fricción). Un valor de impulso típico es 0,9.

Ecuación 11-4. Algoritmo de impulso

1. metro βmetro−η∇θjθ

2. θ θ+metro

Puede verificar fácilmente que si el gradiente permanece constante, la velocidad terminal (es decir, el
tamaño máximo de las actualizaciones de peso) es igual a ese gradiente multiplicado por el

tasa de aprendizajeηmultiplicado por1(ignorando el letrero). Por ejemplo, siβ= 0.9, entonces el


1 -β
la velocidad terminal es igual a 10 veces el gradiente multiplicado por la tasa de aprendizaje, por lo
que la optimización de impulso termina yendo 10 veces más rápido que el descenso de gradiente. Esto
permite que la optimización de Momentum escape de las mesetas mucho más rápido que Gradient
Descent. En particular, vimos enCapítulo 4que cuando los insumos tienen escalas muy diferentes, la
función de costo se verá como un cuenco alargado (verFigura 4-7). Gradient Descent desciende
bastante rápido por la pendiente empinada, pero luego lleva mucho tiempo bajar por el valle.

12 “Algunos métodos para acelerar la convergencia de los métodos de iteración”, B. Polyak (1964).

Optimizadores más rápidos | 345


Ley. Por el contrario, la optimización Momentum bajará por el valle cada vez más rápido hasta llegar al
fondo (lo óptimo). En las redes neuronales profundas que no utilizan la normalización por lotes, las
capas superiores a menudo terminarán teniendo entradas con escalas muy diferentes, por lo que usar
la optimización Momentum ayuda mucho. También puede ayudar a superar los óptimos locales.

Debido al impulso, el optimizador puede pasarse un poco, luego


regresar, pasarse de nuevo y oscilar así muchas veces antes de
estabilizarse al mínimo. Esta es una de las razones por las que es
bueno tener un poco de fricción en el sistema: elimina estas
oscilaciones y, por lo tanto, acelera la convergencia.

Implementar la optimización de Momentum en Keras es una obviedad: solo use elUSD


optimizador y establecer suimpulso¡hiperparámetro, luego recuéstese y obtenga ganancias!

optimizador=queras.optimizadores.USD(yo=0.001,impulso=0.9)

El único inconveniente de la optimización Momentum es que agrega otro hiperparámetro para


ajustar. Sin embargo, el valor de impulso de 0,9 suele funcionar bien en la práctica y casi
siempre va más rápido que el descenso de gradiente normal.

Gradiente acelerado de Nesterov

Una pequeña variante de la optimización Momentum, propuesta porYuri Nésterov en 1983,13


es casi siempre más rápido que la optimización de Momentum vainilla. La idea de
Optimización del impulso de Nesterov, oGradiente acelerado de Nesterov(NAG), es medir
el gradiente de la función de costo no en la posición local sino ligeramente por delante en
la dirección del impulso (verEcuación 11-5). La única diferencia con la optimización de
Momentum vainilla es que el gradiente se mide enθ+βmetroen lugar de enθ.

Ecuación 11-5. Algoritmo de gradiente acelerado de Nesterov

1. metro βmetro−η∇θjθ+βm θ+

2. θ metro

Este pequeño ajuste funciona porque, en general, el vector de momento apuntará en la


dirección correcta (es decir, hacia el óptimo), por lo que será un poco más preciso usar el
gradiente medido un poco más lejos en esa dirección en lugar de usar el gradiente. en la
posición original, como se puede ver enFigura 11-6(dónde∇1representa el gradiente de la
función de costo medido en el punto de partidaθ, y∇2representa el

13 “Un método para el problema de minimización convexa sin restricciones con la tasa de convergencia O(1/k2)”, Yuri
Nésterov (1983).

346 | Capítulo 11: Entrenamiento de redes neuronales profundas


pendiente en el punto situado enθ+βmetro). Como puede ver, la actualización de Nesterov
termina un poco más cerca del óptimo. Después de un tiempo, estas pequeñas mejoras se
suman y NAG termina siendo significativamente más rápido que la optimización Momentum
regular. Además, tenga en cuenta que cuando el impulso empuja los pesos a través de un valle,
∇1continúa empujando más a través del valle, mientras∇2empuja hacia el fondo del valle. Esto
ayuda a reducir las oscilaciones y, por lo tanto, converge más rápido.

NAG casi siempre acelerará el entrenamiento en comparación con la optimización de Momentum


regular. Para usarlo, simplemente configurenesterov=Verdaderoal crear elUSDoptimizador:

optimizador=queras.optimizadores.USD(yo=0.001,impulso=0.9,nésterov=Verdadero)

Figura 11-6. Optimización regular frente a Nesterov Momentum

adagrad
Considere nuevamente el problema del cuenco alargado: el descenso de gradiente comienza descendiendo
rápidamente por la pendiente más empinada, luego desciende lentamente por el fondo del valle. Sería bueno
si el algoritmo pudiera detectar esto desde el principio y corregir su dirección para apuntar un poco más
hacia el óptimo global.

losadagradalgoritmo14logra esto reduciendo el vector de gradiente a lo largo de las


dimensiones más empinadas (verEcuación 11-6):

Ecuación 11-6. Algoritmo de AdaGrad

1. s s+∇θjθ⊗∇θjθ
2. θ θ−η∇θjθ⊘s+�

14 “Métodos adaptativos de subgradiente para el aprendizaje en línea y la optimización estocástica”, J. Duchi et al. (2011).

Optimizadores más rápidos | 347


El primer paso acumula el cuadrado de los gradientes en el vectors(recuerda que el
⊗ símbolo representa la multiplicación por elementos). Esta forma vectorizada es
equivalente a calcularsi←si+ (∂j(θ) / ∂θi)2para cada elementosidel vectors; en otras palabras,
cadasiacumula los cuadrados de la derivada parcial de la función de costo con respecto al
parámetroθi. Si la función de costo es empinada a lo largo de la ieldimensión, entonces sise
hará más y más grande en cada iteración.

El segundo paso es casi idéntico a Gradient Descent, pero con una gran diferencia: el
vector de gradiente se reduce en un factor de� + � (la⊘El símbolo representa la división
por elementos, y ϵ es un término de suavizado para evitar la división por cero,
normalmente establecido en 10–10). Esta forma vectorizada es equivalente a calcular
θi θi−η∂jθ/∂θi/si+�para todos los parámetrosθi(simultaneamente).

En resumen, este algoritmo decae la tasa de aprendizaje, pero lo hace más rápido para dimensiones
empinadas que para dimensiones con pendientes más suaves. Esto se llama untasa de aprendizaje
adaptativo. Ayuda a apuntar las actualizaciones resultantes más directamente hacia el óptimo global
(ver Figura 11-7). Un beneficio adicional es que requiere mucho menos ajuste del hiperparámetro de
tasa de aprendizaje.η.

Figura 11-7. AdaGrad versus descenso de gradiente

AdaGrad a menudo funciona bien para problemas cuadráticos simples, pero desafortunadamente, a
menudo se detiene demasiado pronto cuando se entrenan redes neuronales. La tasa de aprendizaje
se reduce tanto que el algoritmo termina deteniéndose por completo antes de alcanzar el óptimo
global. Entonces, aunque Keras tiene unAdagradoOptimizer, no debe usarlo para entrenar redes
neuronales profundas (aunque puede ser eficiente para tareas más simples como la regresión lineal).
Sin embargo, comprender Adagrad es útil para comprender los otros optimizadores de la tasa de
aprendizaje adaptativo.

348 | Capítulo 11: Entrenamiento de redes neuronales profundas


RMSProp
Aunque AdaGrad se ralentiza un poco demasiado rápido y termina sin converger nunca al
óptimo global, elRMSPropalgoritmo15corrige esto acumulando solo los gradientes de las
iteraciones más recientes (a diferencia de todos los gradientes desde el comienzo del
entrenamiento). Lo hace mediante el uso de decaimiento exponencial en el primer paso (ver
Ecuación 11-7).

Ecuación 11-7. Algoritmo RMSProp

1. s βs+1 -β∇θjθ⊗∇θjθ
2. θ θ−η∇θjθ⊘s+�

La tasa de descomposiciónβnormalmente se establece en 0,9. Sí, una vez más es un nuevo hiperparámetro, pero este valor

predeterminado a menudo funciona bien, por lo que es posible que no necesite ajustarlo en absoluto.

Como era de esperar, Keras tiene unRMSPropoptimizador:

optimizador=queras.optimizadores.RMSprop(yo=0.001,ro=0.9)

Excepto en problemas muy simples, este optimizador casi siempre funciona mucho mejor que
AdaGrad. De hecho, fue el algoritmo de optimización preferido de muchos investigadores hasta
que surgió la optimización de Adam.

Optimización de Adam y Nadam


Adán,dieciséisLo que significaestimación adaptativa del momento, combina las ideas de la optimización
Momentum y RMSProp: al igual que la optimización Momentum, realiza un seguimiento de un promedio
exponencialmente decreciente de gradientes pasados, y al igual que RMSProp, realiza un seguimiento de un
promedio exponencialmente decreciente de gradientes cuadrados pasados (verEcuación 11-8).17

15 Este algoritmo fue creado por Geoffrey Hinton y Tijmen Tieleman en 2012 y presentado por Geoffrey
Hinton en su clase de Coursera sobre redes neuronales (diapositivas:https://homl.info/57; video:https://homl.info/58). Curiosamente, dado que los
autores no escribieron un artículo para describirlo, los investigadores a menudo citan la "diapositiva 29 en la lección 6" en sus artículos.

16 “Adam: un método para la optimización estocástica”, D. Kingma, J. Ba (2015).

17 Estas son estimaciones de la media y la varianza (no centrada) de los gradientes. La media a menudo se llama
primer momento, mientras que la varianza a menudo se denominasegundo momento, de ahí el nombre del algoritmo.

Optimizadores más rápidos | 349


Ecuación 11-8. Algoritmo de Adán

1. metro β1metro−1 -β1∇θjθ


2. s β2s+1 -β2∇θjθ⊗∇θjθ

3.
metro
metro

1 -βt 1
s
4. s
1 -β t 2
5. θ θ+ηmetro⊘s+�

• trepresenta el número de iteración (a partir de 1).

Si solo observa los pasos 1, 2 y 5, notará la gran similitud de Adam con la optimización
Momentum y RMSProp. La única diferencia es que el paso 1 calcula un promedio decreciente
exponencialmente en lugar de una suma decreciente exponencialmente, pero en realidad son
equivalentes excepto por un factor constante (el promedio decreciente es solo 1 –β1veces la
suma decreciente). Los pasos 3 y 4 son algo así como un detalle técnico: dado que metroysse
inicializan en 0, estarán sesgados hacia 0 al comienzo del entrenamiento, por lo que estos dos
pasos ayudarán a impulsarmetroysal comienzo del entrenamiento.

El hiperparámetro de decaimiento del impulsoβ1normalmente se inicializa a 0.9, mientras que el


hiperparámetro de decaimiento de escalaβ2a menudo se inicializa a 0.999. Como antes, el término de
suavizadoϵgeneralmente se inicializa a un número pequeño como 10–7. Estos son los valores
predeterminados para elAdánclase (para ser precisos,épsilonpor defecto aNinguna,que le dice a Keras que use
keras.backend.épsilon(),que por defecto es 10–7; puedes cambiarlo usando
keras.backend.set_epsilon()).
optimizador=queras.optimizadores.Adán(yo=0.001,beta_1=0.9,beta_2=0.999)

Dado que Adam es un algoritmo de tasa de aprendizaje adaptativo (como AdaGrad y RMSProp), requiere
menos ajustes del hiperparámetro de tasa de aprendizaje.η. A menudo puede utilizar el valor
predeterminadoη= 0,001, lo que hace que Adam sea aún más fácil de usar que Gradient Descent.

Si comienza a sentirse abrumado por todas estas técnicas diferentes


y se pregunta cómo elegir las adecuadas para su tarea, no se
preocupe: al final de este capítulo se proporcionan algunas pautas
prácticas.

Finalmente, vale la pena mencionar dos variantes de Adam:

350 | Capítulo 11: Entrenamiento de redes neuronales profundas


• Adamax, presentado en el mismo documento que Adam: observe que en el paso 2 de
Ecuación 11-8, Adam acumula los cuadrados de los gradientes ens(con mayor peso para
pesos más recientes). En el paso 5, si ignoramosϵy los pasos 3 y 4 (que son detalles técnicos
de todos modos), Adam simplemente reduce las actualizaciones de parámetros por la raíz
cuadrada des. En resumen, Adam reduce las actualizaciones de parámetros en ℓ2
norma de los gradientes decaídos en el tiempo (recuerde que el ℓ2norma es la raíz cuadrada de la
suma de los cuadrados). Adamax simplemente reemplaza el ℓ2norma con el ℓ∞norma (una forma
elegante de decir el máximo). Específicamente, reemplaza el paso 2 enEcuación 11-8con �
máximoβ2�, ∇θJ θ,baja el paso 4, y en el paso 5 reduce el gradiente
actualizaciones por un factor des, que es solo el máximo de los gradientes decaídos en el tiempo.
En la práctica, esto puede hacer que Adamax sea más estable que Adam, pero esto realmente
depende del conjunto de datos y, en general, Adam se desempeña mejor. Por lo tanto, es solo un
optimizador más que puede probar si experimenta problemas con Adam en alguna tarea.

• optimización de nadam18es más importante: es simplemente la optimización de Adam más


el truco de Nesterov, por lo que a menudo convergerá un poco más rápido que Adam. En
su informe, Timothy Dozat compara muchos optimizadores diferentes en varias tareas y
encuentra que Nadam generalmente supera a Adam, pero a veces es superado por
RMSProp.

Los métodos de optimización adaptativa (incluidas la optimización de RMSProp,


Adam y Nadam) suelen ser excelentes y convergen rápidamente en una buena
solución. Sin embargo, unpapel de 201719por Ashia C. Wilson et al. mostró que
pueden conducir a soluciones que se generalizan mal en algunos conjuntos de datos.
Entonces, cuando esté decepcionado con el rendimiento de su modelo, intente usar
el degradado acelerado de Nesterov simple en su lugar: su conjunto de datos puede
ser alérgico a los degradados adaptables. Consulte también las últimas
investigaciones, se está moviendo rápidamente (por ejemplo, AdaBound).

Todas las técnicas de optimización discutidas hasta ahora solo se basan en laderivadas parciales de
primer orden(Jacobianos). La literatura de optimización contiene sorprendentes algoritmos basados
en laderivadas parciales de segundo orden(laarpilleras, que son las derivadas parciales de los
jacobianos). Desafortunadamente, estos algoritmos son muy difíciles de aplicar a las redes neuronales
profundas porque haynorte2Arpilleras por salida (dondenortees el número de parámetros), en lugar
de simplementenorteJacobianos por salida. Dado que las DNN suelen tener decenas de miles de
parámetros, los algoritmos de optimización de segundo orden

18 “Incorporación de Nesterov Momentum en Adam”, Timothy Dozat (2015).

19 “El valor marginal de los métodos de gradiente adaptativo en el aprendizaje automático”, AC Wilson et al. (2017).

Optimizadores más rápidos | 351


a menudo ni siquiera caben en la memoria, e incluso cuando lo hacen, calcular las hessianas es
demasiado lento.

Entrenamiento de modelos dispersos

Todos los algoritmos de optimización que acabamos de presentar producen modelos densos, lo que significa
que la mayoría de los parámetros serán distintos de cero. Si necesita un modelo ultrarrápido en tiempo de
ejecución, o si necesita que ocupe menos memoria, es posible que prefiera terminar con un modelo disperso.

Una forma trivial de lograr esto es entrenar al modelo como de costumbre, luego deshacerse de los
pequeños pesos (establecerlos en 0). Sin embargo, esto normalmente no conducirá a un modelo muy
disperso y puede degradar el rendimiento del modelo.

Una mejor opción es aplicar fuerte ℓ1regularización durante el entrenamiento, ya que empuja al
optimizador a cero tantos pesos como sea posible (como se discutió enCapítulo 4sobre la regresión de
Lasso).

Sin embargo, en algunos casos estas técnicas pueden resultar insuficientes. Una última opción es
aplicarDoble promedio, llamado a menudoSiga al líder regularizado(FTRL), untécnica propuesta
por Yurii Nesterov.20Cuando se usa con ℓ1regularización, esta técnica a menudo conduce a
modelos muy dispersos. Keras implementa una variante de FTRL llamadaFTRL-Proximal21en el
FTRLoptimizador

Programación de la tasa de aprendizaje

Encontrar una buena tasa de aprendizaje puede ser complicado. Si lo establece demasiado alto, el
entrenamiento en realidad puede divergir (como discutimos enCapítulo 4). Si lo configura demasiado bajo, el
entrenamiento eventualmente convergerá al óptimo, pero llevará mucho tiempo. Si lo configura un poco
demasiado alto, progresará muy rápidamente al principio, pero terminará bailando alrededor del nivel
óptimo, sin llegar a estabilizarse realmente. Si tiene un presupuesto informático limitado, es posible que deba
interrumpir la capacitación antes de que haya convergido correctamente, lo que generará una solución
subóptima (consulteFigura 11-8).

20 “Métodos de subgradiente primal-dual para problemas convexos”, Yurii Nesterov (2005). 21

“Predicción de clics en anuncios: una vista desde las trincheras”, H. McMahan et al. (2013).

352 | Capítulo 11: Entrenamiento de redes neuronales profundas


Figura 11-8. Curvas de aprendizaje para varias tasas de aprendizaje η

Como discutimos enCapítulo 10, un enfoque es comenzar con una gran tasa de aprendizaje y dividirla
por 3 hasta que el algoritmo de entrenamiento deje de divergir. No estará demasiado lejos de la tasa
de aprendizaje óptima, que aprenderá rápidamente y convergerá en una buena solución.

Sin embargo, puede hacerlo mejor que una tasa de aprendizaje constante: si comienza con una tasa
de aprendizaje alta y luego la reduce una vez que deja de progresar rápidamente, puede llegar a una
buena solución más rápido que con la tasa de aprendizaje constante óptima. Hay muchas estrategias
diferentes para reducir la tasa de aprendizaje durante el entrenamiento. Estas estrategias se llaman
horarios de aprendizaje(presentamos brevemente este concepto enCapítulo 4), los más comunes de
los cuales son:

Programación de energía

Establecer la tasa de aprendizaje en función del número de iteraciónt:η(t) =η0/ (1 +t/k)C. La tasa de
aprendizaje inicialη0, el poderC(normalmente establecido en 1) y los pasossson hiperparámetros.
La tasa de aprendizaje cae en cada paso, y despuésspasos a los que se reduceη0/ 2. Despuéssmás
pasos, se reduce aη0/ 3. Luego hacia abajo hastaη0/ 4, entoncesη0/ 5, y así sucesivamente. Como
puede ver, este horario primero desciende rápidamente, luego cada vez más lentamente. Por
supuesto, esto requiere ajusteη0,s(y posiblementeC).

Programación exponencial
Establezca la tasa de aprendizaje en:η(t) =η00.1t/s. La tasa de aprendizaje se reducirá gradualmente en un
factor de 10 cadaspasos. Mientras que la programación de energía reduce la tasa de aprendizaje cada
vez más lentamente, la programación exponencial sigue reduciéndola en un factor de10cada spasos.

Programación constante por partes


Use una tasa de aprendizaje constante para un número de épocas (p. ej.,η0= 0,1 para 5 épocas),
luego una tasa de aprendizaje más pequeña para otro número de épocas (p. ej.,η1= 0,001 para 50
épocas), y así sucesivamente. Aunque esta solución puede funcionar muy bien, requiere fid‐

Optimizadores más rápidos | 353


dando vueltas para descubrir la secuencia correcta de tasas de aprendizaje y cuánto tiempo usar
cada una de ellas.

Programación de rendimiento
Medir el error de validación cadanortepasos (al igual que para la parada temprana) y
reducir la tasa de aprendizaje por un factor deλcuando el error deja de caer.

Apapel de 201322por Andrew Senior et al. comparó el rendimiento de algunos de los programas de
aprendizaje más populares al entrenar redes neuronales profundas para el reconocimiento de voz
utilizando la optimización de Momentum. Los autores concluyeron que, en este entorno, tanto la
programación por desempeño como la programación exponencial funcionaron bien. Favorecieron la
programación exponencial porque era fácil de ajustar y convergía un poco más rápido a la solución
óptima (también mencionaron que era más fácil de implementar que la programación por
desempeño, pero en Keras ambas opciones son fáciles).

Implementar la programación de energía en Keras es la opción más fácil: simplemente configure eldecadencia

hiperparámetro al crear un optimizador. losdecadenciaes el inverso des(el número de


pasos necesarios para dividir la tasa de aprendizaje por una unidad más), y Keras asume
queCes igual a 1. Por ejemplo:

optimizador=queras.optimizadores.USD(yo=0.01,decadencia=1e-4)

La programación exponencial y la programación por partes también son bastante simples. Primero
debe definir una función que tome la época actual y devuelva la tasa de aprendizaje. Por ejemplo,
implementemos la programación exponencial:

definitivamenteexponential_decay_fn(época):
devolver0.01*0.1**(época/20)

Si no desea codificarη0ys, puede crear una función que devuelva una función
configurada:
definitivamenteDecrecimiento exponencial(lr0,s):

definitivamenteexponential_decay_fn(época):
devolverlr0*0.1**(época/s)
devolverexponential_decay_fn

exponential_decay_fn=Decrecimiento exponencial(lr0=0.01,s=20)

A continuación, simplemente cree unLearningRateSchedulerdevolución de llamada, dándole la función de

programación, y pase esta devolución de llamada aladaptar()método:

lr_scheduler=queras.devoluciones de llamada.LearningRateScheduler(exponential_decay_fn)
historia=modelo.adaptar(X_train_scaled,y_tren, [...],devoluciones de llamada=[lr_scheduler])

22 “Un estudio empírico de las tasas de aprendizaje en redes neuronales profundas para el reconocimiento de voz”, A. Senior et al.
(2013).

354 | Capítulo 11: Entrenamiento de redes neuronales profundas


losLearningRateScheduleractualizará el optimizadortasa de aprendizajeatributo al comienzo de
cada época. Actualizar la tasa de aprendizaje solo una vez por época suele ser suficiente, pero si
desea que se actualice con más frecuencia, por ejemplo, en cada paso, debe escribir su propia
devolución de llamada (consulte el cuaderno para ver un ejemplo). Esto puede tener sentido si
hay muchos pasos por época.

La función de programación puede tomar opcionalmente la tasa de aprendizaje actual como un segundo
argumento. Por ejemplo, la siguiente función de programación simplemente multiplica la tasa de aprendizaje
anterior por 0,1&1/20, lo que da como resultado el mismo decaimiento exponencial (excepto que el
decaimiento ahora comienza al comienzo de la época 0 en lugar de 1). Esta implementación se basa en la tasa
de aprendizaje inicial del optimizador (al contrario de la implementación anterior), así que asegúrese de
configurarla correctamente.

definitivamenteexponential_decay_fn(época,yo):
devolveryo*0.1**(1/20)

Cuando guarda un modelo, el optimizador y su tasa de aprendizaje se guardan junto con él. Esto
significa que con esta nueva función de programación, puede simplemente cargar un modelo
entrenado y continuar entrenando donde lo dejó, sin problema. Sin embargo, las cosas no son tan
simples si su función de programación utiliza elépocaargumento: de hecho, la época no se guarda y se
restablece a 0 cada vez que llama aladaptar()método. Esto podría conducir a una tasa de aprendizaje
muy grande cuando continúa entrenando a un modelo donde lo dejó, lo que probablemente dañaría
los pesos de su modelo. Una solución es configurar manualmente eladaptar()
métodosépoca_inicialargumento por lo que elépocacomienza en el valor correcto.

Para la programación constante por partes, puede usar una función de programación como la
siguiente (como antes, puede definir una función más general si lo desea, vea el cuaderno para ver un
ejemplo), luego cree unaLearningRateSchedulerdevolución de llamada con esta función y pásela al
adaptar()método, tal como lo hicimos para la programación exponencial:

definitivamentepor partes_constante_fn(época):
siépoca<5:
devolver0.01
elifépoca<15:
devolver0.005
más:
devolver0.001

Para programar el rendimiento, simplemente use elReducirLROnPlateaullamar de vuelta. Por


ejemplo, si pasa la siguiente devolución de llamada aladaptar()método, multiplicará la tasa de
aprendizaje por 0,5 siempre que la mejor pérdida de validación no mejore durante 5 épocas
consecutivas (hay otras opciones disponibles, consulte la documentación para obtener más
detalles):

lr_scheduler=queras.devoluciones de llamada.ReducirLROnPlateau(factor=0.5,paciencia=5)

Por último, tf.keras ofrece una forma alternativa de implementar la programación de la tasa de aprendizaje:
simplemente defina la tasa de aprendizaje utilizando uno de los horarios disponibles enkeras.optimiz

Optimizadores más rápidos | 355


ers.horarios,luego pase esta tasa de aprendizaje a cualquier optimizador. Este enfoque actualiza la
tasa de aprendizaje en cada paso en lugar de en cada época. Por ejemplo, aquí está cómo
implementar el mismo programa exponencial que antes:

s=20*Len(X_tren)//32#número de pasos en 20 épocas (tamaño de lote = 32) tasa de


aprendizaje=queras.optimizadores.horarios.Decrecimiento exponencial(0.01,s,0.1)
optimizador=queras.optimizadores.USD(tasa de aprendizaje)

Esto es agradable y simple, además, cuando guarda el modelo, la tasa de aprendizaje y su


programación (incluido su estado) también se guardan. Sin embargo, este enfoque no forma parte de
la API de Keras, es específico de tf.keras.

En resumen, el decaimiento exponencial o la programación del rendimiento pueden acelerar considerablemente la


convergencia, ¡así que pruébelos!

Evitar el sobreajuste mediante la regularización


Con cuatro parámetros puedo encajar un elefante y con cinco puedo hacer que mueva la trompa.

— John von Neumann,citado por Enrico Fermi en Nature 427

Con miles de parámetros puedes ajustar todo el zoológico. Las redes neuronales profundas suelen
tener decenas de miles de parámetros, a veces incluso millones. Con tantos parámetros, la red tiene
una increíble cantidad de libertad y puede adaptarse a una gran variedad de conjuntos de datos
complejos. Pero esta gran flexibilidad también significa que es propenso a sobreajustar el conjunto de
entrenamiento. Necesitamos la regularización.

Ya implementamos una de las mejores técnicas de regularización enCapítulo 10: parada


temprana. Además, aunque Batch Normalization fue diseñado para resolver los problemas de
gradientes que desaparecen o explotan, también actúa como un buen regularizador. En esta
sección presentaremos otras técnicas populares de regularización para redes neuronales: ℓ1y ℓ2
regularización, abandono y regularización max-norma.

ℓ 1y ℓ Regularización
2

Al igual que lo hiciste enCapítulo 4para modelos lineales simples, puede usar ℓ1y ℓ2regularización para
restringir los pesos de conexión de una red neuronal (pero normalmente no sus sesgos). Aquí está
cómo aplicar ℓ2regularización a los pesos de conexión de una capa de Keras, usando un factor de
regularización de 0.01:

capa=queras.capas.Denso(100,activación="elú",
kernel_initializer="él_normal", kernel_regularizer=
queras.regularizadores.l2(0.01))

losl2()La función devuelve un regularizador que será llamado para calcular la pérdida de
regularización, en cada paso durante el entrenamiento. Esta pérdida de regularización se suma
luego a la pérdida final. Como era de esperar, solo puede usarkeras.regularizadores.l1()si tu

356 | Capítulo 11: Entrenamiento de redes neuronales profundas


quiero ℓ1regularización, y si quieres ambas ℓ1y ℓ2regularización, usokeras.regu
larizers.l1_l2() (especificando ambos factores de regularización).

Como normalmente querrá aplicar el mismo regularizador a todas las capas de su red, así como
la misma función de activación y la misma estrategia de inicialización en todas las capas ocultas,
es posible que se encuentre repitiendo los mismos argumentos una y otra vez. Esto lo hace feo
y propenso a errores. Para evitar esto, puede intentar refactorizar su código para usar bucles.
Otra opción es usar Pythonfunctools.parcial()función: le permite crear un contenedor delgado
para cualquier invocable, con algunos valores de argumento predeterminados. Por ejemplo:

deherramientas funcionalesimportarparcial

RegularizadoDenso=parcial(queras.capas.Denso,
activación="elú", kernel_initializer="él_normal",
kernel_regularizer=queras.regularizadores.l2(0.01))

modelo=queras.modelos.Secuencial([
queras.capas.Aplanar(entrada_forma=[28,28]),
RegularizadoDenso(300), RegularizadoDenso(
100), RegularizadoDenso(10,activación=
"softmax",
kernel_initializer="glorot_uniforme")
])

Abandonar

Abandonares una de las técnicas de regularización más populares para redes neuronales
profundas. Fuepropuesto23por Geoffrey Hinton en 2012 y más detallado en unpapel24
por Nitish Srivastava et al., y ha demostrado ser un gran éxito: incluso las redes neuronales de última
generación obtuvieron un aumento de precisión del 1% al 2% simplemente agregando abandono. Puede que
esto no parezca mucho, pero cuando un modelo ya tiene una precisión del 95 %, obtener un aumento de
precisión del 2 % significa reducir la tasa de error en casi un 40 % (pasar del 5 % de error a aproximadamente
el 3 %).

Es un algoritmo bastante simple: en cada paso de entrenamiento, cada neurona (incluidas las de
entrada, pero siempre excluyendo las de salida) tiene una probabilidadpagsde ser "abandonado"
temporalmente, lo que significa que se ignorará por completo durante este paso de entrenamiento,
pero puede estar activo durante el siguiente paso (verFigura 11-9). El hiperparámetro pagsse llama el
tasa de deserción escolar, y normalmente se establece en 50%. Después del entrenamiento, las
neuronas ya no se caen. Y eso es todo (salvo un detalle técnico que comentaremos
momentáneamente).

23 “Mejora de las redes neuronales mediante la prevención de la coadaptación de los detectores de características”, G. Hinton et al.

(2012). 24 “Deserción: una forma sencilla de evitar el sobreajuste de las redes neuronales”, N. Srivastava et al. (2014).

Evitar el sobreajuste a través de la regularización | 357


Figura 11-9. Regularización de la deserción

Es bastante sorprendente al principio que esta técnica bastante brutal funcione. ¿Se
desempeñaría mejor una empresa si a sus empleados se les dijera que lanzaran una moneda
todas las mañanas para decidir si van o no a trabajar? Bueno, quién sabe; ¡tal vez lo haría! La
empresa obviamente se vería obligada a adaptar su organización; no podía depender de una
sola persona para llenar la máquina de café o realizar otras tareas críticas, por lo que esta
experiencia tendría que distribuirse entre varias personas. Los empleados tendrían que
aprender a cooperar con muchos de sus compañeros de trabajo, no solo con un puñado de
ellos. La empresa se volvería mucho más resistente. Si una persona renuncia, no haría mucha
diferencia. No está claro si esta idea realmente funcionaría para las empresas, pero ciertamente
lo hace para las redes neuronales. Las neuronas entrenadas con abandono no pueden
coadaptarse con sus neuronas vecinas; tienen que ser lo más útiles posible por sí mismos.
Tampoco pueden depender excesivamente de unas pocas neuronas de entrada; deben prestar
atención a cada una de sus neuronas de entrada. Terminan siendo menos sensibles a ligeros
cambios en las entradas. Al final, obtienes una red más robusta que generaliza mejor.

Otra forma de comprender el poder de la deserción es darse cuenta de que se genera una red
neuronal única en cada paso de entrenamiento. Como cada neurona puede estar presente o ausente,
hay un total de 2norteposibles redes (dondenortees el número total de neuronas descartables). Este es
un número tan grande que es prácticamente imposible que la misma red neuronal se muestree dos
veces. Una vez que haya ejecutado 10 000 pasos de entrenamiento, esencialmente habrá entrenado
10 000 redes neuronales diferentes (cada una con solo una instancia de entrenamiento). Estas redes
neuronales obviamente no son independientes ya que comparten muchos de sus pesos, pero sin
embargo son todas diferentes. La red neuronal resultante puede verse como un conjunto promedio
de todas estas redes neuronales más pequeñas.

Hay un pequeño pero importante detalle técnico. Suponerpags=50%, en cuyo caso, durante la
prueba, una neurona se conectará al doble de neuronas de entrada que (en promedio) durante
el entrenamiento. Para compensar este hecho, necesitamos multiplicar cada

358 | Capítulo 11: Entrenamiento de redes neuronales profundas


La conexión de entrada de la neurona pesa 0,5 después del entrenamiento. Si no lo hacemos, cada neurona
obtendrá una señal de entrada total aproximadamente el doble de la que se entrenó la red, y es poco
probable que funcione bien. Más generalmente, necesitamos multiplicar cada peso de conexión de entrada
por elmantener la probabilidad(1 -pags) después de entrenar. Alternativamente, podemos dividir la salida de
cada neurona por la probabilidad de mantenimiento durante el entrenamiento (estas alternativas no son
perfectamente equivalentes, pero funcionan igual de bien).

Para implementar el abandono usando Keras, puede usar elkeras.layers.Dropoutcapa. Durante el


entrenamiento, elimina aleatoriamente algunas entradas (configurándolas en 0) y divide las entradas
restantes por la probabilidad de mantenimiento. Después del entrenamiento, no hace nada en absoluto, solo
pasa las entradas a la siguiente capa. Por ejemplo, el código siguiente aplica la regularización de la deserción
antes de cadaDensocapa, utilizando una tasa de abandono de 0,2:

modelo=queras.modelos.Secuencial([
queras.capas.Aplanar(entrada_forma=[28,28]),
queras.capas.Abandonar(Velocidad=0.2),
queras.capas.Denso(300,activación="elú",kernel_initializer="él_normal"), queras.
capas.Abandonar(Velocidad=0.2),
queras.capas.Denso(100,activación="elú",kernel_initializer="él_normal"), queras.
capas.Abandonar(Velocidad=0.2),
queras.capas.Denso(10,activación="softmax")
])

Dado que el abandono solo está activo durante el entrenamiento, la pérdida de entrenamiento se

penaliza en comparación con la pérdida de validación, por lo que comparar los dos puede ser

engañoso. En particular, un modelo puede sobreajustar el conjunto de entrenamiento y, sin

embargo, tener pérdidas de entrenamiento y validación similares. Así que asegúrese de evaluar la

pérdida de entrenamiento sin abandono (por ejemplo, después del entrenamiento).

Alternativamente, puede llamar aladaptar()método dentro de un

con keras.backend.learning_phase_scope(1)bloque: esto será


forzar el abandono para que esté activo durante el entrenamiento y la validación.25

Si observa que el modelo se está sobreajustando, puede aumentar la tasa de abandono. Por el contrario,
debe intentar disminuir la tasa de abandono si el modelo no se ajusta al conjunto de entrenamiento. También
puede ayudar a aumentar la tasa de abandono para las capas grandes y reducirla para las pequeñas.
Además, muchas arquitecturas de última generación solo usan el abandono después de la última capa oculta,
por lo que es posible que desee probar esto si el abandono total es demasiado fuerte.

La deserción tiende a ralentizar significativamente la convergencia, pero generalmente da como resultado un modelo
mucho mejor cuando se ajusta correctamente. Por lo tanto, generalmente vale la pena el tiempo y el esfuerzo extra.

25 Esto es específico de tf.keras, por lo que es posible que prefiera utilizarkeras.backend.set_learning_phase(1)antes de llamar
laadaptar()método (y configúrelo de nuevo en 0 justo después).

Evitar el sobreajuste a través de la regularización | 359


Si desea regularizar una red de autonormalización basada en la función de
activación SELU (como se discutió anteriormente), debe usar Deserción alfa:esta
es una variante de la deserción que conserva la media y la desviación estándar
de sus entradas (se introdujo en el mismo documento que SELU, ya que la
deserción regular rompería la autonormalización).

Abandono de Montecarlo (MC)

En 2016, unpapel26por Yarin Gal y Zoubin Ghahramani agregaron más buenas razones para usar
abandono:

• Primero, el documento establece una conexión profunda entre las redes de abandono (es decir,
las redes neuronales que contienen una capa de abandono antes de cada capa de ponderación) y
la inferencia bayesiana aproximada.27, dando a la deserción una sólida justificación matemática.

• En segundo lugar, introducen una poderosa técnica llamadaAbandono de MC, que puede aumentar el
rendimiento de cualquier modelo de abandono entrenado, sin tener que volver a entrenarlo ni
modificarlo en absoluto.

• Además, MC Dropout también proporciona una medida mucho mejor de la incertidumbre


del modelo.

• Finalmente, también es sorprendentemente simple de implementar. Si todo esto suena como un


anuncio de "un truco extraño", eche un vistazo al siguiente código. Es la implementación
completa deAbandono de MC, impulsando el modelo de abandono que entrenamos
anteriormente, sin volver a entrenarlo:

conqueras.back-end.alcance_de_la_fase_de_aprendizaje(1):#modo de entrenamiento de fuerza = abandono activado


y_probas=notario público.pila([modelo.predecir(X_test_scaled)
pormuestraenrango(100)])
y_proba=y_probas.significar(eje=0)

Primero forzamos el modo de entrenamiento, usando unaprendizaje_fase_alcance(1)contexto. Esto


activa la deserción dentro delconbloquear. Luego hacemos 100 predicciones sobre el conjunto de
prueba y las apilamos. Dado que el abandono está activado, todas las predicciones serán diferentes.
Recordar quepredecir()devuelve una matriz con una fila por instancia y una columna por clase. Como
hay 10 000 instancias en el conjunto de prueba y 10 clases, esta es una matriz de forma [10000, 10].
Apilamos 100 de tales matrices, entoncesy_probases una matriz de forma [100, 10000, 10]. Una vez que
promediamos sobre la primera dimensión (eje=0),obtenemosy_proba,una matriz de forma [10000, 10],
como la que obtendríamos con una sola predicción. ¡Eso es todo! promedio

26 "Abandono como aproximación bayesiana: representación de la incertidumbre del modelo en el aprendizaje profundo", Y. Gal y Z.
Ghahramani (2016).

27 Específicamente, muestran que entrenar una red de deserción es matemáticamente equivalente a aproximar Bayesian
inferencia en un tipo específico de modelo probabilístico llamadoProceso gaussiano profundo.

360 | Capítulo 11: Entrenamiento de redes neuronales profundas


sobre múltiples predicciones con la deserción activada nos da una estimación de Monte Carlo que
generalmente es más confiable que el resultado de una sola predicción con la deserción desactivada.
Por ejemplo, veamos la predicción del modelo para la primera instancia en el conjunto de prueba, con
abandono:

> > > notario público.redondo(modelo.predecir(X_test_scaled[:1]),2)


matriz ([[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.01, 0. , 0,99]],
dtipo=float32)

El modelo parece casi seguro de que esta imagen pertenece a la clase 9 (botín). ¿Deberías
confiar en él? ¿Hay realmente tan poco lugar para la duda? Compare esto con las predicciones
realizadas cuando se activa el abandono:

> > > notario público.redondo(y_probas[:, :1],


2) matriz ([[[0. , 0. , 0. , 0. , 0. , 0.14, 0. , 0.17, 0. , 0,68]],
[[0. , 0. , 0. , 0. , 0. , 0.16, 0. , 0.2 , 0. , 0,64]],
[[0. , 0. , 0. , 0. , 0. , 0.02, 0. , 0.01, 0. , 0,97]],
[...]

Esto cuenta una historia muy diferente: aparentemente, cuando activamos el abandono, el modelo ya
no está seguro. Todavía parece preferir la clase 9, pero a veces duda con las clases 5 (sandalias) y 7
(zapatillas), lo cual tiene sentido dado que son todo calzado. Una vez que promediamos sobre la
primera dimensión, obtenemos las siguientes predicciones de abandono de MC:

> > > notario público.redondo(


y_proba[:1],2) matriz ([[0. , 0. , 0. , 0. , 0. , 0.22, 0. , 0.16, 0. , 0,62]],
dtipo=float32)

El modelo todavía cree que esta imagen pertenece a la clase 9, pero solo con un 62 % de confianza, lo
que parece mucho más razonable que el 99 %. Además, es útil saber exactamente qué otras clases
cree que son probables. Y también puedes echar un vistazo a ladesviación estándar de las
estimaciones de probabilidad:

> > > y_std=y_probas.estándar(eje=0)


> > > notario público.redondo(y_std[:1],
2) matriz ([[0. , 0. , 0. , 0. , 0. , 0.28, 0. , 0.21, 0.02, 0.32]],
dtipo=float32)

Aparentemente, hay mucha variación en las estimaciones de probabilidad: si estuviera construyendo


un sistema sensible al riesgo (por ejemplo, un sistema médico o financiero), probablemente debería
tratar una predicción tan incierta con extrema precaución. Definitivamente no lo trataría como una
predicción con un 99 % de confianza. Además, la precisión del modelo recibió un pequeño impulso de
86,8 a 86,9:

> > > precisión=notario público.suma(y_pred==y_prueba)/Len(y_prueba)


> > > precisión
0.8694

Evitar el sobreajuste a través de la regularización | 361


La cantidad de muestras de Monte Carlo que usa (100 en este ejemplo) es
un hiperparámetro que puede modificar. Cuanto mayor sea, más precisas
serán las predicciones y sus estimaciones de incertidumbre. Sin embargo,
si lo duplica, el tiempo de inferencia también se duplicará. Además, por
encima de un cierto número de muestras, notará poca mejora. Por lo
tanto, su trabajo es encontrar el compromiso adecuado entre latencia y
precisión, según su aplicación.

Si su modelo contiene otras capas que se comportan de una manera especial durante el entrenamiento
(como las capas de normalización por lotes), entonces no debe forzar el modo de entrenamiento como
acabamos de hacer. En su lugar, debe reemplazar elAbandonarcapas con lo siguienteMCAbandono
clase:

claseMCAbandono(queras.capas.Abandonar):
definitivamentellamar(uno mismo,entradas):

devolversúper().llamar(entradas,capacitación=Verdadero)

Simplemente subclasificamos elAbandonarcapa y anular lallamar()método para forzar su


capacitaciónargumento aVerdadero (verCapítulo 12). Del mismo modo, podría definir un
MCAlphaAbandonarclase por subclasificaciónDeserción alfaen cambio. Si está creando un modelo
desde cero, solo es cuestión de usarMCAbandonomás bien queAbandonar.Pero si tiene un
modelo que ya fue entrenado usandoAbandonar,necesita crear un nuevo modelo, idéntico al
modelo existente, excepto que reemplaza elAbandonarcapas conMCAbandono,luego copie los
pesos del modelo existente a su nuevo modelo.

En resumen, MC Dropout es una técnica fantástica que potencia los modelos de abandono y proporciona
mejores estimaciones de incertidumbre. Y por supuesto, dado que es solo un abandono regular durante el
entrenamiento, también actúa como un regularizador.

Regularización Max-Norm
Otra técnica de regularización que es bastante popular para las redes neuronales se
llama regularización de norma máxima: para cada neurona, restringe los pesoswde
las conexiones entrantes tal que∥ *w*∥2≤ _r_, donderes el hiperparámetro max-norm
y∥·∥2es el ℓ2norma.

La regularización de norma máxima no agrega un término de pérdida de regularización a la función de


pérdida general. En su lugar, normalmente se implementa computando∥w∥2después de cada entrenamiento

paso y recortewsi es necesario (wowr ).


∥w∥2

Reduciendoraumenta la cantidad de regularización y ayuda a reducir el sobreajuste. La regularización


Maxnorm también puede ayudar a aliviar los problemas de gradientes que desaparecen o explotan (si
no está utilizando la normalización por lotes).

362 | Capítulo 11: Entrenamiento de redes neuronales profundas


Para implementar la regularización de norma máxima en Keras, simplemente configure cada capa
ocultaker nel_constraintargumento a unnorma_máxima()restricción, con el valor máximo apropiado,
por ejemplo:

queras.capas.Denso(100,activación="elú",kernel_initializer="él_normal",
kernel_constraint=queras.restricciones.max_norm(1.))

Después de cada iteración de entrenamiento, el modeloadaptar()llamará al objeto devuelto por


norma_máxima(),pasándole los pesos de la capa y obteniendo pesos recortados a cambio, que
luego reemplazan los pesos de la capa. Como veremos enCapítulo 12, puede definir su propia
función de restricción personalizada si alguna vez lo necesita, y usarla como elker nel_constraint.
También puede restringir los términos de sesgo configurando eltensión bias_conargumento.

losnorma_máxima()función tiene unejeargumento que por defecto es 0. ADensocapa por lo general


tiene pesos de forma [número de entradas, número de neuronas], por lo que el uso deeje=0
significa que la restricción de norma máxima se aplicará de forma independiente al vector de peso de
cada neurona. Si desea utilizar max-norm con capas convolucionales (vercapitulo 14), asegúrese de
establecer elnorma_máxima()restriccionesejeargumento apropiadamente (usualmente
eje=[0, 1, 2]).

Resumen y Directrices Prácticas


En este capítulo, hemos cubierto una amplia gama de técnicas y es posible que se
pregunte cuáles debe usar. La configuración enTabla 11-2funcionará bien en la mayoría
de los casos, sin requerir muchos ajustes de hiperparámetros.

Tabla 11-2. Configuración DNN predeterminada

hiperparámetro Valor por defecto

Inicializador del núcleo: Inicialización de LeCun

Función de activación: SELU


Normalización: Ninguno (autonormalización)

Regularización: Parada temprana

Optimizador: Nadam

Programa de tasa de aprendizaje: Programación de rendimiento

¡No olvide estandarizar las funciones de entrada! Por supuesto, también debe intentar reutilizar partes de una red
neuronal previamente entrenada si puede encontrar una que resuelva un problema similar, o usar un entrenamiento
previo no supervisado si tiene muchos datos sin etiquetar, o un entrenamiento previo en una tarea auxiliar si tiene
muchos datos. de datos etiquetados para una tarea similar.

La configuración predeterminada enTabla 11-2puede necesitar ser ajustado:

Resumen y Directrices Prácticas | 363


• Si su modelo se autonormaliza:
— Si se ajusta demasiado al conjunto de entrenamiento, entonces debe agregar abandono alfa (y
siempre usar también la detención temprana). No utilice otros métodos de regularización, o de lo
contrario romperían la autonormalización.

• Si su modelo no puede autonormalizarse (por ejemplo, es una red recurrente o contiene


conexiones de salto):

— Puede intentar usar ELU (u otra función de activación) en lugar de SELU, puede
funcionar mejor. Asegúrese de cambiar el método de inicialización en
consecuencia (p. ej., He init para ELU o ReLU).
— Si se trata de una red profunda, debe usar la normalización por lotes después de cada capa oculta. Si
se ajusta demasiado al conjunto de entrenamiento, también puede intentar usar max-norm o ℓ2
regularización.

• Si necesita un modelo disperso, puede usar ℓ1regularización (y opcionalmente poner a cero los
pesos diminutos después del entrenamiento). Si necesita un modelo aún más disperso, puede
intentar usar FTRL en lugar de la optimización de Nadam, junto con ℓ1regularización En cualquier
caso, esto interrumpirá la autonormalización, por lo que deberá cambiar a BN si su modelo es
profundo.

• Si necesita un modelo de baja latencia (uno que realice predicciones ultrarrápidas), es posible que
deba usar menos capas, evitar la normalización por lotes y posiblemente reemplazar la función
de activación de SELU con ReLU con fugas. Tener un modelo disperso también ayudará. También
es posible que desee reducir la precisión flotante de 32 bits a 16 bits (o incluso a 8 bits) (consulte
???).

• Si está creando una aplicación sensible al riesgo o la latencia de inferencia no es muy importante
en su aplicación, puede usar MC Dropout para aumentar el rendimiento y obtener estimaciones
de probabilidad más confiables, junto con estimaciones de incertidumbre.

Con estas pautas, ¡ahora estás listo para entrenar redes muy profundas! Espero que ahora esté
convencido de que puede recorrer un largo camino usando solo Keras. Sin embargo, puede llegar un
momento en el que necesite tener aún más control, por ejemplo, para escribir una función de pérdida
personalizada o modificar el algoritmo de entrenamiento. Para tales casos, deberá usar la API de nivel
inferior de TensorFlow, como veremos en el próximo capítulo.

Ejercicios

1. ¿Está bien inicializar todos los pesos con el mismo valor siempre y cuando ese valor se
seleccione al azar usando la inicialización He?

2. ¿Está bien inicializar los términos de sesgo en 0?

3. Mencione tres ventajas de la función de activación SELU sobre ReLU.

364 | Capítulo 11: Entrenamiento de redes neuronales profundas


4. ¿En qué casos le gustaría usar cada una de las siguientes funciones de activación:
SELU, Leaky ReLU (y sus variantes), ReLU, tanh, logística y softmax?

5. ¿Qué puede pasar si configura elimpulsohiperparámetro demasiado cerca de 1 (por ejemplo,


0.99999) cuando se usa unUSDoptimizador?

6. Nombre tres formas en que puede producir un modelo disperso.

7. ¿La deserción ralentiza el entrenamiento? ¿Ralentiza la inferencia (es decir, hace


predicciones sobre nuevas instancias)? ¿Qué pasa con la deserción de MC?

8. Aprendizaje profundo.

una. Cree una DNN con cinco capas ocultas de 100 neuronas cada una, la inicialización de He y la
función de activación de ELU.

b. Usando la optimización de Adam y la detención temprana, intente entrenarlo en MNIST pero solo en
los dígitos 0 a 4, ya que usaremos el aprendizaje de transferencia para los dígitos 5 a 9 en el
próximo ejercicio. Necesitará una capa de salida softmax con cinco neuronas y, como siempre,
asegúrese de guardar los puntos de control a intervalos regulares y guarde el modelo final para
poder reutilizarlo más tarde.

C. Ajuste los hiperparámetros mediante validación cruzada y vea qué precisión puede
lograr.
d. Ahora intente agregar Normalización por lotes y compare las curvas de aprendizaje: ¿está
convergiendo más rápido que antes? ¿Produce un modelo mejor?

mi. ¿El modelo está sobreajustando el conjunto de entrenamiento? Intente agregar abandono a cada
capa y vuelva a intentarlo. ¿Ayuda?

9. Transferir el aprendizaje.

una. Cree un nuevo DNN que reutilice todas las capas ocultas previamente entrenadas del
modelo anterior, las congele y reemplace la capa de salida softmax por una nueva.

b. Entrene este nuevo DNN en los dígitos 5 a 9, usando solo 100 imágenes por dígito, y mida
cuánto tiempo lleva. A pesar de este pequeño número de ejemplos, ¿puede lograr una alta
precisión?

C. Intente almacenar en caché las capas congeladas y vuelva a entrenar el modelo: ¿cuánto más rápido es
ahora?

d. Vuelva a intentar reutilizar solo cuatro capas ocultas en lugar de cinco. ¿Se puede lograr una
mayor precisión?

mi. Ahora descongele las dos capas superiores ocultas y continúe entrenando: ¿puede lograr que
el modelo funcione aún mejor?

10. Preentrenamiento en una tarea auxiliar.

una. En este ejercicio, construirá un DNN que compare dos imágenes de dígitos
MNIST y prediga si representan el mismo dígito o no. Luego reutilizará las capas
inferiores de esta red para entrenar un clasificador MNIST usando muy poco

Ejercicios | 365
datos de entrenamiento. Comience por construir dos DNN (llamémoslos DNN A y B),
ambos similares al que creó anteriormente pero sin la capa de salida: cada DNN debe
tener cinco capas ocultas de 100 neuronas cada una, inicialización de He y activación de
ELU. A continuación, agregue una capa oculta más con 10 unidades encima de ambos
DNN. Para hacer esto, debe usar unkeras.layers.Concatenarcapa para concatenar las
salidas de ambos DNN para cada instancia, luego alimentar el resultado a la capa
oculta. Finalmente, agregue una capa de salida con una sola neurona utilizando la
función de activación logística.

b. Divida el conjunto de entrenamiento MNIST en dos conjuntos: la división n.º 1 debe contener
55 000 imágenes y la división n.º 2 debe contener 5000 imágenes. Cree una función que
genere un lote de entrenamiento en el que cada instancia sea un par de imágenes MNIST
seleccionadas de la división n.º 1. La mitad de las instancias de entrenamiento deben ser
pares de imágenes que pertenezcan a la misma clase, mientras que la otra mitad deben ser
imágenes de diferentes clases. Para cada par, la etiqueta de entrenamiento debe ser 0 si las
imágenes son de la misma clase o 1 si son de diferentes clases.

C. Entrene el DNN en este conjunto de entrenamiento. Para cada par de imágenes, puede
enviar simultáneamente la primera imagen a DNN A y la segunda imagen a DNN B.
Toda la red aprenderá gradualmente a distinguir si dos imágenes pertenecen a la
misma clase o no.

d. Ahora cree un nuevo DNN reutilizando y congelando las capas ocultas de DNN A y agregando
una capa de salida softmax en la parte superior con 10 neuronas. Entrene esta red en la
división n.º 2 y vea si puede lograr un alto rendimiento a pesar de tener solo 500 imágenes
por clase.

Las soluciones a estos ejercicios están disponibles en???.

366 | Capítulo 11: Entrenamiento de redes neuronales profundas


CAPÍTULO 12

Modelos personalizados y entrenamiento con

TensorFlow

Con los libros electrónicos Early Release, obtiene libros en su forma más
temprana, el contenido sin editar y sin editar del autor mientras escribe,
para que pueda aprovechar estas tecnologías mucho antes del
lanzamiento oficial de estos títulos. El siguiente será el Capítulo 12 en la
versión final del libro.

Hasta ahora, solo hemos usado la API de alto nivel de TensorFlow, tf.keras, pero ya hemos llegado
bastante lejos: construimos varias arquitecturas de redes neuronales, incluidas redes de regresión y
clasificación, redes amplias y profundas y redes de autonormalización, utilizando todo tipo de técnicas,
como la normalización por lotes, la deserción, los horarios de tasa de aprendizaje y más. De hecho, el
95% de los casos de uso que encontrará no requerirán nada más que tf.keras (y tf.data, consulte
Capítulo 13). Pero ahora es el momento de profundizar en TensorFlow y echar un vistazo a su nivel
inferiorAPI de Python. Esto será útil cuando necesite control adicional, para escribir funciones de
pérdida personalizadas, métricas personalizadas, capas, modelos, inicializadores, regularizadores,
restricciones de peso y más. Es posible que incluso necesite controlar completamente el ciclo de
entrenamiento en sí mismo, por ejemplo, para aplicar transformaciones o restricciones especiales a
los gradientes (más allá de simplemente recortarlos), o usar múltiples optimizadores para diferentes
partes de la red. Cubriremos todos estos casos en este capítulo, luego también veremos cómo puede
mejorar sus modelos personalizados y algoritmos de entrenamiento utilizando la función de
generación automática de gráficos de TensorFlow. Pero primero, hagamos un recorrido rápido por
TensorFlow.

367
TensorFlow 2.0 se lanzó en marzo de 2019, lo que hace que TensorFlow sea
mucho más fácil de usar. La primera edición de este libro usó TF 1, mientras
que esta edición usa TF 2.

Un recorrido rápido por TensorFlow

Como sabes,TensorFlowes una biblioteca poderosa para el cálculo numérico, especialmente adecuada
y ajustada para el aprendizaje automático a gran escala (pero podría usarla para cualquier otra cosa
que requiera cálculos pesados). Fue desarrollado por el equipo de Google Brain y potencia muchos de
los servicios a gran escala de Google, como Google Cloud Speech, Google Photos y Google Search. Fue
de código abierto en noviembre de 2015 y ahora es la biblioteca de aprendizaje profundo más popular
(en términos de citas en artículos, adopción en empresas, estrellas en github, etc.): innumerables
proyectos usan TensorFlow para todo tipo de tareas de aprendizaje automático, tales como
clasificación de imágenes, procesamiento de lenguaje natural (NLP), sistemas de recomendación,
pronóstico de series temporales y mucho más.

Entonces, ¿qué ofrece realmente TensorFlow? He aquí un resumen:

• Su núcleo es muy similar a NumPy, pero con soporte para GPU.

• También es compatible con la computación distribuida (a través de múltiples dispositivos y servidores).

• Incluye una especie de compilador justo a tiempo (JIT) que le permite optimizar los cómputos en
cuanto a velocidad y uso de memoria: funciona extrayendo elgráfico de cálculodesde una función
de Python, luego optimizándola (p. ej., eliminando los nodos no utilizados) y finalmente
ejecutándola de manera eficiente (p. ej., ejecutando automáticamente operaciones
independientes en paralelo).

• Los gráficos de cómputo se pueden exportar a un formato portátil, por lo que puede
entrenar un modelo de TensorFlow en un entorno (p. ej., usando Python en Linux) y
ejecutarlo en otro (p. ej., usando Java en un dispositivo Android).

• Implementa autodiff (verCapítulo 10y???), y proporciona algunos optimizadores excelentes,


como RMSProp, Nadam y FTRL (verCapítulo 11), por lo que puede minimizar fácilmente
todo tipo de funciones de pérdida.

• TensorFlow ofrece muchas más funciones, construidas sobre estas funciones principales: la más importante es,
por supuesto, tf.keras1, pero también tiene operaciones de preprocesamiento y carga de datos (tf.data, tf.io,
etc.), operaciones de procesamiento de imágenes (tf.image), operaciones de procesamiento de señales
(tf.signal) y más (verFigura 12-1para obtener una descripción general de la API de Python de TensorFlow).

1 TensorFlow también incluye otra API de aprendizaje profundo llamadaAPI de estimadores, pero ahora se recomienda
para usar tf.keras en su lugar.

368 | Capítulo 12: Modelos personalizados y entrenamiento con TensorFlow


Figura 12-1. API de Python de TensorFlow

Cubriremos muchos de los paquetes y funciones de la API de Tensor‐


Flow, pero es imposible cubrirlos todos, por lo que realmente debería
tomarse un tiempo para navegar por la API: encontrará que es bastante
rica y está bien documentada.

En el nivel más bajo, cada operación de TensorFlow se implementa utilizando código C++ altamente
eficiente.2. Muchas operaciones (ooperacionespara abreviar) tienen múltiples implementaciones,
llamadas granos: cada núcleo está dedicado a un tipo de dispositivo específico, como CPU, GPU o
incluso TPU (Unidades de procesamiento de tensores). Como sabrá, las GPU pueden acelerar
drásticamente los cálculos al dividirlos en muchos fragmentos más pequeños y ejecutarlos en paralelo
en muchos subprocesos de GPU. Los TPU son aún más rápidos. Puede comprar sus propios
dispositivos GPU (por ahora, TensorFlow solo admite tarjetas Nvidia con CUDA Compute Capability
3.5+), pero las TPU solo están disponibles enMotor de aprendizaje automático de Google Cloud(ver???).
3

La arquitectura de TensorFlow se muestra enFigura 12-2: la mayoría de las veces su código usará las
API de alto nivel (especialmente tf.keras y tf.data), pero cuando necesite más flexibilidad, usará la API
de Python de nivel inferior, manejando los tensores directamente. Tenga en cuenta que las API para
otros idiomas también están disponibles. En cualquier caso, la ejecución de TensorFlow

2 Si alguna vez lo necesita (pero probablemente no lo necesite), puede escribir sus propias operaciones utilizando la API de C++.

3 Si es un investigador, puede ser elegible para usar estos TPU de forma gratuita, consultehttps://tensorflow.org/tfrc/para más
detalles.

Un recorrido rápido por TensorFlow | 369


El motor se encargará de ejecutar las operaciones de manera eficiente, incluso en múltiples
dispositivos y máquinas si se lo indica.

Figura 12-2. Arquitectura de TensorFlow

TensorFlow se ejecuta no solo en Windows, Linux y MacOS, sino también en dispositivos


móviles (usandoTensorFlow Lite), incluidos iOS y Android (ver???). Si no desea utilizar la API
de Python, también existen las API de C++, Java, Go y Swift. Incluso hay una
implementación de Javascript llamadaTensorFlow.jseso hace posible ejecutar sus modelos
directamente en su navegador.

Hay más en TensorFlow que solo la biblioteca. TensorFlow está en el centro de un extenso ecosistema
de bibliotecas. Primero, está TensorBoard para la visualización (ver Capítulo 10). A continuación, hay
TensorFlow extendido (TFX), que es un conjunto de bibliotecas creadas por Google para producir
proyectos de TensorFlow: incluye herramientas para validación de datos, preprocesamiento, análisis
de modelos y entrega (con TF Serving, consulte???). Google también lanzóCentro TensorFlow, una
forma de descargar y reutilizar fácilmente redes neuronales preentrenadas. También puede obtener
muchas arquitecturas de redes neuronales, algunas de ellas preentrenadas, en TensorFlow'sjardín
modelo. Revisar laRecursos de TensorFlow, ohttps://github.com/jtoy/awesome-tensorflowpara más
proyectos basados en TensorFlow. Encontrará cientos de proyectos de TensorFlow en GitHub, por lo
que a menudo es fácil encontrar el código existente para cualquier cosa que intente hacer.

Cada vez se publican más documentos de ML junto con su


implementación y, a veces, incluso con modelos preentrenados. Verificar
https://papelesconcodigo.com/para encontrarlos fácilmente.

370 | Capítulo 12: Modelos personalizados y entrenamiento con TensorFlow


Por último, pero no menos importante, TensorFlow cuenta con un equipo dedicado de desarrolladores
apasionados y serviciales, y una gran comunidad que contribuye a mejorarlo. Para hacer preguntas
técnicas, debe utilizarhttp://stackoverflow.com/y etiqueta tu pregunta contensorflow ypitón. Puede
presentar errores y solicitudes de funciones a través de GitHub. Para discusiones generales, únase a la
grupo de google.

¡Bien, es hora de comenzar a codificar!

Usando TensorFlow como NumPy


La API de TensorFlow gira en torno atensores, de ahí el nombre Tensor-Flow. Un tensor suele ser una
matriz multidimensional (exactamente como un NumPyndarray),pero también puede contener un
escalar (un valor simple, como 42). Estos tensores serán importantes cuando creemos funciones de
costo personalizadas, métricas personalizadas, capas personalizadas y más, así que veamos cómo
crearlas y manipularlas.

Tensores y Operaciones
Puedes crear fácilmente un tensor, usandotf.constante().Por ejemplo, aquí hay un tensor que
representa una matriz con dos filas y tres columnas de flotantes:

> > > t.f..constante([[1.,2.,3.], [4.,5.,6.]])#matriz <tf.Tensor:


id=0, forma=(2, 3), dtype=float32, numpy= array([[1., 2., 3.],

[4., 5., 6.]], dtype=float32)>


> > > t.f..constante(42)#escalar
<tf.Tensor: id=1, forma=(), dtype=int32, numpy=42>

Al igual que unndarray,atf.Tensortiene una forma y un tipo de datos (tipo de d):

> > > t=t.f..constante([[1.,2.,3.], [4.,5.,6.]])


> > > t.forma
TensorShape([2, 3])
> > > t.tipo de d
tf.float32

La indexación funciona de manera muy similar a NumPy:

> > > t[:,1:]


<tf.Tensor: id=5, forma=(2, 2), dtype=float32, numpy=
array([[2., 3.],
[5., 6.]], tipod=float32)>
> > > t[...,1,t.f..eje nuevo]
<tf.Tensor: id=15, forma=(2, 1), dtype=float32, numpy=
array([[2.],
[5.]], dtype=float32)>

Lo más importante, todo tipo de operaciones de tensor están disponibles:

> > > t+10


<tf.Tensor: id=18, forma=(2, 3), dtype=float32, numpy=

Usar TensorFlow como NumPy | 371


matriz([[11., 12., 13.],
[14., 15., 16.]], dtype=float32)>
> > > t.f..cuadrado(t)
<tf.Tensor: id=20, forma=(2, 3), dtype=float32, numpy=
array([[ 1., 4., 9.],
[16., 25., 36.]], dtype=float32)>
> > > t@t.f..transponer(t)
<tf.Tensor: id=24, forma=(2, 2), dtype=float32, numpy=
array([[14., 32.],
[32., 77.]], dtype=float32)>

Tenga en cuenta que escribirt + 10es equivalente a llamartf.añadir(t, 10) (de hecho, Python
llama al método mágicot.__añadir__(10),que solo llamatf.añadir(t, 10)).También se admiten
otros operadores (como -, *, etc.). El operador @ se agregó en Python 3.5, para la
multiplicación de matrices: es equivalente a llamar altf.matmul()función.

Encontrará todas las operaciones matemáticas básicas que necesita (p. ej.,tf.añadir(),
tf.multiplicar(), tf.cuadrado(), tf.exp(), tf.sqrt()…) y, en general, la mayoría de las operaciones
que puede encontrar en NumPy (p. ej.,tf.remodelar(), tf.apretar(), tf.tile()),pero a veces
con un nombre diferente (por ejemplo,tf.reduce_mean(), tf.reduce_sum(), tf.reduce_max(),
tf.math.log()son el equivalente denp.media(), np.suma(), np.max()ynp.log()).
Cuando el nombre difiere, a menudo hay una buena razón para ello: por ejemplo, en Tensor-Flow
debes escribirtf.transponer(t),no puedes simplemente escribirtTcomo en NumPy. La razón es que no
hace exactamente lo mismo: en TensorFlow, se crea un nuevo tensor con su propia copia de los datos
transpuestos, mientras que en NumPy,tTes solo una vista transpuesta de los mismos datos. Del
mismo modo, eltf.reduce_sum()La operación recibe este nombre porque su kernel de GPU (es decir, la
implementación de GPU) utiliza un algoritmo de reducción que no garantiza el orden en que se
agregan los elementos: debido a que los elementos flotantes de 32 bits tienen una precisión limitada,
esto significa que el resultado puede cambiar siempre ligeramente cada vez que llame a esta
operación. Lo mismo es cierto detf.reduce_mean() (pero por supuesto
tf.reduce_max()es determinista).

372 | Capítulo 12: Modelos personalizados y entrenamiento con TensorFlow


Muchas funciones y clases tienen alias. Por ejemplo,tf.añadir() y
tf.matemáticas.add()son la misma función. Esto permite que
TensorFlow tenga nombres concisos para las operaciones más
comunes4, conservando paquetes bien organizados.

API de bajo nivel de Keras

La API de Keras en realidad tiene su propia API de bajo nivel, ubicada enkeras.backend.Incluye
funciones comocuadrado(), exp(), raíz cuadrada()y así. En tf.keras, estas funciones generalmente
solo llaman a las operaciones correspondientes de TensorFlow. Si desea escribir código que sea
portátil para otras implementaciones de Keras, debe usar estas funciones de Keras. Sin embargo,
solo cubren un subconjunto de todas las funciones disponibles en TensorFlow, por lo que en este
libro usaremos las operaciones de TensorFlow directamente. Aquí hay un ejemplo simple usando
keras.backend,que comúnmente se nombrakpara abreviar:

> > > detensorflowimportarqueras


> > > k=queras.back-end
> > > k.cuadrado(k.transponer(t))+10
<tf.Tensor: id=39, forma=(3, 2), dtype=float32, numpy=
array([[11., 26.],
[14., 35.],
[19., 46.]], tipod=float32)>

Tensores y NumPy
Los tensores funcionan bien con NumPy: puede crear un tensor a partir de una matriz NumPy y
viceversa, e incluso puede aplicar operaciones TensorFlow a matrices NumPy y operaciones
NumPy a tensores:

> > > a=notario público.formación([2.,4.,5.])


> > > t.f..constante(a)
<tf.Tensor: id=111, forma=(3,), dtype=float64, numpy=array([2., 4., 5.])>
> > > t.entumecido()#o
np.arreglo(t) matriz([[1., 2., 3.],
[4., 5., 6.]], dtype=float32)
> > > t.f..cuadrado(a)
<tf.Tensor: id=116, forma=(3,), dtype=float64, numpy=array([4., 16., 25.])>
> > > notario público.cuadrado(t
) matriz ([[ 1., 4., 9.],
[16., 25., 36.]], dtype=float32)

4 Una notable excepción estf.matemáticas.log()que se usa comúnmente pero no haytf.log()alias (como podría ser
confundirse con registro).

Usar TensorFlow como NumPy | 373


Tenga en cuenta que NumPy utiliza una precisión de 64 bits de forma predeterminada, mientras que

Tensor‐Flow utiliza una precisión de 32 bits. Esto se debe a que la precisión de 32 bits generalmente

es más que suficiente para las redes neuronales, además se ejecuta más rápido y usa menos RAM.

Entonces, cuando crea un tensor a partir de una matriz NumPy, asegúrese de

establecerdtype=tf.float32.

Tipo de conversiones

Las conversiones de tipo pueden dañar significativamente el rendimiento y pueden pasar


desapercibidas fácilmente cuando se realizan automáticamente. Para evitar esto, TensorFlow no
realiza ninguna conversión de tipo automáticamente: solo genera una excepción si intenta ejecutar
una operación en tensores con tipos incompatibles. Por ejemplo, no puede agregar un tensor flotante
y un tensor entero, y ni siquiera puede agregar un flotante de 32 bits y un flotante de 64 bits:

> > > t.f..constante(2.)+t.f..constante(40)


Traceback[...]InvalidArgumentError[...]se esperaba que fuera un float[...]
> > > t.f..constante(2.)+t.f..constante(40,tipo de d=t.f..flotar64)
Traceback[...]InvalidArgumentError[...]se esperaba que fuera un doble[...]

Esto puede ser un poco molesto al principio, ¡pero recuerda que es por una buena causa! Y, por
supuesto, puedes usartf.cast()cuando realmente necesitas convertir tipos:

> > > t2=t.f..constante(40,tipo de d=t.f..flotar64)


> > > t.f..constante(2.0)+t.f..emitir(t2,t.f..flotar32) <tf.Tensor:
id=136, forma=(), dtype=float32, numpy=42.0>

Variables
Hasta ahora, hemos usado tensores constantes: como su nombre indica, no puedes modificarlos. Sin
embargo, los pesos en una red neuronal deben modificarse mediante la retropropagación, y es posible que
otros parámetros también deban cambiar con el tiempo (p. ej., un optimizador de momento realiza un
seguimiento de los gradientes anteriores). Lo que necesitamos es untf.Variable:

> > > v=t.f..Variable([[1.,2.,3.], [4.,5.,6.]])


> > >v
<tf.Variable 'Variable:0' forma=(2, 3) dtype=float32, numpy=
array([[1., 2., 3.],
[4., 5., 6.]], dtype=float32)>

Atf.Variableactúa como un tensor constante: puede realizar las mismas operaciones con él,
también funciona bien con NumPy y es igual de exigente con los tipos. Pero también se
puede modificar en su lugar usando elasignar()método (oasignar_añadir()o
asignar_sub()que incrementan o decrementan la variable por el valor dado). También
puede modificar celdas individuales (o sectores), usando la celda (o sector)asignar()
(la asignación directa de elementos no funcionará), o usando el métododispersión_actualizar()o
dispersión_nd_actualizar()métodos:

v.asignar(2*v) v[0,1]. # => [[2., 4., 6.], [8., 10., 12.]]


asignar(42) # => [[2., 42., 6.], [8., 10., 12.]]

374 | Capítulo 12: Modelos personalizados y entrenamiento con TensorFlow

También podría gustarte