Han habido algunos desarrollos notables últimamente en el mundo de la inteligencia artificial, desde el progreso muy publicitado con los autos sin conductor hasta las máquinas que ahora componen imitaciones Chopin o solo ser muy bueno en los videojuegos.
Para estos avances, son fundamentales algunas herramientas que ayudan a derivar el aprendizaje profundo y otros modelos de aprendizaje automático, entre los que destacan Torch, Caffe y Theano. Sin embargo, desde que Google Brain fue fuente abierta en noviembre de 2015 con su propio framework, TensorFlow, hemos visto que la popularidad de esta biblioteca de software se dispara para ser el framework de aprendizaje profundo más popular.
¿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 distribución de 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 gran facilidad de uso.
En este artículo, obtendrás una comprensión de la mecánica de esta herramienta al usarla para resolver un problema numérico general bastante fuera de lo que normalmente implica el aprendizaje automático antes de introducir sus usos en el aprendizaje profundo con una simple implementación de red neuronal.
Antes de que Empieces
Se asume un conocimiento básico de los métodos de aprendizaje automático. Si necesitas ponerte al día, consulta esta publicación.
Como vamos a demostrar la API de Python, una comprensión de Numpy también es beneficiosa.
Para configurar TensorFlow, sigue las instrucciones que se encuentran aquí.
Si usas Windows, debes tener en cuenta que en el momento de redactar este documento, debes usar Python 3.4+, no 2.7.
Luego, cuando estés listo, deberías poder importar la biblioteca con:
import tensorflow as tf
Paso 1 de 2 a una solución TensorFlow: crea una gráfica
La construcción de los programas de TensorFlow generalmente consta de dos pasos principales, el primero de los cuales es construir una gráfica computacional que describirá los cálculos que deseas llevar a cabo, pero en realidad no los llevarás a cabo ni tendrán ningún valor.
Como con cualquier gráfica, tenemos nodos y bordes. Los bordes representan tensores, un tensor que representa una matriz n-dimensional. Por ejemplo, un tensor con dimensión (o rango en TensorFlow speak) 0 es un escalar, rango 1 un vector, rango 2 una matriz y así sucesivamente.
Los nodos representan operaciones que producen tensores de salida y toman tensores como entradas si es necesario. Tales operaciones incluyen adiciones (
tf.add
), multiplicaciones de matriz (tf.matmul
), al igual que la creación de constantes (tf.constant
).
Así que combinemos algunos de estos para nuestra primera gráfica.
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 éstas para crear matrices de constante 1-d.
Los tipos de data se deducen del argumento de valores que se pasó o puedes denotarlos con el argumento
dtype
. Si yo no hubiese hecho esto con b
, entonces un int32
hubiese sido inferido y un error dado como tf.add
hubiese tratado de definir una adición en dos tipos diferentes.Paso 2 de 2 para una Solución TensorFlow: Ejecuta las Operaciones
La gráfica está definida, pero para poder hacer los cálculos en ella (o cualquier parte de ella) debemos instalar una Sesión TensorFlow.
sess = tf.Session()
De manera alternativa, si estamos ejecutando una sesión en una consola interactiva, como IPython, luego utilizamos:
sess = tf.InteractiveSession()
El método
run
en el objeto de sesión es una forma de evaluar un Tensor.
Por ende, para evaluar el cálculo de adición definido arriba, pasamos ‘total’, el Tensor para retirar, el cual representa la salida de la op
tf.add
.print(sess.run(total)) # [ 5.5 8. ]
En este punto introducimos la Variable TensorFlow. Donde las constantes son una parte fija de la definición de la gráfica, las variables pueden ser actualizadas. El constructor de la clase requiere un valor inicial, aun así, las variables necesitan una operación para inicializarlas explícitamente antes de que se lleven a cabo otras operaciones en ellas.
Las variables mantienen el estado de la gráfica en una sesión particular por lo que debemos observar lo que ocurre con las sesiones múltiples usando la misma gráfica para comprender mejor las variables.
# Crea una variable con un valor inicial de 1
some_var = tf.Variable(1)
# Crea una op para ejecutar inicializadores de variables
init_op = tf.global_variables_initializer()
# Crea una op para reemplazar el valor mantenido por some_var a 3
assign_op = some_var.assign(3)
# Instala dos instancias de una sesión
sess1 = tf.Session()
sess2 = tf.Session()
# Inicializa variables en ambas sesiones
sess1.run(init_op)
sess2.run(init_op)
print(sess1.run(some_var)) # Salidas 1
# Cambia some_var en sesión1
sess1.run(assign_op)
print(sess1.run(some_var)) # Salidas 3
print(sess2.run(some_var)) # Salidas 1
# Cierra Sesiones
sess1.close()
sess2.close()
Hemos instalado la gráfica y dos sesiones.
Después de ejecutar la inicialización en ambas sesiones (si no ejecutamos esto y luego evaluamos la variable, llegamos a un error) solo ejecutamos la op de asignación en una sesión. Como se puede ver, el valor de la variable persiste, pero no en todas las sesiones.
Alimentando la Gráfica para Abordar los 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 usan para definir qué entradas puede esperar la gráfica y su tipo de datos (y opcionalmente su forma). Luego podemos alimentar datos en la gráfica a través de estos marcadores de posición cuando ejecutamos el cálculo.
La gráfica TensorFlow está comenzando a parecerse a las redes neuronales que queremos entrenar 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:
para un
v
dado (con la constante C
y P
).
Esta es una fórmula para calcular el rendimiento hasta el vencimiento (
y
) en un bono con valor de mercado v
, principal P
, y cupón C
pagado semestralmente, pero con los flujos de efectivo descontados de en composición continua.
Básicamente, tenemos que resolver una ecuación como ésta 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 una gráfica de TensorFlow.
C
y P
son constantes fijas y forman parte de la definición de nuestra gráfica. Queremos tener un proceso que refine los límites inferior y superior de y
. Por lo tanto, estos límites (denotados como “a” y “b”) son buenos candidatos para las variables que deben modificarse después de cada conjetura de “y” (que se toma como el punto medio de “a” y “b”).# Especifica los valores que generarán nuestras operaciones constantes
C = tf.constant(5.0)
P = tf.constant(100.0)
# Especificamos los valores iniciales que serán nuestros límites inferior y superior cuando se inicialicen.
# Obviamente, el éxito final de este algoritmo depende de puntos de partida decentes
a = tf.Variable(-10.0)
b = tf.Variable(10.0)
# Esperamos que se inserte un número flotante en la gráfica
v_target = tf.placeholder("float")
# Recuerda que las siguientes operaciones son definiciones,
# ninguna se lleva a cabo hasta que se evalúa una operación en una sesión!
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)
# Operaciones para establecer valores temporales (a_ y b_) destinado a ser los próximos valores de a y b.
# ej. si la conjetura resulta en una v mayor que la v objetivo,
# estableceremos a_ como el valor actual de y
a_ = tf.where(v_guess > v_target, y, a)
b_ = tf.where(v_guess < v_target, y, b)
# La última etapa de nuestra gráfica es asignar los dos valores temporales a nuestras variables
step = tf.group( a.assign(a_), b.assign(b_) )
Entonces ahora tenemos una lista de operaciones y variables, cualquiera de las cuales puede ser evaluada contra una sesión en particular. Algunas de estas operaciones dependen de que se ejecuten otras operaciones, por lo que ejecutar, por ejemplo,
v_guess
activará una reacción en cadena para que otros tensores, como C
y P
, se evalúen primero.
Algunas de estas operaciones dependen de un marcador de posición para el cual se debe especificar un valor entonces, ¿cómo alimentamos realmente ese valor?
Esto se hace mediante el argumento
feed_dict
en la función run
.
Si quieres evaluar
a_
, conectamos el valor de nuestro marcador de posición v_target
, así:sess.run(a_, feed_dict={v_target: 100})
Dando como resultado 0.0.
Conecta un
v_target
de 130 y obtenemos -10.0.
Es nuestra operación de “paso” que realmente requiere que todas las demás operaciones se realicen como un requisito previo y, de hecho, ejecuta toda la gráfica. También es una operación que realmente 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. Establezcamos una sesión y ejecutemos nuestra gráfica en ella 100 veces.# Configurar una sesión e inicializar variables
sess = tf.Session()
tf.global_variables_initializer().run()
# Ejecuta la operación de paso (y, por lo tanto, toda la gráfica) 100 veces
para i en el rango (100):
sess.run(step, feed_dict={v_target:95})
Si evaluamos el tensor
y
ahora, obtenemos algo parecido a una respuesta deseableprint(sess.run(y)) # 0.125163
Redes Neuronales
Ahora que tenemos una comprensión de 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 puntos de data en un sistema coordinado 2d, dependiendo de si caen en una región en particular en un círculo de radio 0.5 centrado en el origen.
Por supuesto, esto se puede verificar de manera concreta simplemente buscando un punto dado
(a,b)
si a^2 + b^2 <0.5
, pero a los efectos de este experimento de aprendizaje automático, nos gustaría en su lugar pasar un conjunto de entrenamiento: una serie de puntos aleatorios y si caen o no 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)
Crearemos una red neuronal con las siguientes características:
- Consiste en una capa de entrada con dos nodos, en la que alimentamos nuestra serie de vectores bidimensionales contenidos en “random_spots”. Esto estará representado por un marcador de posición que espera los datos de entrenamiento.
- La capa de salida también tendrá dos nodos, por lo que necesitamos alimentar nuestra serie de etiquetas de capacitación (“is_inside_circle”) en un marcador de posición para un escalar y luego convertir esos valores en un vector caliente de dos dimensiones.
- Tendremos una capa oculta que consta de tres nodos, por lo que necesitaremos usar variables para nuestras matrices de ponderaciones y vectores de sesgo ya que estos son los valores que deben ser refinados al realizar la capacitación.
INPUT_LAYER_SIZE = 2
HIDDEN_LAYER_SIZE = 3
OUTPUT_LAYER_SIZE = 2
# Los valores iniciales para los pesos y los sesgos se dibujan aleatoria y uniformemente a partir de [-1, 1]
# Por ejemplo, W1 es una matriz de forma 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))
# Especificando que el marcador de posición X puede esperar una matriz de 2 columnas (pero cualquier cantidad de filas)
# representando lugares aleatorios
X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE])
# El marcador de posición Y puede esperar números enteros que representen si el punto correspondiente está en el círculo
# o no (sin forma específica)
Y = tf.placeholder(tf.uint8)
# Una op para convertir a un único vector caliente
onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)
Para completar la definición de nuestra gráfica, definimos algunas operaciones que nos ayudarán a entrenar las variables para llegar a un mejor clasificador. Estos incluyen los cálculos de la matriz, las funciones de activación y el optimizador.
LEARNING_RATE = 0.01
# Op para ejecutar un cálculo de matriz X*W1 + b1
hidden_layer = tf.add(tf.matmul(X, W1), b1)
# Utiliza la función de activación sigmoidea en el resultado
activated_hidden_layer = tf.sigmoid(hidden_layer)
# Aplica los siguientes pesos y sesgo (W2, b2) a la capa oculta y luego aplica la función softmax
# para obtener nuestra capa de salida (cada vector sumando 1)
output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2))
# Calcula la entropía cruzada para nuestra función de pérdida
loss = -tf.reduce_sum(onehot_output * tf.log(output_layer))
# Utiliza el optimizador de descenso de gradiente a la velocidad de aprendizaje especificada para minimizar el valor dado por el tensor de pérdida
train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
Después de configurar nuestra gráfica, es hora de configurar una sesión y ejecutar el “paso de tren” (que también ejecuta las operaciones de requisitos previos). Algunas de estas op usan marcadores de posición, por lo que se deben proporcionar los valores. Este paso de entrenamiento representa una época en nuestro algoritmo de aprendizaje y, como tal, se enlaza a la cantidad de épocas que deseamos ejecutar. Podemos ejecutar otras partes de la gráfica, como el tensor de “pérdida” para fines informativos.
EPOCH_COUNT = 1000
sess = tf.Session()
tf.global_variables_initializer().run()
para i en rango(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 en un punto y obtener la salida de la red neuronal así:
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
output_layer
para muchos puntos, podemos tener una idea de cómo el aprendiz 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.
No hay comentarios:
Publicar un comentario