En los artículos anteriores, introducción y mecanismos de atención, estuvimos hablando de las circunstancias que nos llevaron a embarcarnos en la construcción de este modelo y los principales enfoques que se han venido usando para el entrenamiento de redes neuronales con información secuencial. En este veremos como llevamos a cabo la construcción de nuestro primer modelo de speech to text, ¡¡¡es español!!!

Así, una vez definida la arquitectura que se quería usar, quedaba el último paso, entrenar y construir el modelo de Speech to Text. Proceso que queda reflejado en la siguiente figura:

Speech to Text con Tensorflow modelo datahack

Figura 7 - Esquema general del entrenamiento y construcción del modelo de speech to text

Construyendo el dataset inicial

Lo primero que se hizo fue construir un dataset (conjunto de datos) con el que entrenar al modelo. Este estaría formado por una serie de audiolibros y los ebooks correspondientes. Lo que se buscaba era tener un buen número de ejemplos de personas hablando y la transcripción a texto de lo que iban diciendo. Para esto se usaron tanto audiolibros como ebooks libres de derechos y en español, obtenidos de sitios como LibriVox y Project Gutenberg, entre otros.

Se recopilaron un total de 23 audiolibros, y los 23 ebooks correspondientes, que sumaban un total de 180 horas de audio.

Antes de entrenar el modelo de Speech to text

El siguiente paso fue procesar los audiolibros y ebooks recopilados para adaptarlos al formato LibriSpeech, ampliamente usado para el entrenamiento de modelos de Speech to Text. En este formato hay muchos recursos disponibles en inglés, pero no en español, de ahí que se tomará la decisión de dataset propio que sirviera para cumplir con los objetivos del proyecto.

Paso 1: procesar los audiolibros

El primer paso fue procesar los audiolibros para eliminar todas aquellas cosas que eran dichas por los narradores y que no aparecían en los ebooks. Por ejemplo, comentarios acerca del autor y menciones acerca del proyecto al que pertenecía el audiolibro en cuestión. También se ajustó el sampling rate de los mismos a 16 KHz, por motivos de compatibilidad con el modelo a entrenar. Todas  estas operaciones se hicieron con el programa Audacity.

Paso 2: procesar los ebooks

A continuación, se procesaron los ebooks. Para ello, se dividieron en el mismo número de partes que tenía el audiolibro correspondiente. Por ejemplo, si el audiolibro “La batalla de Trafalgar” estaba compuesto por 17 archivos mp3, el ebook de trafalgar, se dividió en 17 partes, de forma que cada una de ellas coincidiera con el archivo mp3 correspondiente. Además de esto, se hizo una revisión ortográfica, se eliminaron aquellas partes de texto que no aparecían en los audios y se trataron elementos del texto como abreviaturas y números romanos, es decir, donde ponía Sr., se sustituía por Señor y el XIX, se sustituía por 19, por ejemplo. Todo esto se hizo con herramientas de procesamiento de texto y la librería NLTK (Natural Language Toolkit) de Python.

Speech to Text con Tensorflow modelo datahack

Figura 8 - Archivos del audiolibro “La batalla de Trafalgar” y archivos de texto generados a partir del ebook, para conseguir la paridad entre el audio y texto disponible

Paso 3: Alineando audio y texto

Lo siguiente fue hacer el alineamiento de los pares audio-texto con la librería Aeneas. El resultado final fueron 17 directorios, siguiendo con el ejemplo anterior, que contenían: un archivo de texto, con el nombre del directorio, y un número determinado de archivos de audio, uno por cada párrafo existente en el archivo de texto de referencia. Cada uno de los archivos de texto que están dentro de los directorios, serían los índices que indican qué archivo de audio le corresponde a cada uno de los párrafos.

Speech to Text con Tensorflow modelo datahackFigura 9 - Directorios generados, y ejemplo del contenido del primero de ellos, tras alinear los parejas audio-texto correspondientes al audiolibro y ebook de “La batalla de Trafalgar”.

Además de los directorios, la librería Aeneas genera unos archivos json (mapas de sincronización audio-texto), uno por cada pareja de archivos de audio-texto, en los que queda recogida la alineación realizada. Estos se usaron, junto con la herramienta finetuneas, para comprobar si la alineación audio-texto se había hecho bien y aplicar correcciones, si fuera necesario.

Paso 4: división en subconjuntos

Tras alinear todos los audiolibros y ebooks correspondientes, los datos resultantes se distribuyeron los datos en tres subconjuntos: train, dev y test, siguiendo la siguiente proporción 60/20/20: 60% en train, 20% en dev y 20% en test. Los dos primeros, train y dev, se usarían para entrenar el modelo y el subconjunto de test, serviría para evaluar la eficiencia del modelo obtenido. Después de esto, se transformaron en tfrecords, formato nativo de tensorflow.

Speech to Text con Tensorflow modelo datahack

Figura 10 - Distribución de los datos en los tres subconjuntos: train, dev y test

Entrenamiento del modelo de Speech to text

Para el entrenamiento del modelo, se uso una librería de modelos de deep learning de Google, llamada tensor2tensor, que incluye la implementación de la arquitectura descrita del modelo The Transformer.

Lo primero que se hizo, es subir los tfrecords a un bucket de GCP (Google Cloud Platform), para poder entrenar el modelo se pudiera entrenar con una TPU (Tensor Processing Unit), una unidad de procesamiento creada por Google, que está optimizada para el desempeño de tareas de machine learning y deep learning.

Speech to Text con Tensorflow modelo datahack

Figura 11 - Vista de algunos de los tfrecords que se subieron al bucket de GCP

A continuación se creo una instancia de computación con una Cloud TPU, es decir, una máquina virtual con TPU. En la misma se instaló la librería tensor2tensor y se procedió a realizar el entrenamiento del modelo, lanzando una sentencia similar a esta:

Speech to Text con Tensorflow modelo datahack

Figura 12 - Sentencia para iniciar el entrenamiento del modelo, en ella se indica la orden de entrenamiento y los parámetros  asociados a la misma

Los progresos obtenidos durante el entrenamiento se fueron guardando en una carpeta “output”, situada en el mismo bucket donde estaban los datos de entrenamiento. Al finalizar el mismo, se obtuvieron una serie de checkpoints del modelo, versiones de progresión del entrenamiento. El entrenamiento se mantuvo hasta que el valor de pérdida (loss), estuvo por debajo de 0,010. Con esto se aseguraba que la precisión del mismo (accuracy), estuviera cerca del 100%.

Speech to Text con Tensorflow modelo datahack

Figura 13 - Vista de una parte de la carpeta output, donde se fueron guardando los resultados obtenidos durante el entrenamiento del modelo

Probando la eficacia del modelo...

Una vez entrenado el modelo, se llevó  a cabo una prueba de la eficiencia del mismo. Para ello, se hizo una prueba de la capacidad de inferencia (predicción) del mismo, haciendo uso del subconjunto de test, mencionado anteriormente. Los resultados fueron muy prometedores y el modelo lo hizo bien en casi el 100% de los casos.

Con el modelo  entrenado y validado, se procedió a realizar una prueba en real del mismo. Para ello, se les pasaron diferentes mensajes de voz, obtenidos a través de diferentes dispositivos, micrófono de ordenador, móviles, etc. Al hacer esto, se vio que el modelo era bastante sensible al ruido ambiental y, si este era demasiado acentuado, perjudicaba en gran medida a las predicciones realizadas.

Debido a esto, se decidió implementar un proceso de limpieza y normalización del audio entrante, antes de que este se pasara al modelo que hacía la predicción. Para ello, se usó una librería de python, SpeechRecognition, especializada en la detección y tratamiento de la voz humana. Con la limpieza previa del audio, los resultados mejoran bastante, aunque seguía teniendo cierta debilidad al ruido ambiental.

Resultados

El resultado final de todo esto fue la obtención de una primera versión de un modelo de speech to text, capaz de pasar la voz humana (en español) a texto con una eficiencia más que aceptable pero que aún sigue siendo sensible al ruido ambiental. No obstante, como he comentado, esta es una primera versión que esperamos mejorar en el futuro, llevando a cabo una o varias de las siguientes acciones:

Y hasta llega nuestra singladura acerca del desarrollo y puesta en marcha de nuestro primer modelo de speech to text, espero que os haya sido interesante. Por nuestra parte, seguiremos trabajando en ello y os iremos contando nuestros avances, ya sea a través de este canal u otros que tenemos disponibles.

¡Saludos a tod@s y buena semana!


Javier Moralo, Data & AI Creative de datahack


El proyecto empresarial de DATAHACK CONSULTING SL., denominado “DESARROLLO DE INTELIGENCIA ARTIFICIAL EN ROBOTS APLICADOS AL TRATAMIENTO DEL ALZHEIMER Y LA DEMENCIA” y número de expediente 00104725 / SNEO-20171211 ha sido subvencionado por el CENTRO PARA EL DESARROLLO TECNOLÓGICO INDUSTRIAL (CDTI)

MÁSTER EXPERTO BIG DATA ANALYTICS

Gracias al Master en Big Data Analytics 100% Online tendrás amplios conocimientos sobre las herramientas y técnicas analíticas necesarias para la modelización de los principales retos de negocio, con el fin de mejorar la toma de decisiones a través de los datos y el conocimiento.

Tal y como vimos la semana pasada en el artículo introductorio de Speech to text con Tensorflow, los mecanismos de atención  surgieron para resolver los “problemas de olvido” que sufrían la redes encoder, a la hora de memorizar secuencias muy largas. Su funcionamiento se basa en emular el procesamiento humano, es decir, en vez de memorizar y procesar la secuencia de una vez, lo van haciendo por partes. Así, estos mecanismos de atención se sitúan entre la red encoder y decoder, y le van diciendo a la red decoder qué parte de la oración tiene que mirar (poner atención), a la hora de realizar la transformación de la palabra correspondiente al paso en el que está, es decir, le van diciendo la influencia que tiene una palabra sobre las otras.

Speech to Text con Tensorflow (2) - Mecanismos de Atención

Figura 3 - Estructura encoder-decoder con mecanismos de atención

Esto es así, porque los mecanismos de atención crean conexiones entre el context vector y la frase entrante, asignándole un peso a cada una de ellas, y evitando los problemas de olvido. Estos pesos indican el grado de correlación existente entre los elementos entrantes (source) y los posibles targets.

Speech to Text con Tensorflow (2) - Mecanismos de Atención

Por ejemplo, en esta frase, cuando vemos la palabra “comiendo”, esperaremos encontrar una palabra que haga referencia a comida cerca (alta atención). El tamaño describe a la comida, pero no tiene una relación directa con “comiendo” (baja atención). Los mecanismos de atención solucionan eso señalando la palabra a la que deben prestar alta atención.

The Transformer, la elección de datahack

Teniendo en cuenta todo lo visto anteriormente y nuestra experiencia en el desarrollo de modelos de deep learning, procedimos a la elección de la arquitectura que mejor se adaptaba a nuestro caso de uso, que fue el The Transformer, un modelo publicado en “Attention is All you Need” (Vaswani, et al., 2017), que mejora en gran medida las operaciones seq2seq, usando los mecanismos de atención y prescindiendo de las Redes Neuronales Recurrentes.

Como vimos anteriormente, las RNNs manejan la información secuencial (word-by-word) y no permiten la paralelización. The Transformer la permite y reduce significativamente los tiempos de entrenamiento, eliminando las Redes Neuronales Recurrentes y usando unidades de atención, organizadas en estructura Encoder-Decoder.


Javier Moralo, Data & AI Creative de datahack


El proyecto empresarial de DATAHACK CONSULTING SL., denominado “DESARROLLO DE INTELIGENCIA ARTIFICIAL EN ROBOTS APLICADOS AL TRATAMIENTO DEL ALZHEIMER Y LA DEMENCIA” y número de expediente 00104725 / SNEO-20171211 ha sido subvencionado por el CENTRO PARA EL DESARROLLO TECNOLÓGICO INDUSTRIAL (CDTI)

MÁSTER EXPERTO BIG DATA ANALYTICS

Gracias al Master en Big Data Analytics 100% Online tendrás amplios conocimientos sobre las herramientas y técnicas analíticas necesarias para la modelización de los principales retos de negocio, con el fin de mejorar la toma de decisiones a través de los datos y el conocimiento.

Uno de los resultados del proyecto DIA4RA, que estamos actualmente desarrollando en datahack y cuyo objetivo es dotar a un robot humanoide “Pepper”, de una serie de capacidades cognitivas que le permitan asistir a personas con Alzhéimer, ha sido la creación de un modelo de speech to text, es decir, un software que convierte la voz humana a texto, gracias al uso de técnicas de deep learning.

Dentro del proyecto DIA4RA, este modelo es el encargado de transcribir la voz humana a texto, de modo que otros modelos y sistemas implementados en el robot puedan interpretar lo que dicen los humanos que se dirigen a él. Así, este modelo es fundamental para la operativa de la máquina de estados, encargada de definir los diferentes estados que puede adoptar el robot y de la orquestación de todos los modelos y sistemas implicados en el funcionamiento del robot.

speech to text con tensorflow

A priori, uno puede pensar que para hacer esto se podría recurrir a alguno de los servicios cognitivos disponibles en la red, y no tener que embarcarse en el desarrollo de un modelo de deep learning, con todas las implicaciones que ello conlleva. Sin embargo, en datahack se decidió optar por la creación de un modelo Speech to Text propio, por la siguientes razones:

Trabajando con información secuencial

El Speech to Text es un caso de uso donde se trabaja con información secuencial, es decir, una serie o sucesión de cosas que siguen un orden y/o guardan entre sí una determinada relación.

Tradicionalmente este tipo de información se ha tratado con redes neuronales recurrentes (Recurrent Neural Networks o RNN), redes neuronales con memoria que se usan para trabajar con información secuencial.

En este tipo redes la neuronas se disponen de forma secuencial, una a continuación de la otra. Cada una de ellas representa un momento temporal y tienen la capacidad de pasar la información recopilada a la siguiente. Así, a la hora de hacer las predicciones (O),  se tendrá en cuenta tanto el vector de entrada (X) como un vector de estado (h). Así, teniendo en cuenta el siguiente gráfico, para predecir Ot se tendrá en cuenta Xt  y ht-1, vector de estado resultante de la etapa anterior. Además de predecir el valor de Ot , se generaría un nuevo vector de estado ht que sería usado en la siguiente etapa (h+1). Esto se iría repitiendo de forma sucesiva.

speech to text con tensorflow

Figura 1 - Disposición de las neuronas y flujo de información en una capa de RNN

Un problema de las Redes Neuronales Recurrentes y su solución

Uno de los problemas de las Redes Neuronales Recurrentes (RNN) es que sufren lo que se conoce como el short-term memory, es decir, funcionan muy bien cuando la información útil está próxima al estado actual, pero su rendimiento va empeorando conforme esta se va alejando, es decir, no capturan bien las long-term dependencies, dependencias de larga distancia. Esto se debe a que con secuencias muy largas (más de 100 elementos), sufren lo que se conoce como vanishing gradient y tienden a “olvidar” el principio de las mismas. Así, teniendo en cuenta el siguiente ejemplo:

El gato, que ya había comido mucho pollo con verduras, estaba lleno.

Podría ocurrir que al llegar al verbo “estaba”, la RNN puede haber olvidado el principio de la frase y no sea capaz de ver que esta relacionado con la palabra “gato”.

Para minimizar este problema se usan: las GRU (Gated Recurrent Unit) y las LSTM (Long-Short Term Memory), dos tipos especiales de neuronas que cuentan con una célula de memoria, que les permite capturar mucho mejor las long-term dependencies. Y las  BRNN (Bidirectional Recurrent Neural Networks), que tienen en cuenta tanto la información anterior como  posterior a la hora de hacer una predicción en el momento actual.

Todo esto se puede combinar y crear BRNN con GRU o LSTM, con un aumento considerable de la capacidad de computación requerida durante el entrenamiento de la red neuronal.

Todo esto, aplicado al Speech to text...

En el caso del Speech To Text, donde la entrada es una secuencia y la salida también, se pueden reducir los costes de computación, haciendo uso de la arquitectura seq2seq. Está se caracteriza por tener una estructura encoder-decoder compuesta por dos Redes Neuronales Recurrentes, que usan LSTM o GRU (normalmente más la primera) y trabajan de forma que la primera red (encoder) memoriza el audio, codificándolo en un vector de contexto o sentence embedding, y la segunda red (decoder) y la segunda genera el texto correspondiente, a partir del vector de contexto que ha creado la primera.

speech to text con tensorflow

Figura 2 - Estructura encoder(verde) - decoder(morado)

Aunque esta estructura mejora los costes de computación, tiene el problema de que cuando la secuencia de audio entrante es muy larga, la red neuronal encoder tiende a olvidar la primera parte de la mismas, tras concluir su procesamiento. Para resolver esto, surgieron los mecanismos de atención.

De los mecanismos de atención y de más cosas hablaremos en el próximo post.

Muchas gracias por vuestra atención, ¡valga la redundancia! 🙂


Javier Moralo, Data & AI Creative de datahack


dia4ra cdtiEl proyecto empresarial de DATAHACK CONSULTING SL., denominado “DESARROLLO DE INTELIGENCIA ARTIFICIAL EN ROBOTS APLICADOS AL TRATAMIENTO DEL ALZHEIMER Y LA DEMENCIA” y número de expediente 00104725 / SNEO-20171211 ha sido subvencionado por el CENTRO PARA EL DESARROLLO TECNOLÓGICO INDUSTRIAL (CDTI)

MÁSTER EXPERTO BIG DATA ANALYTICS

Gracias al Master en Big Data Analytics 100% Online tendrás amplios conocimientos sobre las herramientas y técnicas analíticas necesarias para la modelización de los principales retos de negocio, con el fin de mejorar la toma de decisiones a través de los datos y el conocimiento.

En el artículo anterior hicimos una introducción a las Spatial Transformer Networks (STN) y desarrollamos su componente Localisation Network. Hoy nos centraremos en desarrollar los dos componentes restantes.

Grid Generator

El objetivo de Grid Generator es obtener un “Parameterised Sampling Grid”, que son un conjunto de puntos del feature map de entrada U a partir de los cuales se generarán los puntos del feature map de salida V que contendrá la imagen transformada.

Para ello, en primer lugar el Grid Generator creará un meshgrid del mismo tamaño que el feature map de entrada U. De forma que si la altura es H, habrá en dicho eje H valores igualmente espaciados entre -1 y 1 y si la anchura es W dispondrá en ese eje de W valores igualmente espaciados entre -1 y 1. Esos valores se corresponderán con el conjunto de índices (xt, yt) que hacen referencia a las coordenadas en el feature map de salida V. Como queremos aplicar transformaciones afines a este grid y entre esas transformaciones están las traslaciones, para poder hacerlo empleando la operación de multiplicación de matrices habrá que añadir una fila de 1s (eje z) al vector de coordenadas (xt, yt) para obtener sus correspondientes coordenadas homogéneas. Esta técnica se explicó en el artículo anterior sobre cómo “Potenciar Convoluciones con Transformaciones Afines”.

En la siguiente captura de pantalla puede verse el código en Tensorflow para la generación de ese meshgrid tomando como ejemplo unas dimensiones de 10x10:

Spatial Transformer Networks - grid generator

Para entenderlo mejor vamos a ir ejecutando paso a paso esas operaciones:

Spatial Transformer Networks - grid generator

Spatial Transformer Networks - grid generator

Spatial Transformer Networks - grid generator

Spatial Transformer Networks - grid generator

Spatial Transformer Networks - grid generator

Spatial Transformer Networks - grid generator

Spatial Transformer Networks - grid generator

Spatial Transformer Networks - grid generator

El conjunto de coordenadas  source (xs, ys) del  Parameterised Sampling Grid son los índices de los píxeles del feature map de entrada U que habrá que extraer para obtener la imagen transformada del feature map de salida. Para obtener ese conjunto de índices (xs, ys) habrá que obtener los 6 valores de θ que se corresponden con la salida de la Localisation Network, redimensionarlos en forma de matriz de transformación de (2 filas, 3 columnas) y multiplicar esa matriz por el grid de índices target (xt, yt, 1) en formato de vector columnar:

Spatial Transformer Networks - grid generator

En la imagen (a) de la izquierda se puede comprobar como a partir del feature map U de entrada se genera un grid G del mismo tamaño. Al aplicar a G la transformación identidad TI se obtiene el Parameterised Sampling Grid, es decir, las coordenadas source (xs, ys) que se samplearán del feature map de entrada U. Como se ha aplicado la transformación identidad, la imagen presente en U será igual a la de V (que se obtiene al aplicar el Parameterised Sampling Grid a U).

En la imagen (b) de la derecha se observa que el Parameterised Sampling Grid se obtiene aplicando una transformación afín Tθ al grid G.

Spatial Transformer Networks - grid generator

Sampler

Como los valores de (xs, ys) serán fraccionales necesitamos mapearlos a valores enteros para lo que emplearemos alguna función de interpolación.

El sampler empleará las coordenadas del Parameterised Sampling Grid, el feature map de entrada U y la función de interporlación para generar el feature map de salida V.

Según el paper, cada coordenada (xs, ys) indica la localización del feature map de entrada U donde se aplicará un sampling kernel “k” para obtener el valor de un píxel concreto “i” en el feature map de salida V para cada canal “c” empleando la siguiente fórmula:

Spatial Transformer Networks - sampler

Donde Φx y Φy son los parámetros de un sampling kernel k() que establece la interpolación a aplicar. Los autores del paper emplearon interpolación bilineal, pero se puede emplear cualquier sampling kernel siempre que sea diferenciable con respecto a  (xs, ys) para poder aplicar el módulo de STN durante el backpropagation de la red principal.

Conclusión

Combinando el Localisation Network, el Grid Generator y el Sampler se consigue construir un módulo Spatial Transformer. Como se comentó anteriormente, dicho módulo se puede colocar en cualquier parte de una Red Convolucional y permitirá que la red aprenda cómo aplicar transformaciones a los feature maps mientras se minimiza el error de entrenamiento de la red.

Finalmente, el conocimiento de cómo transformar cada ejemplo de entrenamiento quedará almacenado en los pesos de la Localisation Network y su salida podrá ser utilizada cuando se considere oportuno para codificar la transformación de un objeto.

Combinar esta técnica de Spatial Transformer con un aumentado explícito de los ejemplos de entrenamiento a los que se les apliquen transformaciones y distintos tipos de ruido potenciará los resultados de reconocimiento de la Red Convolucional.


MÁSTER EXPERTO BIG DATA ANALYTICS

Gracias al Master en Big Data Analytics 100% Online tendrás amplios conocimientos sobre las herramientas y técnicas analíticas necesarias para la modelización de los principales retos de negocio, con el fin de mejorar la toma de decisiones a través de los datos y el conocimiento.

En el artículo anterior vimos en qué consistía el concepto de convolución. Esta, aunque está dando muy buenos resultados en ámbitos de visión artificial e incluso en problemas de series temporales, tiene una serie de debilidades en cuanto a su capacidad de resistencia frente a variaciones en los datos. La capa de Pooling aplica un kernel o ventana sobre el feature map resultante de la convolución.

Dicho filtro hace un “resumen”, por ejemplo, tomando el valor máximo o la media del área del feature map sobre el que se aplica para posteriormente ir deslizándolo por el resto de dicho feature map. De esta forma es posible trabajar con Redes Convolucionales profundas reduciendo el coste computacional necesario. Además, proporciona una cierta invarianza a la traslación.

No obstante, como el tamaño de la ventana de pooling suele ser pequeño (2x2), esa fortaleza estará limitada a las últimas capas de la red cuando el feature map sea también pequeño. También se hizo una introducción a una técnica empleada para aumentar el poder de generalización de las Redes Convolucionales aumentando las imágenes del conjunto de entrenamiento. Y para conseguirlo se aplicaban transformaciones afines (rotaciones, escalados, transvecciones y traslaciones) a las imágenes del dataset. Así se conseguían nuevos ejemplos con los que entrenar la red. Esas transformaciones las podemos representar mediante una matriz de 2 filas y 3 columnas.

La idea es utilizar la operación de multiplicación de matrices para aplicar una transformación afín (representada mediante esa matriz de 2 filas y 3 columnas) a un punto identificado mediante sus coordenadas (x, y, 1) pero expresadas en forma de vector columnar (3 filas y 1 columna). La técnica de añadir un 1 en el eje z a las coordenadas de un punto de 2 dimensiones se explicó en el artículo anterior y permite representar la traslación en forma matricial.

A continuación, se muestran algunos ejemplos de los valores que debería tomar la matriz de transformación para aplicar diversas transformaciones afines a un punto.

Matriz identidad

valores que debería tomar la matriz de transformación para aplicar transformaciones afines a un punto

Escalado de aumento con factor de 2

valores que debería tomar la matriz de transformación para aplicar transformaciones afines a un punto

Escalado de reducción con factor de 2

valores que debería tomar la matriz de transformación para aplicar transformaciones afines a un punto

Rotación de 45º en contra de las agujas del reloj ()

valores que debería tomar la matriz de transformación para aplicar transformaciones afines a un punto

 Aumentar el dataset incorporando modificaciones de las imágenes presentes en los datos con los que se va a entrenar ayuda a dicho proceso al tratarse de un aprendizaje supervisado.

Spatial Transformer Networks

Con la técnica de Spatial Transformer Networks (STN) se pretende potenciar esa mejora además de, en cierto modo, automatizarla. Esto permite que sea la propia Red Neuronal la que aplique transformaciones a la imagen de entrada o al feature map para que se fije en el objeto a reconocer. De esta forma, se consigue una mayor capacidad de generalización abstrayéndose de su varianza posicional. Como os habréis dado cuenta, he escrito que con las STN será la propia red la que aplique modificaciones a la imagen de entrada o a los feature maps. Eso es así porque una STN se comportará como un módulo diferenciable que se podrá enganchar en cualquier parte de la red neuronal principal. El hecho de que las operaciones de este módulo sean derivables permitirá que pueda ser entrenado mediante Backpropagation al mismo tiempo que la red principal. Esto hace posible colocarlo en cualquier parte de dicha red, como se comentó anteriormente. Normalmente se suele colocar al principio de la Red Convolucional para que los siguientes feature maps se obtengan a partir de la salida del módulo STN. La arquitectura de un módulo STN puede apreciarse en la siguiente imagen en la que comprobamos que está formada por 3 componentes llamados Localisation NetworkGrid Generator y Sampler

Spatial Transformer Networks

Localisation Network

Este componente recibe el feature map U, hace que pase a través de una Red Neuronal que puede ser un Perceptrón Multicapa (en este caso lo redimensionará previamente para que tenga formato (Tamaño_Batch, Altura * Anchura * Número_Canales) o una Red Convolucional y finalmente hará una regresión para obtener los 6 parámetros correspondientes a los valores que tomará la matriz de transformación.

Spatial Transformer Networks - localisation network

Los valores de esta red se entrenarán de forma supervisada al mismo tiempo que la Red Neuronal principal en la que se inserte el módulo STN. A continuación, se adjunta un código de ejemplo en Tensorflow para mostrar cómo podría implementarse el componente de Localisation Network mediante un Perceptrón de 2 capas con Dropout (para añadir regularización), en la que el bias de la última capa estará inicializado con los valores de la matriz identidad: 

Spatial Transformer Networks - localisation network

Si quieres saber más sobre Grid Generator y Sampler, ¡atento al próximo artículo!

Ya estamos aquí para continuar el relato donde lo dejamos en el artículo anterior de esta serie. La trama quedó en suspense después de mostrar que un programa de Tensorflow se divide en 2 fases. La primera consiste en la construcción del grafo que alberga por un lado las operaciones a realizar y por otro los tensores que contienen los valores que alimentan a dichas operaciones. La segunda etapa se basa en la creación de un objeto de sesión que permitirá ejecutar el grafo sobre dispositivos locales o remotos. Sigamos con este recorrido por los principales componentes del Core de Tensorflow.

Sesión

La clase tf.Session permite conectar un programa cliente, que actúa como frontend en alguno de los lenguajes disponibles como Python, gracias al “Tensorflow Distributed Execution Engine” de C++. Un objeto de esta clase tf.Session proporciona el entorno para ejecutar objetos tf.Operation y evaluar objetos tf.Tensor. Esto lo hace tanto en dispositivos (como CPUs, GPUs, TPUs) de la máquina local como de máquinas remotas. También proporciona el entorno para cachear el grafo que tenga asociada dicha sesión.

Como se ha comentado, en DIA4RA vamos a emplear la API de alto nivel Estimator que, entre otros beneficios, creará y gestionará el objeto tf.Session por nosotros. Además, nos permitirá abstraernos de escribir el código de bajo nivel necesario para poder realizar un entrenamiento distribuido entre múltiples máquinas. Por ello, aunque no vayamos a manejar sesiones de forma explícita en nuestro proyecto, vamos a mostrar una pequeña introducción en las siguientes líneas.

El constructor de esta clase recibe de forma opcional 3 argumentos:

Una práctica habitual a la hora de crear una sesión es la de hacerlo mediante un bloque “with” de Python. De esta forma, la sesión estará activa dentro del ámbito de dicho bloque y al salir se cerrará automáticamente y se liberarán sus recursos. En caso de no emplearse un bloque “with” será necesario cerrar explícitamente esa sesión mediante tf.Session.close para que se liberen sus recursos.

Para realizar la ejecución propiamente dicha se emplea el método tf.Session.run que recibe una lista con los tf.Operation o/y tf.Tensor que se pretenden ejecutar y que deben estar presentes en el grafo asociado a dicha sesión.

En la siguiente captura de pantalla se muestra la creación del grafo por defecto que contendrá la operación creada mediante tf.add que trabajará sobre los arrays de numpy “a” y “b” (cuyo valor se obtiene inmediatamente al no ser tensores de Tensorflow). A continuación, se genera mediante un bloque “with” la sesión “sess” y dentro de su ámbito se ejecuta empleando sess.run la operación de suma. Al finalizar el ámbito del bloque “with” se cerrará automáticamente la sesión.

Programando con la API de Bajo Nivel de Tensorflow

A continuación, se puede ver otra imagen en la que se ha creado el grafo “g1” al que se le ha añadido la operación creada mediante tf.multiply que trabajará sobre los arrays de numpy “d” y “e”. Una vez hecho eso, se crea la sesión “sess1” que operará sobre el contenido del grafo “g1” únicamente. Mediante sess1.run se ejecuta la multiplicación presente en “g1” y como la sesión no se ha creado mediante un bloque “with”, como buena práctica cuando ya no se va a emplear más dicha sesión, se cierra utilizando sess1.close.

Programando con la API de Bajo Nivel de Tensorflow

En estos ejemplos se han utilizado sesiones que trabajan sobre recursos presentes en la máquina local. La API de Bajo Nivel de Tensorflow permite que la sesión pueda acceder a dispositivos presentes en otras máquinas que trabajen conjuntamente formando un clúster permitiendo un cálculo distribuido. En DIA4RA emplearemos la abstracción de ejecución distribuida que ofrece la Estimator API para realizar entrenamientos en paralelo entre múltiples máquinas presentes en el Cloud.

Los clúster distribuidos de Tensorflow

Un clúster distribuido de Tensorflow estará formado por uno o más jobs, de forma que cada job puede contener una o más tasks. Las tasks de un job colaboran en una ejecución distribuida de un grafo. Normalmente cada task se ejecuta en una máquina diferente pero también es posible ejecutar varias tasks en la misma máquina, por ejemplo, empleando un dispositivo diferente presente en dicha máquina (si dispone de varias GPUs). Cada tarea estará asociada a un tf.train.Server que estará formado por un master para crear sesiones y por un worker para ejecutar operaciones del grafo.

Una técnica muy utilizada cuando se realiza un entrenamiento distribuido consiste en emplear procesos que actuarán como Parameter Servers. Las variables serán repartidas entre los parameter servers disponibles en función de su tamaño para balancear la carga.  Cuando un proceso worker necesite un variable almacenada en un parameter server, hará referencia directamente a ella. Cuando un worker calcule un gradiente, será enviado al parameter server que almacene esa variable concreta.

Para crear un clúster será necesario por un lado crear un objeto de la clase tf.train.ClusterSpec y uno o varios tf.train.Server.

La clase tf.train.ClusterSpec permite indicar todas las tasks del clúster. Su constructor recibirá un diccionario mapeando cada nombre de job a una lista de direcciones de red de las tasks de ese job o a otro diccionario en el que cada clave será un índice de task y su correspondiente valor será la dirección de red de dicha task.

El siguiente fragmento de código crea la configuración de un clúster formado 2 workers y 1 parameter server.

cluster = tf.train.ClusterSpec({“worker”: [“192.168.1.120:3333”, “192.168.1.121:3335”],
                                            “ps”: [“192.168.1.122:3335”]
                                            })

A continuación, lo que habrá que hacer es instanciar  los servers correspondientes a los 2 workers y al parameter server.

server0 = tf.train.Server(cluster, job_name=“worker”, task_index=0)
server1 = tf.train.Server(cluster, job_name=“worker”, task_index=1)
serverPs = tf.train.Server(cluster, job_name=“ps”, task_index=0)

El siguiente paso será emplear la función tf.device para especificar en qué proceso se colocará cada operación:

with tf.device(“/job:ps/task:0”):
    var0 = tf.Variable( … )
    var1 = tf.Variable( … )
    var2 = tf.Variable( … )
with tf.device(“/job:worker/task:0”):
    res0 = tf.matmul(var0, var1)  
with tf.device(“/job:worker/task:0”):
    res1 = tf.matmul(var1, var2)

En este punto, se crearán las sesiones necesarias y cada una de ellas se asociará con un server (2 workers y 1 parameter server).

sessPs = tf.Session(target=serverPs.target)
sess0 = tf.Session(target=server0.target)
sess1 = tf.Session(target=server1.target)

Dichas sesiones se emplearán para ejecutar las operaciones asociadas a esos servers (tasks). En un futuro artículo se presentará de forma práctica un entrenamiento distribuido empleando la API de Bajo Nivel de Tensorflow. Pero antes, vamos a continuar con el recorrido por sus componentes básicos.

Asignación de dispositivos mediante tf.device()

En Tensorflow los dispositivos disponibles para ejecutar operaciones son CPUs y GPUs. La notación que se emplea para referenciarlos es la siguiente:

La secuencia seguiría aumentándose el índice de la GPU si existiesen más de estos dispositivos. Por defecto, si una operación está implementada para poder ejecutarse tanto en CPU como en GPU, los dispositivos GPU tendrán prioridad a la hora de recibir esa operación.

En primer lugar, para visualizar los dispositivos disponibles en la máquina se podrá ejecutar el siguiente código:

from tensorflow.python.client import device_lib
device_lib.list_local_devices()

Programando con la API de Bajo Nivel de Tensorflow

Si se desea colocar una o varias operaciones en un dispositivo en concreto la forma de hacerlo será empleando un bloque “with” con la función tf.device() que recibirá el nombre de dicho dispositivo.

with tf.device(“/gpu:0”):
    a = tf.constant([2, 7, 3])

Constantes

Se trata de un tensor que tiene un valor constante que se crea mediante la función tf.constant() y dispone de los siguientes argumentos:

Programando con la API de Bajo Nivel de Tensorflow

Variables

Una variable de Tensorflow permite almacenar un tensor cuyo valor puede ser cambiado ejecutando una serie de operaciones sobre ella. Esas modificaciones son visibles desde múltiples objetos tf.Session lo que permite que diferentes workers puedan acceder al valor de una misma variable.

A bajo nivel la clase que se encarga de su implementación se trata de tf.Variable. En cuanto a la creación de variables la forma recomendada es mediante la función tf.get_variable, que recibe el nombre de dicha variable y su shape entre otros argumentos opcionales como su valor inicial.

var1 = tf.get_variable(“test”, [2, 1, 4], dtype=tf.int64)

Ese comando creará una variable llamada “test” de 3 dimensiones y shape [2, 1, 4], cuyos valores serán de tipo tf.int64 (en caso de no indicarse el parámetro dtype, el tipo por defecto será tf.float32) y su valor inicial se establece por defecto a partir a partir de la distribución aleatoria determinada por tf.glorot_uniform_initializer.

Esta función (también llamada inicialización Xavier) generará muestras aleatorias entre [-a, a], donde:

Si se desea indicar un initializer en concreto se puede hacer de la siguiente forma:

var2 = tf.get_variable(“test2”, [5, 2, 4, 1], dtype=tf.int32, initializer=tf.zeros_initializer)

En caso de que el initializer sea un tf.Tensor entonces no habrá que indicar el shape en el constructor de tf.get_variable ya que lo inferirá a partir del shape de ese initializer ([2, 3]):

var4 = tf.get_variable(“test4”, initializer=tf.constant([2,3]))

Las “collections” de Tensorflow son listas con nombre de tensores u otros objetos como variables. Cuando se crea una variable se añadirá por defecto en las colecciones tf.GraphKeys.TRAINABLE_VARIABLES (son las variables para las que Tensorflow calculará sus gradientes) y tf.GraphKeys.GLOBAL_VARIABLES (variables que pueden ser compartidas entre múltiples dispositivos).

Para hacer que una variable no sea tenida en cuenta a la hora de calcular los gradientes habría que pasar el parámetro “trainable=False” a la hora de su creación.

var5 = tf.get_variable(“test5”, [4, 2], trainable=False)

Se pueden crear collections personalizadas utilizando cualquier cadena como nombre. Para añadir un objeto ya creado a una colección se empleará el método tf.add_to_collection.

tf.add_to_collection(“col1”, var4)

La forma de conocer todos los objetos de una collection es mediante la siguiente función:

tf.get_collection(“col1”)

Hasta este punto hemos visto cómo crear variables y cómo unirlas a collections. Pero antes de poder utilizar una variable hay que añadir una operación al grafo para que la inicialice explícitamente, lo que permite evitar que se vuelvan a ejecutar initializers que pueden consumir un tiempo importante, por ejemplo, al volver a cargar un modelo desde un “checkpoint”.

La función que emplearemos para inicializar una única variable será:

sess = tf.Session()
sess.run(var1.initializer)

Pero como ir inicializando variable por variable puede ser muy tedioso, hay una función que viene a nuestro rescate, tf.global_variables_initializer(). Se encarga de añadir una única operación al grafo que, cuando sea ejecutada, inicializará todas las variables presentes en la collection  tf.GraphKeys.GLOBAL_VARIABLES.

init = tf.global_variables_initializer()
sess1 = tf.Session()
sess1.run(init)

Esta función no asegura el orden en que serán inicializadas las variables. De forma que si hay variables que dependen de otras y no estamos seguros de si ya han sido inicializadas, una forma de programar de forma segura es empleando en la variable dependiente la función var_de_la_que_se_depende.initialized_value().

a = tf.get_variable(“a”, [1,2], initializer=tf.zeros_initializer())
b = tf.get_varible(“b”, initializer=a.initialized_value() * 2)

Si se quiere asignar un nuevo valor a una variable se podrán emplear la familia de funciones assign, assign_add.

sum = a.assign(a + 7.0)
sum2 = a.assign_add(8.0)

La instrucción tf.get_variable puede encontrarse en un bloque de código (una función) que puede ser ejecutado repetidas veces. No quedaría claro si lo que se pretende es volver a crear la misma variable o reutilizarla. Una forma de solucionar esto es creando una variable dentro de un bloque “with” con tf.variable_scope (eso añadirá el nombre del variable_scope al principio del nombre de la variable) y cuando se quiera reutilizar dicha variable, la instrucción tf.get_variable se incluirá en el interior de  un tf.variable_scope con el mismo nombre que cuando se creó pero que además recibirá el parámetro "reuse=True".

En caso de no utilizar el flag "reuse=True", si la variable ya había sido previamente creada mediante una llamada a get_variable, al volver a intentar crearla se obtendrá una excepción o si no se creó mediante una llamada a get_variable. Esto permite evitar reutilizar variables por error. Una vez establecido reuse a True ya no se puede poner a False dentro del bloque. También se puede fijar el flag reuse a True dentro del bloque llamando a scope.reuse_variables().

A continuación, vamos a ver un ejemplo de utilización de la función get_variable para crear una variable compartida si no existe o reutilizarla en caso de que exista. Esto se controla dentro del scope "relu". Si se definen otros variable scopes dentro del actual, heredarán el flag "reuse=True".

Las variables creadas usando get_variable siempre se nombran utilizando el nombre de su variable scope como prefijo. Por ejemplo, relu/threshold. Si se crea un scope con un nombre que ya había sido utilizado por otro scope, se añadirá un sufijo "índice" para que el nombre sea único.

Se definirá una función "relu()" que reutilizará una variable relu/threashold. Dicha variable será creada fuera de la función inicializándola a 0.0. A continuación se construirán 5 relus llamando 5 veces a la función "relu()".

tf.reset_default_graph()

def relu(X):
    with tf.variable_scope("relu", reuse=True): # Reutilización de la variable threshold
        threshold = tf.get_variable("threshold", shape=(), initializer=tf.constant_initializer(0.0))
        w_shape = int(X.get_shape()[1]), 1
        w = tf.Variable(tf.random_normal(w_shape), name="weights")
        b = tf.Variable(0.0, name="bias")
        linear = tf.add(tf.matmul(X, w), b, name="linear")
        return tf.maximum(linear, threshold, name="max")

X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
with tf.variable_scope("relu"): # Creación de la variable threshold
    threshold = tf.get_variable("threshold", shape=(), initializer=tf.constant_initializer(0.0))
relus = [relu(X) for i in range(5)]
output = tf.add_n(relus, name="output")

Placeholders

Un tf.placeholder es una estructura que almacenará datos pero dichos datos serán cargados en tiempo de ejecución. Por tanto, nos permitirá definir un almacén de información que podrá ser añadido al grafo sin tener que alimentarlo hasta que se cree una sesión y se desee ejecutar operaciones que dependan de este placeholder.

Para ello, el método sess.run deberá recibir como parámetro además de las operaciones a ejecutar, un argumento llamado feed_dict que será un diccionario donde cada clave será el nombre de un placeholder y su correspondiente valor será justamente el valor que queramos que reciba esa estructura.

import tensorflow as tf

a = tf.placeholder(“float”, None)
b = a + 7

with tf.Session() as sess:
    r = sess.run(b, feed_dict={a:[6,3]})

Como se ha podido apreciar, no es necesario definir estáticamente un tamaño para los datos que albergará un tf.placeholder. También puede tener cualquier número de dimensiones y cuando alguna de sus dimensiones sea “None”, significa que puede tener cualquier número de elementos en ella.

import tensorflow as tf

a = tf.placeholder(“float”, [None, 2])
b = a + 7

with tf.Session() as sess:
    r = sess.run(b, feed_dict={a:[[1, 4], [3, 5], [7, 9]]})

---

Hasta aquí este breve repaso a los componentes básicos del Core de Tensorflow. En próximos artículos continuaremos viendo APIs de más alto nivel de este potente framework de Google.

 

El proyecto empresarial de DATAHACK CONSULTING SL., denominado “DESARROLLO DE INTELIGENCIA ARTIFICIAL EN ROBOTS APLICADOS AL TRATAMIENTO DEL ALZHEIMER Y LA DEMENCIA” y número de expediente 00104725 / SNEO-20171211 ha sido subvencionado por el CENTRO PARA EL DESARROLLO TECNOLÓGICO INDUSTRIAL (CDTI)

MÁSTER EXPERTO BIG DATA ANALYTICS

Gracias al Master en Big Data Analytics 100% Online tendrás amplios conocimientos sobre las herramientas y técnicas analíticas necesarias para la modelización de los principales retos de negocio, con el fin de mejorar la toma de decisiones a través de los datos y el conocimiento.

chevron-down