Raul E. Lopez Briega

Matemáticas, análisis de datos y python

TensorFlow y Redes Neuronales

Sobre TensoFlow

TensorFlow es una biblioteca open source desarrollada por Google que nos permite realizar cálculos numéricos usando diagramas de flujo de datos. Los nodos del grafo representan operaciones matemáticas, mientras que los arcos del grafo representan los arreglos de datos multidimensionales (tensores) comunicados entre ellos. Esta arquitectura flexible nos permite realizar los cálculos en más de un CPU o GPU utilizando la misma API.

¿Qué es un diagrama de flujo de datos?

Los diagramas de flujo de datos describen cálculos matemáticos con un grafo de nodos y arcos. Los nodos normalmente implementan operaciones matemáticas, pero también pueden representar los puntos para alimentarse de datos, devolver resultados, o leer / escribir variables persistentes. Los arcos o aristas describen las relaciones de entrada / salida entre los nodos. Estos arcos están representados por los arreglos de datos multidimensionales o tensores. El flujo de los tensores a través del grafo es de donde TensorFlow recibe su nombre. Los nodos se asignan a los dispositivos computacionales y se ejecutan de forma asincrónica y en paralelo una vez que todos los tensores en los arcos de entrada están disponibles.

Introducción a TensorFlow

Para poder utilizar TensorFlow primero es necesario entender cómo la librería:

  • Representa cálculos en forma de grafos.
  • Ejecuta los grafos en el contexto de Sesiones.
  • Representa los datos como tensores.
  • Mantiene el estado con variables.
  • Se alimenta de datos y devuelve los resultados de cada operación.

Funcionamiento general

TensorFlow es un sistema de programación en el que representamos cálculos en forma de grafos. Los nodos en el grafo se llaman ops (abreviatura de operaciones). Una op tiene cero o más tensores, realiza algún cálculo, y produce cero o más tensores.

Un grafo de TensorFlow es una descripción de cálculos. Para calcular cualquier cosa dentro de TensorFlow, el grafo debe ser lanzado dentro de una sesión. La Sesión coloca las operaciones del grafo en los diferentes dispositivos, tales como CPU o GPU, y proporciona métodos para ejecutarlas.

Creando un Grafo

Para construir un grafo simple, podemos comenzar con ops que no necesitan ningún dato de entrada, como son las constantes y luego le pasamos su salida a ops que realizan cálculos.

In [1]:
# importamos la libreria
import tensorflow as tf

# importamos librerías adicionales
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import pandas as pd

%matplotlib inline

Constantes

Podemos construir ops de constantes utilizando constant, su API es bastante simple:

constant(value, dtype=None, shape=None, name='Const')

Le debemos pasar un valor, el cual puede ser cualquier tipo de tensor (un escalar, un vector, una matriz, etc) y luego opcionalmente le podemos pasar el tipo de datos, la forma y un nombre.

In [2]:
# Creación de Constantes
# El valor que retorna el constructor es el valor de la constante.

# creamos constantes a=2 y b=3
a = tf.constant(2)
b = tf.constant(3)

# creamos matrices de 3x3
matriz1 = tf.constant([[1, 3, 2],
                       [1, 0, 0],
                       [1, 2, 2]])

matriz2 = tf.constant([[1, 0, 5],
                       [7, 5, 0],
                       [2, 1, 1]])
In [3]:
# Realizamos algunos cálculos con estas constantes
suma = tf.add(a, b)
mult = tf.mul(a, b)
cubo_a = a**3

# suma de matrices
suma_mat = tf.add(matriz1, matriz2)

# producto de matrices
mult_mat = tf.matmul(matriz1, matriz2)

Sesiones

Ahora que ya definimos algunas ops constantes y algunos cálculos con ellas, debemos lanzar el grafo dentro de una Sesión. Para realizar esto utilizamos el objeto Session. Este objeto va a encapsular el ambiente en el que las operaciones que definimos en el grafo van a ser ejecutadas y los tensores son evaluados.

In [4]:
# Todo en TensorFlow ocurre dentro de una Sesión

# creamos la sesion y realizamos algunas operaciones con las constantes
# y lanzamos la sesión
with tf.Session() as sess: 
    print("Suma de las constantes: {}".format(sess.run(suma)))
    print("Multiplicación de las constantes: {}".format(sess.run(mult)))
    print("Constante elevada al cubo: {}".format(sess.run(cubo_a)))
    print("Suma de matrices: \n{}".format(sess.run(suma_mat)))
    print("Producto de matrices: \n{}".format(sess.run(mult_mat)))
Suma de las constantes: 5
Multiplicación de las constantes: 6
Constante elevada al cubo: 8
Suma de matrices: 
[[2 3 7]
 [8 5 0]
 [3 3 3]]
Producto de matrices: 
[[26 17  7]
 [ 1  0  5]
 [19 12  7]]

Las Sesiones deben ser cerradas para liberar los recursos, por lo que es una buena práctica incluir la Sesión dentro de un bloque "with" que la cierra automáticamente cuando el bloque termina de ejecutar.

Para ejecutar las operaciones y evaluar los tensores utilizamos Session.run().

Variables persistentes

Las Variables mantienen el estado a través de las ejecuciones del grafo. Son buffers en memoria que contienen tensores. Se deben inicializar explícitamente y se pueden guardar en el disco para luego restaurar su estado de necesitarlo. Se crean utilizando el objeto Variable.

In [5]:
# Creamos una variable y la inicializamos con 0
estado = tf.Variable(0, name="contador")

# Creamos la op que le va a sumar uno a la Variable `estado`.

uno = tf.constant(1)
nuevo_valor = tf.add(estado, uno)
actualizar = tf.assign(estado, nuevo_valor)

# Las Variables deben ser inicializadas por la operación `init` luego de 
# lanzar el grafo.  Debemos agregar la op `init` a nuestro grafo.
init = tf.initialize_all_variables()

# Lanzamos la sesion y ejecutamos las operaciones
with tf.Session() as sess:
    # Ejecutamos la op `init`
    sess.run(init)
    # imprimir el valor de la Variable estado.
    print(sess.run(estado))
    # ejecutamos la op que va a actualizar a `estado`.
    for _ in range(3):
        sess.run(actualizar)
        print(sess.run(estado))
0
1
2
3

Variables simbólicas (contenedores)

Las Variables simbólicas o Contenedores nos van a permitir alimentar a las operaciones con los datos durante la ejecución del grafo. Estos contenedores deben ser alimentados antes de ser evaluados en la sesión, sino obtendremos un error.

In [6]:
# Ejemplo variables simbólicas en los grafos
# El valor que devuelve el constructor representa la salida de la 
# variable (la entrada de la variable se define en la sesion)

# Creamos un contenedor del tipo float. Un tensor de 4x4.
x = tf.placeholder(tf.float32, shape=(4, 4))
y = tf.matmul(x, x)

with tf.Session() as sess:
#    print(sess.run(y))  # ERROR: va a fallar porque no alimentamos a x.
    rand_array = np.random.rand(4, 4)
    print(sess.run(y, feed_dict={x: rand_array}))  # ahora esta correcto.
[[ 2.27301431  2.39163661  1.22738445  1.87839973]
 [ 2.66718912  2.76533985  1.08909523  1.96862805]
 [ 2.38245845  2.37843513  1.0873785   1.67218387]
 [ 1.68678236  1.77147484  1.0363127   1.36901033]]

Ahora ya conocemos en líneas generales como es la mecánica detrás del funcionamiento de TensorFlow y como deberíamos proceder para crear las operaciones dentro de los grafos. Veamos si podemos implementar modelos de neuronas simples con la ayuda de esta librería.

Ejemplo de neuronas simples

Una neurona simple, va a tener una forma similar al siguiente diagrama:

En donde sus componentes son:

  • $x_1, x_2, \dots, x_n$: son los datos de entrada en la neurona, los cuales también puede ser que sean producto de la salida de otra neurona de la red.

  • $x_0$: Es la unidad de sesgo; un valor constante que se le suma a la entrada de la función de activación de la neurona. Generalmente tiene el valor 1. Este valor va a permitir cambiar la función de activación hacia la derecha o izquierda, otorgándole más flexibilidad para aprender a la neurona.

  • $w_0, w_1, w_2, \dots, w_n$: Los pesos relativos de cada entrada. Tener en cuenta que incluso la unidad de sesgo tiene un peso.

  • a: La salida de la neurona. Que va a ser calculada de la siguiente forma:

$$a = f\left(\sum_{i=0}^n w_i \cdot x_i \right)$$

Aquí $f$ es la función de activación de la neurona. Esta función es la que le otorga tanta flexibilidad a las redes neuronales y le permite estimar complejas relaciones no lineales en los datos. Puede ser tanto una función lineal, una función logística, hiperbólica, etc.

Ahora que ya conocemos como se construye una neurona tratemos de implementar con este modelo las funciones lógicas AND, OR y XNOR. Podemos pensar a estas funciones como un problema de clasificación en el que la salida va a ser 0 o 1, de acuerdo a la combinación de las diferentes entradas.

Las podemos modelar linealmente con la siguiente función de activación:

$$f(x) = \left\{ \begin{array}{ll} 0 & \mbox{si } x < 0 \\ 1 & \mbox{si } x \ge 0 \end{array} \right.$$

Neurona AND

La neurona AND puede ser modelada con el siguiente esquema:

La salida de esta neurona entonces va a ser:

$$a = f(-1.5 + x_1 + x_2)$$

Veamos como la podemos implementar en TensorFlow.

In [7]:
# Neurona con TensorFlow
# Defino las entradas
entradas = tf.placeholder("float", name='Entradas')
datos = np.array([[0, 0]
                 ,[1, 0]
                 ,[0, 1]
                 ,[1, 1]])

# Defino las salidas
uno = lambda: tf.constant(1.0)
cero = lambda: tf.constant(0.0)

with tf.name_scope('Pesos'):
    # Definiendo pesos y sesgo
    pesos = tf.placeholder("float", name='Pesos')
    sesgo = tf.placeholder("float", name='Sesgo')

with tf.name_scope('Activacion'):
    # Función de activación
    activacion = tf.reduce_sum(tf.add(tf.matmul(entradas, pesos), sesgo))

with tf.name_scope('Neurona'):
    # Defino la neurona
    def neurona():
        return tf.case([(tf.less(activacion, 0.0), cero)], default=uno)
    
    # Salida
    a = neurona()

# path de logs
logs_path = '/tmp/tensorflow_logs/neurona'
In [8]:
# Lanzar la Sesion
with tf.Session() as sess:
    # para armar el grafo
    summary_writer = tf.train.SummaryWriter(logs_path, 
                                             graph=sess.graph)
    # para armar tabla de verdad
    x_1 = []
    x_2 = []
    out = []
    act = []
    for i in range(len(datos)):
        t = datos[i].reshape(1, 2)
        salida, activ = sess.run([a, activacion], feed_dict={entradas: t,
                                        pesos:np.array([[1.],[1.]]),
                                        sesgo: -1.5})
        # armar tabla de verdad en DataFrame
        x_1.append(t[0][0])
        x_2.append(t[0][1])
        out.append(salida)
        act.append(activ)
    tabla_info = np.array([x_1, x_2, act, out]).transpose()
    tabla = pd.DataFrame(tabla_info, 
                         columns=['x1', 'x2', 'f(x)', 'x1 AND x2'])
tabla
Out[8]:
x1 x2 f(x) x1 AND x2
0 0.0 0.0 -1.5 0.0
1 1.0 0.0 -0.5 0.0
2 0.0 1.0 -0.5 0.0
3 1.0 1.0 0.5 1.0

Aquí podemos ver los datos de entrada de $x_1$ y $x_2$, el resultado de la función de activación y la decisión final que toma la neurona de acuerdo este último resultado. Como podemos ver en la tabla de verdad, la neurona nos dice que $x_1$ and $x_2$ solo es verdad cuando ambos son verdaderos, lo que es correcto.

Neurona OR

La neurona OR puede ser modelada con el siguiente esquema:

La salida de esta neurona entonces va a ser:

$$a = f(-0.5 + x_1 + x_2)$$

Como se puede ver a simple vista, el modelo de esta neurona es similar a la de la neurona AND, con el único cambio en el valor del sesgo, por lo tanto solo tendríamos que cambiar ese valor en nuestro modelo anterior para crear esta nueva neurona.

In [9]:
# Neurona OR, solo cambiamos el valor del sesgo
with tf.Session() as sess:
    # para armar el grafo
    summary_writer = tf.train.SummaryWriter(logs_path, 
                                             graph=sess.graph)
    # para armar tabla de verdad
    x_1 = []
    x_2 = []
    out = []
    act = []
    for i in range(len(datos)):
        t = datos[i].reshape(1, 2)
        salida, activ = sess.run([a, activacion], feed_dict={entradas: t,
                                        pesos:np.array([[1.],[1.]]),
                                        sesgo: -0.5}) # sesgo ahora -0.5
        # armar tabla de verdad en DataFrame
        x_1.append(t[0][0])
        x_2.append(t[0][1])
        out.append(salida)
        act.append(activ)
    tabla_info = np.array([x_1, x_2, act, out]).transpose()
    tabla = pd.DataFrame(tabla_info, 
                         columns=['x1', 'x2', 'f(x)', 'x1 OR x2'])
tabla
Out[9]:
x1 x2 f(x) x1 OR x2
0 0.0 0.0 -0.5 0.0
1 1.0 0.0 0.5 1.0
2 0.0 1.0 0.5 1.0
3 1.0 1.0 1.5 1.0

Como vemos, cambiando simplemente el peso del sesgo, convertimos a nuestra neurona AND en una neurona OR. Como muestra la tabla de verdad, el único caso en que $x_1$ OR $x_2$ es falso es cuando ambos son falsos.

Red Neuronal XNOR

El caso de la función XNOR, ya es más complicado y no puede modelarse utilizando una sola neurona como hicimos con los ejemplos anteriores. $x_1$ XNOR $x_2$ va a ser verdadero cuando ambos son verdaderos o ambos son falsos, para implementar esta función lógica debemos crear una red con dos capas, la primer capa tendrá dos neuronas cuya salida servirá de entrada para una nueva neurona que nos dará el resultado final. Esta red la podemos modelar de acuerdo al siguiente esquema:

Veamos entonces si podemos implementar este modelo en TensorFlow.

In [10]:
# Red Neuronal XNOR con TensorFlow
# Defino las entradas
entradas = tf.placeholder("float", name='Entradas')
datos = np.array([[0, 0]
                 ,[1, 0]
                 ,[0, 1]
                 ,[1, 1]])

# Defino las salidas
uno = lambda: tf.constant(1.0)
cero = lambda: tf.constant(0.0)

with tf.name_scope('Pesos'):
    # Definiendo pesos y sesgo
    pesos = {
        'a1': tf.constant([[-1.0], [-1.0]], name='peso_a1'),
        'a2': tf.constant([[1.0], [1.0]], name='peso_a2'),
        'a3': tf.constant([[1.0], [1.0]], name='peso_a3')
    }
    sesgo = {
        'a1': tf.constant(0.5, name='sesgo_a1'),
        'a2': tf.constant(-1.5, name='sesgo_a2'),
        'a3': tf.constant(-0.5, name='sesgo_a3')
    }


with tf.name_scope('Red_neuronal'):
    # Defino las capas
    def capa1(entradas, pesos, sesgo):
        # activacion a1
        a1 = tf.reduce_sum(tf.add(tf.matmul(entradas, pesos['a1']), sesgo['a1']))
        a1 = tf.case([(tf.less(a1, 0.0), cero)], default=uno)
        # activacion a2
        a2 = tf.reduce_sum(tf.add(tf.matmul(entradas, pesos['a2']), sesgo['a2']))
        a2 = tf.case([(tf.less(a2, 0.0), cero)], default=uno)
        return a1, a2
    
    def capa2(entradas, pesos, sesgo):
        # activacion a3
        a3 = tf.reduce_sum(tf.add(tf.matmul(entradas, pesos['a3']), sesgo['a3']))
        a3 = tf.case([(tf.less(a3, 0.0), cero)], default=uno)
        return a3

# path de logs
logs_path = '/tmp/tensorflow_logs/redXNOR'
In [11]:
# Sesion red neuronal XNOR
with tf.Session() as sess:
    # para armar el grafo
    summary_writer = tf.train.SummaryWriter(logs_path, 
                                             graph=sess.graph)
    # para armar tabla de verdad
    x_1 = []
    x_2 = []
    out = []
    for i in range(len(datos)):
        t = datos[i].reshape(1, 2)
        # obtenos resultados 1ra capa
        a1, a2 = sess.run(capa1(entradas, pesos, sesgo), feed_dict={entradas: t})
        # pasamos resultados a la 2da capa
        ent_a3 = np.array([[a1, a2]])
        salida = sess.run(capa2(ent_a3, pesos, sesgo))
        # armar tabla de verdad en DataFrame
        x_1.append(t[0][0])
        x_2.append(t[0][1])
        out.append(salida)
    tabla_info = np.array([x_1, x_2, out]).transpose()
    tabla = pd.DataFrame(tabla_info, 
                         columns=['x1', 'x2', 'x1 XNOR x2'])
tabla
Out[11]:
x1 x2 x1 XNOR x2
0 0.0 0.0 1.0
1 1.0 0.0 0.0
2 0.0 1.0 0.0
3 1.0 1.0 1.0

Como vemos, la red neuronal nos da el resultado correcto para la función lógica XNOR, solo es verdadera si ambos valores son verdaderos, o ambos son falsos.

Hasta aquí implementamos simples neuronas y les pasamos los valores de sus pesos y sesgo a mano; esto es sencillo para los ejemplos; pero en la vida real, si queremos utilizar redes neuronales necesitamos implementar un procesos que vaya actualizando los pesos a medida que la red vaya aprendiendo con el entrenamiento. Este proceso se conoce con el nombre de propagación hacia atrás o backpropagation.

Propagación hacia atrás

La propagación hacia atrás o backpropagation es un algoritmo que funciona mediante la determinación de la pérdida (o error) en la salida y luego propagándolo de nuevo hacia atrás en la red. De esta forma los pesos se van actualizando para minimizar el error resultante de cada neurona. Este algoritmo es lo que les permite a las redes neuronales aprender.

Veamos un ejemplo de como podemos implementar una red neuronal que pueda aprender por sí sola con la ayuda de TensorFlow.

Ejemplo de Perceptron multicapa para reconocer dígitos escritos

En este ejemplo vamos a construir un peceptron multicapa para clasificar dígitos escritos. Antes de pasar a la construcción del modelo, exploremos un poco el conjunto de datos con el que vamos a trabajar en la clasificación.

MNIST dataset

MNIST es un simple conjunto de datos para reconocimiento de imágenes por computadora. Se compone de imágenes de dígitos escritos a mano como los siguientes:

Para más información sobre el dataset pueden visitar el siguiente enlace, en donde hacen un análisis detallado del mismo.

In [12]:
# importando el dataset
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting MNIST_data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz

Explorando MNIST dataset

In [13]:
# forma del dataset 55000 imagenes
mnist.train.images.shape
Out[13]:
(55000, 784)
In [14]:
# cada imagen es un array de 28x28 con cada pixel 
# definido como escala de grises.
digito1 = mnist.train.images[0].reshape((28, 28))
In [15]:
# visualizando el primer digito
plt.imshow(digito1, cmap = cm.Greys)
plt.show()
In [16]:
# valor correcto
mnist.train.labels[0].nonzero()[0][0]
Out[16]:
7
In [17]:
# visualizando imagenes de 5 en 5
def visualizar_imagenes(dataset, cant_img):
    img_linea = 5
    lineas = int(cant_img / img_linea)
    imagenes = []
    for i in range(lineas):
        datos = []
        for img in dataset[img_linea* i:img_linea* (i+1)]:
            datos.append(img.reshape((28,28)))
            imgs = np.hstack(datos)
        imagenes.append(imgs)
    data = np.vstack(imagenes)
    plt.imshow(data, cmap = cm.Greys )
    plt.show()
In [18]:
# visualizando los primeros 30 dígitos
plt.figure(figsize=(8, 8))
visualizar_imagenes(mnist.train.images, 30)

Construyendo el perceptron multicapa

Ahora que ya conocemos los datos con los que vamos a trabajar, ya estamos en condiciones de construir el modelo. Vamos a construir un peceptron multicapa que es una de las redes neuronales más simples. El modelo va a tener dos capas ocultas, que se van a activar con la función de activación ReLU y vamos a optimizar los pesos reduciendo la entropía cruzada utilizando el algoritmo Adam que es un método para optimización estocástica.

In [19]:
# Parametros
tasa_aprendizaje = 0.001
epocas = 15
lote = 100
display_step = 1
logs_path = "/tmp/tensorflow_logs/perceptron"

# Parametros de la red
n_oculta_1 = 256 # 1ra capa de atributos
n_oculta_2 = 256 # 2ra capa de atributos
n_entradas = 784 # datos de MNIST(forma img: 28*28)
n_clases = 10 # Total de clases a clasificar (0-9 digitos)

# input para los grafos
x = tf.placeholder("float", [None, n_entradas],  name='DatosEntrada')
y = tf.placeholder("float", [None, n_clases], name='Clases')
In [20]:
# Creamos el modelo
def perceptron_multicapa(x, pesos, sesgo):
    # Función de activación de la capa escondida
    capa_1 = tf.add(tf.matmul(x, pesos['h1']), sesgo['b1'])
    # activacion relu
    capa_1 = tf.nn.relu(capa_1)
    # Función de activación de la capa escondida
    capa_2 = tf.add(tf.matmul(capa_1, pesos['h2']), sesgo['b2'])
    # activación relu
    capa_2 = tf.nn.relu(capa_2)
    # Salida con activación lineal
    salida = tf.matmul(capa_2, pesos['out']) + sesgo['out']
    return salida
In [21]:
# Definimos los pesos y sesgo de cada capa.
pesos = {
    'h1': tf.Variable(tf.random_normal([n_entradas, n_oculta_1])),
    'h2': tf.Variable(tf.random_normal([n_oculta_1, n_oculta_2])),
    'out': tf.Variable(tf.random_normal([n_oculta_2, n_clases]))
}
sesgo = {
    'b1': tf.Variable(tf.random_normal([n_oculta_1])),
    'b2': tf.Variable(tf.random_normal([n_oculta_2])),
    'out': tf.Variable(tf.random_normal([n_clases]))
}

with tf.name_scope('Modelo'):
    # Construimos el modelo
    pred = perceptron_multicapa(x, pesos, sesgo)

with tf.name_scope('Costo'):
    # Definimos la funcion de costo
    costo = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pred, y))
    
with tf.name_scope('optimizador'):
    # Algoritmo de optimización
    optimizar = tf.train.AdamOptimizer(
        learning_rate=tasa_aprendizaje).minimize(costo)

with tf.name_scope('Precision'):
    # Evaluar el modelo
    pred_correcta = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
    # Calcular la precisión
    Precision = tf.reduce_mean(tf.cast(pred_correcta, "float"))
    
# Inicializamos todas las variables
init = tf.initialize_all_variables()

# Crear sumarización para controlar el costo
tf.scalar_summary("Costo", costo)
# Crear sumarización para controlar la precisión
tf.scalar_summary("Precision", Precision)
# Juntar los resumenes en una sola operación
merged_summary_op = tf.merge_all_summaries()
In [22]:
# Lanzamos la sesión
with tf.Session() as sess:
    sess.run(init)
    
    # op to write logs to Tensorboard
    summary_writer = tf.train.SummaryWriter(
        logs_path, graph=tf.get_default_graph())

    # Entrenamiento
    for epoca in range(epocas):
        avg_cost = 0.
        lote_total = int(mnist.train.num_examples/lote)

        for i in range(lote_total):
            lote_x, lote_y = mnist.train.next_batch(lote)
            # Optimización por backprop y funcion de costo
            _, c, summary = sess.run([optimizar, costo, merged_summary_op],
                                     feed_dict={x: lote_x, y: lote_y})
            # escribir logs en cada iteracion
            summary_writer.add_summary(summary, epoca * lote_total + i)
            # perdida promedio
            avg_cost += c / lote_total
        # imprimir información de entrenamiento
        if epoca % display_step == 0:
            print("Iteración: {0: 04d} costo = {1:.9f}".format(epoca+1, 
                                                            avg_cost))
    print("Optimización Terminada!\n")

    
    print("Precisión: {0:.2f}".format(Precision.eval({x: mnist.test.images,
                                                y: mnist.test.labels})))

    print("Ejecutar el comando:\n",
          "--> tensorboard --logdir=/tmp/tensorflow_logs ",
          "\nLuego abir https://0.0.0.0:6006/ en el navegador")
Iteración:  001 costo = 190.739139247
Iteración:  002 costo = 42.639138275
Iteración:  003 costo = 26.239370855
Iteración:  004 costo = 18.236157751
Iteración:  005 costo = 13.129509245
Iteración:  006 costo = 9.765473726
Iteración:  007 costo = 7.159448563
Iteración:  008 costo = 5.309303818
Iteración:  009 costo = 3.940411947
Iteración:  010 costo = 2.904317733
Iteración:  011 costo = 2.179349244
Iteración:  012 costo = 1.597618810
Iteración:  013 costo = 1.215200688
Iteración:  014 costo = 0.875238173
Iteración:  015 costo = 0.760177279
Optimización Terminada!

Precisión: 0.94
Ejecutar el comando:
 --> tensorboard --logdir=/tmp/tensorflow_logs  
Luego abir https://0.0.0.0:6006/ en el navegador

Como vemos TensorFlow nos da mucha flexibilidad para construir el modelo, modificando muy pocas líneas podríamos cambiar el algoritmo de optimización o el calculo del error y obtener otros resultados; de esta forma vamos a poder personalizar el modelo para alcanzar mayores niveles de precisión.

TensorBoard

Otra gran herramienta que nos proporciona TensorFlow es TensorBoard que nos permite visualizar nuestros grafos y nos ayudan a alcanzar un mayor entendimiento del flujo de cálculos que ocurre en nuestro modelo.

Para crear la información de la que se va a nutrir el TensorBoard, podemos definir algunos scopes utilizando tf.name_scope; también podemos incluir algunos gráficos sumarizados con tf.scalar_summary y luego llamamos a la función tf.train.SummaryWriter dentro de una Sesión.

Luego podemos iniciar el board con el comando tensorboard --logdir=logpath como se puede ver en la salida del último ejemplo.

Los grafos de los casos que vimos por ejemplo, se ven así.

Los invito a explorar la herramienta y adentrarse en el fascinante mundo de las redes neuronales.

Saludos!

Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer.

Comments