Las múltiples aplicaciones del descenso de gradiente en TensorFlow
Publicado: 2022-03-11TensorFlow de Google es una de las herramientas líderes para entrenar e implementar modelos de aprendizaje profundo. Es capaz de optimizar arquitecturas de redes neuronales tremendamente complejas con cientos de millones de parámetros, y viene con una amplia gama de herramientas para la aceleración de hardware, la capacitación distribuida y los flujos de trabajo de producción. Estas potentes funciones pueden hacer que parezca intimidante e innecesario fuera del dominio del aprendizaje profundo.
Pero TensorFlow puede ser accesible y utilizable para problemas más simples que no están directamente relacionados con el entrenamiento de modelos de aprendizaje profundo. En esencia, TensorFlow es solo una biblioteca optimizada para operaciones de tensor (vectores, matrices, etc.) y las operaciones de cálculo utilizadas para realizar el descenso de gradiente en secuencias arbitrarias de cálculos. Los científicos de datos experimentados reconocerán el "descenso de gradiente" como una herramienta fundamental para las matemáticas computacionales, pero generalmente requiere la implementación de códigos y ecuaciones específicos de la aplicación. Como veremos, aquí es donde entra en juego la moderna arquitectura de "diferenciación automática" de TensorFlow.
Casos de uso de TensorFlow
- Ejemplo 1: regresión lineal con descenso de gradiente en TensorFlow 2.0
- ¿Qué es el descenso de gradiente?
- Ejemplo 2: vectores unitarios de dispersión máxima
- Ejemplo 3: Generación de entradas de IA contradictorias
- Pensamientos finales: Optimización de descenso de gradiente
- Descenso de gradiente en TensorFlow: desde encontrar mínimos hasta atacar sistemas de IA
Ejemplo 1: regresión lineal con descenso de gradiente en TensorFlow 2.0
Ejemplo 1 Cuaderno
Antes de llegar al código de TensorFlow, es importante familiarizarse con el descenso de gradiente y la regresión lineal.
¿Qué es el descenso de gradiente?
En los términos más simples, es una técnica numérica para encontrar las entradas de un sistema de ecuaciones que minimizan su salida. En el contexto del aprendizaje automático, ese sistema de ecuaciones es nuestro modelo , las entradas son los parámetros desconocidos del modelo y la salida es una función de pérdida a minimizar, que representa cuánto error hay entre el modelo y nuestros datos. Para algunos problemas (como la regresión lineal), existen ecuaciones para calcular directamente los parámetros que minimizan nuestro error, pero para la mayoría de las aplicaciones prácticas, necesitamos técnicas numéricas como el descenso de gradiente para llegar a una solución satisfactoria.
El punto más importante de este artículo es que el descenso de gradiente generalmente requiere diseñar nuestras ecuaciones y usar cálculo para derivar la relación entre nuestra función de pérdida y nuestros parámetros. Con TensorFlow (y cualquier herramienta moderna de diferenciación automática), nosotros manejamos el cálculo, por lo que podemos centrarnos en diseñar la solución y no tener que dedicar tiempo a su implementación.
Esto es lo que parece en un problema de regresión lineal simple. Tenemos una muestra de las alturas (h) y los pesos (w) de 150 machos adultos y comenzamos con una suposición imperfecta de la pendiente y la desviación estándar de esta línea. Después de aproximadamente 15 iteraciones de descenso de gradiente, llegamos a una solución casi óptima.
Veamos cómo producimos la solución anterior usando TensorFlow 2.0.
Para la regresión lineal, decimos que los pesos se pueden predecir mediante una ecuación lineal de alturas.
Queremos encontrar parámetros α y β (pendiente e intersección) que minimicen el error cuadrático promedio (pérdida) entre las predicciones y los valores verdaderos. Entonces, nuestra función de pérdida (en este caso, el "error cuadrático medio" o MSE) se ve así:
Podemos ver como el error cuadrático medio busca un par de líneas imperfectas, y luego con la solución exacta (α=6.04, β=-230.5).
Pongamos esta idea en acción con TensorFlow. Lo primero que hay que hacer es codificar la función de pérdida usando tensores y funciones tf.*
.
def calc_mean_sq_error(heights, weights, slope, intercept): predicted_wgts = slope * heights + intercept errors = predicted_wgts - weights mse = tf.reduce_mean(errors**2) return mse
Esto parece bastante sencillo. Todos los operadores algebraicos estándar están sobrecargados para los tensores, por lo que solo tenemos que asegurarnos de que las variables que estamos optimizando sean tensores, y usamos los métodos tf.*
para cualquier otra cosa.
Luego, todo lo que tenemos que hacer es poner esto en un ciclo de descenso de gradiente:
def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): # Any values to be part of gradient calcs need to be vars/tensors tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') # Hardcoding 25 iterations of gradient descent for i in range(25): # Do all calculations under a "GradientTape" which tracks all gradients with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) # This is the same mean-squared-error calculation as before predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) # Auto-diff magic! Calcs gradients between loss calc and params dloss_dparams = tape.gradient(loss, [tf_slope, tf_icept]) # Gradients point towards +loss, so subtract to "descend" tf_slope = tf_slope - learning_rate * dloss_dparams[0] tf_icept = tf_icept - learning_rate * dloss_dparams[1]
Tomemos un momento para apreciar lo bueno que es esto. El descenso de gradiente requiere calcular derivadas de la función de pérdida con respecto a todas las variables que estamos tratando de optimizar. Se supone que el cálculo está involucrado, pero en realidad no hicimos nada de eso. La magia está en el hecho de que:
- TensorFlow crea un gráfico de cálculo de cada cálculo realizado bajo un
tf.GradientTape()
. - TensorFlow sabe cómo calcular las derivadas (gradientes) de cada operación, por lo que puede determinar cómo cualquier variable en el gráfico de cálculo afecta a cualquier otra variable.
¿Cómo se ve el proceso desde diferentes puntos de partida?
El descenso de gradiente se acerca notablemente al MSE óptimo, pero en realidad converge a una pendiente e intersección sustancialmente diferentes que el óptimo en ambos ejemplos. En algunos casos, esto es simplemente un descenso de gradiente que converge al mínimo local, lo cual es un desafío inherente con los algoritmos de descenso de gradiente. Pero es probable que la regresión lineal solo tenga un mínimo global. Entonces, ¿cómo terminamos en la pendiente e intersección equivocadas?
En este caso, el problema es que simplificamos demasiado el código por el bien de la demostración. No normalizamos nuestros datos y el parámetro de pendiente tiene una característica diferente al parámetro de intersección. Pequeños cambios en la pendiente pueden producir cambios masivos en la pérdida, mientras que pequeños cambios en la intercepción tienen muy poco efecto. Esta enorme diferencia en la escala de los parámetros entrenables lleva a que la pendiente domine los cálculos de gradiente, con el parámetro de intercepción casi ignorado.
Entonces, el descenso de gradiente encuentra efectivamente la mejor pendiente muy cerca de la conjetura de intercepción inicial. Y dado que el error está tan cerca del óptimo, los gradientes a su alrededor son pequeños, por lo que cada iteración sucesiva se mueve solo un poquito. La normalización de nuestros datos primero habría mejorado drásticamente este fenómeno, pero no lo habría eliminado.
Este fue un ejemplo relativamente simple, pero veremos en las siguientes secciones que esta capacidad de "diferenciación automática" puede manejar algunas cosas bastante complejas.
Ejemplo 2: vectores unitarios de dispersión máxima
Ejemplo 2 Cuaderno
El siguiente ejemplo se basa en un divertido ejercicio de aprendizaje profundo en un curso de aprendizaje profundo que tomé el año pasado.
La esencia del problema es que tenemos un "codificador automático variacional" (VAE) que puede producir caras realistas a partir de un conjunto de 32 números normalmente distribuidos. Para la identificación de sospechosos, queremos usar el VAE para producir un conjunto diverso de caras (teóricas) para que un testigo elija, luego restringir la búsqueda produciendo más caras similares a las que se eligieron. Para este ejercicio, se sugirió aleatorizar el conjunto inicial de vectores, pero quería encontrar un estado inicial óptimo.
Podemos expresar el problema de esta manera: dado un espacio de 32 dimensiones, encuentre un conjunto de X vectores unitarios que estén separados al máximo. En dos dimensiones, esto es fácil de calcular exactamente. Pero para tres dimensiones (¡o 32 dimensiones!), no hay una respuesta sencilla. Sin embargo, si podemos definir una función de pérdida adecuada que esté en su mínimo cuando hayamos alcanzado nuestro estado objetivo, tal vez el gradiente descendente pueda ayudarnos a llegar allí.
Comenzaremos con un conjunto aleatorio de 20 vectores como se muestra arriba y experimentaremos con tres funciones de pérdida diferentes, cada una con una complejidad creciente, para demostrar las capacidades de TensorFlow.
Primero definamos nuestro ciclo de entrenamiento. Pondremos toda la lógica de TensorFlow bajo el método self.calc_loss()
, y luego simplemente podemos anular ese método para cada técnica, reciclando este bucle.
# Define the framework for trying different loss functions # Base class implements loop, sub classes override self.calc_loss() class VectorSpreadAlgorithm: # ... def calc_loss(self, tensor2d): raise NotImplementedError("Define this in your derived class") def one_iter(self, i, learning_rate): # self.vecs is an 20x2 tensor, representing twenty 2D vectors tfvecs = tf.convert_to_tensor(self.vecs, dtype=tf.float32) with tf.GradientTape() as tape: tape.watch(tfvecs) loss = self.calc_loss(tfvecs) # Here's the magic again. Derivative of spread with respect to # input vectors gradients = tape.gradient(loss, tfvecs) self.vecs = self.vecs - learning_rate * gradients
La primera técnica a probar es la más simple. Definimos una métrica de dispersión que es el ángulo de los vectores que están más juntos. Queremos maximizar la dispersión, pero es convencional convertirlo en un problema de minimización. Así que simplemente tomamos el negativo de la métrica de propagación:
class VectorSpread_Maximize_Min_Angle(VectorSpreadAlgorithm): def calc_loss(self, tensor2d): angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi spread_metric = tf.reduce_min(angle_pairs + disable_diag) # Convention is to return a quantity to be minimized, but we want # to maximize spread. So return negative spread return -spread_metric
Un poco de magia de Matplotlib producirá una visualización.
Esto es torpe (¡literalmente!) pero funciona. Solo dos de los 20 vectores se actualizan a la vez, aumentando el espacio entre ellos hasta que ya no son los más cercanos y luego cambiando para aumentar el ángulo entre los nuevos dos vectores más cercanos. Lo importante a tener en cuenta es que funciona . Vemos que TensorFlow pudo pasar gradientes a través del método tf.reduce_min()
y el método tf.acos()
para hacer lo correcto.
Probemos algo un poco más elaborado. Sabemos que en la solución óptima, todos los vectores deben tener el mismo ángulo con sus vecinos más cercanos. Así que agreguemos "varianza de ángulos mínimos" a la función de pérdida.
class VectorSpread_MaxMinAngle_w_Variance(VectorSpreadAlgorithm): def spread_metric(self, tensor2d): """ Assumes all rows already normalized """ angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi all_mins = tf.reduce_min(angle_pairs + disable_diag, axis=1) # Same calculation as before: find the min-min angle min_min = tf.reduce_min(all_mins) # But now also calculate the variance of the min angles vector avg_min = tf.reduce_mean(all_mins) var_min = tf.reduce_sum(tf.square(all_mins - avg_min)) # Our spread metric now includes a term to minimize variance spread_metric = min_min - 0.4 * var_min # As before, want negative spread to keep it a minimization problem return -spread_metric
Ese vector solitario hacia el norte ahora se une rápidamente a sus pares, porque el ángulo con su vecino más cercano es enorme y aumenta el término de varianza que ahora se minimiza. Pero, en última instancia, sigue siendo impulsado por el ángulo mínimo global, que sigue siendo lento para aumentar. Las ideas que tengo para mejorar esto generalmente funcionan en este caso 2D, pero no en dimensiones superiores.
Pero centrarse demasiado en la calidad de este intento matemático es perder el sentido. Observe cuántas operaciones de tensor están involucradas en los cálculos de media y varianza, y cómo TensorFlow rastrea y diferencia con éxito cada cálculo para cada componente en la matriz de entrada. Y no tuvimos que hacer ningún cálculo manual. Simplemente hicimos algunos cálculos matemáticos simples y TensorFlow hizo el cálculo por nosotros.
Finalmente, probemos una cosa más: una solución basada en la fuerza. Imagina que cada vector es un pequeño planeta atado a un punto central. Cada planeta emite una fuerza que lo repele de los otros planetas. Si tuviéramos que ejecutar una simulación física de este modelo, deberíamos terminar en la solución deseada.
Mi hipótesis es que el descenso de gradiente también debería funcionar. En la solución óptima, la fuerza tangente en cada planeta de todos los demás planetas debería cancelarse a una fuerza neta cero (si no fuera cero, los planetas se estarían moviendo). Así que calculemos la magnitud de la fuerza en cada vector y usemos el gradiente descendente para empujarlo hacia cero.
Primero, necesitamos definir el método que calcula la fuerza usando los métodos tf.*
:
class VectorSpread_Force(VectorSpreadAlgorithm): def force_a_onto_b(self, vec_a, vec_b): # Calc force assuming vec_b is constrained to the unit sphere diff = vec_b - vec_a norm = tf.sqrt(tf.reduce_sum(diff**2)) unit_force_dir = diff / norm force_magnitude = 1 / norm**2 force_vec = unit_force_dir * force_magnitude # Project force onto this vec, calculate how much is radial b_dot_f = tf.tensordot(vec_b, force_vec, axes=1) b_dot_b = tf.tensordot(vec_b, vec_b, axes=1) radial_component = (b_dot_f / b_dot_b) * vec_b # Subtract radial component and return result return force_vec - radial_component
Luego, definimos nuestra función de pérdida usando la función de fuerza anterior. Acumulamos la fuerza neta en cada vector y calculamos su magnitud. En nuestra solución óptima, todas las fuerzas deberían cancelarse y deberíamos tener fuerza cero.

def calc_loss(self, tensor2d): n_vec = tensor2d.numpy().shape[0] all_force_list = [] for this_idx in range(n_vec): # Accumulate force of all other vecs onto this one this_force_list = [] for other_idx in range(n_vec): if this_idx == other_idx: continue this_vec = tensor2d[this_idx, :] other_vec = tensor2d[other_idx, :] tangent_force_vec = self.force_a_onto_b(other_vec, this_vec) this_force_list.append(tangent_force_vec) # Use list of all N-dimensional force vecs. Stack and sum. sum_tangent_forces = tf.reduce_sum(tf.stack(this_force_list)) this_force_mag = tf.sqrt(tf.reduce_sum(sum_tangent_forces**2)) # Accumulate all magnitudes, should all be zero at optimal solution all_force_list.append(this_force_mag) # We want to minimize total force sum, so simply stack, sum, return return tf.reduce_sum(tf.stack(all_force_list))
La solución no solo funciona a la perfección (además de algo de caos en los primeros cuadros), sino que el mérito real es de TensorFlow. Esta solución involucró múltiples bucles for
, una declaración if
y una gran red de cálculos, y TensorFlow rastreó con éxito los gradientes a través de todo eso para nosotros.
Ejemplo 3: Generación de entradas de IA contradictorias
Ejemplo 3 Cuaderno
En este punto, los lectores pueden estar pensando: "¡Oye! ¡No se suponía que esta publicación fuera sobre aprendizaje profundo!" Pero técnicamente, la introducción se refiere a ir más allá de " entrenar modelos de aprendizaje profundo". En este caso, no estamos entrenando , sino explotando algunas propiedades matemáticas de una red neuronal profunda preentrenada para engañarla y que nos dé resultados incorrectos. Esto resultó ser mucho más fácil y efectivo de lo imaginado. Y todo lo que se necesitó fue otra pequeña gota de código TensorFlow 2.0.
Comenzamos por encontrar un clasificador de imágenes para atacar. Usaremos una de las mejores soluciones para la competencia Kaggle Dogs vs. Cats; específicamente, la solución presentada por Kaggler “uysimty”. Todo el crédito para ellos por proporcionar un modelo efectivo de gato contra perro y proporcionar una excelente documentación. Este es un modelo poderoso que consta de 13 millones de parámetros en 18 capas de redes neuronales. (Los lectores pueden leer más sobre esto en el cuaderno correspondiente).
Tenga en cuenta que el objetivo aquí no es resaltar ninguna deficiencia en esta red en particular, sino mostrar cómo es vulnerable cualquier red neuronal estándar con una gran cantidad de entradas.
Con algunos retoques, pude averiguar cómo cargar el modelo y preprocesar las imágenes para clasificarlas.
¡Esto parece un clasificador realmente sólido! Todas las clasificaciones de las muestras son correctas y superan el 95 % de confianza. ¡Vamos a atacarlo!
Queremos producir una imagen que obviamente sea un gato pero que el clasificador decida que es un perro con mucha confianza. ¿Cómo podemos hacer eso?
Comencemos con la imagen de un gato que clasifica correctamente, luego descubramos cómo las pequeñas modificaciones en cada canal de color (valores 0-255) de un píxel de entrada determinado afectan la salida final del clasificador. La modificación de un píxel probablemente no sirva de mucho, pero quizás los ajustes acumulativos de todos los valores de 128x128x3 = 49 152 píxeles logren nuestro objetivo.
¿Cómo sabemos en qué dirección empujar cada píxel? Durante el entrenamiento normal de la red neuronal, tratamos de minimizar la pérdida entre la etiqueta de destino y la etiqueta predicha mediante el descenso de gradiente en TensorFlow para actualizar simultáneamente los 13 millones de parámetros libres. En este caso, dejaremos fijos los 13 millones de parámetros y ajustaremos los valores de píxel de la entrada en sí.
¿Cuál es nuestra función de pérdida? Bueno, ¡es lo mucho que la imagen se parece a un gato! Si calculamos la derivada del valor cat con respecto a cada píxel de entrada, sabemos de qué manera empujar cada uno para minimizar la probabilidad de clasificación cat.
def adversarial_modify(victim_img, to_dog=False, to_cat=False): # We only need four gradient descent steps for i in range(4): tf_victim_img = tf.convert_to_tensor(victim_img, dtype='float32') with tf.GradientTape() as tape: tape.watch(tf_victim_img) # Run the image through the model model_output = model(tf_victim_img) # Minimize cat confidence and maximize dog confidence loss = (model_output[0] - model_output[1]) dloss_dimg = tape.gradient(loss, tf_victim_img) # Ignore gradient magnitudes, only care about sign, +1/255 or -1/255 pixels_w_pos_grad = tf.cast(dloss_dimg > 0.0, 'float32') / 255. pixels_w_neg_grad = tf.cast(dloss_dimg < 0.0, 'float32') / 255. victim_img = victim_img - pixels_w_pos_grad + pixels_w_neg_grad
La magia de Matplotlib nuevamente ayuda a visualizar los resultados.
¡Guau! Para el ojo humano, cada una de estas imágenes es idéntica. Sin embargo, después de cuatro iteraciones, hemos convencido al clasificador de que es un perro, ¡con un 99,4 por ciento de confianza!
Asegurémonos de que esto no sea una casualidad y que funcione en la otra dirección también.
¡Éxito! El clasificador originalmente predijo esto correctamente como un perro con un 98,4 % de confianza, y ahora cree que es un gato con un 99,8 % de confianza.
Finalmente, veamos un parche de imagen de muestra y veamos cómo cambió.
Como era de esperar, el parche final es muy similar al original, con cada píxel cambiando solo de -4 a +4 en el valor de intensidad del canal rojo. Este cambio no es suficiente para que un humano distinga la diferencia, pero cambia por completo la salida del clasificador.
Pensamientos finales: Optimización de descenso de gradiente
A lo largo de este artículo, hemos analizado la aplicación manual de gradientes a nuestros parámetros entrenables en aras de la simplicidad y la transparencia. Sin embargo, en el mundo real, los científicos de datos deberían usar optimizadores de inmediato, ya que tienden a ser mucho más efectivos, sin agregar ningún exceso de código.
Hay muchos optimizadores populares, incluidos RMSprop, Adagrad y Adadelta, pero el más común es probablemente Adam . A veces, se denominan "métodos de tasa de aprendizaje adaptativo" porque mantienen dinámicamente una tasa de aprendizaje diferente para cada parámetro. Muchos de ellos utilizan términos de momento y derivadas de orden superior aproximadas, con el objetivo de escapar de los mínimos locales y lograr una convergencia más rápida.
En una animación tomada de Sebastian Ruder, podemos ver el camino de varios optimizadores descendiendo por una superficie de pérdida. Las técnicas manuales que hemos demostrado son más comparables a "SGD". El optimizador de mejor rendimiento no será el mismo para cada superficie de pérdida; sin embargo, los optimizadores más avanzados normalmente funcionan mejor que los más simples.
Sin embargo, rara vez es útil ser un experto en optimizadores, incluso para aquellos interesados en proporcionar servicios de desarrollo de inteligencia artificial. Es un mejor uso del tiempo de los desarrolladores familiarizarse con un par, solo para comprender cómo mejoran el gradiente descendente en TensorFlow. Después de eso, pueden usar Adam de manera predeterminada y probar diferentes solo si sus modelos no convergen.
Para los lectores que estén realmente interesados en cómo y por qué funcionan estos optimizadores, la descripción general de Ruder, en la que aparece la animación, es uno de los mejores y más completos recursos sobre el tema.
Actualicemos nuestra solución de regresión lineal de la primera sección para usar optimizadores. El siguiente es el código de descenso de gradiente original usando gradientes manuales.
# Manual gradient descent operations def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') for i in range(25): with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) gradients = tape.gradient(loss, [tf_slope, tf_icept]) tf_slope = tf_slope - learning_rate * gradients[0] tf_icept = tf_icept - learning_rate * gradients[1]
Ahora, aquí está el mismo código usando un optimizador en su lugar. Verá que casi no hay código adicional (las líneas modificadas están resaltadas en azul):
# Gradient descent with Optimizer (RMSprop) def run_gradient_descent (heights, weights, init_slope, init_icept, learning_rate) : tf_slope = tf.Variable(init_slope, dtype= 'float32' ) tf_icept = tf.Variable(init_icept, dtype= 'float32' ) # Group trainable parameters into a list trainable_params = [tf_slope, tf_icept] # Define your optimizer (RMSprop) outside of the training loop optimizer = keras.optimizers.RMSprop(learning_rate) for i in range( 25 ): # GradientTape loop is the same with tf.GradientTape() as tape: tape.watch( trainable_params ) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors** 2 ) # We can use the trainable parameters list directly in gradient calcs gradients = tape.gradient(loss, trainable_params ) # Optimizers always aim to *minimize* the loss function optimizer.apply_gradients(zip(gradients, trainable_params))
¡Eso es todo! Definimos un optimizador RMSprop
fuera del ciclo de descenso de gradiente y luego usamos el optimizer.apply_gradients()
después de cada cálculo de gradiente para actualizar los parámetros entrenables. El optimizador se define fuera del ciclo porque realizará un seguimiento de los gradientes históricos para calcular términos adicionales como impulso y derivados de orden superior.
Veamos cómo se ve con el optimizador RMSprop .
¡Se ve muy bien! Ahora probemos con el optimizador de Adam .
Vaya, ¿qué pasó aquí? Parece que la mecánica del impulso en Adam hace que sobrepase la solución óptima e invierta el curso varias veces. Normalmente, esta mecánica de impulso ayuda con superficies de pérdida complejas, pero nos perjudica en este caso simple. Esto enfatiza el consejo de elegir el optimizador como uno de los hiperparámetros a ajustar al entrenar su modelo.
Cualquiera que desee explorar el aprendizaje profundo querrá familiarizarse con este patrón, ya que se usa ampliamente en arquitecturas personalizadas de TensorFlow, donde es necesario tener mecanismos de pérdida complejos que no se incluyen fácilmente en el flujo de trabajo estándar. En este ejemplo simple de descenso de gradiente de TensorFlow, solo había dos parámetros entrenables, pero es necesario cuando se trabaja con arquitecturas que contienen cientos de millones de parámetros para optimizar.
Descenso de gradiente en TensorFlow: desde encontrar mínimos hasta atacar sistemas de IA
Todos los fragmentos de código y las imágenes se generaron a partir de los cuadernos en el repositorio de GitHub correspondiente. También contiene un resumen de todas las secciones, con enlaces a los cuadernos individuales, para los lectores que deseen ver el código completo. En aras de simplificar el mensaje, se omitieron muchos detalles que se pueden encontrar en la extensa documentación en línea.
Espero que este artículo haya sido revelador y te haya hecho pensar en formas de usar el gradiente descendente en TensorFlow. Incluso si no lo usa usted mismo, es de esperar que aclare cómo funcionan todas las arquitecturas de redes neuronales modernas: cree un modelo, defina una función de pérdida y use el descenso de gradiente para ajustar el modelo a su conjunto de datos.
Como Google Cloud Partner, los expertos certificados por Google de Toptal están disponibles para las empresas que lo soliciten para sus proyectos más importantes.