TensorFlowPost

 

Hoy, en esta séptima entrega de la introducción práctica al Deep Learning con TensorFlow de Google (primera entrega,  segunda entrega, tercera entrega, cuarta entrega, quinta entrega, sexta entrega) vamos a presentar como puede implementar el modelo softmax en TensorFlow. Nuestro objetivo en esta entrega no es crear un modelo muy elaborado con un alto grado de acierto, sino uno simple que nos permita entender conceptualmente el uso de TensorFlow.

14- Implementación del modelo softmax en TensorFlow

Después de esta visión rápida de la teoría estamos ya en disposición de ponernos a avanzar con la creación del modelo de detección de dígitos usando TensorFlow desde Python. Para ello lo primero es importar la librería:

import tensorflow tf

A partir de este momento podemos empezar a especificar como será nuestro modelo.

En realidad Tensorflow nos permite en lenguaje Python describir las operaciones que queremos realizar. A partir de aquí el creará un grafo interno de estas operaciones y las interacciones entre estas que será usado durante la ejecución de todas las operaciones. Una vez finalizada toda la especificación es cuando Tensorflow empieza a ejecutar todas las operaciones. Remarcar que esto se hace totalmente fuera del contexto de Python. Más adelante ya hablaremos de esta pieza tan importante como es el grafo, pero por ahora podemos avanzar sin entrar en más detalle.

Primero empecemos a ver cómo se pueden definir variables simbólicas para poder manipularlas durante la ejecución del programa. Podemos hacerlo de la siguiente manera:

x = tf.placeholder("float", [None, 784])

en este caso x no es un valor específico, sino lo que en TensorFlow se llama placeholder , un valor que damos de entrada cuando le pidamos a TensorFlow ejecutar un cálculo. Queremos ser capaces de introducir cualquier número de imágenes MNISTcada una aplanada en un vector de 784 dimensiones.  Esto lo vamos a representar como un tensor 2D de números de coma flotantecon una forma [None, 784](Aquí None significa que la dimensión puede ser de cualquier longitud).

También necesitamos mantener la información de los pesos y sesgos de nuestro modelo. Podríamos imaginar el tratamiento de estos como entradas adicionales, pero TensorFlow propone una manera mejor de manejarlo,  mediante lo que llama Variable . Una Variable es un tensor modificable que reside en el gráfico de operaciones que construye TensorFlow que hemos mencionado antes. Se puede utilizar e incluso modificar durante la ejecución de las operaciones del grafo. En nuestro caso podemos crear estas dos variables de la siguiente manera:

W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))

Se crean estas Variables dando a tf.Variable el valor inicial de la variable, que en este caso se inician con ceros, puesto que como se va a aprender el valor de W y b, no es muy no importa mucho cual es el valor inicial. Debemos fijarnos que W  tiene una forma de [784, 10] porque queremos multiplicar el vector de imagen de 784 dimensiones por cada clase de 10 dimensiones, para producir un vector de evidencias. Por otro lado b tiene la forma de [10] puesto que debemos sumar cada una de sus componentes a las componentes respectivas de la variable de output. La forma de expresarlo en TensorFlow es:

y = tf.nn.softmax(tf.matmul(x,W) + b)

Inicialmente multiplicamos x por W mediante la expresión tf.matmul(x,W) y después sumamos el sesgo b, para finalmente aplicar tf.nn.softmax a todo.

15- Training del modelo

Una vez tenemos la implementación del modelo debemos pasar a encontrar los pesos y sesgos de la red mediante lo que se conoce como algoritmo de training. Para entrenar una red neuronal hay varios algoritmos disponibles que buscan un peso óptimo para los parámetros de cada neurona mediante iteraciones (también conocidas como epochs). Para cada iteración, el algoritmo de training vuelve a coger los datos de entrenamiento, aplica la red neuronal y compara el resultado obtenido con el esperado.

Con el fin de entrenar a nuestro modelo y saber cuándo ya tenemos un buen modelo, tenemos que definir de alguna manera lo que significa que el modelo sea “bueno”. Lo que se hace habitualmente es definir lo contrario, es decir calcular lo “malo” que es ese modelo con funciones de coste (cost) o pérdida (loss). En este caso el objetivo es intentar conseguir valores de los parámetros W y b  que  minimicen el valor de la métrica que indica cuan malo es el modelo.

Hay diferentes métricas del grado de error entre salidas calculadas y las salidas deseadas de los datos de entrenamiento. Una medida común de error es el conocido error cuadrático medio (mean squared error). Sin embargo, hay algunos resultados de las investigaciones que sugieren utilizar otras métricas diferentes para estas redes neuronales. Un ejemplo puede ser el llamado error de entropía cruzada (cross entropy error) y es el que usaremos en nuestro ejemplo.  Esta métrica se define como:

Screen Shot 2015-12-01 at 09.36.40

 

donde y es la distribución de probabilidad predecida, y’ la distribución real (obtenida a partir del etiquetado de los datos de entrada). Puesto que la matemática detrás de cross-entropy y su papel en redes neuronales es bastante complejo no entraré en más detalle.

Para implementar el cross-entropy necesitamos un nuevo placeholder para entrar la respuesta correcta.

y_ = tf.placeholder("float", [None,10])

A partir de aquí podemos implementar el cross-entropy con la siguiente sentencia:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

Primero se calcula el logaritmo de cada elemento y con tf.log  y después se multiplica por cada elemento y_ . Finalmente, tf.reduce_sum suma todos los elementos del tensor  (un detalle: más adelante veremos que se mira por paquetes las imágenes, y en este caso el valor de cross-entropy corresponde a la suma de las cross-entropies de las imágenes y no a la de una sola).

Para entrenar una red neuronal hay varios algoritmos disponibles en la literatura. Todos estos algoritmos buscan los pesos óptimos para cada neurona. Esto lo hacen mediante iteraciones (también conocidas como epochs). Para cada iteración, el algoritmo de training vuelve a coger los datos de entrenamiento, aplica la red neuronal y compara el resultado obtenido con el esperado.

En nuestro ejemplo usaremos el conocido como Backpropagation, abreviación de backward propagation of errors, que como su nombre indica propaga hacia atrás el error para poder recalcular los pesos. Y esto se hace a cada iteración (epochs) en que el algoritmo vuelve a coger los datos de entrenamiento, aplica la red neuronal y compara el resultado obtenido con el esperado.

Este método se usa conjuntamente con algún método de optimización como puede ser el gradient descent, que mediante el cálculo del gradiente de la función de error nos permite ir calculando el mínimo error en cada iteración de acuerdo a la información local que se dispone .  Dado el carácter introductorio de este tutorial no entraremos en detalle de estos métodos, pero intuitivamente consiste en cambiar el valor de cada variable un poquito (expresado con un parámetro llamado learning rate) en la dirección que reduce la función de error.

Para nuestro ejemplo de imagenes de MNIST a continuación vamos a expresar el comando que pide a Tensorflow que use el algoritmo de backpropagation  para minimizar el cross-entropy usando el algoritmo gradient descent (con un learning rate de 0.01, que ya comentaremos en futuros tutorials).

 

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

Solo nos falta un paso final antes de empezar, especificar la inicialización de todas las variables:

init = tf.initialize_all_variables()

16- Ejecución en Tensorflow

Desde el punto de vista de un programador los programas en TensorFlow están estructurados en dos fases básicas, una de construcción y otra de ejecución. Hasta aquí hemos hecho la primera, en la que hemos descrito los datos y los cálculos que se requiere hacer sobre ellos. Pero para empezar a computar se necesita lanzar una sesión (Session), que reparte los cálculos (ops) y datos entre los dispositivos (Devices) CPUs o GPUs disponibles en el sistema.

A continuación veremos cómo lanzar la ejecución, pero antes echemos un vistazo rápido a la estructura interna de Tensorflow. TensorFlow representa la computación en forma de grafo, donde los nodos del grafo se llaman ops (diminutivo de operaciones) que reciben y se envían datos entre ellos en forma de Tensors. En este contexto se puede entender que en realidad, cuando TensorFlow recibe la línea de código anterior

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

lo que hace es añadir una nueva operación al grafo interno que tiene, además de todo lo necesario para implementar el backpropagation y el gradientdescent. Entonces se devuelve una simple operación que, cuando se ejecuta, va a realizar un paso del entrenamiento del gradientdescent,  ajustando ligeramente las variables para reducir  la métrica de error o coste definida.

Empecemos lanzando la ejecución de nuestro modelo definido creando primero una sesión:

sess = tf.Session()

a continuación podemos ejecutar la primera operación que inicializa las variables

sess.run(init)

A partir de este momento ya podemos entrenar nuestro modelo.  Por ejemplo supongamos que lo queremos ejecutar 1.000 veces:

for i in range(1000):
   batch_xs, batch_ys = mnist.train.next_batch(100)
   sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

En la primera línea del bucle se especifica que en cada paso de este bucle se coge un lote de 100 datos escogidos al azar del conjunto de datos de entreno. Podríamos usar todos los datos en cada iteración pero para hacer más rápido este primer programa usamos solo un pequeño conjunto en cada iteración. En la segunda línea se indica que se ejecute train_step indicándole que los datos leídos anteriormente alimenten a los respectivos placeholders.

17- Evaluación del modelo

Finalmente nos queda valorar cuan bueno es el modelo.  Primero vamos a averiguar dónde predijimos la etiqueta correcta. tf.argmax es una función muy útil que retorna el índice de la entrada más alta en un tensor lo largo de un eje. Por ejemplo, tf.argmax (y, 1) es la etiqueta de nuestro modelo que es más probable para cada entrada, mientras que tf.argmax (y_, 1) es la etiqueta correcta. Podemos utilizar tf.equal comprobar si nuestra predicción coincide con la verdad:

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

Esto nos retorna una lista de booleanos. Para determinar que fracción es correcta, podemos pasarlos números en coma flotante y entonces calcular la media.

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

Por ejemplo, [True, False, True, True] se convertirán en [1,0,1,1] que la media sale 0.75 y que representa el porcentaje de exactitud (accuracy). Ahora podemos pedir la accuracy de en nuestro datos de test.

print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})

A mí me ha salido un valor alrededor del 91%. This should be about 91%.  ¿Son buenos estos resultados? Yo creo que fantásticos porque esto significa que ya se ha programado y ejecutado su primera red neuronal con TensorFlow. Otra cosa es que hay modelos que permiten mejorar la accuracy mucho más. Pero esto ya lo dejamos para más adelante. ¿Qué les parece?

 Link a siguiente entrega: parte 8

  Cualquier sugerencia pueden contactar a través de DeepLearningBarcelona@gmail.com 

Apéndice: Código:

import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
import tensorflow as tf
x = tf.placeholder("float", [None, 784])
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x,W) + b)
y_ = tf.placeholder("float", [None,10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)
for i in range(1000):
 batch_xs, batch_ys = mnist.train.next_batch(100)
 sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
 correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
 accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
 print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
2017-08-09T12:15:15+00:00 December 3rd, 2015|