De la resolución de ecuaciones al aprendizaje profundo: un tutorial de Python de TensorFlow

Publicado: 2022-03-11

Ha habido algunos desarrollos notables últimamente en el mundo de la inteligencia artificial, desde avances muy publicitados con automóviles autónomos hasta máquinas que ahora componen imitaciones de Chopin o simplemente son realmente buenos en los videojuegos.

Un elemento central de estos avances es una serie de herramientas que ayudan a derivar el aprendizaje profundo y otros modelos de aprendizaje automático, con Torch, Caffe y Theano entre los primeros. Sin embargo, desde que Google Brain pasó a ser de código abierto en noviembre de 2015 con su propio marco, TensorFlow, hemos visto que la popularidad de esta biblioteca de software se ha disparado hasta convertirse en el marco de aprendizaje profundo más popular.

TensorFlow

¿Por qué ha sucedido esto? Las razones incluyen la gran cantidad de soporte y documentación disponible, su preparación para la producción, la facilidad de distribuir los cálculos en una variedad de dispositivos y una excelente herramienta de visualización: TensorBoard.

En última instancia, TensorFlow logra combinar un conjunto completo y flexible de características técnicas con una gran facilidad de uso.

En este artículo, obtendrá una comprensión de la mecánica de esta herramienta al usarla para resolver un problema numérico general, bastante fuera de lo que suele implicar el aprendizaje automático, antes de introducir sus usos en el aprendizaje profundo con una implementación de red neuronal simple.

Antes de que empieces

Se supone un conocimiento básico de los métodos de aprendizaje automático. Si necesita ponerse al día, consulte esta publicación muy útil.

Como demostraremos la API de Python, también es beneficioso comprender Numpy.

Para configurar TensorFlow, siga las instrucciones que se encuentran aquí.

Si está utilizando Windows, debe tener en cuenta que, en el momento de escribir este artículo, debe utilizar Python 3.4+, no 2.7.

Luego, cuando esté listo, debería poder importar la biblioteca con:

 import tensorflow as tf

Paso 1 de 2 para una solución de TensorFlow: crear un gráfico

La construcción de los programas de TensorFlow generalmente consta de dos pasos principales, el primero de los cuales es construir un gráfico computacional, que describirá los cálculos que desea realizar, pero en realidad no los llevará a cabo ni mantendrá ningún valor.

Como con cualquier gráfico, tenemos nodos y aristas. Los bordes representan tensores, un tensor que representa una matriz de n dimensiones. Por ejemplo, un tensor con dimensión (o rango en TensorFlow) 0 es un escalar, el rango 1 es un vector, el rango 2 es una matriz, etc.

Los nodos representan operaciones que producen un tensor de salida, tomando tensores como entradas si es necesario. Tales operaciones incluyen adiciones ( tf.add ), multiplicaciones de matrices ( tf.matmul ) y también la creación de constantes ( tf.constant ).

Entonces, combinemos algunos de estos para nuestro primer gráfico.

 a = tf.constant([2.5, 2]) b = tf.constant([3, 6], dtype=tf.float32) total = tf.add(a, b)

Aquí hemos creado tres operaciones, dos de ellas para crear matrices unidimensionales constantes.

Los tipos de datos se deducen del argumento de valores pasado, o puede indicarlos con el argumento dtype . Si no hubiera hecho esto para b , se habría inferido un int32 y se habría arrojado un error ya que tf.add habría estado tratando de definir una adición en dos tipos diferentes.

Paso 2 de 2 para una solución de TensorFlow: ejecutar las operaciones

El gráfico está definido, pero para realizar cálculos en él (o cualquier parte de él) tenemos que configurar una sesión de TensorFlow.

 sess = tf.Session()

Alternativamente, si estamos ejecutando una sesión en un shell interactivo, como IPython, entonces usamos:

 sess = tf.InteractiveSession()

El método de run en el objeto de sesión es una forma de evaluar un tensor.

Por lo tanto, para evaluar el cálculo de suma definido anteriormente, pasamos 'total', el Tensor a recuperar, que representa la salida de la tf.add .

 print(sess.run(total)) # [ 5.5 8. ]

En este punto, presentamos la clase Variable de TensorFlow. Mientras que las constantes son una parte fija de la definición del gráfico, las variables se pueden actualizar. El constructor de clases requiere un valor inicial, pero incluso entonces, las variables necesitan una operación para inicializarlas explícitamente antes de que se lleve a cabo cualquier otra operación sobre ellas.

Las variables mantienen el estado del gráfico en una sesión en particular, por lo que debemos observar lo que sucede con múltiples sesiones usando el mismo gráfico para comprender mejor las variables.

 # Create a variable with an initial value of 1 some_var = tf.Variable(1) # Create op to run variable initializers init_op = tf.global_variables_initializer() # Create an op to replace the value held by some_var to 3 assign_op = some_var.assign(3) # Set up two instances of a session sess1 = tf.Session() sess2 = tf.Session() # Initialize variables in both sessions sess1.run(init_op) sess2.run(init_op) print(sess1.run(some_var)) # Outputs 1 # Change some_var in session1 sess1.run(assign_op) print(sess1.run(some_var)) # Outputs 3 print(sess2.run(some_var)) # Outputs 1 # Close sessions sess1.close() sess2.close()

Hemos configurado el gráfico y dos sesiones.

Después de ejecutar la inicialización en ambas sesiones (si no ejecutamos esto y luego evaluamos la variable, obtenemos un error) solo ejecutamos la operación de asignación en una sesión. Como se puede ver, el valor de la variable persiste, pero no entre sesiones.

Alimentar el gráfico para abordar problemas numéricos

Otro concepto importante de TensorFlow es el marcador de posición. Mientras que las variables mantienen el estado, los marcadores de posición se utilizan para definir qué entradas puede esperar el gráfico y su tipo de datos (y, opcionalmente, su forma). Luego podemos introducir datos en el gráfico a través de estos marcadores de posición cuando ejecutamos el cálculo.

El gráfico de TensorFlow comienza a parecerse a las redes neuronales que queremos entrenar eventualmente, pero antes de eso, usemos los conceptos para resolver un problema numérico común del mundo financiero.

Supongamos que queremos encontrar y en una ecuación como esta:

v = Ce -0.5y + Ce -y +Ce -1.5y +(C+P)e -2y

para una v dada (con C y P constantes).

Esta es una fórmula para calcular el rendimiento al vencimiento ( y ) de un bono con valor de mercado v , principal P y cupón C pagado semestralmente pero con los flujos de efectivo descontados con capitalización continua.

Básicamente, tenemos que resolver una ecuación como esta con prueba y error, y elegiremos el método de bisección para concentrarnos en nuestro valor final para y .

Primero, modelaremos este problema como un gráfico de TensorFlow.

C y P son constantes fijas y forman parte de la definición de nuestro gráfico. Deseamos tener un proceso que refine los límites inferior y superior de y . Por lo tanto, estos límites (denotados a y b ) son buenos candidatos para las variables que deben cambiarse después de cada suposición de y (considerado el punto medio de a y b ).

 # Specify the values our constant ops will output C = tf.constant(5.0) P = tf.constant(100.0) # We specify the initial values that our lower and upper bounds will be when initialised. # Obviously the ultimate success of this algorithm depends on decent start points a = tf.Variable(-10.0) b = tf.Variable(10.0) # We expect a floating number to be inserted into the graph v_target = tf.placeholder("float") # Remember the following operations are definitions, # none are carried out until an operation is evaluated in a session! y = (a+b)/2 v_guess = C*tf.exp(-0.5*y) + C*tf.exp(-y) + C*tf.exp(-1.5*y) + (C + P)*tf.exp(-2*y) # Operations to set temporary values (a_ and b_) intended to be the next values of a and b. # eg if the guess results in av greater than the target v, # we will set a_ to be the current value of y a_ = tf.where(v_guess > v_target, y, a) b_ = tf.where(v_guess < v_target, y, b) # The last stage of our graph is to assign the two temporary vals to our variables step = tf.group( a.assign(a_), b.assign(b_) )

Así que ahora tenemos una lista de operaciones y variables, cualquiera de las cuales puede evaluarse en una sesión en particular. Algunas de estas operaciones dependen de que se ejecuten otras operaciones, por lo que ejecutar, por ejemplo, v_guess desencadenará una reacción en cadena para evaluar primero otros tensores, como C y P .

Algunas de estas operaciones se basan en un marcador de posición para el cual se debe especificar un valor, entonces, ¿cómo ingresamos realmente ese valor?

Esto se hace a través del argumento feed_dict en la propia función de run .

Si queremos evaluar a_ , ingresamos el valor de nuestro marcador de posición v_target , así:

 sess.run(a_, feed_dict={v_target: 100})

dándonos 0.0.

Conecte un v_target de 130 y obtenemos -10.0.

Es nuestra operación de "paso" que en realidad requiere que todas las demás operaciones se realicen como requisito previo y, en efecto, ejecuta todo el gráfico. También es una operación que en realidad cambia el estado real en nuestra sesión. Por lo tanto, cuanto más ejecutamos el paso, más empujamos incrementalmente nuestras variables a y b hacia el valor real de y .

Entonces, digamos que nuestro valor para v en nuestra ecuación es igual a 95. Configuremos una sesión y ejecutemos nuestro gráfico en ella 100 veces.

 # Set up a session and initialize variables sess = tf.Session() tf.global_variables_initializer().run() # Run the step operation (and therefore whole graph) 100 times for i in range (100): sess.run(step, feed_dict={v_target:95})

Si evaluamos el tensor y ahora, obtenemos algo parecido a una respuesta deseable

 print(sess.run(y)) # 0.125163

Redes neuronales

Ahora que comprendemos la mecánica de TensorFlow, podemos combinar esto con algunas operaciones adicionales de aprendizaje automático integradas en TensorFlow para entrenar una red neuronal simple.

Aquí, nos gustaría clasificar los puntos de datos en un sistema de coordenadas 2d dependiendo de si se encuentran dentro de una región particular: un círculo de radio 0,5 centrado en el origen.

Por supuesto, esto se puede verificar de manera concreta simplemente verificando un punto dado (a,b) si a^2 + b^2 < 0.5 , pero para los propósitos de este experimento de aprendizaje automático, nos gustaría pasar en su lugar un conjunto de entrenamiento: una serie de puntos aleatorios y si caen en nuestra región prevista. Aquí hay una forma de crear esto:

 import numpy as np NO_OF_RANDOM_POINTS = 100 CIRCLE_RADIUS = 0.5 random_spots = np.random.rand(NO_OF_RANDOM_POINTS, 2) * 2 - 1 is_inside_circle = (np.power(random_spots[:,0],2) + np.power(random_spots[:,1],2) < CIRCLE_RADIUS).astype(int)

Haremos una red neuronal con las siguientes características:

  1. Consiste en una capa de entrada con dos nodos, en la que alimentamos nuestra serie de vectores bidimensionales contenidos dentro de “random_spots”. Esto estará representado por un marcador de posición en espera de los datos de entrenamiento.
  2. La capa de salida también tendrá dos nodos, por lo que debemos alimentar nuestra serie de etiquetas de entrenamiento ("is_inside_circle") en un marcador de posición para un escalar y luego convertir esos valores en un vector bidimensional único.
  3. Tendremos una capa oculta que consta de tres nodos, por lo que necesitaremos usar variables para nuestras matrices de pesos y vectores de sesgo, ya que estos son los valores que deben refinarse al realizar el entrenamiento.
 INPUT_LAYER_SIZE = 2 HIDDEN_LAYER_SIZE = 3 OUTPUT_LAYER_SIZE = 2 # Starting values for weights and biases are drawn randomly and uniformly from [-1, 1] # For example W1 is a matrix of shape 2x3 W1 = tf.Variable(tf.random_uniform([INPUT_LAYER_SIZE, HIDDEN_LAYER_SIZE], -1, 1)) b1 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE], -1, 1)) W2 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE, OUTPUT_LAYER_SIZE], -1, 1)) b2 = tf.Variable(tf.random_uniform([OUTPUT_LAYER_SIZE], -1, 1)) # Specifying that the placeholder X can expect a matrix of 2 columns (but any number of rows) # representing random spots X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE]) # Placeholder Y can expect integers representing whether corresponding point is in the circle # or not (no shape specified) Y = tf.placeholder(tf.uint8) # An op to convert to a one hot vector onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)

Para completar la definición de nuestro gráfico, definimos algunas operaciones que nos ayudarán a entrenar las variables para llegar a un mejor clasificador. Estos incluyen los cálculos matriciales, las funciones de activación y el optimizador.

 LEARNING_RATE = 0.01 # Op to perform matrix calculation X*W1 + b1 hidden_layer = tf.add(tf.matmul(X, W1), b1) # Use sigmoid activation function on the outcome activated_hidden_layer = tf.sigmoid(hidden_layer) # Apply next weights and bias (W2, b2) to hidden layer and then apply softmax function # to get our output layer (each vector adding up to 1) output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2)) # Calculate cross entropy for our loss function loss = -tf.reduce_sum(onehot_output * tf.log(output_layer)) # Use gradient descent optimizer at specified learning rate to minimize value given by loss tensor train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)

Habiendo configurado nuestro gráfico, es hora de configurar una sesión y ejecutar el "train_step" (que también ejecuta cualquier operación de requisito previo). Algunas de estas operaciones usan marcadores de posición, por lo que es necesario proporcionar los valores correspondientes. Este paso de entrenamiento representa una época en nuestro algoritmo de aprendizaje y, como tal, se repite sobre el número de épocas que deseamos ejecutar. Podemos ejecutar otras partes del gráfico, como el tensor de "pérdida" con fines informativos.

 EPOCH_COUNT = 1000 sess = tf.Session() tf.global_variables_initializer().run() for i in range(EPOCH_COUNT): if i%100 == 0: print('Loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle}))) sess.run(train_step, feed_dict={X: random_spots, Y: is_inside_circle}) print('Final loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle})))

Una vez que hemos entrenado el algoritmo, podemos alimentar un punto y obtener la salida de la red neuronal de la siguiente manera:

 sess.run(output_layer, feed_dict={X: [[1, 1]]}) # Hopefully something close to [1, 0] sess.run(output_layer, feed_dict={X: [[0, 0]]}) # Hopefully something close to [0, 1]

Podemos clasificar el punto como fuera del círculo si el primer miembro del vector de salida es mayor que 0,5, dentro de lo contrario.

Al ejecutar el tensor de la output_layer de salida para muchos puntos, podemos tener una idea de cómo el alumno visualiza la región que contiene los puntos clasificados positivamente. Vale la pena jugar con el tamaño del conjunto de entrenamiento, la tasa de aprendizaje y otros parámetros para ver qué tan cerca podemos llegar al círculo que pretendíamos.

casi triangulo
Conjunto de entrenamiento: 100 puntos
Tasa de aprendizaje: 0.01
Épocas: 1000

pequeño triángulo
Conjunto de entrenamiento: 1000 puntos
Tasa de aprendizaje: 0.01
Épocas: 1000

triángulo grande
Conjunto de entrenamiento: 1000 puntos
Tasa de aprendizaje: 0.01
Épocas: 10000

casi círculo
Conjunto de entrenamiento: 1000 puntos
Tasa de aprendizaje: 0.001
Épocas: 10000

Envolver

Esta es una buena lección de que un mayor conjunto de entrenamiento o cantidad de época no es garantía para un buen alumno: la tasa de aprendizaje debe ajustarse adecuadamente.

Esperamos que estas demostraciones le hayan dado una buena idea de los principios básicos de TensorFlow y le proporcionen una base sólida sobre la cual implementar técnicas más complejas.

No hemos cubierto conceptos como Tensorboard o entrenar nuestros modelos a través de GPU, pero estos están bien cubiertos en la documentación de TensorFlow. Se pueden encontrar varias recetas en la documentación que pueden ayudarlo a ponerse al día para abordar emocionantes tareas de aprendizaje profundo utilizando este poderoso marco.

Relacionado: Las muchas aplicaciones del descenso de gradiente en TensorFlow