Documentos de Académico
Documentos de Profesional
Documentos de Cultura
com
En la práctica, rara vez tendrá que crear variables manualmente, ya que Keras
proporciona unañadir_peso()método que se encargará de ello por usted, como
veremos. Además, los parámetros del modelo generalmente serán actualizados
directamente por los optimizadores, por lo que rara vez necesitará actualizar las
variables manualmente.
TensorFlow admite varias otras estructuras de datos, incluidas las siguientes (consulte el
cuaderno o???para más detalles):
• Colas, incluidas las colas Primero en entrar, Primero en salir (FIFO) (Cola FIFO),colas que pueden
priorizar algunos artículos (cola de prioridad),colas que barajan sus elementos (Cola aleatoria
aleatoria),y colas que pueden agrupar elementos de diferentes formas mediante el relleno
(Relleno FIFOQueue).Estas clases están todas en eltf.colapaquete.
Con tensores, operaciones, variables y varias estructuras de datos a su disposición, ¡ahora está
listo para personalizar sus modelos y algoritmos de entrenamiento!
Comencemos por crear una función de pérdida personalizada, que es un caso de uso simple y común.
Suponga que desea entrenar un modelo de regresión, pero su conjunto de entrenamiento es un poco
ruidoso. Por supuesto, comienza intentando limpiar su conjunto de datos eliminando o reparando los
valores atípicos, pero resulta ser insuficiente, el conjunto de datos sigue siendo ruidoso. ¿Qué función
de pérdida debería utilizar? El error cuadrático medio puede penalizar demasiado los errores grandes,
por lo que su modelo terminará siendo impreciso. El error absoluto medio no penalizaría tanto a los
valores atípicos, pero el entrenamiento podría tardar un tiempo en converger y el modelo entrenado
podría no ser muy preciso. Este es probablemente un buen momento para usar la pérdida de Huber
(introducida enCapítulo 10) en lugar del buen viejo MSE. La pérdida de Huber no es actualmente parte
de la API oficial de Keras, pero está disponible en tf.keras (solo use una instancia de la
keras.losses.Huberclase). Pero supongamos que no está allí: ¡implementarlo es muy fácil! Simplemente
cree una función que tome las etiquetas y las predicciones como argumentos, y use las operaciones
de TensorFlow para calcular la pérdida de cada instancia:
definitivamentehuber_fn(y_verdadero,y_pred):
error=y_verdadero-y_pred es_pequeño_error
=t.f..abdominales(error)<1 pérdida_cuadrada
=t.f..cuadrado(error)/2 pérdida_lineal=t.f..
abdominales(error)-0.5
devolvert.f..dónde(es_pequeño_error,pérdida_cuadrada,pérdida_lineal)
También es preferible devolver un tensor que contenga una pérdida por instancia, en lugar de
devolver la pérdida media. De esta manera, Keras puede aplicar pesos de clase o pesos de muestra
cuando se le solicite (verCapítulo 10).
A continuación, puede usar esta pérdida cuando compile el modelo de Keras y luego entrene su
modelo:
modelo.compilar(pérdida=huber_fn,optimizador=
"nadam") modelo.adaptar(X_tren,y_tren, [...])
¡Y eso es! Para cada lote durante el entrenamiento, Keras llamará alhuber_fn()para calcular
la pérdida y usarla para realizar un paso de descenso de gradiente. Además, realizará un
seguimiento de la pérdida total desde el comienzo de la época y mostrará la pérdida
media.
Guardar un modelo que contiene una función de pérdida personalizada en realidad funciona bien, ya
que Keras solo guarda el nombre de la función. Sin embargo, siempre que lo cargue, debe
proporcionar un diccionario que asigne el nombre de la función a la función real. En términos más
generales, cuando carga un modelo que contiene objetos personalizados, debe asignar los nombres a
los objetos:
modelo=queras.modelos.cargar_modelo("mi_modelo_con_una_pérdida_personalizada.h5",
objetos_personalizados={"huber_fn":huber_fn})
definitivamentecrear_huber(límite=1.0):
definitivamentehuber_fn(y_verdadero,y_pred):
error=y_verdadero-y_pred es_pequeño_error=t.f..
abdominales(error)<Umbral de pérdida al cuadrado=
t.f..cuadrado(error)/2
pérdida_lineal=límite*t.f..abdominales(error)-límite**2/2 devolvert.f..
dónde(es_pequeño_error,pérdida_cuadrada,pérdida_lineal) devolver
huber_fn
modelo.compilar(pérdida=crear_huber(2.0),optimizador="nadam")
modelo=queras.modelos.cargar_modelo("mi_modelo_con_un_umbral_de_pérdida_personalizado_2.h5",
objetos_personalizados={"huber_fn":crear_huber(2.0)})
• Losllamar()El método toma las etiquetas y predicciones, calcula todas las pérdidas de
instancias y las devuelve.
Luego puede usar cualquier instancia de esta clase cuando compile el modelo:
modelo.compilar(pérdida=HuberLoss(2.),optimizador="nadam")
Cuando guarde el modelo, el umbral se guardará junto con él, y cuando cargue el
modelo, solo necesita asignar el nombre de la clase a la clase en sí:
modelo=queras.modelos.cargar_modelo("my_model_with_a_custom_loss_class.h5",
objetos_personalizados={"HuberLoss":HuberLoss})
¡Eso es todo por pérdidas! No fue demasiado difícil, ¿verdad? Bueno, es igual de simple para funciones de
activación personalizadas, inicializadores, regularizadores y restricciones. Veamos estos ahora.
5 No sería una buena idea usar una media ponderada: si lo hiciéramos, entonces dos instancias con el mismo peso pero en
diferentes lotes tendrían un impacto diferente en el entrenamiento, dependiendo del peso total de cada lote.
definitivamentemi_glorot_inicializador(forma,tipo de d=t.f..flotar32):
dev estándar=t.f..sqrt(2./(forma[0]+forma[1]))
devolvert.f..aleatorio.normal(forma,dev estándar=dev estándar,tipo de d=tipo de d)
definitivamentemy_l1_regularizador(pesos):
devolvert.f..reducir_sum(t.f..abdominales(0.01*pesos))
Como puede ver, los argumentos dependen del tipo de función personalizada. Estas funciones
personalizadas se pueden usar normalmente, por ejemplo:
capa=queras.capas.Denso(30,activación=mi_softplus,
kernel_initializer=mi_glorot_inicializador,
kernel_regularizer=my_l1_regularizador,
kernel_constraint=mis_pesos_positivos)
claseRegularizador MyL1(queras.regularizadores.regularizador):
definitivamente__en eso__(uno mismo,factor):
__llamar__(uno mismo,pesos):
devolvert.f..reducir_sum(t.f..abdominales(uno mismo.factor*pesos))
definitivamenteget_config(uno mismo):
devolver{"factor":uno mismo.factor}
Tenga en cuenta que debe implementar elllamar()método para pérdidas, capas (incluyendo funciones
de activación) y modelos, o el __llamar__()método para regularizadores, inicializadores y restricciones.
Para las métricas, las cosas son un poco diferentes, como veremos ahora.
Métricas personalizadas
Las pérdidas y las métricas no son conceptualmente lo mismo: Gradient Descent utiliza las
pérdidas paratrenun modelo, por lo que deben ser diferenciables (al menos donde se evalúan) y
sus gradientes no deben ser 0 en todas partes. Además, está bien si los humanos no los pueden
interpretar fácilmente (por ejemplo, entropía cruzada). Por el contrario, las métricas se utilizan
para evaluarun modelo, deben ser más fáciles de interpretar y pueden ser no diferenciables o
tener gradientes de 0 en todas partes (p. ej., precisión).
Dicho esto, en la mayoría de los casos, definir una función de métrica personalizada es exactamente lo mismo
que definir una función de pérdida personalizada. De hecho, incluso podríamos usar la función de pérdida de
Huber que creamos anteriormente como una métrica6, funcionaría bien (y la persistencia también funcionaría
de la misma manera, en este caso solo guardando el nombre de la función, "huber_fn"):
modelo.compilar(pérdida="mse",optimizador="nadam",métrica=[crear_huber(2.0)])
Para cada lote durante el entrenamiento, Keras calculará esta métrica y realizará un seguimiento de su
media desde el comienzo de la época. La mayoría de las veces, esto es exactamente lo que quieres.
¡Pero no siempre! Considere la precisión de un clasificador binario, por ejemplo. Como vimos en
Capítulo 3, la precisión es el número de verdaderos positivos dividido por el número de predicciones
positivas (incluidos tanto los verdaderos positivos como los falsos positivos). Suponga que el modelo
hizo 5 predicciones positivas en el primer lote, 4 de las cuales fueron correctas: eso es 80% de
precisión. Luego suponga que el modelo hizo 3 predicciones positivas en el segundo lote, pero todas
fueron incorrectas: eso es 0% de precisión para el segundo lote. Si solo calcula la media de estas dos
precisiones, obtiene el 40%. Pero espera un segundo, esto esnola precisión del modelo sobre estos
dos lotes! De hecho, hubo un total de 4 verdaderos positivos (4 + 0) de 8 predicciones positivas (5 + 3),
por lo que la precisión general es del 50 %, no del 40 %. Lo que necesitamos es un objeto que pueda
realizar un seguimiento del número de verdaderos positivos y el número
6 Sin embargo, la pérdida de Huber rara vez se usa como métrica (se prefieren el MAE o el MSE).
En este ejemplo, creamos unPrecisiónobjeto, luego lo usamos como una función, pasándole las
etiquetas y predicciones para el primer lote, luego para el segundo lote (tenga en cuenta que también
podríamos haber pasado pesos de muestra). Usamos el mismo número de verdaderos y falsos
positivos que en el ejemplo que acabamos de discutir. Después del primer lote, devuelve la precisión
del 80 %, luego, después del segundo lote, devuelve el 50 % (que es la precisión general hasta el
momento, no la precisión del segundo lote). Esto se llama unmétrica de transmisión(o métrica con
estado), ya que se actualiza gradualmente, lote tras lote.
Si necesita crear una métrica de transmisión de este tipo, simplemente puede crear una subclase de la
keras.metrics.Metricclase. Aquí hay un ejemplo simple que realiza un seguimiento de la pérdida total de Huber
y la cantidad de instancias vistas hasta el momento. Cuando se le pregunta por el resultado, devuelve la
relación, que es simplemente la pérdida media de Huber:
claseHuberMetric(queras.métrica.Métrico):
definitivamente__en eso__(uno mismo,límite=1.0,**kwargs):
súper().__en eso__(**kwargs)#maneja argumentos base (por ejemplo,
dtype) uno mismo.límite=límite
uno mismo.huber_fn=crear_huber(límite)
uno mismo.total=uno mismo.añadir_peso("total",inicializador="ceros") uno mismo.
contar=uno mismo.añadir_peso("contar",inicializador="ceros") definitivamente
actualizar_estado(uno mismo,y_verdadero,y_pred,muestra_peso=Ninguna):
métrico=uno mismo.huber_fn(y_verdadero,y_pred) uno
mismo.total.asignar_añadir(t.f..reducir_sum(métrico))
uno mismo.contar.asignar_añadir(t.f..emitir(t.f..Talla(y_verdadero),t.f..flotar32)) definitivamente
resultado(uno mismo):
devolveruno mismo.total/uno mismo.contar
definitivamenteget_config(uno mismo):
base_config=súper().get_config()
devolver{**base_config,"límite":uno mismo.límite}
• El constructor utiliza elañadir_peso()método para crear las variables necesarias para realizar
un seguimiento del estado de la métrica en varios lotes, en este caso, la suma de todas las
pérdidas de Huber (total)y el número de instancias vistas hasta ahora (contar).Si lo prefiere,
puede crear variables manualmente. Keras rastrea cualquiertf.Variableque se establece
como un atributo (y más generalmente, cualquier objeto "rastreable", como capas o
modelos).
• Losactualizar_estado()se llama al método cuando usa una instancia de esta clase como una
función (como hicimos con elPrecisiónobjeto). Actualiza las variables dadas las etiquetas y
predicciones para un lote (y pesos de muestra, pero en este caso simplemente los
ignoramos).
• Losresultado()El método calcula y devuelve el resultado final, en este caso solo la métrica de
Huber media en todas las instancias. Cuando usa la métrica como una función, el
actualizar_estado()Primero se llama al método, luego se llama alresultado()Se llama al
método y se devuelve su salida.
Cuando define una métrica usando una función simple, Keras la llama automáticamente para cada
lote y realiza un seguimiento de la media durante cada época, tal como lo hicimos manualmente. Así
que el único beneficio de nuestroHuberMetricclase es que ellímiteserá salvado. Pero, por supuesto,
algunas métricas, como la precisión, no se pueden promediar simplemente en lotes: en esos casos, no
hay otra opción que implementar una métrica de transmisión.
Ahora que hemos creado una métrica de transmisión, ¡crear una capa personalizada parecerá
pan comido!
7 Esta clase es solo para fines ilustrativos. Una implementación más simple y mejor simplemente subclasificaría el
keras.metrics.Mediaclase, consulte el cuaderno para ver un ejemplo.
En ocasiones, es posible que desee crear una arquitectura que contenga una capa exótica para
la que TensorFlow no proporcione una implementación predeterminada. En este caso, deberá
crear una capa personalizada. O, a veces, es posible que desee construir una arquitectura muy
repetitiva, que contenga bloques idénticos de capas repetidos muchas veces, y sería
conveniente tratar cada bloque de capas como una sola capa. Por ejemplo, si el modelo es una
secuencia de capas A, B, C, A, B, C, A, B, C, es posible que desee definir una capa D
personalizada que contenga las capas A, B, C y su modelo. sería simplemente D, D, D. Veamos
cómo crear capas personalizadas.
Esta capa personalizada se puede usar como cualquier otra capa, usando la API secuencial, la
API funcional o la API de subclases. También puede usarlo como una función de activación.
(o simplemente podría usaractivación=tf.exp,oactivación=keras.activaciones.expo
esencial,o simplementeactivación="exponencial").La capa exponencial a veces se usa en la
capa de salida de un modelo de regresión cuando los valores a predecir tienen escalas
muy diferentes (p. ej., 0,001, 10, 1000).
Como probablemente ya haya adivinado, para crear una capa con estado personalizada (es
decir, una capa con pesos), necesita crear una subclase de lakeras.layers.Capaclase. Por ejemplo,
la siguiente clase implementa una versión simplificada delDensocapa:
clasemidenso(queras.capas.Capa):
definitivamente__en eso__(uno mismo,unidades,activación=Ninguna,**kwargs):
súper().__en eso__(**kwargs) uno
mismo.unidades=unidades
uno mismo.activación=queras.activaciones.obtener(activación)
definitivamenteconstruir(uno mismo,lote_entrada_forma):
nombre="parcialidad",forma=[uno mismo.unidades],inicializador=
"ceros") súper().construir(lote_entrada_forma)#debe estar al final
definitivamentellamar(uno mismo,X):
definitivamenteforma_de_salida_de_cómputo(uno mismo,lote_entrada_forma):
devolvert.f..TensorShape(lote_entrada_forma.como_lista()[:-1]+[uno mismo.unidades])
base_config=súper().get_config() devolver{**
base_config,"unidades":uno mismo.unidades,
"activación":queras.activaciones.publicar por fascículos(uno mismo.activación)}
• El constructor toma todos los hiperparámetros como argumentos (en este ejemplo solo
unidadesyactivación),y lo que es más importante, también se necesita un **kwargs
argumento. Llama al constructor padre, pasándole elkwargs:esto se encarga de los
argumentos estándar comoinput_shape, entrenable, nombre,y así. Luego guarda los
hiperparámetros como atributos, convirtiendo elactivaciónargumento a la función de
activación apropiada usando elkeras.activaciones.get()función (acepta funciones,
cadenas estándar como "relú"o "selu",o simplementeNinguna)8.
9 La API de Keras llama a este argumentoforma_entrada,pero como también incluye la dimensión de lote, prefiero llamar
esolote_entrada_forma.Igual porcomputar_salida_forma().
Para crear una capa con múltiples entradas (p. ej.,Concatenar),el argumento a lallamar()
El método debe ser una tupla que contenga todas las entradas y, de manera similar, el argumento del
computar_salida_forma()El método debe ser una tupla que contenga la forma del lote de
cada entrada. Para crear una capa con múltiples salidas, elllamar()El método debe devolver
la lista de salidas, y elcomputar_salida_forma()debe devolver la lista de formas de salida por
lotes (una por salida). Por ejemplo, la siguiente capa de juguetes toma dos entradas y
devuelve tres salidas:
claseMiMultiLayer(queras.capas.Capa):
definitivamentellamar(uno mismo,X):
X1,X2=X
devolver[X1+X2,X1*X2,X1/X2]
definitivamenteforma_de_salida_de_cómputo(uno mismo,lote_entrada_forma):
b1,b2=lote_entrada_forma
devolver[b1,b1,b1]#probablemente debería manejar las reglas de transmisión
Esta capa ahora se puede usar como cualquier otra capa, pero por supuesto solo usando
las API funcionales y de subclasificación, no la API secuencial (que solo acepta capas con
una entrada y una salida).
definitivamentellamar(uno mismo,X,capacitación=Ninguna):
sicapacitación:
ruido=t.f..aleatorio.normal(t.f..forma(X),dev estándar=uno mismo.dev estándar)
devolverX+ruido más:
devolverX
definitivamenteforma_de_salida_de_cómputo(uno mismo,lote_entrada_forma):
devolverlote_entrada_forma
Modelos personalizados
Las entradas pasan por una primera capa densa, luego por unabloque residualcompuesto por dos
capas densas y una operación de suma (como veremos encapitulo 14, un bloque residual suma sus
entradas a sus salidas), luego a través de este mismo bloque residual 3 veces más, luego a través de
un segundo bloque residual, y el resultado final pasa por una densa capa de salida. Tenga en cuenta
que este modelo no tiene mucho sentido, es solo un ejemplo para ilustrar el hecho de que puede
construir fácilmente cualquier tipo de modelo que desee, incluso contener:
10 El nombre “API de subclases” generalmente se refiere solo a la creación de modelos personalizados mediante subclases, aunque
se pueden crear muchas otras cosas mediante subclases, como vimos en este capítulo.
claseResidualBlock(queras.capas.Capa):
definitivamente__en eso__(uno mismo,n_capas,n_neuronas,**kwargs):
súper().__en eso__(**kwargs)
uno mismo.oculto=[queras.capas.Denso(n_neuronas,activación="elú",
kernel_initializer="él_normal")
por_enrango(n_capas)]
definitivamentellamar(uno mismo,entradas):
Z=entradas
porcapaenuno mismo.oculto:
Z=capa(Z)
devolverentradas+Z
Esta capa es un poco especial ya que contiene otras capas. Keras lo gestiona de forma transparente:
detecta automáticamente que elocultoEl atributo contiene objetos rastreables (capas en este caso),
por lo que sus variables se agregan automáticamente a la lista de variables de esta capa. El resto de
esta clase se explica por sí mismo. A continuación, usemos la API de subclases para definir el modelo
en sí:
claseRegresor Residual(queras.modelos.Modelo):
definitivamente__en eso__(uno mismo,salida_dim,**kwargs):
súper().__en eso__(**kwargs)
uno mismo.oculto1=queras.capas.Denso(30,activación="elú",
kernel_initializer="él_normal")
uno mismo.bloque1=ResidualBlock(2,30) uno mismo.
bloque2=ResidualBlock(2,30) uno mismo.afuera=
queras.capas.Denso(salida_dim)
definitivamentellamar(uno mismo,entradas):
Z=uno mismo.oculto1(
entradas) por_enrango(1+3):
Z=uno mismo.bloque1(Z) Z=
uno mismo.bloque2(Z) devolveruno
mismo.afuera(Z)
Con eso, puede construir de forma bastante natural y concisa casi cualquier modelo que encuentre en un
documento, ya sea utilizando la API secuencial, la API funcional, la API de subclases o incluso una
combinación de estas. ¿“Casi” cualquier modelo? Sí, todavía hay un par de cosas que debemos analizar:
primero, cómo definir pérdidas o métricas en función de las características internas del modelo y, segundo,
cómo crear un ciclo de entrenamiento personalizado.
Las pérdidas y métricas personalizadas que definimos anteriormente se basaron todas en las
etiquetas y las predicciones (y, opcionalmente, en los pesos de muestra). Sin embargo, en ocasiones
querrá definir pérdidas en función de otras partes de su modelo, como los pesos o las activaciones de
sus capas ocultas. Esto puede ser útil para fines de regularización o para monitorear algún aspecto
interno de su modelo.
Para definir una pérdida personalizada basada en las características internas del modelo, simplemente
calcúlela en función de cualquier parte del modelo que desee, luego pase el resultado alañadir_pérdida()
método. Por ejemplo, el siguiente modelo personalizado representa un regresor MLP estándar con 5 capas
ocultas, excepto que también implementa unpérdida de reconstrucción(ver???): añadimos un extraDenso
capa encima de la última capa oculta, y su función es tratar de reconstruir las entradas del
modelo. Dado que la reconstrucción debe tener la misma forma que las entradas del modelo,
necesitamos crear esteDensocapa en elconstruir()para tener acceso a la forma de las entradas.
En elllamar()método, calculamos tanto la salida regular de la MLP, más la salida de la capa de
reconstrucción. Luego calculamos la diferencia cuadrática media entre las reconstrucciones y
las entradas, y agregamos este valor (multiplicado por 0.05) a la lista de pérdidas del modelo
llamandoañadir_pérdida().Durante el entrenamiento, Keras agregará esta pérdida a la pérdida
principal (por eso redujimos la pérdida de reconstrucción, para garantizar que la pérdida
principal domine). Como resultado, el modelo se verá obligado a conservar la mayor cantidad
de información posible a través de las capas ocultas, incluso información que no sea
directamente útil para la tarea de regresión en sí. En la práctica, esta pérdida a veces mejora la
generalización; es una pérdida de regularización:
claseReconstruyendo Regresor(queras.modelos.Modelo):
definitivamente__en eso__(uno mismo,salida_dim,**kwargs):
súper().__en eso__(**kwargs)
uno mismo.oculto=[queras.capas.Denso(30,activación="selu",
kernel_initializer="lecun_normal")
definitivamenteconstruir(uno mismo,lote_entrada_forma):
n_entradas=lote_entrada_forma[-1]
uno mismo.reconstruir=queras.capas.Denso(n_entradas)
súper().construir(lote_entrada_forma)
definitivamentellamar(uno mismo,entradas):
Z=entradas
porcapaenuno mismo.oculto:
Z=capa(Z)
reconstrucción=uno mismo.reconstruir(Z)
recon_loss=t.f..reducir_media(t.f..cuadrado(reconstrucción-entradas)) uno mismo.
añadir_pérdida(0.05*recon_loss) devolveruno mismo.afuera(Z)
De manera similar, puede agregar una métrica personalizada basada en las características internas del
modelo calculándola de la forma que desee, siempre que el resultado sea la salida de un objeto de métrica.
Por ejemplo, puede crear unkeras.metrics.Mean()objeto en el constructor, luego llámelo en el
llamar()método, pasándole elrecon_pérdida,y finalmente agréguelo al modelo llamando al
modeloañadir_métrica()método. De esta forma, cuando entrene el modelo, Keras mostrará
la pérdida media de cada época (la pérdida es la suma de la pérdida principal más 0,05
veces la pérdida de reconstrucción) y el error de reconstrucción medio de cada época.
Ambos bajarán durante el entrenamiento:
Época 1/5
11610/11610 [=============] [...] pérdida: 4.3092 - reconstrucción_error: 1.7360 Época
2/5
11610/11610 [=============] [...] pérdida: 1.1232 - reconstrucción_error: 0.8964 [...]
En más del 99% de los casos, todo lo que hemos discutido hasta ahora será suficiente para
implementar cualquier modelo que desee construir, incluso con arquitecturas complejas,
pérdidas, métricas, etc. Sin embargo, en algunos casos excepcionales, es posible que deba
personalizar el ciclo de entrenamiento. Sin embargo, antes de llegar allí, debemos ver cómo
calcular gradientes automáticamente en TensorFlow.
definitivamenteF(w1,w2):
devolver3*w1**2+2*w1*w2
¡Se ve bien! Esto funciona bastante bien y es trivial de implementar, pero es solo una
aproximación y, lo que es más importante, debe llamarF()al menos una vez por parámetro (no
dos, ya que podríamos calcularf(w1, w2)sólo una vez). Esto hace que este enfoque sea intratable
para grandes redes neuronales. Entonces, en su lugar, deberíamos usar autodiff (verCapítulo 10
y???). TensorFlow hace esto bastante simple:
w1,w2=t.f..Variable(5.),t.f..Variable(3.) cont.f..
cinta de degradado()comocinta:
z=F(w1,w2)
gradientes=cinta.degradado(z, [w1,w2])
¡Perfecto! No solo el resultado es preciso (la precisión solo está limitada por los errores de coma
flotante), sino que ladegradado()El método solo realiza los cálculos registrados una vez (en
orden inverso), sin importar cuántas variables haya, por lo que es increíblemente eficiente. ¡Es
como magia!
Si necesitas llamardegradado()más de una vez, debe hacer que la cinta sea persistente y
eliminarla cuando haya terminado con ella para liberar recursos:
cont.f..cinta de degradado(persistente=Verdadero)comocinta:
z=F(w1,w2)
De manera predeterminada, la cinta solo rastreará las operaciones que involucren variables, por lo que si
intenta calcular el gradiente dezcon respecto a cualquier otra cosa que no sea una variable, el resultado será
serNinguna:
c1,c2=t.f..constante(5.),t.f..constante(3.) cont.f..
cinta de degradado()comocinta:
z=F(c1,c2)
Sin embargo, puede forzar la cinta para que mire los tensores que desee, para grabar cada
operación que los involucre. Luego puede calcular gradientes con respecto a estos tensores,
como si fueran variables:
cont.f..cinta de degradado()comocinta:
cinta.reloj(c1)
cinta.reloj(c2) z=
F(c1,c2)
Esto puede ser útil en algunos casos, por ejemplo si se quiere implementar una regularización
de pérdidas que penalice activaciones que varíen mucho cuando las entradas varíen poco: la
pérdida estará en función del gradiente de las activaciones respecto a las entradas. Dado que
las entradas no son variables, deberá indicarle a la cinta que las observe.
Si calcula el gradiente de una lista de tensores (por ejemplo, [z1, z2, z3])con respecto a algunas
variables (por ejemplo, [w1, w2]),TensorFlow en realidad calcula de manera eficiente la suma de
los gradientes de estos tensores (es decir,gradiente (z1, [w1, w2]),másgradiente (z2, [w1, w2]),
másgradiente (z3, [w1, w2])).Debido a la forma en que funciona la diferencia automática en
modo inverso, no es posible calcular los gradientes individuales (z1, z2yz3)sin realmente llamar
degradado()varias veces (una vez porz1,una vez porz2y de una vez porz3), lo que requiere hacer
que la cinta sea persistente (y borrarla después).
cont.f..cinta de degradado(persistente=Verdadero)comocinta_hessian:
cont.f..cinta de degradado()comocinta_jacobiana:
z=F(w1,w2)
jacobianos=cinta_jacobiana.degradado(z, [w1,w2])
arpilleras=[cinta_hessian.degradado(jacobiano, [w1,w2])
porjacobianoenjacobianos]
delcinta_hessian
La cinta interior se usa para calcular los jacobianos, como hicimos antes. La cinta exterior se utiliza para
calcular las derivadas parciales de cada jacobiano. Ya que tenemos que llamardegradado()una vez para cada
jacobiano (o de lo contrario obtendríamos la suma de las derivadas parciales sobre todos los jabobianos,
como se explicó anteriormente), necesitamos que la cinta exterior sea persistente, por lo que la eliminamos al
final. Los jacobianos son obviamente los mismos que antes (36 y 5), pero ahora también tenemos los
hessianos:
Verifiquemos estas arpilleras. Las dos primeras son las derivadas parciales de6 * w1 + 2 * w2
(que es, como vimos antes, la derivada parcial deFcon respecto aw1),con respecto a
w1yw2.El resultado es correcto: 6 paraw1y 2 paraw2.Los dos siguientes son las
derivadas parciales de2 * w1 (la derivada parcial deFcon respecto aw2),con respecto a
w1yw2,cuales son 2 paraw1y 0 paraw2.Tenga en cuenta que TensorFlow devuelve
Ningunaen lugar de 0 desdew2no aparece en absoluto en2 * w1.TensorFlow también regresa
Ningunacuando utiliza una operación cuyos gradientes no están definidos (por ejemplo,tf.argmax()).
En algunos casos raros, es posible que desee evitar que los gradientes se propaguen hacia atrás
a través de alguna parte de su red neuronal. Para ello, debe utilizar eltf.stop_gradient()
función: simplemente devuelve sus entradas durante el pase hacia adelante (comotf.identidad()),pero
no deja pasar los gradientes durante la retropropagación (actúa como una constante). Por ejemplo:
definitivamenteF(w1,w2):
devolver3*w1**2+t.f..detener_gradiente(2*w1*w2)
cont.f..cinta de degradado()comocinta:
z=F(w1,w2)#mismo resultado que sin stop_gradient()
Esto se debe a que calcular los gradientes de esta función usando autodiff genera algunas
dificultades numéricas: debido a errores de precisión de punto flotante, autodiff termina
calculando infinito dividido por infinito (lo que devuelve NaN). Afortunadamente, podemos
encontrar analíticamente que la derivada de la función softplus es solo 1 / (1 + 1 / exp(x)), que es
numéricamente estable. Luego, podemos decirle a TensorFlow que use esta función estable
cuando calcule los gradientes delmi_softplus()función, decorándolo con
@tf.gradiente_personalizado,y haciendo que devuelva tanto su salida normal como la función que
calcula las derivadas (tenga en cuenta que recibirá como entrada los gradientes que se propagaron
hacia atrás hasta el momento, hasta la función softplus, y de acuerdo con la regla de la cadena
deberíamos multiplicarlos con el valor de esta función gradientes):
@tf.gradiente_personalizado
definitivamentemi_mejor_softplus(z):
Exp=t.f..Exp(z)
definitivamentemy_softplus_gradientes(graduado):
devolvergraduado/(1+1/Exp)
devolvert.f..Matemáticas.Iniciar sesión(Exp+1),my_softplus_gradientes
¡Felicidades! Ahora puede calcular los gradientes de cualquier función (siempre que sea diferenciable
en el punto donde lo calcula), incluso puede calcular hessianos, bloquear la propagación hacia atrás
cuando sea necesario e incluso escribir sus propias funciones de gradiente. Esta es probablemente
más flexibilidad de la que necesitará, incluso si crea sus propios bucles de entrenamiento
personalizados, como veremos ahora.
En algunos casos raros, eladaptar()El método puede no ser lo suficientemente flexible para lo
que necesita hacer. Por ejemplo, el documento Wide and Deep que discutimos enCapítulo 10en
realidad usa dos optimizadores diferentes: uno para el camino ancho y el otro para el camino
profundo. Desde eladaptar()El método solo usa un optimizador (el que especificamos cuando
También puede escribir sus propios bucles de entrenamiento personalizados simplemente para
sentirse más seguro de que hace exactamente lo que pretende que haga (tal vez no esté seguro de
algunos detalles deladaptar()método). A veces puede sentirse más seguro hacer todo explícito. Sin
embargo, recuerde que escribir un ciclo de entrenamiento personalizado hará que su código sea más
largo, más propenso a errores y más difícil de mantener.
Primero, construyamos un modelo simple. No es necesario compilarlo, ya que manejaremos el ciclo de entrenamiento
manualmente:
l2_reg=queras.regularizadores.l2(0.05)
modelo=queras.modelos.Secuencial([
queras.capas.Denso(30,activación="elú",kernel_initializer="él_normal",
kernel_regularizer=l2_reg),
queras.capas.Denso(1,kernel_regularizer=l2_reg)
])
A continuación, vamos a crear una pequeña función que muestree aleatoriamente un lote de instancias del
conjunto de entrenamiento (enCapítulo 13hablaremos de la API de datos, que ofrece una alternativa mucho
mejor):
Definamos también una función que muestre el estado de entrenamiento, incluido el número
de pasos, el número total de pasos, la pérdida media desde el comienzo de la época (es decir,
usaremos elSignificarmétrica para calcularlo), y otras métricas:
definitivamenteprint_status_bar(iteración,total,pérdida,métrica=Ninguna):
métrica="-".unirse(["{}: {:.4f}".formato(metro.nombre,metro.resultado())
pormetroen[pérdida]+(métricao[])])
final=""siiteración<totalmás"\norte"
impresión("\r{}/{} - ".formato(iteración,total)+métrica,
final=final)
Este código se explica por sí mismo, a menos que no esté familiarizado con el formato de cadena de
Python: {:.4f}formateará un flotante con 4 dígitos después del punto decimal. Además, usando
\r (retorno de carro) junto confinal=""asegura que la barra de estado siempre se imprima en la
misma línea. En el cuaderno, elprint_status_bar()La función también incluye una barra de
progreso, pero podría usar la práctica biblioteca tqdm en su lugar.
porépocaenrango(1,n_épocas+1):
impresión("Época {}/{}".formato(época,n_épocas))
porpasoenrango(1,n_pasos+1):
X_lote,y_lote=lote_aleatorio(X_train_scaled,y_tren) cont.f..cinta
de degradado()comocinta:
y_pred=modelo(X_lote,capacitación=Verdadero) pérdida_principal=t.f..
reducir_media(loss_fn(y_lote,y_pred)) pérdida=t.f..añadir_n([pérdida_principal]+
modelo.pérdidas) gradientes=cinta.degradado(pérdida,modelo.
variables_entrenables) optimizador.aplicar_gradientes(Código Postal(gradientes,modelo
.variables_entrenables)) pérdida_media(pérdida) pormétricoenmétrica:
métrico(y_lote,y_pred)
print_status_bar(paso*tamaño del lote,Len(y_tren),pérdida_media,métrica)
print_status_bar(Len(y_tren),Len(y_tren),pérdida_media,métrica) pormétricoen[
pérdida_media]+métrica:
métrico.restablecer_estados()
• Creamos dos bucles anidados: uno para las épocas, el otro para los lotes dentro de
una época.
• Luego muestreamos un lote aleatorio del conjunto de entrenamiento.
• A continuación, actualizamos la pérdida media y las métricas (sobre la época actual) y mostramos
la barra de estado.
• Al final de cada época, mostramos la barra de estado nuevamente para que se vea completa11y
para imprimir un salto de línea, y restablecemos los estados de la pérdida media y las métricas.
porvariableenmodelo.Variables:
sivariable.restricciónno esNinguna:
variable.asignar(variable.restricción(variable))
Lo que es más importante, este ciclo de entrenamiento no maneja capas que se comporten de manera
diferente durante el entrenamiento y la prueba (p. ej.,Normalización por lotesoAbandonar).Para manejar
esto, necesita llamar al modelo conentrenamiento=Verdaderoy asegúrese de que propague esto a cada capa
que lo necesite.12
Como puede ver, hay muchas cosas que debe hacer bien, es fácil cometer un error. Pero
en el lado positivo, obtienes el control total, así que es tu decisión.
Ahora que ya sabes cómo personalizar cualquier parte de tus maquetas13y algoritmos de
entrenamiento, veamos cómo puede usar la función de generación automática de gráficos de
TensorFlow: puede acelerar considerablemente su código personalizado y también lo hará portátil a
cualquier plataforma compatible con TensorFlow (ver???).
11 La verdad es que no procesamos cada una de las instancias del conjunto de entrenamiento porque muestreamos las instancias ejecutadas.
domly, por lo que algunos se procesaron más de una vez, mientras que otros no se procesaron en absoluto. En la práctica está bien. Además, si el
tamaño del conjunto de entrenamiento no es un múltiplo del tamaño del lote, nos perderemos algunos casos.
13 Con la excepción de los optimizadores, muy pocas personas los personalizan: consulte el cuaderno para ver un ejemplo.
definitivamentecubo(X):
devolverX**3
Obviamente, podemos llamar a esta función con un valor de Python, como un int o un float, o
podemos llamarla con un tensor:
Esta función TF se puede usar exactamente como la función original de Python y devolverá el
mismo resultado (pero como tensores):
@tf.función
definitivamentecubo_tf(X):
devolverX**3
TensorFlow optimiza el gráfico de cálculo, elimina los nodos no utilizados, simplifica las
expresiones (p. ej., 1 + 2 se reemplazaría por 3) y más. Una vez que el gráfico optimizado está
listo, la Función TF ejecuta eficientemente las operaciones en el gráfico, en el orden apropiado
(y en paralelo cuando puede). Como resultado, una función TF generalmente se ejecutará
mucho más rápido que la función original de Python, especialmente si realiza tareas complejas.
Además, cuando escribe una función de pérdida personalizada, una métrica personalizada, una capa
personalizada o cualquier otra función personalizada, y la usa en un modelo Keras (como hicimos a lo largo
de este capítulo), Keras convierte automáticamente su función en una función TF, no hay necesidad de usar
tf.función().Entonces, la mayoría de las veces, toda esta magia es 100% transparente.
TF Function genera un nuevo gráfico para cada conjunto único de formas de entrada y tipos de
datos, y lo almacena en caché para llamadas posteriores. Por ejemplo, si llama
tf_cube(tf.constant(10)),se generará un gráfico para int32 tensores de forma []. Entonces si llamas
tf_cubo(tf.constante(20)),se reutilizará el mismo gráfico. Pero si luego llamas
tf_cubo(tf.constante([10, 20])),se generará un nuevo gráfico para int32 tensores de forma
[2]. Así es como las funciones TF manejan el polimorfismo (es decir, tipos y formas de
argumentos variables). Sin embargo, esto solo es cierto para los argumentos de tensor: si
pasa valores numéricos de Python a una función TF, se generará un nuevo gráfico para
cada valor distinto: por ejemplo, llamandotf_cubo(10)ytf_cubo(20)generará dos gráficos.
Autógrafo y Trazado
Entonces, ¿cómo genera TensorFlow gráficos? Bueno, primero comienza analizando el código fuente
de la función de Python para capturar todas las declaraciones de flujo de control, comoporbucles y
tiempobucles,sideclaraciones, así comoromper, continuarydevolverdeclaraciones. Este primer paso se
llamaautógrafo. La razón por la que TensorFlow tiene que analizar el código fuente es que Python no
proporciona ninguna otra forma de capturar declaraciones de flujo de control: ofrece métodos
mágicos como __agregar__()o __mul__()para capturar operadores como
14 Sin embargo, en este ejemplo trivial, el gráfico de cálculo es tan pequeño que no hay nada que optimizar, por lo que
tf_cubo()en realidad corre mucho más lento quecubo().
Luego, TensorFlow llama a esta función "actualizada", pero en lugar de pasar el argumento real,
pasa untensor simbólico, es decir, un tensor sin ningún valor real, solo un nombre, un tipo de
datos y una forma. Por ejemplo, si llamasuma_cuadrados(tf. constante(10)),entonces el
tf__suma_cuadrados()La función se llamará con un tensor simbólico de tipo int32 y forma []. La
función se ejecutará enmodo gráfico, lo que significa que cada operación de TensorFlow
simplemente agregará un nodo en el gráfico para representarse a sí mismo y su(s) tensor(es) de
salida (a diferencia del modo normal, llamadoejecución ansiosa, o modo ansioso). En el modo
gráfico, las operaciones TF no realizan ningún cálculo real. Esto debería resultarte familiar si
conoces TensorFlow 1, ya que el modo gráfico era el modo predeterminado. EnFigura 12-4,
puedes ver eltf__suma_cuadrados()función que se llama con un tensor simbólico como
argumento (en este caso, un tensor int32 de forma []), y el gráfico final generado durante el
rastreo. Las elipses representan operaciones y las flechas representan tensores (tanto la función
generada como el gráfico están simplificados).
Reglas de la función TF
La mayoría de las veces, convertir una función de Python que realiza operaciones de TensorFlow en
una función TF es trivial: simplemente decórela con @función tfo deja que Keras se encargue de ello
por ti. Sin embargo, hay algunas reglas a respetar:
• Si llama a cualquier biblioteca externa, incluida NumPy o incluso la biblioteca estándar, esta
llamada se ejecutará solo durante el seguimiento, no será parte del gráfico. De hecho, un gráfico
de TensorFlow solo puede incluir construcciones de TensorFlow (tensores, operaciones, variables,
conjuntos de datos, etc.). Así que asegúrate de usartf.reduce_sum()en vez de
np.suma(),ytf.sort()en lugar del incorporadoordenado ()función, y así sucesivamente (a menos que
realmente desee que el código se ejecute solo durante el seguimiento).
• Puede llamar a otras funciones de Python o funciones TF, pero deben seguir las
mismas reglas, ya que TensorFlow también capturará sus operaciones en el gráfico de
cálculo. Tenga en cuenta que estas otras funciones no necesitan estar decoradas con
@tf.función.
• Si la función crea una variable de TensorFlow (o cualquier otro objeto de TensorFlow con estado,
como un conjunto de datos o una cola), debe hacerlo en la primera llamada, y solo entonces, de
lo contrario obtendrá una excepción. Por lo general, es preferible crear variables fuera de la
función TF (por ejemplo, en elconstruir()método de una capa personalizada).
• TensorFlow solo capturaráporbucles que iteran sobre un tensor o unConjunto de datos.Asi que
asegúrate de usarpara i en tf.range(10)más bien quepara i en el rango (10),o
de lo contrario, el bucle no se capturará en el gráfico. En su lugar, se ejecutará durante el rastreo.
Esto puede ser lo que quieres, si elporloop está destinado a construir el gráfico, por ejemplo,
para crear cada capa en una red neuronal.
¡Es hora de resumir! En este capítulo, comenzamos con una breve descripción general de
TensorFlow, luego analizamos la API de bajo nivel de TensorFlow, incluidos tensores,
operaciones, variables y estructuras de datos especiales. Luego usamos estas herramientas
para personalizar casi todos los componentes en tf.keras. Finalmente, analizamos cómo las
funciones TF pueden mejorar el rendimiento, cómo se generan los gráficos usando autógrafos y
trazados, y qué reglas seguir al escribir funciones TF (si desea abrir un poco más la caja negra,
por ejemplo, para explorar el gráficos generados, encontrará más detalles técnicos en???).
En el próximo capítulo, veremos cómo cargar y preprocesar datos de manera eficiente 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 13 en la
versión final del libro.
Hasta ahora, solo hemos utilizado conjuntos de datos que caben en la memoria, pero los sistemas de
aprendizaje profundo a menudo se entrenan en conjuntos de datos muy grandes que no caben en la RAM.
Ingerir un gran conjunto de datos y preprocesarlo de manera eficiente puede ser complicado de implementar
con otras bibliotecas de aprendizaje profundo, pero TensorFlow lo hace fácil gracias a laAPI de datos:
simplemente crea un objeto de conjunto de datos, le dice dónde obtener los datos, luego lo transforma de la
manera que desee, y TensorFlow se encarga de todos los detalles de implementación, como subprocesos
múltiples, colas, procesamiento por lotes, captura previa, etc. .
De serie, la API de datos puede leer archivos de texto (como archivos CSV), archivos binarios con
registros de tamaño fijo y archivos binarios que usan el formato TFRecord de TensorFlow, que
admite registros de diferentes tamaños. TFRecord es un formato binario flexible y eficiente
basado en Protocol Buffers (un formato binario de código abierto). La API de datos también
tiene soporte para leer desde bases de datos SQL. Además, muchas extensiones de código
abierto están disponibles para leer todo tipo de fuentes de datos, como el servicio BigQuery de
Google.
Sin embargo, leer grandes conjuntos de datos de manera eficiente no es la única dificultad: los datos
también deben ser preprocesados. De hecho, no siempre se compone estrictamente de campos
numéricos convenientes: a veces habrá características de texto, características categóricas, etc. Para
manejar esto, TensorFlow proporciona laFunciones API: le permite convertir fácilmente estas
funciones en funciones numéricas que su red neuronal puede consumir. Para
403
ejemplo, las características categóricas con una gran cantidad de categorías (como ciudades o
palabras) se pueden codificar usandoincrustaciones(como veremos, una incrustación es un vector
denso entrenable que representa una categoría).
• TF Datasets (TFDS) proporciona una función conveniente para descargar muchos conjuntos de datos
comunes de todo tipo, incluidos los grandes como ImageNet, y proporciona objetos de conjuntos de
datos convenientes para manipularlos usando la API de datos.
¡Entonces empecemos!
La API de datos
Toda la API de datos gira en torno al concepto de unconjunto de datos: como puede sospechar, esto representa una
secuencia de elementos de datos. Por lo general, usará conjuntos de datos que leen gradualmente los datos del disco,
pero para simplificar, creemos un conjunto de datos completamente en RAM usando
tf.data.Dataset.from_tensor_slices():
> > > X=t.f..rango(10)#cualquier tensor de datos
> > > conjunto de datos=t.f..datos.conjunto de datos.from_tensor_slices(X)
> > > conjunto de datos
<Formas de TensorSliceDataset: (), tipos: tf.int32>
Simplemente puede iterar sobre los elementos de un conjunto de datos como este:
Transformaciones de encadenamiento
Una vez que tenga un conjunto de datos, puede aplicarle todo tipo de transformaciones llamando a sus
métodos de transformación. Cada método devuelve un nuevo conjunto de datos, por lo que puede encadenar
transformaciones como esta (esta cadena se ilustra enFigura 13-1):
...
tf.Tensor([0 1 2 3 4 5 6], forma=(7,), dtype=int32)
tf.Tensor([7 8 9 0 1 2 3], forma=(7,), dtype=int32)
tf.Tensor([4 5 6 7 8 9 0], forma=(7,), dtype=int32)
tf.Tensor([1 2 3 4 5 6 7], forma=(7,), dtype=int32)
tf.Tensor([8 9], forma=(2,), dtype=int32)
En este ejemplo, primero llamamos alrepetir()en el conjunto de datos original y devuelve un nuevo conjunto
de datos que repetirá los elementos del conjunto de datos original 3 veces. ¡Por supuesto, esto no copiará
todos los datos en la memoria 3 veces! De hecho, si llama a este método sin argumentos, el nuevo conjunto
de datos repetirá el conjunto de datos de origen para siempre. Entonces llamamos a lalote()método en este
nuevo conjunto de datos, y nuevamente esto crea un nuevo conjunto de datos. Este agrupará los elementos
del conjunto de datos anterior en lotes de 7 elementos. Finalmente, iteramos sobre los elementos de este
conjunto de datos final. Como puedes ver, ellote()
El método tuvo que generar un lote final de tamaño 2 en lugar de 7, pero puede llamarlo con
drop_remainder=Verdaderosi desea que suelte este lote final para que todos los lotes tengan exactamente el
mismo tamaño.
que asegúrese de mantener una referencia a estos nuevos conjuntos de datos (por ejemplo,conjunto
También puede aplicar cualquier transformación que desee a los elementos llamando almapa()
método. Por ejemplo, esto crea un nuevo conjunto de datos con todos los elementos duplicados:
Esta función es la que llamará para aplicar cualquier preprocesamiento que desee a sus datos. A
veces, esto incluirá cálculos que pueden ser bastante intensivos, como remodelar o rotar una
imagen, por lo que normalmente querrá generar varios subprocesos para acelerar las cosas: es
tan simple como configurar elnum_llamadas_paralelasargumento.
Mientras que lamapa()aplica una transformación a cada elemento, elaplicar()El método aplica una
transformación al conjunto de datos como un todo. Por ejemplo, el siguiente código "desmonta" el
conjunto de datos aplicando eldeshacer lote()función al conjunto de datos (esta función es actualmente
experimental, pero lo más probable es que se mueva a la API central en una versión futura). Cada
elemento del nuevo conjunto de datos será un único tensor de enteros en lugar de un lote de 7
enteros:
A menudo querrá ver solo algunos elementos de un conjunto de datos. Puedes usar eltomar()
método para eso:
...
tf.Tensor(0, forma=(), dtype=int64)
tf.Tensor(2, forma=(), dtype=int64)
tf.Tensor(4, forma=(), dtype=int64)
...
tf.Tensor([0 2 3 6 7 9 4], forma=(7,), dtype=int64)
tf.Tensor([5 0 1 1 8 6 5], forma=(7,), dtype=int64)
tf.Tensor([4 8 7 1 2 3 0], forma=(7,), dtype=int64)
tf.Tensor([5 4 2 7 8 9 9], forma=(7,), dtype=int64)
tf.Tensor([3 6], forma=(2,), dtype=int64)
Para un conjunto de datos grande que no cabe en la memoria, este enfoque simple de búfer de
barajado puede no ser suficiente, ya que el búfer será pequeño en comparación con el conjunto de
datos. Una solución es mezclar los datos de origen en sí (por ejemplo, en Linux puede mezclar
archivos de texto usando elshufdominio). ¡Esto definitivamente mejorará mucho la reproducción
aleatoria! Sin embargo, incluso si se mezclan los datos de origen, por lo general querrá mezclarlos un
poco más, o de lo contrario se repetirá el mismo orden en cada época, y el modelo puede terminar
sesgado (por ejemplo, debido a algunos patrones falsos presentes por probabilidad en el orden de los
datos de origen). Para mezclar un poco más las instancias, un enfoque común es dividir los datos de
origen en varios archivos y luego leerlos en un orden aleatorio durante el entrenamiento. Sin
embargo, las instancias ubicadas en el mismo archivo seguirán estando cerca unas de otras. Para
evitar esto, puede elegir varios archivos al azar y leerlos simultáneamente, intercalando sus líneas.
Luego, además de eso, puede agregar un búfer de barajado usando elbarajar()
método. Si todo esto le parece mucho trabajo, no se preocupe: la API de datos en realidad hace que todo esto
sea posible en solo unas pocas líneas de código. Veamos cómo hacer esto.
1 Imagine una baraja ordenada de cartas a su izquierda: suponga que solo toma las 3 cartas superiores y las baraja, luego elige
uno al azar y colócalo a tu derecha, manteniendo los otros 2 en tus manos. Tome otra carta a su izquierda, baraje
las 3 cartas en sus manos y elija una de ellas al azar, y colóquela a su derecha. Cuando termines de pasar todas
las cartas así, tendrás una baraja de cartas a tu derecha: ¿crees que estará perfectamente barajada?
Primero, supongamos que cargó el conjunto de datos de vivienda de California, lo mezcló (a menos que ya lo
haya hecho), lo dividió en un conjunto de entrenamiento, un conjunto de validación y un conjunto de prueba,
luego dividió cada conjunto en muchos archivos CSV que cada se vea así (cada fila contiene 8 características
de entrada más el valor de la casa mediana objetivo):
MedInc,HouseAge,AveRooms,AveBedrms,Popul,AveOccup,Lat,Long,MedianHouseValue
3.5214,15.0,3.0499,1.1065,1447.0,1.6059,37.63,-122.43,1.442
5.3275,5.0,6.4900,0.9910,3464.0,3.4433,33.69,-117.39,1.687
3.1,29.0,7.5423,1.5915,1328.0,2.2508,38.44,-122.98,1.621 [...]
Ahora vamos a crear un conjunto de datos que contenga solo estas rutas de archivo:
ruta_archivo_conjunto_datos=t.f..datos.conjunto de datos.list_files(tren_filepaths,semilla=42)
Por defecto, ellist_files()La función devuelve un conjunto de datos que mezcla las rutas de los
archivos. En general, esto es algo bueno, pero puede establecerbarajar = falsosi no quieres eso,
por alguna razón.
A continuación, podemos llamar a laintercalar()método para leer de 5 archivos a la vez e intercalar sus
líneas (saltando la primera línea de cada archivo, que es la fila del encabezado, usando el
saltar()método):
n_lectores=5
conjunto de datos=ruta_archivo_conjunto_datos.intercalar(
lambdaruta de archivo:t.f..datos.TextLineDataset(ruta de archivo).saltar(1),
duración del ciclo=n_lectores)
losintercalar()El método creará un conjunto de datos que extraerá 5 rutas de archivo del
ruta de archivo_conjunto de datos,y para cada uno llamará a la función que le dimos (una lambda en este ejemplo) para
crear un nuevo conjunto de datos, en este caso unConjunto de datos de línea de texto.Luego recorrerá estos 5 conjuntos
de datos, leyendo una línea a la vez de cada uno hasta que todos los conjuntos de datos se queden sin elementos.
Luego obtendrá las siguientes 5 rutas de archivo desde elruta de archivo_conjunto de datos,e intercalarlos de la misma
manera, y así sucesivamente hasta que se quede sin rutas de archivo.
Estas son las primeras filas (ignorando la fila del encabezado) de 5 archivos CSV, elegidos al azar. ¡Se
ve bien! Pero como puede ver, estas son solo cadenas de bytes, necesitamos analizarlas y también
escalar los datos.
definitivamentepreprocesar(línea):
defs=[0.]*n_entradas+[t.f..constante([],tipo de d=t.f..flotar32)]
campos=t.f..yo.decodificar_csv(línea,record_defaults=defs) X=t.f..pila
(campos[:-1]) y=t.f..pila(campos[-1:]) devolver(X-X_media)/X_std,y
• Lospreprocesar()La función toma una línea CSV y comienza analizándola. Para ello utiliza el
tf.io.decode_csv()función, que toma dos argumentos: el primero es la línea para analizar, y el
segundo es una matriz que contiene el valor predeterminado para cada columna en el archivo
CSV. Esto le dice a TensorFlow no solo el valor predeterminado para cada columna, sino también
la cantidad de columnas y el tipo de cada columna. En este ejemplo, le decimos que todas las
columnas de características son flotantes y que los valores faltantes deberían ser 0 por defecto,
pero proporcionamos una matriz vacía de tipotf.float32como el valor predeterminado para la
última columna (el destino): esto le dice a TensorFlow que esta columna contiene
• Losdecodificar_csv()La función devuelve una lista de tensores escalares (uno por columna) pero
necesitamos devolver matrices de tensores 1D. Entonces llamamostf.pila()en todos los tensores
excepto el último (el objetivo): esto apilará estos tensores en una matriz 1D. Luego hacemos lo
mismo para el valor objetivo (esto lo convierte en una matriz de tensores 1D con un solo valor, en
lugar de un tensor escalar).
• Finalmente, escalamos las características de entrada restando las medias de las características y luego
dividiéndolas por las desviaciones estándar de las características, y devolvemos una tupla que contiene las
características escaladas y el objetivo.
definitivamentecsv_reader_dataset(rutas de archivos,repetir=Ninguna,n_lectores=5,
n_read_threads=Ninguna,shuffle_buffer_size=10000,
n_parse_hilos=5,tamaño del lote=32):
conjunto de datos=t.f..datos.conjunto de datos.list_files(rutas de archivos).repetir(repetir) conjunto de
datos=conjunto de datos.intercalar(
lambdaruta de archivo:t.f..datos.TextLineDataset(ruta de archivo).saltar(1),
duración del ciclo=n_lectores,num_llamadas_paralelas=n_read_threads) conjunto
de datos=conjunto de datos.barajar(shuffle_buffer_size)
conjunto de datos=conjunto de datos.mapa(preprocesar,num_llamadas_paralelas=n_parse_hilos) conjunto de
datos=conjunto de datos.lote(tamaño del lote) devolverconjunto de datos.captación previa(1)
Todo debería tener sentido en este código, excepto la última línea (captación previa(1)), que en
realidad es bastante importante para el rendimiento.
captación previa
Llamandocaptación previa(1)al final, estamos creando un conjunto de datos que hará todo lo posible
para estar siempre un lote por delante2. En otras palabras, mientras nuestro algoritmo de
entrenamiento está trabajando en un lote, el conjunto de datos ya estará trabajando en paralelo para
preparar el siguiente lote. Esto puede mejorar drásticamente el rendimiento, como se ilustra enFigura
13-3. Si también nos aseguramos de que la carga y el preprocesamiento sean multiproceso
(configurandonum_par allel_callsal llamarintercalar()ymapa()),podemos explotar múltiples núcleos en la
CPU y, con suerte, hacer que la preparación de un lote de datos sea más corta que ejecutar un paso de
entrenamiento en la GPU: de esta manera, la GPU se utilizará casi al 100% (excepto por el tiempo de
transferencia de datos de la CPU a la GPU) y el entrenamiento se ejecutará mucho más rápido.
2 En general, la obtención previa de un lote está bien, pero en algunos casos es posible que deba obtener algunos más. alternativa
Por lo tanto, puede dejar que TensorFlow decida automáticamente al pasartf.data.experimental.AUTOTONE (esta es una
característica experimental por ahora).
Con eso, ahora puede crear canalizaciones de entrada eficientes para cargar y preprocesar
datos de varios archivos de texto. Hemos discutido los métodos de conjuntos de datos más
comunes, pero hay algunos más que quizás desee ver:concatenar(), zip(), ventana(),
reducir(), caché(), fragmento(), flat_map()ypadded_batch().También hay
ple más métodos de clase:desde_generador()yde_tensores(),que crean un nuevo conjunto de datos a
partir de un generador de Python o una lista de tensores, respectivamente. Consulte la
documentación de la API para obtener más detalles. También tenga en cuenta que hay características
experimentales disponibles entf.data.experimental,muchos de los cuales probablemente llegarán a la
API principal en futuras versiones (por ejemplo, consulte elCsvDatasetclase y elSqlDataset
clases).
Ahora podemos usar elcsv_reader_conjunto de datos()función para crear un conjunto de datos para el conjunto de entrenamiento
(asegurándose de que repite los datos para siempre), el conjunto de validación y el conjunto de prueba:
juego de trenes=csv_reader_dataset(tren_filepaths,repetir=Ninguna)
conjunto_valido=csv_reader_dataset(rutas_de_archivo_válidas) equipo de prueba=
csv_reader_dataset(rutas_de_archivo_de_prueba)
Y ahora podemos simplemente construir y entrenar un modelo de Keras utilizando estos conjuntos de datos.3Todo lo
que tenemos que hacer es llamar aladaptar()método con los conjuntos de datos en lugar deX_treny
modelo=queras.modelos.Secuencial([...])
modelo.compilar([...])
modelo.adaptar(juego de trenes,pasos_por_época=Len(X_tren)//tamaño del lote,épocas=10,
datos_de_validación=conjunto_valido, pasos_de_validación=
Len(X_válido)//tamaño del lote)
A diferencia de los otros conjuntos, elnuevo setnormalmente no contendrá etiquetas (si las contiene, Keras
simplemente las ignorará). Tenga en cuenta que en todos estos casos, aún puede usar matrices NumPy en
lugar de conjuntos de datos si lo desea (pero, por supuesto, primero deben haberse cargado y preprocesado).
Si desea crear su propio ciclo de entrenamiento personalizado (como enCapítulo 12), puede simplemente iterar sobre
el conjunto de entrenamiento, de forma muy natural:
porX_lote,y_loteenjuego de trenes:
[...]#realizar un paso de descenso de gradiente
De hecho, incluso es posible crear una función tf. (verCapítulo 12) que realiza todo el ciclo
de entrenamiento.5
@tf.función
definitivamentetren(modelo,optimizador,loss_fn,n_épocas, [...]):
juego de trenes=csv_reader_dataset(tren_filepaths,repetir=n_épocas, [...]) por
X_lote,y_loteenjuego de trenes:
cont.f..cinta de degradado()comocinta:
3 La compatibilidad con conjuntos de datos es específica de tf.keras, no funcionará en otras implementaciones de la API de Keras.
4 La cantidad de pasos por época es opcional si el conjunto de datos solo pasa por los datos una vez, pero si no
especificarlo, la barra de progreso no se mostrará durante la primera época.
5 Tenga en cuenta que, por ahora, el conjunto de datos debe crearse dentro de la función TF. Esto puede solucionarse cuando lea
estas líneas (consulte el problema de TensorFlow n.º 25414).
¡Felicitaciones, ahora sabe cómo crear canalizaciones de entrada potentes con la API de datos!
Sin embargo, hasta ahora hemos utilizado archivos CSV, que son comunes, simples y
convenientes, pero no son realmente eficientes y no admiten muy bien estructuras de datos
grandes o complejas, como imágenes o audio. Así que usemos TFRecords en su lugar.
Si está satisfecho con los archivos CSV (o cualquier otro formato que esté
utilizando), notenerpara usar TFRecords. Como dice el dicho, si no está roto,
¡no lo arregles! Los TFRecords son útiles cuando el cuello de botella durante el
entrenamiento es cargar y analizar los datos.
El formato TFRecord
El formato TFRecord es el formato preferido de TensorFlow para almacenar grandes cantidades de datos y
leerlos de manera eficiente. Es un formato binario muy simple que solo contiene una secuencia de registros
binarios de diferentes tamaños (cada registro solo tiene una longitud, una suma de verificación CRC para
verificar que la longitud no esté dañada, luego los datos reales y finalmente una suma de verificación CRC
para el datos). Puede crear fácilmente un archivo TFRecord usando el
tf.io.TFRecordWriterclase:
cont.f..yo.Escritor de registros TF("mis_datos.tfrecord")comoF:
F.escribe(b"Este es el primer disco") F.escribe(b
"Y este es el segundo disco")
Esto generará:
tf.Tensor(b'Este es el primer registro', forma=(), dtipo=cadena) tf.Tensor(b'Y
este es el segundo registro', forma=(), dtipo=cadena)
A veces puede ser útil comprimir sus archivos TFRecord, especialmente si necesitan cargarse a
través de una conexión de red. Puede crear un archivo TFRecord comprimido configurando el
opcionesargumento:
opciones=t.f..yo.TFRecordOpciones(tipo_de_compresión="GZIP") cont.f..yo.
Escritor de registros TF("mi_comprimido.tfrecord",opciones)comoF:
[...]
Aunque cada registro puede usar cualquier formato binario que desee, los archivos TFRecord
generalmente contienen búferes de protocolo serializados (también llamadosprototipos). Este es un
formato binario portátil, extensible y eficiente desarrollado en Google en 2001 y de código abierto en
2008, y ahora se usa ampliamente, en particular engRPC, el sistema de llamadas a procedimientos
remotos de Google. Los búferes de protocolo se definen usando un lenguaje simple que se ve así:
sintaxis="proto3";
mensaje Persona{
nombre de cadena=1;
identificación int32=2;
correo electrónico de cadena repetida=3;
}
Esta definición dice que estamos usando la versión 3 del formato protobuf y especifica que cada
Personaobjeto6puede (opcionalmente) tener unnombrede tipocuerda,unidentificaciónde tipoint32,
y cero o másEmailcampos, cada uno de tipocuerda.Los números 1, 2 y 3 son los identificadores
de campo: se utilizarán en la representación binaria de cada registro. Una vez que tenga una
definición en un archivo .prototipoarchivo, puede compilarlo. Esto requiereprotocolo,el
compilador proto‐buf, para generar clases de acceso en Python (o algún otro lenguaje). Tenga
en cuenta que las definiciones de protobuf que usaremos ya se compilaron para usted, y sus
clases de Python son parte de TensorFlow, por lo que no necesitará usarprotocoloTodo lo que
necesita saber es cómo usar las clases de acceso protobuf en Python. Para ilustrar los conceptos
básicos, veamos un ejemplo simple que usa las clases de acceso generadas para elPersona
protobuf (el código se explica en los comentarios):
6 Dado que los objetos protobuf están destinados a ser serializados y transmitidos, se denominanmensajes.
Protobufs de TensorFlow
El protobuf principal que normalmente se usa en un archivo TFRecord es elEjemploprotobuf, que representa
una instancia en un conjunto de datos. Contiene una lista de funciones con nombre, donde cada función
puede ser una lista de cadenas de bytes, una lista de flotantes o una lista de enteros. Aquí está la definición
de protobuf:
sintaxis="proto3";
lista de bytes del mensaje{valor de bytes repetidos=1; }
mensaje lista flotante{repetidoflotarvalor=1[lleno=verdadero]; }
mensaje Int64List{valor int64 repetido=1[lleno=verdadero]; }
7 Este capítulo contiene lo mínimo que necesita saber sobre protobufs para usar TFRecords. Aprender más
sobre protobufs, visitehttps://homl.info/protobuf.
persona_ejemplo=Ejemplo(
caracteristicas=Características(
rasgo={
"nombre":Rasgo(bytes_list=Lista de bytes(valor=[b"Alicia"])),
"identificación":Rasgo(lista_int64=Int64Lista(valor=[123])), "correos
electrónicos":Rasgo(bytes_list=Lista de bytes(valor=[b" a@b.com ",
b" c@d.com "]))
}))
El código es un poco detallado y repetitivo, pero es bastante sencillo (y podría envolverlo
fácilmente dentro de una pequeña función auxiliar). Ahora que tenemos unEjemploprotobuf,
podemos serializarlo llamando a suSerializeToString()luego escriba los datos resultantes en un
archivo TFRecord:
¡Normalmente escribirías mucho más que un solo ejemplo! Por lo general, crearía un script de
conversión que lea desde su formato actual (por ejemplo, archivos CSV), cree unEjemplo
protobuf para cada instancia, los serializa y los guarda en varios archivos TFRecord, idealmente
mezclándolos en el proceso. Esto requiere un poco de trabajo, así que una vez más, asegúrese
de que sea realmente necesario (tal vez su tubería funcione bien con archivos CSV).
8 ¿Por qué fueEjemploincluso definido ya que no contiene más que unCaracterísticas¿objeto? Bueno, TensorFlow puede uno
day decide agregarle más campos. Mientras el nuevoEjemplodefinición todavía contiene elcaracteristicascampo, con la misma
identificación, será compatible con versiones anteriores. Esta extensibilidad es una de las grandes características de protobufs.
característica_descripción={
"nombre":t.f..yo.FixedLenFeature([],t.f..cuerda,valor por defecto=""),
"identificación":t.f..yo.FixedLenFeature([],t.f..int64,valor por defecto=0), "correos
electrónicos":t.f..yo.Característica VarLen(t.f..cuerda),
}
porserializado_ejemploent.f..datos.TFRecordDataset(["mis_contactos.tfrecord"]):
ejemplo_analizado=t.f..yo.parse_single_example(serializado_ejemplo,
característica_descripción)
Las características de longitud fija se analizan como tensores regulares, pero las características de longitud
variable se analizan como tensores dispersos. Puede convertir un tensor disperso en un tensor denso usando
tf.disperso.a_denso(),pero en este caso es más sencillo simplemente acceder a sus valores:
ALista de bytespuede contener cualquier dato binario que desee, incluido cualquier objeto
serializado. Por ejemplo, puedes usartf.io.encode_jpeg()para codificar una imagen usando
el formato JPEG, y poner estos datos binarios en unLista de bytes.Más tarde, cuando su
código lea el TFRecord, comenzará analizando elEjemplo,entonces tendrás que llamar
tf.io.decode_jpeg()para analizar los datos y obtener la imagen original (o puede usar
tf.io.decode_image(),que puede decodificar cualquier imagen BMP, GIF, JPEG o PNG). También
puede almacenar cualquier tensor que desee en unLista de bytesserializando el tensor usando
tf.io.serialize_tensor(),luego colocando la cadena de bytes resultante en unLista de bytes
rasgo. Más tarde, cuando analice el TFRecord, puede analizar estos datos usando
tf.io.parse_tensor().
Como puedes ver, elEjemploproto probablemente será suficiente para la mayoría de los casos de uso.
Sin embargo, puede ser un poco engorroso de usar cuando se trata de listas de listas. Por ejemplo,
suponga que desea clasificar documentos de texto. Cada documento puede representarse como una
lista de oraciones, donde cada oración se representa como una lista de palabras. Y quizás cada
documento también tenga una lista de comentarios, donde cada comentario también se representa
como una lista de palabras. Además, también puede haber algunos datos contextuales, como el autor
del documento, el título y la fecha de publicación. TensorFlow
SecuenciaEjemploprotobuf está diseñado para tales casos de uso.
contexto_analizado,listas_características analizadas=t.f..yo.parse_single_sequence_example(
serializado_secuencia_ejemplo,context_feature_descriptions,
descripciones_de_funciones_de_secuencia)
contenido_analizado=t.f..RaggedTensor.from_sparse(listas_características analizadas["contenido"])
Ahora que sabe cómo almacenar, cargar y analizar datos de manera eficiente, el siguiente paso es
prepararlos para que puedan alimentarse a una red neuronal. Esto significa convertir todas las características
La API de características
El preprocesamiento de sus datos se puede realizar de muchas maneras: se puede hacer con anticipación al
preparar sus archivos de datos, utilizando cualquier herramienta que desee. O puede preprocesar sus datos
sobre la marcha al cargarlos con la API de datos (p. ej., utilizando el conjunto de datosmapa()
método, como vimos anteriormente). O puede incluir una capa de preprocesamiento directamente en
su modelo. Cualquiera que sea la solución que prefiera, la API de características puede ayudarlo: es un
conjunto de funciones disponibles en eltf.feature_columnpaquete, que le permite definir cómo se
debe preprocesar cada característica (o grupo de características) en sus datos (por lo tanto, puede
pensar en esta API como el análogo de Scikit-Learn).ColumnaTransformadorclase). Comenzaremos
observando los diferentes tipos de columnas disponibles y luego veremos cómo usarlas.
Volvamos a la variante del conjunto de datos de vivienda de California que usamos enCapitulo 2, ya
que incluye una característica categórica y datos faltantes. Aquí hay una numeración simple
columna cal denominada "vivienda_edad_media":
vivienda_median_age=t.f..característica_columna.columna_numérica("vivienda_median_age")
Las columnas numéricas le permiten especificar una función de normalización usando elnormalizador_fn
argumento. Por ejemplo, modifiquemos el "edad_media_vivienda"columna para definir cómo se debe
escalar. Tenga en cuenta que esto requiere calcular con anticipación la media y la desviación estándar
de esta característica en el conjunto de entrenamiento:
Características categóricas
Para columnas categóricas con un vocabulario extenso (p. ej., para códigos postales, ciudades, palabras,
productos, usuarios, etc.), puede que no sea conveniente obtener la lista completa de posibles categorías, o
quizás se pueden agregar o eliminar categorías para frecuentemente que el uso de índices de categoría sería
demasiado poco confiable. En este caso, es posible que prefiera utilizar un
columna_categorial_con_hash_bucket().Si tuviéramos un "ciudad"característica en el conjunto de datos,
Podríamos codificarlo así:
ciudad_hash=t.f..característica_columna.categorical_column_with_hash_bucket(
"ciudad",hash_bucket_size=1000)
Esta función calculará un hash para cada categoría (es decir, para cada ciudad), módulo el
número de cubos de hash (hash_bucket_size).Debe configurar el número de cubos lo
suficientemente alto para evitar demasiadas colisiones (es decir, diferentes categorías que
terminan en el mismo cubo), pero cuanto más alto lo configure, más RAM se utilizará (por la
tabla de incrustación, como veremos). ver en breve).
Si sospecha que dos (o más) características categóricas son más significativas cuando se usan juntas,
entonces puede crear unacolumna cruzada. Por ejemplo, supongamos que a la gente le gustan
especialmente las casas antiguas en el interior y las casas nuevas cerca del océano, entonces podría ser útil
cuboizado_edad=t.f..característica_columna.columna_cubetada(
vivienda_median_age,límites=[-1.,-0.5,0.,0.5,1.])#la edad fue escalada
edad_y_ocean_proximity=t.f..característica_columna.columna_cruzada(
[cuboizado_edad,proximidad_océano],hash_bucket_size=100)
Otro caso de uso común para las columnas cruzadas es cruzar la latitud y la longitud en
una sola característica categórica: comienza dividiendo la latitud y la longitud, por ejemplo
en 20 cubos cada uno, luego cruza estas características divididas en unubicacióncolumna.
Esto creará una cuadrícula de 20×20 sobre California, y cada celda de la cuadrícula
corresponderá a una categoría:
latitud=t.f..característica_columna.columna_numérica("latitud")
longitud=t.f..característica_columna.columna_numérica("longitud")
cuboizado_latitud=t.f..característica_columna.columna_cubetada(
latitud,límites=lista(notario público.espacio lineal(32.,42.,20-1)))
longitud_en cubos=t.f..característica_columna.columna_cubetada(
longitud,límites=lista(notario público.espacio lineal(-125.,-114.,20-1)))
ubicación=t.f..característica_columna.columna_cruzada(
[cuboizado_latitud,longitud_en cubos],hash_bucket_size=1000)
Independientemente de la opción que elija para crear una característica categórica (columnas
categóricas, columnas divididas en cubos o columnas cruzadas), debe codificarse antes de que
pueda enviarla a una red neuronal. Hay dos opciones para codificar una característica
categórica: vectores one-hot oincrustaciones. Para la primera opción, simplemente use el
columna_indicador()función:
ocean_proximity_one_hot=t.f..característica_columna.indicador_columna(proximidad_océano)
Una codificación de vector one-hot tiene el tamaño de la longitud del vocabulario, lo cual está
bien si solo hay unas pocas categorías posibles, pero si el vocabulario es grande, terminará con
demasiadas entradas alimentadas a su red neuronal: tendrá demasiados pesos para aprender y
probablemente no funcionará muy bien. En particular, este suele ser el caso cuando usa cubos
de hash. En este caso, probablemente debería codificarlos usando incrustacionesen cambio.
9 Desde elvivienda_median_agecaracterística fue normalizada, los límites son para edades normalizadas.
Una incrustación es un vector denso entrenable que representa una categoría. De forma
predeterminada, las incrustaciones se inicializan aleatoriamente, por ejemplo, el "CERCA DE LA
BAHÍA"categoría podría representarse inicialmente por un vector aleatorio como [0,131, 0,890],
mientras que la "CERCA DEL OCÉANO"la categoría puede estar representada por otro vector
aleatorio como [0,631, 0,791] (en este ejemplo, estamos usando incrustaciones 2D, pero el
número de dimensiones es un hiperparámetro que puede modificar). Dado que estas
incrustaciones son entrenables, mejorarán gradualmente durante el entrenamiento y, dado que
representan categorías bastante similares, Gradient Descent ciertamente terminará
acercándolas, mientras que tenderá a alejarlas del "INTERIOR"incrustación de categoría (ver
Figura 13-4). De hecho, cuanto mejor sea la representación, más fácil será para la red neuronal
hacer predicciones precisas, por lo que el entrenamiento tiende a hacer que las incrustaciones
sean representaciones útiles de las categorías. Se llamarepresentación aprendizaje(veremos
otros tipos de aprendizaje de representación en???).
incrustaciones de palabras
Las incrustaciones generalmente no solo serán representaciones útiles para la tarea en cuestión, sino
que, con bastante frecuencia, estas mismas incrustaciones también se pueden reutilizar con éxito para
otras tareas. El ejemplo más común de esto esincrustaciones de palabras(es decir, incrustaciones de
palabras individuales): cuando está trabajando en una tarea de procesamiento de lenguaje natural, a
menudo es mejor reutilizar incrustaciones de palabras preentrenadas que entrenar las suyas propias. La
idea de utilizar vectores para representar palabras se remonta a la década de 1960 y se han utilizado
muchas técnicas sofisticadas para generar vectores útiles, incluido el uso de redes neuronales, pero las
cosas realmente despegaron en 2013, cuando Tomáš Mikolov y otros investigadores de Google
publicaron unpapel10describiendo cómo aprender incrustaciones de palabras utilizando redes
neuronales profundas, mucho más rápido que los intentos anteriores. Esto les permitió aprender
incrustaciones en un corpus de texto muy grande: entrenaron una red neuronal profunda para predecir
las palabras cercanas a cualquier palabra dada. Esto les permitió obtener asombrosas incrustaciones de
palabras. Por ejemplo, los sinónimos tenían incrustaciones muy cercanas y palabras relacionadas
semánticamente como Francia, España, Italia, etc., terminaron agrupadas. Pero no se trata solo de
proximidad: las incrustaciones de palabras también se organizaron a lo largo de ejes significativos en el
espacio de incrustación. He aquí un ejemplo famoso: si calcula Rey – Hombre
+ Mujer (sumando y restando los vectores de incrustación de estas palabras), entonces el
resultado será muy parecido a la incrustación de la palabra Reina (verFigura 13-5). En otras
palabras, ¡la palabra incrustaciones codifica el concepto de género! De manera similar, puede
calcular Madrid – España + Francia y, por supuesto, el resultado está cerca de París, lo que parece
mostrar que la noción de ciudad capital también estaba codificada en las incrustaciones.