Introducción a TensorFlow (Parte 1)

Omar Sanseviero
AI Learners
Published in
11 min readMar 11, 2019

Esta es una serie de artículos que es parte de nuestro curso de TensorFlow de AI Learners. Pueden ver el video de este artículo en nuestro canal de YouTube. Para aprender TensorFlow se recomienda tener sólidas nociones de programación con python y conocimiento, aunque sea general, de técnicas de Machine Learning y de matemáticas. Algunos recursos que recomendamos para esto son:

En ese curso:

  • Entenderemos los diferentes niveles de TensorFlow.
  • Aprenderemos las diferencias con TF 2.0.
  • Implementaremos modelos de Machine Learning tanto en bajo nivel como con bibliotecas.
  • Desarrollaremos pipelines para llevar los proyectos a producción.

En este artículo

  • ¿Qué es TensorFlow?
  • Introducción a TensorFlow
  • Grafos y sus ventajas
  • Paralelización
  • Eager Execution
  • NumPy y TensorFlow

¿Qué es TensorFlow?

TensorFlow es una herramienta de computación numérica creada por Google. Actualmente se usa para cientos de proyectos de Machine Learning tanto dentro como fuera de Google. Desde que se volvió open source en el 2015, su repositorio en Github tiene más de 120,000 estrellas.

Interés de TensorFlow en los últimos años

Es importante recordar que TensorFlow no es la única herramienta para hacer Machine Learning en producción. Existen herramientas como MXNet, Pytorch, Chainer y Caffee. Es importante recordar que estas sólo son herramientas y cada una tiene sus ventajas y desventajas.

TensorFlow tiene un excelente equilibrio de flexibilidad y escalabilidad. La flexibilidad permite a los desarrolladores e investigadores probar ideas nuevas en un tiempo corto. Aunque muchos piensan que TensorFlow sólo sirve para redes neuronales (Deep Learning), realmente te permite hacer todo tipo de cosas como aprendizaje por refuerzo. La escalabilidad permite que los modelos desarrollados puedan ser usados por millones de usuarios. TensorFlow, además, es portable, por lo que puede ser utilizado en todo tipo de dispositivos. ¿Quieres entrenar tu modelo en el browser? Se puede. ¿Y en un Raspberry Pi? ¡También!

Adicionalmente, TensorFlow cuenta con un conjunto diverso de herramientas para poder tener los proyectos en producción de una manera muy sencilla. Compañías como OpenAI, NVIDIA, Airbnb e Intel usan TensorFlow por estas razones.

TensorFlow realmente sirve para todo tipo de cosas. Detección de cáncer de piel, generación de audio, predicción de trazos a la hora de hacer dibujos o reconstruir una foto con el estilo de pintura de Van Gogh son sólo algunos de los demos interesantes.

Introducción a TensorFlow

TensorFlow consiste de APIs en diferentes niveles. En su nivel más alto, están los Estimators. Los Estimators permiten desarrollar un pipeline de Machine Learning de manera muy sencilla. En el nivel más alto también tenemos a Keras, un API amigable para crear y entrenar redes neuronales. Keras es un componente central de TensorFlow 2.0. En el nivel más bajo tenemos el API deI python, el cual es el más usado, pero también existen de C++, Java, Go, JavaScript y Swift. En el curso empezaremos con el API de Python e iremos subiendo la pila hasta llegar a las herramientas de nivel más alto.

Los diferentes APIs que hay con la versión 1 de TensorFlow

Los APIs para construir modelos cambian con TensorFlow 2.0 dado que está más centrado en tf.keras y tf.layers se abandona.

TensorFlow tiene un mayor enfoque en tf.keras para construir modelos.

Grafos y sus ventajas

Un componente esencial para entender TensorFlow es el grafo. Es importante destacar que con TensorFlow 2.0 se va a mover hacia el modelo de Eager Execution, el cual se presenta más adelante. Aunque TensorFlow 2.0 usa Eager Execution, este post y el siguiente se enfocarán en el modelo de grafo pues la mayor parte del código actual de TensorFlow usa este paradigma.

Los grafos nos permiten representar programas. Por ejemplo, si quisieras hacer (x²)*y + y + 2 lo podemos representar así:

En la parte de abajo están las entradas, las cuales, en este caso, son dos variables y una constante. Los nodos del grafo representan operaciones (suma y multiplicación).

Lo importante de entender es que TensorFlow separa la definición de sus operaciones de la ejecución. Los dos pasos de un programa son, por lo tanto:

  1. Ensamblar un grafo
  2. Ejecutar las operaciones del grafo

En TensorFlow tenemos tensores. Un tensor es un arreglo de n dimensiones. Al número de dimensiones se le conoce como rank. Por ejemplo:

  • Tensor de 0 dimensiones: escalar
  • Tensor de 1 dimensión: vector
  • Tensor de n dimensiones: matriz

¡Suficiente introducción! Es hora de ver código. Yo recomiendo usar Colaboratory para evitar tener que hacer instalaciones. Colab es un sistema gratuito de Google que permite crear cuadernos de Python (similar a cuadernos de Jupyter si has trabajado antes con ellos). Tiene muchas ventajas como GPUs gratuitos y un conjunto de herramientas interesantes. El código completo de este artículo lo puedes encontrar en nuestro repositorio de GitHub. Colab también te permite pasar como parámetro el link de un cuaderno en GitHub y lo abre directamente. Por ejemplo: https://colab.research.google.com/github/AILearnersMX/TensorFlow-Course/blob/master/lesson1-intro/intro_to_tensorflow.ipynb.

Colab ya viene con TensorFlow instalado, así que podemos pasar a importarlo directamente.

En este ejemplo se tiene una operación, tf.add, que suma dos tensores (de 0 dimensiones) con valor de 3 y 5. Dado que una imagen dice más que mil palabras:

El grafo generado por el código anterior

TensorFlow creó el nodo de suma (Add) a partir del código de arriba. Dado que no definimos nombres en las entradas, TensorFlow determinó automáticamente los nombres. En este caso, x es 3 y y es 5. En el grafo, los nodos representan operaciones, variables y constantes (las últimas dos las veremos en el siguiente post). Las aristas, las conexiones entre los nodos, son tensores. Los tensores representan datos y fluyen a través del grafo. Los datos llegan a nodos que realizan operaciones con ellos. Es por eso que se llama TensorFlow.

Como se mencionó antes, el grafo está compuesto por operaciones, ops, las cuales reciben como entrada cero o más tensores o pueden generar nuevos tensores.

Regresemos al ejemplo. Al utilizar print, no se está desplegando el resultado esperado. En cambio, sólo imprime información del tensor. Esto es porque sólo hemos creado el grafo, pero no lo estamos ejecutando.

Se debe crear una session desde la cuál se ejecutará el grafo. En otras palabras, el código de antes sólo genera el grafo que determina los tamaños de los tensores y las operaciones que se ejecutarán dentro de él. Para que los valores fluyan a través del grafo, se debe hacer con una sesión.

Tener que crear y cerrar la instancia de la sesión una y otra vez es algo molesto. En general, suele ser más idiomático hacer lo siguiente (es equivalente):

Veamos un ejemplo un poco más complejo. Digamos que quieren hacer (2*3)^(2+5). El grafo para representar esto es el siguiente:

El código y el grafo generado por el código

El código es intuitivo. Hay tres operaciones que se utilizan: suma, multiplicación y potencia. Al igual que antes, se inicia definiendo el grafo. Luego, se ejecuta la última operación en la sesión.

Un grafo muestra las dependencias entre las operaciones. Esto es una gran ventaja pues evita ejecutar código innecesario. En la siguiente celda, tenemos una operación inútil, useless, que no se utiliza dentro de la sesión. Gracias a poder representar el programa como un grafo, esa operación nunca se ejecuta. Aunque parece tonto para estos números, si en vez de números se tuvieran tensores con un rango muy alto, podríamos evitar hacer operaciones (imaginen evitar operaciones con matrices de millones de elementos).

El código y el grafo generado por el código

Al ejecutar op3 en la sesión, no se están realizando todas las operaciones. En el grafo, se puede ver claramente que el nodo Pow sólo depende de Add y Mul. Por lo tanto, al llamarlo, no se ejecutará Mul_1 en ningún momento.

Ahora veamos cómo ejecutar más de una operación dentro de la sesión. Esta es una buena oportunidad para aprender a buscar en la documentación oficial. tf.Session().run(…) tiene varios parámetros. El que nos interesa es fetches.

Como dice la documentación, el método run ejecuta el fragmento del grafo necesario para ejecutar esa operación. Viendo la documentación, sess.run puede recibir una lista de elementos de grafo como tensores y operaciones. Aquí un ejemplo:

Paralelización

En esta sección vamos a hablar brevemente de paralelización. Si no han visto antes paralelización, no se preocupen, sólo vamos a verlo en un nivel alto en el contexto de TensorFlow.

Algo interesante de los grafos es que se pueden paralelizar al dividirlo en subgrafos. Regresando al ejemplo del inicio, podemos dividirlo en dos partes diferentes. Dado que las operaciones no dependen entre sí, cada subgrafo se ejecuta en un dispositivo de aceleración (GPU, TPU) y se sincronizan en la última operación.

Veamos un ejemplo más:

En este ejemplo, Mul y Add no dependen entre ellas por lo tanto las podemos ejecutar de manera paralela. Si en vez de 15 y 5 tuviéramos matrices de 10,000x10,000 elementos, probablemente acabamos de conseguir un buen speedup.

Pero también hay una pregunta interesante. La multiplicación de dos matrices puede ser en sí misma una operación costosa computacionalmente hablando. ¿Podríamos paralelizar una operación? ¡La respuesta es sí!

Vamos a hablar de cómo se implementan las operaciones en TensorFlow. Tomemos como ejemplo tf.transpose().

Ejemplo de cómo usar tf.transpose

Tanto el CPU como el GPU comparten la misma interfaz, la cuál se encuentra en array_ops.cc. Esta interfaz describe cosas como el tipo y tamaño de las entradas y salidas.

A su vez, la operación tiene dos implementaciones: una en C++ y otra en CUDA. CUDA es una plataforma de programación paralela. El código es similar al de C, pero permite sacar el mayor provecho posible del hardware disponible. Podemos encontrar el header en transpose_op.h. El código de la izquierda (no importa las cosas específicas del código) y el de la izquierda hacen lo mismo, sólo que uno usa el hardware típico (CPU) con C++ mientras que el otro paraleliza las operaciones con CUDA.

Código en C++ y CUDA de tf.transpose

No se preocupen, TensorFlow tiene todo tipo de operaciones ya implementadas, por lo que no tendrán que implementar cosas en bajo nivel. Por ahora, nos podemos mantener con el API de “bajo” nivel en Python.

Vamos a probar código con GPU. Si no tienes GPU, Colab nos da GPU y TPU gratuitos (con algunas limitaciones). En la barra de herramientas, podemos agregar GPU en: Runtime > Change Runtime Type > Hardware Acceleration > GPU. Nota que esto cambia a otro ambiente, por lo que tendrás que volver a correr las celdas para importar TensorFlow.

Explicit Device Placement significa que podemos ejecutar algo directamente en un pedazo de hardware específico. En este ejemplo se multiplican dos tensores.

Concluyendo, session utiliza el grafo default. En teoría puedes crear más de un grafo, pero esto es propenso a errores. Múltiples grafos no son distribuidos. Es mejor tener un grafo con partes no conectadas y distribuir esto.

Las ventajas de los grafos son:

  • Ahorra recursos computacionales
  • Separa la computación en pequeñas partes diferenciables.
  • Computación distribuida — CPU, GPU, TPU
  • Muchos modelos de Machine Learning se pueden ver como grafos

Eager Execution

La realidad es que gran parte de esto está cambiando con el anuncio de TensorFlow 2. TensorFlow utiliza un paradigma imperativo que se llama Eager Execution. La idea es hacer el desarrollo en TensorFlow más amigable y más similar al estilo de programación de Python. En vez de tener que definir un grafo y luego ejecutarlo, Eager Execution te permite ejecutarlo directamente. Las tres principales ventajas de este paradigma son:

  • Debugging facilitado.
  • Más fácil de aprender.
  • Desarrollo más intuitivo.
  • Menos código boilerplate (adiós sesiones).

Por otro lado, hay un trade-off con desempeño. El código de Eager Execution no es tan rápido En un post más adelante veremos a fondo cómo funciona Eager Execution y cómo se puede sacar el mayor desempeño con Autograph y tf.function, pero, por ahora, sólo vamos a introducir a su funcionamiento.

Para correr Eager Execution, es necesario reiniciar el runtime y llamar enable_eager_execution(). Ahora, en vez de tener que crear una sesión y un grafo, podemos ejecutar directamente el nodo. Por ejemplo, podemos usar tf.add para sumar dos tensores directamente.

Ejemplo de cómo usar Eager Execution

TensorFlow tiene todo tipo de operaciones con tensores. Aquí hay otros ejemplos

Recuerden que estos métodos se pueden utilizar sin ningún problema con el paradigma de grafo siempre que se utilice una sesión para ejecutarlo.

TensorFlow y NumPy

NumPy es una librería muy utilizada en Data Science y computación científica. La estructura más utilizada en NumPy son los ndarrays. A diferencia de estos, los Tensor son inmutables y tienen acceso a la memoria acelerada (GPU). Aún así, trabajar con NumPy y TensorFlow es extremadamente sencillo y podemos utilizar ambos en un proyecto. Por ejemplo, si tienes un pipeline en el cual haces limpieza de datos y luego lo quieres alimentar a un pipeline de Machine Learning, se puede hacer. Hay alta compatibilidad entre sus operaciones.

Las funciones de NumPy pueden recibir tensores
Las funciones de TensorFlow pueden recibir ndarrays

¡Esto fue todo por hoy! Para saber más de nosotros, nos pueden seguir en Meetup, Facebook, Twitter e Instagram. Nuestras charlas se encuentran en nuestro canal de YouTube.

--

--