domingo, 10 de septiembre de 2023

Reduciendo el uso de recursos en visión artificial



En un post anterior hablaba de la importancia que tiene el empleo de deep learning en las contribuciones a la emisión de gases de efecto invernadero, especialmente durante las fase de aprendizaje. Para optimizar recursos y emitir menos existen varias técnicas, de las que hablaré en alguna otra entrada. En esta ocasión quisiera describir las ventajas que hemos introducido en Aitea para reducir en la medida de lo posible el uso intensivo de hardware en la fase de predicción.  

Antes de nada describamos un poco el contexto. Aitea es un software de visión artificial desarrollado por Aerin Sistemas y que tiene la virtud de ser altamente modular y flexible. Una de sus grandes virtudes es que nos permite introducir, ya sea para ensayar o poner en producción, nuevos algoritmos, sea cual sea su naturaleza. Esta ventaja nace, además de en su diseño, en el hecho de que toda la codificación es nuestra, lo que nos permite introducir prácticamente cualquier descubrimiento que se realice en este campo, siempre y cuando se pueda codificar en una máquina de propósito general. Una línea de investigación consiste en buscar optimizar el uso de los recursos, no solo para ganar rapidez, sino para ahorrar consumos, alargar la vida útil de los equipos y en general ayudar a nuestros clientes y al medio ambiente al mismo tiempo. La versión 1.3 de Aitea incorpora un salto cualitativo con respecto al los consumos en general y de GPU/CPU en particular. En las siguientes líneas quiero mostrar los resultados objetivos con respecto al consumo y uso de la GPU durante la ejecución de Aitea antes y después de introducir las mejoras de la versión 1.3.

Para las pruebas se ejecutaron tres analíticas sobre el mismo equipo, una de reconocimiento facial, otra de seguimiento de personas y una tercera de conteo de personas. Todas estas analíticas se ejecutaron de forma paralela para un total de 17 cámaras en ambientes muy complicados en algunos casos - salas con muchas personas, pasillos con mucho transito, ... -. Lógicamente se trataba de no perder exactitud en los resultados,  solo debía haber impacto en los consumos. Vemos los resultados:


Potencia usada por la GPU a lo largo del tiempo

 En azul podemos ver la versión previa de Aitea, el consumo es lineal ya que el esfuerzo que se realiza es constante ante la presencia de personas. En naranja podemos ver la versión mejorada del software, el esfuerzo ahora es dinámico, se reduce cuando hay que prestar menos atención a una escena y aumenta cuando lo que sucede es de interés. 


Calcular la emisión de CO2 es inmediato, el siguiente gráfico dibuja las emisiones de CO2 emitidas de forma acumulativa. 

Emisiones acumuladas, en azul previa a la nueva versión

A grandes rasgos podemos decir que con la versión previa a la 1.3 las emisiones tras cinco horas de uso eran de 15 gramos frente los 9 con la nueva versión. Esto significa una reducción de un 40% aproximadamente. Estos valores, aunque importantes, pueden parecer insignificantes frente lo conlleva entrenar un modelo de lenguaje como chat GPT3. Sin embargo, si tenemos encuentra que en un edificio de oficinas de cierto tamaño se pueden llegar a usar dos equipos, se concluye que en 24 horas con 20 oficinas analizándose se pueden emitir unos 3 Kg de CO2/día, o lo que es lo mismo, una tonelada al año. Aún queda lejos de las emisiones producidas por el entrenamiento de un modelo con un alto número de parámetros - similar a GPT -, que supera las 200 toneladas. Sin embargo, desde nuestra modesta situación podemos considerar que reducir de 1000 Kg/año a 600 Kg/año es un hito digno de destacar.  También se puede esgrimir que el uso de Aitea esta generando por si mismo unas  emisiones que de otra forma no se realizaría, sin embargo algunos de los datos que ofrece Aitea se están usando para ayudar en la gestión de los sistemas  HVAC, reduciendo las emisiones debidas a calefacción/refrigeración, por lo que podemos considerar que el uso de Aitea - ya sea en su versión 1.3 o en la anterior - esta reduciendo las emisiones de CO2 a la atmósfera gracias a su aplicación.

Además, hay que considerar lo que supone alargar la vida útil de los diferentes componentes, especialmente de la GPU. 

Temperatura de la GPU para una sola tarjeta
   

La temperatura es un valor decisivo a la hora de estimar la vida útil de la GPU, cualquier reducción en ese sentido se traduce en un alargamiento de la vida útil. Como puede comprenderse esto permite reducir el consumo de materias primas escasas como puedan ser los componentes de las GPUs. Estos ensayos se han realizado únicamente durante altas cargas de trabajo, es decir, durante un uso intensivo del edifico, estos resultados podrían ser más llamativos durante los periodos - por la noche - donde la actividad del edificio se reduce considerablemente. Tampoco se han analizado los valores de CPU y sus temperaturas que también se ven reducidas con la versión 1.3. 



Machine Learning y cambio climático



Poco se habla de la contribución del Machine Learning a las emisiones de gases de efecto invernadero. En contraste encontramos miles de artículos sobre las ventajas del Deep Learning y las maravillas de las inteligencias artificiales generativas. Como punto negativo se menciona la posible pérdida de puestos de trabajo,  algunos inconvenientes relacionados con los derechos de autor, los sesgos o si estamos marcando el camino hacia la imbecilidad colectiva confiando ciegamente en la tecnocracia de las grandes corporaciones.  Evidentemente estos temas son muy interesantes y deben ser tratados desde el punto de vista de la ética y la política, pero también me parece fundamental hablar de las consecuencias climáticas que tiene el empleo de los servidores de gran potencia que normalmente se encuentra detrás de la magia de la inteligencia artificial. Considero además que somo quienes nos dedicamos a estos asuntos los primeros que deberíamos meditar sobre las consecuencias de nuestro trabajo, por su puesto cada uno desde la modestia de su posición.

Las ideas subyacentes

Para entender el impacto sobre el medio ambiente pueden tener el uso de la inteligencia artificial repasemos antes algunos conceptos básicos:

El paradigma de algoritmo de inteligencia artificial se encuentra en las llamadas redes neuronales artificiales, existen muchos otros métodos pero para ejemplificar la explosión actual de las AI nos referiremos principalmente a ellas.  Las redes neuronales artificiales son un tipo de algoritmo del tipo denominado aprendizaje supervisado que emula en cierta medida el funcionamiento de la neurona natural. El perceptrón, que es la unidad básica de estas redes, fue desarrollada teóricamente en el año 1943 por Walter Pitts y Warren McCulloch. La primera red neuronal artificial fue implementada algo después, en 1957 Frank Rosenblatt empezó a trabajar en una máquina capaz de emular físicamente la idea teórica subyacente en la ideal del perceptrón. Estamos hablando de una implementación por hardware {Figura 1} no por software, para una codificación de los algoritmos en una máquina de propósito general hubo que esperar a la década de los 60  usando para su ejecución un IBM 704. Estas redes se podrían usar para aprender ciertas tareas, entre las más simples clasificar entradas, como por ejemplo, dada una foto donde aparecía una persona,  decidir si esta era una mujer o un hombre. 


Mark 1
Figura 1: Mark 1


Tras la construcción y ejecución del perceptrón se comprobó por un lado la considerable potencia de estos métodos y por otro, el enorme costo en hardware y tiempo de cómputo para ejecutar estas redes.  Desde la invención del perceptrón hasta la actualidad, se han sucedido diversos "inviernos de la AI" consistentes en periodos  de escasos avances prácticos en esta disciplina. Las redes neuronales artificiales usan básicamente calculo matricial lo que implica un coste computacional considerable y las máquinas de la época no estaban preparadas para realizar de forma eficiente estos cálculos. Por tanto, aunque ya en los años 80 del siglo pasado se hablara de deep learning - redes neuronales multicapa - su construcción  experimental no estaba al alcance del hardware de la época. El invierno se ceñía sobre el campo de la AI, las ejecuciones prácticas eran inviables lo que generó  un desincentivo que afecto a los desarrollos teóricos.   


Figura 2. Los inviernos de la AI {1}.


Los avances en hardware de principios de los años 2000 abrieron la puerta al uso en aplicaciones reales, pero aún faltaba un pequeño pero importante empujón. Este empujón vino del mundo de los videojuegos, generar movimiento en los gráficos conlleva un buen número de operaciones de algebra matricial, por lo que se pensó en desarrollar hardware altamente especializado que realizará de forma eficiente estos cálculos. A partir de ese momento y gracias al uso de las tarjetas gráficas usadas en consolas y PCs para jugar, se logró superar las dificultades que presentaban los tiempos de entrenamiento y uso de este tipo de algoritmos.  En cierta medida los jugadores pusimos nuestro grano de arena en el deshielo del invierno de la AI. 

Pese a las considerables ventajas de su uso, estos dispositivos tienen sus inconvenientes,  por un lado un consumo nada desdeñable, por otro su nivel de complejidad y por último la necesidad de emplear materiales escasos para su construcción. 

Figura 3. Consumos de algunas GPUs, para el caso que nos ocupa, el entrenamiento de una red,  podemos considerar  el valor de TGP , total graphics power 

 

Usar una red neuronal artificial  para distinguir, por ejemplo, entre perros y gatos,  conlleva dos tareas:

- Entrenamiento: Básicamente consiste en ofrecerle al algoritmo un conjunto de imágenes etiquetadas  - perros vs gatos - para que mediante métodos de control del error configure unos pesos, esto es, una gran matriz que sirva precisamente para clasificar las entradas - imágenes - en dos conjuntos. 

- Predicción: Una vez entrenada la red se le pasa la imagen y la matriz de pesos da como salida una predicción sobre la clase a la pertenece - perro o gato - dicha imagen. 

El entrenamiento conlleva un considerable esfuerzo de cómputo y para poder hacerlo en un tiempo razonable, lo más conveniente es llevar este proceso a las GPUs. El proceso de predicción es más liviano, pero las necesidades de acelerar la predicción en entornos reales - la detección de objetos en la conducción autónoma, por ejemplo - obligan a usar el hardware de forma intensiva, por lo que el uso de la GPU también esta generalizado. Sea como fuere, el uso de deep learning  conlleva unos consumos energéticos y de materias primas considerables ya que se debe usar de forma intensiva las GPUs durante los dos procesos antes citados  -  {2, 3} - . Por ejemplo:

  • El entrenamiento de GPT-3 expulso a la atmósfera unas 552 toneladas de CO2.
  • Alpha Zero de Google libero unas 100 toneladas de CO2 a la atmósfera durante su fase de aprendizaje. 
  • Entrenar una red neuronal de gran tamaño, 200 millones de parámetros, produce aproximadamente unas 240 toneladas de CO2.
La ejecución de la predicción tiene un coste mucho menor, pero su uso generalizado es una parte fundamental del problema. Por otro lado los requisitos de hardware no van a reducirse y el empleo de estas técnicas no para de extenderse. Es evidente que el uso del deep learning participa  de forma considerable en el  cambio climático  de origen atropogénico. Sin embargo, de poco sirve ser un ludita en estos asuntos, las ventajas que ofrece esta tecnología son enormes, incluidas una inestimable ayuda precisamente en la búsqueda de soluciones a nuestros problemas climáticos. Lo más sensato es, sin renunciar al uso de la tecnología, meditar sobre su empleo y reflexionar en que medida podemos reducir o mitigar el impacto negativo. Precisamente aquí planteo algunas de estas reflexiones:

¿Deep learning para todo? 

Disponer de un martillo puede ser imprescindible en algunos ámbitos pero si pretendemos resolver todos lo problemas a martillazos nos vamos a encontrar en serias dificultades.  ¿De verdad es necesario extender  tanto el uso del deep learning?, ¿no existen otras técnicas alternativas?. Por ejemplo, desde Aerín hemos investigado algoritmos alternativos, menos costosos computacionalmente, para detectar la presencia de mascarillas. Mientras que durante la pandemia se multiplicaban las publicaciones científicas que resolvían este problema  usando redes convolucionales  {4} , desde el departamento de I+D de Aerin buscábamos soluciones por vías alternativas, usando entre otras cosas análisis del histograma con resultados mejores que los modelos CNN. 

Pero podemos llegar más lejos, que una inteligencia artificial pueda hacer un trabajo no significa que automáticamente tenga que emplearse para tal fin. No hay nada malo en analizar si hay una alternativa más  sencilla o si un operador humano  asistido por una AI puede ser más eficiente que emplear pesados modelos de deep learning. La tecnología es una gran herramienta pero no es ni la única ni la mejor en todos los ámbitos

Una clave, la investigación básica

Muchos son los matemáticos y los físicos que deciden seguir la senda de la empresa privada, dedicando su vida laboral al desarrollo y la aplicación práctica de técnicas de inteligencia artificial. Esta decisión conlleva en muchos casos  el sacrificio de la otra vía, la de la investigación básica. Es conocido que la vocación eminentemente teórica en matemáticas esta descendiendo en favor de un carrera profesional más elocuente desde el punto de vista de lo material. Sin embargo,  el trabajo teórico es absolutamente fundamental, no solo para la AI, sino para todas las ciencias en general. Por tanto dignificar el trabajo de la investigación en  ciencia básica, aumentar las cuantías de las becas  y asegurar un futuro estable y bien remunerado es fundamental en todos los sentidos. Un ejemplo, recientemente se ha logrado por parte del Instituto de Telecomunicaciones y Aplicaciones Multimedia en la Universitat Politècnica de València  {5}, al encontrar una mejora en el método de calculo de funciones de matrices basado en la técnica de Paterson-Stockmeyer. Este hallazgo teórico puede tener un impacto importante en cualquier algoritmo que trabaje con matrices, incluida la gran mayoría de las técnicas de deep learning reduciendo las necesidades de cómputo

En otro caso, optimizar


En caso de que tengamos que usar algoritmos complejo, con la necesidad de entrenar mediante largos conjuntos de datos etiquetados, debería ser obligado realizar una correcta planificación con la finalidad de reducir en la medida de lo posible el impacto. Esto se puede realizar de varias formas, usando métodos de "transferencia de conocimiento", eligiendo con cuidado la arquitectura o construyendo un buen dataset. También es posible ajustar la potencia requerida de las GPUs sin que esto suponga un gran impacto en los tiempos de entrenamiento, ni en los resultados. Mi próxima entrada realiza un análisis de estas técnicas de reducción de potencia. 

Finalmente podemos trabajar en mejorar el impacto de nuestros algoritmos y considerar en que medida podemos reducir el impacto en las emisiones de gases contaminantes.


{1}. Schuchmann, Sebastian. (2019). Analyzing the Prospect of an Approaching AI Winter. 10.13140/RG.2.2.10932.91524. 
{2}.  Lacoste, A., Luccioni, A., Schmidt, V., & Dandres, T. (2019). Quantifying the carbon emissions of machine learning. arXiv preprint arXiv:1910.09700. 
{3}.  Schwartz, R., Dodge, J., Smith, N. A., & Etzioni, O. (2020). Green ai. Communications of the ACM, 63(12), 54-63. 
{4}.  JIGNESH CHOWDARY, G., et al. Face mask detection using transfer learning of inceptionv3. En Big Data Analytics: 8th International Conference, BDA 2020, Sonepat, India, December 15–18, 2020, Proceedings 8. Springer International Publishing, 2020. p. 81-90. 
{5}.  Sastre, J., & Ibáñez, J. (2021). Efficient evaluation of matrix polynomials beyond the Paterson–Stockmeyer method. Mathematics, 9(14), 1600. 


miércoles, 31 de marzo de 2021

Introducción a la computación cuántica II

 Alicia al otro lado del espejo





En la anterior entrada habíamos explicado las diferencias que había entre un sistema clásico y un sistema cuántico, también habíamos anunciado que las peculiaridades de los sistemas cuánticos permitían usarlas con un enorme provecho. Toca ahora explicar qué cualidades son esas y donde podemos encontrar provecho computacional. Antes de continuar me gustaría explicar de forma muy concisa cómo avanza la ciencia, particularmente en  campos tan especulativos como lo era la física cuántica en aquella época. Una vez establecidos los primeros principios de la mecánica cuántica se comenzó en algunos casos a realizar especulaciones teóricas; esto pasa por desarrollar los diferentes elementos matemáticos, añadiendo nuevas piezas, en definitiva creado un cuerpo teórico. Estos desarrollos se podían inspirar en experimentos o fenómenos naturales, pero en muchos casos solo era un juego teórico. En estas situaciones no queda más remedio que trabajar así, actualmente por ejemplo teorizando sobre supercuerdas o en aquella época elucubrando sobre hasta dónde podría llegar aquello de la cuantificación. En aquel momento era muy difícil experimentar a nivel cuántico, no existían las técnicas apropiadas, había que desarrollarlas. Por ese motivo muchas de las respuestas que ofrecía la teoría parecían absurdas, nada intuitivas, incluso directamente una locura que nada tenía que ver con la experiencia real. Pero si para el caso de la fórmula de Planck había funcionado tan a la perfección, ¿no serían esas locuras teóricas también verdaderas?.  Evidentemente esto no es suficiente, había que confirmar experimentalmente que efectivamente la realidad confirmaba los aspectos de la teoría cuántica. Cuando desgrane las diferentes particularidades de la teoría cuántica habrá que tener en cuenta que la misma sorpresa, incluso idéntica  incomprensión, debió invadir la mente de los físicos de la época, incluso de los propios padres del asunto. Einstein, uno de los padres cuánticos, llegó a decir horrorizado que  Dios no podía jugar a los dados con el Universo, cuando empezó a leer lo que estaba dando de sí la teoría que él mismo había ayudado a parir. Lo cierto es que poco a poco cada una de las maravillas predichas por la teoría se han ido confirmado una tras otra, siendo hoy en día la mecánica cuántica una de las teorías más confirmadas, hasta tal punto que la mayoría de los físicos piensa que en su campo podría resultar definitiva. Ahora toca el momento de maravillarse, sin darle más vueltas al asunto, pasemos sin miedo al otro lado del espejo.


Los postulados y sus increíbles consecuencias.

La mecánica cuántica se basa en cuatro postulados que pasamos a describir de forma un tanto heterodoxa:

1. La realidad ya no es lo que era.

En la realidad de la física clásica podríamos caracterizar un sistema totalmente con dos valores; su posición en el tiempo x(t) y su momento, producto de la masa y la velocidad, en el tiempo p(t). Con esas relaciones podríamos saber de antemano que va a pasar exactamente en cualquier instante anterior o posterior. Pero en los sistemas cuánticos la realidad es otra, solo podemos aspirar a conocer como mucho la probabilidad de la posición y la probabilidad del momento. Toda la información de un sistema se encuentra guardada en lo que llamamos vector de estado que matemáticamente pertenece a los espacios de Hilbert. Quedémonos con las consecuencias, se terminó el determinismo, es imposible predecir el futuro, como mucho podemos aspirar a conocer probabilidades de que suceda esto o aquello. Pero lo más interesante es que este no es un problema que se solucione disponiendo de la suficiente información, ni de capacidad para medirlo todo... simplemente es que la naturaleza de la realidad es tal que a niveles cuánticos es borrosa.  


2. Los observables.

En mecánica cuántica las cosas que se pueden medir se llaman observables -posición, momento angular,  energía,...- pero mientras que en la física clásica estos observables evolucionan en el tiempo; la posición de la piedra que cae evoluciona, ahora está a 12 metros, luego a 11, etc.., ahora lo que evoluciona es la probabilidad. En estos sistemas podemos decir que la probabilidad de su posición evoluciona; la piedra cuántica tiene probabilidad alta de estar a 12 metros, luego evoluciona y tiene máxima probabilidad de estar a 11 metros, etc... Pero claro, que la máxima probabilidad esté en un momento dado a x=11 metros no significa que sea cero estar a x=12 o a x=10.7. 

3. El mundo es una cosa borrosa. 

Los observables solo pueden dar ciertos valores al ser medidos y solo podemos dar la probabilidad de que de que el resultado de una medida sea tal o cual valor antes de medir. Una vez medido obtenemos un valor, pero nada podemos decir sobre si el observable tenía ese valor justo antes de medir. Es decir, cuando no miramos la realidad es como si no estuviese concretizada, como si fuese un borrón de probabilidades. Es al medir cuando la cosa se concretiza, antes el sistema solo era una nube de probabilidades. Se pueden encontrar sistemas donde solo exista un valor para la medida, en ese caso sí podemos decir que el sistema tiene un observable con un valor concreto, incluso sin medirlo. En el resto de los casos podemos calcular valores medios o valor esperado, pero nada de certezas. 

4. Mirar es construir la realidad.

Aunque solo podemos predecir las probabilidades antes de medir, si sabemos que una vez medidos el sistema se concretiza a esa realidad, de tal forma que si medidos inmediatamente después el sistema se encontrará en ese mismo estado, lo que nos permite predecir con exactitud la segunda medida. Es decir las medidas hacen que el sistema se convierta en una concreción igual que la de un sistema clásico. Imaginemos una piedra cuántica, antes de medir solo podemos hablar de probabilidades, pero cuando observamos y nos da 12 metros, ya sabemos que inmediatamente después estará exactamente a 12 metros.

Estos cuatro postulados tienen consecuencias realmente increíbles, enumeremos algunas:

1. Atravesar paredes: Una partícula podría atravesar un muro de potencial mayor que su energía cinética.  Dado que la posición es una probabilidad, una partícula puede tener cierta probabilidad de estar más allá de un muro de potencial que clásicamente no podría atravesar, puede ser muy pequeña pero no nula. Este fenómeno ha sido observado y es usado en los microscopios de efecto túnel.   

2. Estar vivo y muerto a la vez es posible: Como hemos visto los sistemas cuánticos se representan con el llamado vector de estado, este vector de estado es una composición de todos los estados posibles para ese sistema. Solo cuando medimos el sistema se concretiza en uno, al estilo determinista de la física clásica. En el mundo cuántico las cosas pueden estar en muchos estados a la vez, mientras no miremos. Esto no lo advertimos en nuestro día a día ya que los objetos que nos rodean son demasiado grandes para comportarse cuánticamente. Schrödinger ideó su famoso experimento mental para trasladar este efecto cuántico a un entorno macroscópico, como el de un gato que está en dos estados; vivo y muerto.  

3. Entrelazados: Una propiedad curiosa es la del entrelazamiento cuántico, esto sucede cuando un sistema cuántico, formado por dos o más partes debe ser descrito por un único vector de estado. Como hemos visto ese estado es mezcla de todos los estados posibles y no se concretiza hasta que no se mide. Lo curioso del asunto es que un sistema así entrelazado, cuando se separa en sus componentes y se mide uno de ellos, esa medida parece afectar de forma inmediata a cada una de los constituyentes, por separados que estos estén.  

Vistas estas curiosidades empecemos con algunas nociones elementales de mecánica cuántica.

Nociones básicas de mecánica cuántica 

Del postulado 2 se deduce que un sistema se describe completamente en función de sus observables. Los observables son esos atributos medibles -posición, velocidad, etc.- Para que el sistema se pueda definir de forma correcta tendremos que elegir los observables que sean lineal mente independientes, que formen una base del espacio. Es decir, no tiene sentido elegir la velocidad y el momento ya que ambas cosas están relacionadas -p=m*v-.   

sábado, 27 de marzo de 2021

Introducción a la programación cuántica I

Abriendo la puerta a un nuevo universo.




Início esta serie de entradas para ofrecer una introducción a la programación cuántica, tema absolutamente apasionante. Antes de nada me gustaría recordar que es un tema de considerable complejidad y que por mucho que quisiera no podría abarcarlo en un puñado de entradas. Se trata de tener una nociones básicas para ya en las últimas entradas adquirir un componente más práctico.  No se van a requerir conocimientos previos, más allá de algo de matemáticas y programación por lo que empezaré por las nociones básicas con un carácter divulgativo.  


Sistemas clásicos vs sistemas cuánticos:


La computación de todos los días, la que realizamos en nuestros ordenadores, tablet, móviles o la que acontece en los grandes servidores o en los cluster de supercomputación usan sistemas que podríamos denominar como clásicos. Este nombre no se refiere a sistemas de "toda la vida" ni nada por el estilo, simplemente hace referencia a que estos sistemas físicos pueden explicarse, por lo menos en su comportamiento macroscópico, mediante la física clásica. Pero ¿qué es la física clásica?; básicamente es aquella que se rige por las leyes clásicas de la física, leyes de Newton, ecuaciones de Maxwell, etc. Una piedra, en su caída desde lo alto de un acantilado, se explica perfectamente empleando las leyes de Newton, de igual modo un ordenador corriente también se comporta según las leyes clásicas, todas ellas previas a las teorías cuánticas. Alguien dirá que en lo más pequeño de un ordenador clásico - transistores - ya hay efectos cuánticos, cierto es, pero igual de cierto es que para  el tamaño actual las leyes clásicas del transistor dan cuenta de todo su comportamiento y no se necesita más.  En el mundo clásico todo es predecible, la piedra que lanzamos desde el acantilado tardará en alcanzar el agua el tiempo que estima la ecuación de la caída libre y un microprocesador de un ordenador ejecutará las órdenes que le den como la máquina de Turing que es, sin variar ni un ápice los resultados si se ejecuta la misma orden una y otra vez. Los sistemas clásicos son deterministas, de tal manera que si conociéramos la posición y las condiciones de partida de todos los objetos que forman en Universo, podríamos predecir exactamente las posición y estado de todos los elementos del mismo en cualquier momento posterior. Los sistemas clásicos parecen aburridos precisamente por predecibles y si no somos capaces de predecir el futuro es por no disponer de todos los datos, por la imposibilidad material de medirlo todo. 

La física clásica creyó tenerlo todo atado y bien atado, entre el electromagnetismo de Maxwell, las leyes de Newton junto con las de Kepler, parecían suficientes para explicar la naturaleza en su conjunto. Así transcurría el siglo XIX, mostrando un panorama desalentador para la profesión de físico teórico..., o al menos eso creían algunos científicos. Sin embargo había algunas cosas que ya dejaban asomar que no todo estaba tan bien atado. Por ejemplo; Newton 1642-1727  descubrió que la luz se descomponía en sus colores, luego ya en el siglo XIIX que los metales, cuando se calientan, son capaces de emitir luz y en el siglo XIX que la descomposición de colores del Sol aparecen algunas líneas oscuras. Newton pensaba, cuando descompuso la luz, que esas pequeñas rayas eran simplemente las divisiones entre colores. Lo más interesante era que esas líneas oscuras se podían generar artificialmente, si una luz blanca se hacía pasar a través de ciertas sustancias, era como si el material "absorviese" parte de la luz y cada sustancia parecía absorber un color determinado. Eso ya era un misterio considerable ya que no encaja con las ecuaciones de Maxwell. Otro de los misterios era el del cuerpo en equilibrio térmico con el ambiente -cuerpo negro-, según se deducía de las ecuaciones de Mawell este  debía emitir luz en todas las frecuencias, y a mayor frecuencia mayor energía. Se habían deducido algunas leyes directamente de la de Maxwell para explicar la radiación de estos cuerpos, como la de  Rayleigh-Jeans. El problema era que:

Si se suma la energía para cada frecuencia (integral de la ecuación Rayleigh-Jeans), el resultado es.. infinito!!. Lógicamente nada emite una energía infinita. 

Podemos citar muchos otros fenómenos que no se dejaban dominar totalmente por las leyes clásicas, pero en aquellos momentos se pensaba que eran pequeñas discrepancias que había que limar. Cuando se descubrió la radiactividad y los rayos X la cosa se complicó considerablemente y empezó a comprenderse que el viejo mundo Mawell-Newtoniano no era suficiente y  debía haber un marco teórico detrás aún por descubrir. Esto inició un drama en tres actos:

Acto I. Max Plank:

Max Plank 1858-1947 fue un físico brillante que inició su carrera investigando en termodinámica y la entropía. En un principio parecía que la radiación de un cuerpo negro y su misterio de emisión infinita no tenía nada que ver con la entropía -medida de desorden de un sistema- , concepto que Planck manejaba con soltura. No obstante su intuición le decía que alguna relación existía y que merecía la pena introducir esta magnitud física en el estudio del cuerpo negro. Cuando se encontró una expresión que relacionaba la entropía de un oscilador   lineal cargado que interacciona con la radiación y la energía vibracional, supo que efectivamente podía atacar  el problema de la radiación infinita introduciendo la entropía. No vamos a contar aquí los detalles, pero trabajando con la entropía Plank sacó una ley nueva para la radiación, diferente a la de Rayleigh-Jeans y a otra debida a Wien, la llamada "Ley de Radiación de Planck" no solamente justificaba bien los valores medidos, sino que además no hacía que las cosas emitieran infinita energía:

$u(\upsilon,T) = \frac{A\upsilon ^{3}}{e^{B\upsilon /T}-1}$

Al principio aparecieron algunas dudas, discrepancias con las mediciones, pero pronto se averiguó que eran problemas de cálculo y que cuanta más exacta era la medida, mejor se justificaba la ley de Planck. Bueno, se dijo Planck, he encontrado la solución al problema del cuerpo negro, pero realmente no tengo ni idea de qué implicaciones tiene. Tras un análisis de la ley concluyó que de ser cierta, como lo era, la radiación de un cuerpo negro se emite en paquetes discretos, de tal forma que E=hv, célebre ecuación de cuantificación de la energía. Es decir, la energía que emiten los cuerpos se encuentra en paquetes o cuantos, no es algo continuo como toda la física clásica dejaba ver.

Acto II. Einstein:

El efecto fotoeléctrico es un proceso que consiste en la emisión de electrones por parte de un metal cuando absorbe energía luminosa. Pero la teoría de Maxwell, que considera la luz continua, como una onda electromagnética, no era capaz de explicar este fenómeno. Es más, lo increíble es que la velocidad máxima con la que los electrones abandonan el metal no tiene relación con la intensidad de la luz sino con su frecuencia. Einstein 1879-1955 encontró la solución, si la energía de la luz fuese en paquetes de un valor cuantizado sería posible que los electrones, captando la energía de estos paquetes en forma de energía cinética, fueran capaces de escapar del metal. Para terminar de explicar el fenómeno hay que suponer que la energía de los fotones depende de su frecuencia, longitud de onda, de esta forma habría materiales que al absorber luz a cierta frecuencia, hacían saltar los electrones y generar esa corriente. Pero el proceso también es posible hacerlo en sentido contrario, los electrones parecían perder cierta energía podían emitir luz de una frecuencia determinada. Esto explicaba los patrones de absorción de la luz y también el de emisión, tal y como se observaba, pero la implicación era que los electrones se encontraban en ciertos orbitales bien definidos, con unas energías igualmente cuantificadas. De nuevo había que ir más allá de Maxwell para explicar la realidad. 

Acto III. Primer congreso de Solvay

Quinto congreso de Solvay: Singularidad espacio-temporal de inteligencia.  



Tras estos descubrimientos algunos científicos empezaron indagar sobre lo que podría pasar al generalizar el concepto de cuanto de energía y en general sobre estas discontinuidades cuánticas. Un puñado de los mejores científicos de la época se reunieron en Solvay en 1911 para debatir estas cuestiones donde ya se advirtió que los cuantos abrían una nueva teoría que en muchos casos habría de sustituir la la vieja física. Era el nacimiento de la mecánica cuántica.

Tras esta introducción histórica se podría pensar que la vieja física o física clásica ya no tendría cabida, pero no es así en absoluto. Muchos de los fenómenos macroscópicos se explican perfectamente mediante los conceptos clásicos de Newton y Maxwell; nadie mete por medio la mecánica cuántica para explicar la caída de una piedra desde un acantilado. La física clásica es por así decirlo una buena aproximación válida para ciertos ámbitos. Pero, ¿cuando hay que usar la mecánica cuántica?. Bueno eso depende de muchos factores ya que no hay una frontera clara, lo cierto es que para una escala muy pequeña la física clásica no resulta válida, pero  también es cierto que existen sistemas macroscópicos que muestran efectos cuánticos, superfluidez, superconducción,...  No hay que olvidar que las leyes de Newton y las ecuaciones de Maxwell son muy válidas, son aproximaciones perfectas cuando se le imponen ciertos requisitos.  

Ya conocemos la distinción entre sistemas clásicos; ámbito de la física clásica determinista y los sistemas cuánticos; ámbito de la física cuántica, donde no funcionan las leyes de Newton-Maxwell. Pero ¿qué hay de interesante en los sistemas cuánticos? y ¿qué relación tienen con esta nueva forma de computación?.  Antes de profundizar en estos aspectos, que dejamos ya para la siguiente entrada, podemos en pocas palabras adelantar una respuesta:

La computación cuántica, al hacer uso de  sistemas cuánticos, saca provecho de las propiedades cuánticas que exhiben  estos sistemas y que no tienen parangón en la física clásica. 

Os espero en la siguiente entrada: Introducción a la computación cuántica II. Alicia al otro lado del espejo.

En Aerin Sistemas somos expertos en desarrollo de soluciones innovadoras en el campo de la inteligencia artificial y como podéis ver también nos gusta jugar con la computación cuántica.  

  

miércoles, 9 de mayo de 2018

TensorFlow, machine learning I: Perceptrón multicapa

Esta entrada presupone, por parte del lector, ciertos conocimientos de redes neuronales artificiales. No obstante ofrezco una descripción teórica muy rápida.

Las redes neuronales artificiales se basan en el funcionamiento de las redes formadas por células de los sistemas nerviosos cuya principal capacidad es la de excitarse eléctricamente según una serie de estímulos.



Los estímulos son recibidos a través de las dentritas  y en función de estas entradas la neurona puede activarse y mandar estímulos a través de los axones terminales a otras neuronas o no activarse. Poniendo un ejemplo muy sencillo; supongamos que tenemos una red neuronal encargada de mandar la señal a los músculos para encestar una bola de papel en una papelera; las entradas a las neuronas podría ser al ángulo de lanzamiento y la fuerza, las redes se activan o no según una función de activación determinada que tiene como entrada el valor de las entradas multiplicadas por unos pesos -inicialmente aleatorios-. Este proceso de activación pasará por diferentes capas neuronales, cada una con su función de activación y sus pesos. Finalmente la capa de salida da un valor que se interpreta para colocar el brazo y lanzar. A continuación se observa el resultado y se corrige los pesos usando como criterio la minimización del error -por ejemplo según mínimos cuadrados-. El proceso de lanzamiento-evaluación del error-modificación de pesos-lanzamiento continua hasta que se alcanza el error deseado, a continuación se fijan esos pesos "ganadores". A partir de ese momento lanzar y encestar -en esas mismas condiciones- no debería requerir más entrenamiento, usando siempre los mismos pesos. Evidentemente esto es una simplificación muy grande pero es justo la idea que subyace en las redes neuronales artificiales.

Una vez establecidas estas pequeñas ideas teóricas vamos a implementar una red neuronal  en TensorFlow. Básicamente esto consiste en realizar el siguiente esquema:
Por sencillez vamos a usar solo una capa oculta, un solo atributo de entrada y una sola salida. Ampliar a más capas, a más entradas o más salidas es inmediato. 

Básicamente lo que tenemos que hacer es:
  1. Establecer el número de entradas.
  2. Definir cuantas capas ocultas -hidden layers- que deseamos y cuantas neuronas por capa.
  3. Cuales será los pesos iniciales -W-.
  4. Añadir el bias.
  5. Cual será la función de activación de cada capa.
  6. Definir la capa de salida. 
  7. Cual será el criterio de error.
  8. Entrenar la red.
  9.  
A continuación el código que realiza todo este trabajo:

def neural_net(input_x, output_y, total_epoch):
    x = ts.placeholder("float", [None, 1])
    y = ts.placeholder("float", [None])
    batch_size = 10
    n_input = 1
    n_output = 1
    n_hidden_layer = 10
    w_layer1 = ts.Variable(ts.random_normal([n_input, n_hidden_layer]))
    bias = ts.Variable(ts.random_normal([n_hidden_layer]))
    hidden_layer = ts.nn.sigmoid(ts.add(ts.matmul(x, w_layer1), bias))
    w_layer2 = ts.Variable(ts.random_normal([n_hidden_layer, n_output]))
    bias_out = ts.Variable(ts.random_normal([n_output]))
    output_layer = ts.matmul(hidden_layer, w_layer2) + bias_out

    cost = ts.reduce_mean(ts.square(output_layer-y))
    optimizer = ts.train.AdamOptimizer(learning_rate=0.001).minimize(cost)

    with ts.Session() as sess:
        sess.run(ts.global_variables_initializer())
        for epoch in range(total_epoch):
            error = 0
            total_batch = int(len(input_x) / batch_size)
            for i in range(total_batch - 1):
                batch_x = input_x[i * batch_size:(i + 1) * batch_size]
                batch_y = output_y[i * batch_size:(i + 1) * batch_size]
                feed_dict = {x: batch_x, y: batch_y}
                _, c, p = sess.run([optimizer, cost, output_layer], feed_dict=feed_dict)
                error += c/total_batch
        print("Training finalizado")


x = [[x/100] for x in range(-20, 20)]
y = [y[0]*y[0] for y in x]
neural_net(x, y, 200)
Y ahora una explicación más detallada.

1. Establecer el número de entradas

Esto es establecer cuantos atributos tendremos. En un ejemplo muy sencillo, para hacer regresión de una función desconocida $f(x)$, solo tendremos una entrada.
Otro aspecto importante es crear un placeholter para las entradas y las salidas.  Los placeholter son variables vacías que serán alimentadas más adelante. Si no se alimentan, cuando ejecutemos el algoritmo nos dará un error. Hay que tener en cuenta que si empleáramos Variables en lugar de placeholder los valores podrían ser sobrescritos, de esta forma aseguramos que los valores de entrada y salida para entrenamiento permanecen constantes a lo largo del entrenamiento. Es cierto que también podrían usarse constantes en vez de estas estructuras, sin embargo TensorFlow penaliza este uso limitando el tamaño de las constantes -2 Gb- .Por otro lado los placeholder pueden aumentar dinámicamente el tamaño de la memoria, lo que permite más flexibilidad a la hora de nutrir un modelo con diferentes conjuntos de entrenamiento.

x = ts.placeholder("float", [None, 1])
y = ts.placeholder("float", [None])
En este caso  las entradas se han representado como una matriz de una columna, mientras que la salida está representada por un array.
 

2. Definir cuantas capas ocultas que deseamos y las neuronas por capa

Por lo general no es necesario emplear más de dos capas ocultas, evidentemente hay una razón para ello. Por una lado hay que tener en cuenta que al añadir capas ocultas incrementamos el número de pesos, si la capa anterior tiene m neuronas -o si es la capa de entrada, m entradas- y la nueva capa oculta aporta n neuronas, tendremos en total mxn nuevos pesos. El exceso de pesos puede generar un problema de sobre aprendizaje siendo capaz de aprender "de memoria"  acomodando los pesos para cumplir con unas salidas dentro del error solicitado. El problema es que ante cualquier modificación de las condiciones no es capaz de ofrecer respuestas satisfactorias. Por otro lado el aumento de neuronas implica tiempos de cálculo crecientes. Lo más adecuado es empezar con una sola capa, ir aumentando las neuronas de esa capa esperando que el error se ajuste a lo solicitado. Si los resultados no mejoran entonces habría que pensar en el aumento de capas ocultas. Esto lo definiremos como una contante:

n_hidden_layer = 10

3. Cuales será los pesos iniciales, W. 

Como puede verse cada entrada, o cada neurona, aporta a la activación de todas las que tiene por delante según un peso. Inicialmente estos pesos son aleatorios. 

w_layer = ts.Variable(ts.random_normal([n_input,n_hidden_layer])) 

Esto nos da una matriz de pesos que une cada entrada -una en este caso- con cada una de las neuronas de la primera capa -10-.

4. Establecer el bias.

Cada entrada se multiplica con el peso que le corresponde para cada neurona -tal y como se muestra en el esquema-. Es decir, si tuviésemos 3 entradas $x_{0}, x_{1}, x_{2}$ la entrada a la primera neurona seria $x_{0}*w_{0,0}+x_{1}*w_{1,0}+x_{2}*w_{2,0}$, de igual manera para el resto de las neuronas. Sin embargo resulta conveniente añadir un grado de libertad más al conjunto (un bias), de tal forma que la entrada a la primera neurona de la capa oculta quede como:

$x_{0}*w_{0,0}+x_{1}*w_{1,0}+x_{2}*w_{2,0}+bias_{0}$

En nuestro caso como solo hay una entrada tendremos:

$x_{0}*w_{0,0}+bias_{0}$

El bias fue introducido en el modelo de red Adalina -ADAptative LInear Neuron- por Widrow y Ho (1960).

En nuestro caso el bias para la primera capa oculta lo calculamos como:

bias = ts.Variable(ts.random_normal([n_hidden_layer]))

5. Cual será la función de activación de cada capa.

Lo recibido por cada neurona es procesado por esta y activa la salida en función de la llamada función de activación $f(x_{0}*w_{0,0}+bias_{0})$. Existen diferentes funciones de activación:
  • Neurona todo/nada. Representada por una función escalón con un umbral -salida digital-.
  • Neurona continua tipo sigmoide.
Hay algunas otras funciones de activación pero estas son las más empleadas. En cualquier caso es necesario que sean derivables como exige el algoritmo de retropropagación del error. El módulo nn de TensorFlow nos proporciona un buen número de funciones de activación. En nuestro caso emplearemos la función sigmoide para calcular la salida de las neuronas de la capa oculta, que no es más que la suma de las entradas pasadas por la función de activación:

hidden_layer = ts.nn.sigmoid(ts.add(ts.matmul(input_x, w_layer), bias))
Esto último requiere una pequeña explicación, aunque realmente no es más que cálculo matricial simple. En cualquier caso tenemos:
  
ts.matmul(input_x, w_layer) -> Producto matricial de la matriz de 
entradas por la matriz de los pesos, si recordamos el producto 
matricial reconoceremos que el resultado es la suma de los productos
de las entradas por los pesos correspondientes, cada fila es justo 
la entrada a cada neurona de la capa oculta.  
 
ts.addts.matmul(input_x, w_layer), bias)) ->  Le sumamos a la matriz anterior los bias. 
6. Definir la capa de salida 
De igual manera debemos establecer lo que llega a la salida desde las neuronas de la última capa oculta -en este caso coincide también con la primera oculta-. Esto se hace con el mismo razonamiento que empleamos en el paso 5. 

w_layer2 =  ts.Variable(ts.random_normal([n_hidden_layer, n_output]))
bias_out = ts.Variable(ts.random_normal([n_output]))
output_layer = ts.matmul(hidden_layer, w_layer2) + bias_out

6. Cual será el criterio de error y el algoritmo de optimización

El aprendizaje de una red neuronal es de tipo supervisado, esto significa que deberemos disponer de un conjunto de datos con sus entradas y con sus salidas correctas, de tal manera que la propia red sepa en que medida su predicción es la adecuada, corrigiendo los pesos para mejorar su predicción. Para ello debemos dar a la red un criterio para establecer lo bien que esta realizando su tarea. La función de error cumple precisamente este cometido, también llamada función coste.

cost = ts.reduce_mean(ts.square(output_layer-y)

Como puede observarse esto no es más que el error cuadrático medio. 

Una vez establecido el error a estimar hay que decidir como buscar el mínimo. Cambiar los pesos sin criterio, al zar, no es un método efectivo para minimizar una función de coste. Entre los muchos métodos que se pueden elegir, uno muy efectivo es el de descenso de gradiente. De forma muy resumida, dada una función F que queremos minimizar, probablemente $F(x_{n+1})<F(x_{n})$ para:

$x_{n+1}=x_{n}-\gamma \bigtriangledown F(x_{n})$

El método de descenso de gradiente Adaptive Moment Estimation o Adam es una implementación más efectiva del descenso de gradiente: 

optimizer = ts.train.AdamOptimizer(learning_rate=0.001).minimize(cost)

7. Entrenar la red

Una vez configurada la red debemos empezar el entrenamiento. Básicamente esa tarea se realiza en el siguiente código:

with ts.Session() as sess:
        sess.run(ts.global_variables_initializer())
        for epoch in range(total_epoch):
            error = 0
            total_batch = int(len(input_x) / batch_size)
            for i in range(total_batch - 1):
                batch_x = input_x[i * batch_size:(i + 1) * batch_size]
                batch_y = output_y[i * batch_size:(i + 1) * batch_size]
                feed_dict = {x: batch_x, y: batch_y}
                _, c, p = sess.run([optimizer, cost, output_layer], feed_dict=feed_dict)
                error += c/total_batch
        print("Training finalizado")

Como puede verse es imprescindible crear una sesión y e inicializar las variables. A continuación corremos el entrenamiento un numero suficiente de veces epoch, que buscando un símil cotidiano equivaldría al número de clases de conducir que necesitamos para entrenar a nuestras neuronas para que sepan conducir un vehículo. En la línea 5 separamos el conjunto de entrenamiento en lotes, en la línea 9 creamos un diccionario para alimentar los placeholder y en la siguiente entremos la red, los valores _, c, p corresponde al optimizador, al coste y a la salida que proporciona la red, es decir a la predicción.

Logicamente habría que controlar el error y salir del bucle si se alcanza  antes de finalizar todos los ciclos de entrenamiento. Tampoco es suficiente con entrenar la red, a continuación habría que validar con un conjunto diferente de datos y en última instancia pasarle un último test con un tercer conjunto. Estos detalles los dejo para las siguientes entradas. En cualquier caso los resultados son los siguientes para los siguientes datos:
x = [[x/100] for x in range(-300, 300)]
y = [1+math.sin(3*y[0]) for y in x]
En azul la predicción de la red. Evidentemente la función no es extraordinariamente complicada, no obstante es útil para verificar que esta bien configurada. Una recomendación es siempre pasar a nuestra red configurada un patrón bien conocido -una función matemática- con pocos ejemplos pero suficiente para comprobar que la red esta bien configurada; por ejemplo, un valor excesivo en el factor $\gamma=0.01$ genera resultados menos satisfactorios.

Incluso con idénticos valores de entrenamiento una mala configuración de la red -en este caso simplemente hemos elegido mal el tamaño de los lotes de entrenamiento-.

Una vez comprobada la bondad de la red para un problema de regresión genérico, sabiendo que no hay fallos de construcción, podemos atacar el  problema de regresión real -evidentemente será necesario ajustar ciertos parámetros, aumentar las neuronas,..- pero sabremos que nuestra red esta bien construida.
  

domingo, 6 de mayo de 2018

TensorFlow, primeros pasos: Algebra matricial y fractales


Inicio una serie dedicada exclusivamente a TensorFlow, siendo esta la primera entrada con los aspectos más básicos. La intención es describir la potencia matemática de esta herramienta, atendiendo principalmente a la API de bajo nivel.


TensorFlow y el álgebra matricial:

 

Antes de nada describiremos el termino tensor, ampliamente usado en el ámbito de la física y que guarda muchas similitudes con el definido por la gente de TensorFlow.

"Un tensor es una forma de representar un valor o una magnitud y que además permanece invariante con los cambios de coordenadas".

Desde este punto de vista todas las magnitudes son tensores:
  • Un escalar es un tensor de orden 0. 
  • Un vector es un tensor de orden 1 ya que requiere n dimensiones para ser descrito.
  • Una matriz es un tensor de orden 2 ya que requiere nxn componentes.
  • Un tensor de orden m requiere $n^{m}$ componentes para ser descrito. 
Para TensorFlow los tensores son contenedores de datos, al igual que el tensor físico-matemático, y donde todos los datos son de un mismo tipo.  Algunos ejemplos podrían ser:

import tesnorflow as ts
order0 = ts.constant(12, ts.float32)
order1 = ts.constant([12, 4], ts.int8)
order2 = ts.constant([[2, 3], [0, 1]])


Sabiendo esto ya estamos en disposición de realizar ciertos cálculos matemáticos, por ejemplo entre matrices.

A = ts.constant([[2, 3], [2, 1]], ts.float32)
A_transpuesta = ts.transpose(A)
A_traza = ts.trace(A)
A_solve = ts.matrix_solve(A, [[10, 1], [1,10]])
print(sess.run(A_solve))
print(sess.run(A_traza))
  
Para evaluar los resultados es necesario crear una sesión para ejecutar las operaciones:

sess = ts.Session()
sess.run(A_transpuesta) 

Estas y otras muchas operaciones pueden consultase en https://www.tensorflow.org/api_guides/python/math_ops.


Un pequeño ejemplo:


Como ejercicio intentaremos dibujar un fractal empleando las herramientas matemáticas que nos proporciona TersorFlow.  Sin entrar en los detalles matemáticos un conjunto fractal se puede representar iterando los puntos del plano -en su expresión de plano complejo- y luego representando los puntos en función de la velocidad de escape de un circulo de radio r a lo largo de la iteración. Para el caso que nos ocupa iteraremos según la fórmula $Z_{n+1}=f(Z_{n})+c$. La función f y la constante c pueden dar lugar o no a un conjunto fractal, es decir, el resultado puede cumplir los criterios fractales -autosemejanza, dimensión no entera- o bien no cumplirlos. 

import numpy as np
x, y = np.mgrid[-2:1:0.005, -1.3:1.3:0.005]
points = x+1j*y
z = ts.Variable(points)
fractal = ts.Variable(ts.zeros_like(z, ts.float32))
ts.global_variables_initializer().run(session=sess)
z_ = ts.exp(z*z*z) - 0.59-0.0041j
no_diverge = ts.cast(ts.abs(z_) < 10, ts.float32)
step = ts.group(z.assign(z_), fractal.assign_add(no_diverge))
for cont in range(150):
   step.run(session=sess)
   fractal_ = fractal.eval(session=sess)
1:  Importamos numpy.
2:  Generamos los puntos del plano como una malla.
3:  Pasamos estos puntos al plano complejo.
4:  Pasamos los puntos del plano complejo a un tensor variable.
5: Creamos un tensor de ceros, este se usará para clasificar los puntos del plano complejo según un criterio métrico determinado -ver línea 7-.
6:  Antes de usar los tensores variable deben ser incializados para una sesión.
7:  Función a iterar, en ese caso dará como resultado un conjunto Julia. Aquí se puede jugar con la función a iterar y comprobar que tipo de fractal resulta.
8:   El método cast transforma un tensor en otro de tipos diferentes, en este caso $ts.abs(z$_$)<10$ es un tensor de booleanos True cuando el punto z_ -en módulo- es menor que 10.
9:   Cada uno de los pasos iterativos, group agrupa varias operaciones, en ese caso asignamos a z el valor de z_ y por otro lado añadimos al valor anterior de fractal el valor de no_diverge.
10:  Iteración de los cálculos.
10:  Corremos el iterador 150 veces.

Para representar el fractal podemos por ejemplo usar matplolib -usando from matplotlib import pyplot as plt-:

plt.imshow(fractal_, interpolation='gaussian')
plt.show()
De este ejemplo podemos extraer algunas peculiaridades de TF.
  1. Existe una diferencia notable entre variables -línea 3- y tensores normales. Una variable es un tensor mutable, es decir, se pude ir actualizando su valor, como en 8 que se actualiza z y fractal. Tienen también métodos propios que no pueden empelarse con los tensores y es imprescindibles inicializarlos antes de usarlos -línea 5-.
  2.  Los algoritmos se apilan antes de realizarse, como en la línea 8. Algoritmos, tensores, etc... deben evaluarse sobre una sesión o correrse sobre ella -líneas 10 y 11-.
 En la siguiente entrada realizaremos un perceptrón multicapa y lo ilustraremos con un curioso ejemplo.



martes, 27 de marzo de 2018

Python 3.x, que versión elegir




Estoy buscando la mejor versión de Python para afrontar un nuevo proyecto de machine-learning. Se trata de un proyecto muy ambicioso cuyo desarrollo se dilatará en el tiempo y que servirá también como laboratorio para ensayar la implementación de nuevas técnicas de IA. Es importante que la herramienta pueda perdurar sin tener que migrar, en mucho tiempo, a nuevas versiones de Python. He realizado un pequeño análisis para valorar cual será la mejor elección y he querido compartir este pequeño esfuerzo.

Empezaré describiendo los condicionantes que se han de cumplir:
  1. Debe ser compatible con Django 2.
  2. Debe ser compatible con la mayoría de las librerías importantes de machine-learning y matemáticas.
Antes de nada justificar la elección de 3.x y no de 2.x pero esto es inmediato si tenemos en cuenta que 2.7 tiene los días contados, concretamente el soporte finalizará en el 2020 y ya no habrá más versiones de 2.x. Una vez establecido este extremo pasamos a discutir los siguientes puntos:

1. Compatibilidad Django 2.

Como podemos ver aquí Django 2 solo es compatible con 3.x, concretamente nos ofrecen la siguiente tabla de compatibilidades:

 Django  Ptyhon
2.0:         3.4, 3.5, 3.6
2.1:         3.5, 3.6, 3.7

Ni Django 2.0 ni 2.1 son del tipo LTS, será la 2.2 la próxima LTS y como podemos ver nada se nos dice sobre la compatibilidad de Python para esta versión LTS, no obstante si confiamos en que continúen con la mima serie tendremos:

Django  Ptyhon
2.2:         3.6, 3.7, 3.8

Bajo este ángulo tenemos tres opciones, la 3.5, la 3.6 y la 3.7. Es evidente que la mejor opción en este caso es la 3.6 ya que por un lado 3.5 pierde su compatibilidad con la versión LTS (2.2) y la 3.7 necesita de la versión 2.1 de Django que saldrá en Agosto del 2018. 

La mejor elección: Python 3.6

2. Compatibilidad con librerías machine-learning.

En este apartado se incluyen todas las necesarias para el desarrollo de algoritmos y algunas muy recomendadas. A continuación una lista de las imprescindibles:

  1. Numpy: Paquete de para computación científica, imprescindible para afrontar cualquier problema matemático. La versión 1.14 requiere Python >= 2.7 pero es incompatible con 3.0, 3.1, 3.2, 3.3 y compatible con 3.4-3.6. Numpy 1.14 se ha compilado con Cython 0.26.1 que no es compatible con Python 3.7.
  2. Pandas:  Paquete para análisis de datos, totalmente imprescindible. Las versión 0.22 es compatible con 2.7, 3.5 y 3.6, por el momento no soporta 3.7.
  3. ScyPy: Totalmente imprescindible. Requiere Python >= 2.7 o Python >= 3.4, no dice nada respecto a Python 3.7. 
  4. SymPy: Matemáticas simbólicas. No es imprescindible pero si muy recomendada. La última versión 1.1.1 requiere Python 2.7, 3.3, 3.4, 3.5, 3.6.
  5. Matplotlib: Representación gráfica. No es imprescindible pero si muy recomendada. Python >= 2.7 o >= 3.4, no se dice nada respecto a 3.7.
  6. TensorFlow: API de Google para deep-learning. Muy recomendable.  Requiere 2.7 o 3.x.
  7. OpenCV: Para visión artificial. Muy recomendable. Soporta las siguientes versiones de Python:  2.7, 3.4, 3.5, 3.6. 
  8. Numba: Para correr nuestro código usando también la GPU. Muy recomendable. Sigue exactamente el mismo criterio de Numpy por lo que podemos decir que es compatible con 2.7 y 3.4-3.6 pero no es compatible con Python 3.7. 
  9. Python StatsModels: Para estadśitica. Imprescindible, requiere 2.7 o 3.x.  
La mejor elección: Python 3.6, Python 3.5 

Todo parece indicar que la mejor elección es Python 3.6, no obstante repasemos algunas de las mejoras de la versión 3.6 respecto a la 3.5:
  1. Se pueden usar guiones bajos para leer mejor los números grandes 1_000_250=1000250.
  2. Se introduce en el estándar la anotación de los tipos para las variables, listas, diccionarios y clases.
  3. Se puede usar await y async en las list, set, dict comprehensions.
  4. Se puede usar await y async en una misma función. 
  5. Se incorpora el módulo secret para generar números aleatorios válidos para criptografía.   
  6. Se agrega un nuevo protocolo file system path.
Parece que la inminente versión 3.7 de Python traerá mejoras considerables respecto a la depuración del código, sin embargo introducen un cambio en el manejo de las excepciones de tal manera que estas serán incompatibles con las versiones anteriores de Python.
Conclusión: La versión de Python 3.5 es compatible con todos los paquetes de machine-learning y aunque las mejoras de 3.6 con respecto a 3.5 no son espectaculares Python 3.5 dejará de ser compatible con la versión de Django 2.2. Por otro lado la versión 3.7 parece introducir muchas mejoras pero no estará disponible la versión estable hasta el verano del 2018. Por otro lado no es soportado por muchos paquetes debido a la notable diferencia al tratar las excepciones. Por todo ello parece que la mejor elección es Python 3.6.