La belleza de los lenguajes funcionales en el aprendizaje profundo: Clojure y Haskell

El aprendizaje profundo es un subconjunto de métodos de aprendizaje automático basados en redes neuronales artificiales. Estos se inspiran en el procesamiento de la información y los nodos de comunicación distribuidos en sistemas biológicos como el cerebro. En el aprendizaje profundo, cada nivel aprende a transformar los datos de entrada en una representación un poco más abstracta y compuesta. Por ejemplo, en un sistema de reconocimiento facial, los píxeles pueden ser una capa del sistema, mientras que los bordes pueden ser otra, los ojos pueden ser otra y la cara puede ser otra. La complejidad de los métodos de aprendizaje profundo hace que el uso de paquetes existentes sea popular en la comunidad de programación. TensorFlow y PyTorch en Python son populares, al igual que el paquete Keras en R. Sin embargo, en la producción de sistemas de aprendizaje profundo, el rendimiento y la seguridad son dos problemas que impulsan a las empresas a elegir lenguajes de programación funcionales como Clojure y Haskell en su lugar.

image

Las dificultades de las implementaciones de aprendizaje profundo

Al poner en producción sistemas de aprendizaje profundo, las redes neuronales pueden contener un millón de parámetros. Los datos pueden explotar rápidamente para entrenar estos parámetros. Esta explosión de datos requiere un rendimiento que solo puede lograrse mediante un lenguaje de programación eficiente con capacidades de paralelismo y concurrencia seguras. Debido a la complejidad de las redes neuronales, con datos pasados de capa a capa, la simplicidad y consistencia en la forma en que el lenguaje de programación maneja estos datos es importante. La seguridad, en este caso, significa la capacidad de preservar el estado de los datos originales de manera consistente, mientras que la simplicidad significa ser capaz de leer y mantener la base de código fácilmente al tiempo que maximiza el rendimiento.

Por qué la programación funcional es más adecuada para el aprendizaje profundo

En un intento de resolver algunas de las dificultades que pueden ocurrir al implementar el aprendizaje profundo, los programadores están descubriendo que los lenguajes de programación funcionales pueden proporcionar soluciones.

En informática, la programación funcional es un paradigma de programación que trata la computación como la evaluación de funciones matemáticas y evita el cambio de estado y los datos mutables. Es un patrón de programación que está más cerca del pensamiento matemático.

Los modelos de aprendizaje profundo son esencialmente modelos matemáticos. Por ejemplo, las redes neuronales artificiales comprenden nodos conectados, cada uno de los cuales realiza operaciones matemáticas simples. Mediante el uso de un lenguaje de programación funcional, los programadores son capaces de describir estas operaciones matemáticas en un lenguaje que está más cerca de las operaciones en sí. La forma explícita en que se escriben estos programas facilita mucho la lectura y el mantenimiento de la base de código.

Al mismo tiempo, la naturaleza compositiva de los algoritmos de aprendizaje profundo significa que, en cada capa del trabajo neuronal, las capas o las funciones tienden a encadenarse para realizar tareas. Esto se puede implementar fácilmente utilizando el encadenamiento funcional de un lenguaje de programación funcional.

image

Además, en el aprendizaje profundo, cuando se aplican funciones a los datos, los datos no cambian. Es posible que se generen nuevos valores secuencialmente en la línea, pero los datos en sí permanecen consistentes. La característica de inmutabilidad de un lenguaje de programación funcional permitirá al programador crear un nuevo conjunto de datos cada vez que se generen nuevos valores sin alterar el conjunto de datos inmutable original. Esto facilita el mantenimiento de la consistencia de los datos en toda la red neuronal.

Finalmente, la gran cantidad de parámetros y datos de entrenamiento involucrados en la implementación del aprendizaje profundo significa que el paralelismo y la concurrencia son las claves para crear sistemas de aprendizaje profundo a nivel de producción. Paralelismo significa ejecutar subprocesos en diferentes CPU para acelerar el proceso de aprendizaje. Concurrencia significa la capacidad de administrar subprocesos para evitar conflictos. La programación funcional permite la concurrencia y el paralelismo sin costo alguno. Esto significa que, por su naturaleza, la programación funcional, donde la función pura es sin estado, siempre producirá la misma salida para una entrada en particular, lo que conducirá a la capacidad de aislar cualquier función y ejecutarla cuando lo desee. Esto hace que la concurrencia y el paralelismo sean mucho más fáciles de administrar. No tienes que lidiar con problemas como bloqueos y condiciones de carrera. Diferentes subprocesos que acceden a diferentes CPU podrán ejecutarse de forma independiente sin disputas.

Clojure

Con la programación funcional ganando popularidad en el aprendizaje profundo, y con los paquetes robustos disponibles para el aprendizaje profundo, Clojure ahora es favorecido por compañías como Walmart y Facebook. Es un lenguaje de programación funcional dinámico de alto nivel basado en el lenguaje de programación LISP y cuenta con compiladores que permiten ejecutarse tanto en el entorno de ejecución Java como en el.NET.

El poder de la programación concurrente en Clojure

Clojure no reemplaza al sistema de subprocesos Java, sino que funciona con él. Dado que las estructuras de datos principales son inmutables, se pueden compartir fácilmente entre subprocesos. Al mismo tiempo, los cambios de estado en el programa son posibles, pero Clojure proporciona mecanismos para garantizar que los estados permanezcan consistentes. Si se producen conflictos entre 2 transacciones que intentan modificar la misma referencia, una de ellas se retirará. No hay necesidad de bloqueo explícito.

(import '(java.util.concurrent Executors))(defn test-stm (let (fn (dotimes (dosync (doseq (alter r + 1 t)))))) (range nthreads))] (doseq (.get future)) (.shutdown pool) (map deref refs)))(test-stm 10 10 10000) -> (550000 550000 550000 550000 550000 550000 550000 550000 550000 550000)

Fuente

El paralelismo en Clojure es barato

En aprendizaje profundo, los modelos tienen que entrenar con grandes cantidades de datos. El paralelismo implica ejecutar múltiples subprocesos en diferentes CPU. El paralelismo que es barato significará mejoras de rendimiento significativas. El uso de la partición junto con el mapa puede lograr un paralelismo que es menos costoso.

(defn calculate-pixels-2 (let (doall (map (fn (let (get-color (process-pixel (/ row (double *width*)) (/ col (double *height*)))))) x))) work)] (doall (apply concat result))))

Fuente

Las funciones de encadenamiento en Clojure significan claridad

En Clojure, hay muchas funciones para muy pocos tipos de datos. Las funciones también se pueden pasar como argumentos a otras funciones, lo que hace posible las funciones de encadenamiento en el aprendizaje profundo. Con una implementación más cercana al modelo matemático real, el código Clojure puede ser fácil de leer y mantener.

;; pipe arg to function(-> "x" f1) ; "x1";; pipe. function chaining(-> "x" f1 f2) ; "x12"

Fuente

Identidad y estado en Clojure proporcionan seguridad

En Clojure, la identidad de cada modelo tiene un estado en cualquier momento. Ese estado es un valor verdadero que nunca cambia. Si la identidad parece cambiar, es porque está asociada a un estado diferente. Los nuevos valores son funciones antiguas. Dentro de cada capa de la red neuronal, el estado de los datos originales siempre se conserva. Cada conjunto de datos con nuevos valores que son salidas de funciones puede funcionar de forma independiente. Esto significa que se pueden realizar acciones en estos conjuntos de datos de forma segura o sin tener en cuenta la contención. Podemos volver al estado original de los datos en cualquier momento. Por lo tanto, la consistencia, en este caso, significa seguridad.

Bibliotecas y limitaciones

Históricamente, la biblioteca de aprendizaje automático de cortex contiene todo lo que necesita para implementar algoritmos de aprendizaje automático en Clojure. Con la reciente creciente popularidad de MXNet framework de código abierto para aprendizaje profundo, es más fácil implementar el aprendizaje profundo utilizando la API MXNet-Clojure.

Aunque ahora hay diferentes API y bibliotecas de aprendizaje automático disponibles para Clojure, todavía hay una curva de aprendizaje pronunciada para llegar a ser fluido en ti. Los mensajes de error pueden ser crípticos y las empresas tendrán que estar dispuestas a invertir por adelantado para usarlo para ampliar sus sistemas de aprendizaje automático. A medida que se escriban más ejemplos de sistemas listos para la producción en Clojure, el lenguaje ganará más popularidad en los próximos años, pero solo si el número y el tamaño de las bibliotecas que acompañan el uso del Clojure crecen constantemente.

Haskell

Haskell es un lenguaje funcional que se escribe estáticamente con inferencia de tipos y evaluación perezosa. Se basa en la semántica del lenguaje de programación Miranda y se considera más expresivo, más rápido y más seguro para implementar aprendizaje automático.

Seguridad de tipo en Haskell proporciona seguridad y flexibilidad

La seguridad de tipo define restricciones en los tipos de valores que una variable puede contener. Esto ayudará a prevenir operaciones ilegales, proporcionará una mejor seguridad de la memoria y conducirá a menos errores lógicos. Evaluación perezosa significa que Haskell retrasará la evaluación de una expresión hasta que se necesite su valor. También evita evaluaciones repetidas, lo que ahorrará tiempo de ejecución. Al mismo tiempo, la evaluación perezosa permite definir estructuras de datos infinitas. Esto le da a un programador posibilidades matemáticas ilimitadas.

El código explícito simple en Haskell proporciona implementaciones claras

Uno de los mayores beneficios de Haskell es que puede describir algoritmos en construcciones matemáticas muy explícitas. Puede representar un modelo en unas pocas líneas de código. También puede leer el código de la misma manera que puede leer una ecuación matemática. Esto puede ser muy poderoso en algoritmos complejos como los algoritmos de aprendizaje profundo en el aprendizaje automático. Por ejemplo, la siguiente implementación de una sola capa de una red neuronal de alimentación muestra cuán legible puede ser el código.

import Numeric.LinearAlgebra.Static.Backproplogistic :: Floating a => a -> alogistic x = 1 / (1 + exp (-x))feedForwardLog :: (KnownNat i, KnownNat o) => Model (L o i :& R o) (R i) (R o)feedForwardLog (w :&& b) x = logistic (w #> x + b)

Fuente

El paralelismo multinúcleo en Haskell proporciona rendimiento

En el aprendizaje profundo, las redes neuronales típicas contendrán un millón de parámetros que definen el modelo. Además, se requiere una gran cantidad de datos para aprender estos parámetros, lo que, computacionalmente, consume mucho tiempo. En una sola máquina, el uso de varios núcleos para compartir la memoria y el proceso en paralelo puede ser muy potente a la hora de implementar el aprendizaje profundo. En Haskell, sin embargo, implementar paralelismo multinúcleo es fácil.

Bibliotecas y limitaciones

La biblioteca HLearn de Haskell contiene implementaciones de algoritmos de aprendizaje automático, mientras que el enlace de flujo tensor para Haskell se puede usar para aprendizaje profundo. Paralelo y concurrente, mientras tanto, se utilizan para paralelismo y concurrencia.

Aunque hay algunas bibliotecas de aprendizaje automático desarrolladas en Haskell, aún será necesario realizar implementaciones desde cero para implementaciones de Haskell listas para producción. Si bien las bibliotecas públicas disponibles para tareas específicas de aprendizaje profundo y aprendizaje automático son limitadas, el uso de Haskell en IA también será limitado. Empresas como Aetion Technologies y Credit Suisse Global Modeling and Analytics Group están utilizando Haskell en sus implementaciones: aquí hay una lista completa de las organizaciones que lo utilizan.

Conclusión

Los modelos de aprendizaje profundo son modelos matemáticos complejos que requieren capas específicas de funciones. Los lenguajes de programación funcionales como Clojure y Haskell a menudo pueden representar la complejidad con un código más limpio y cercano a las matemáticas del modelo. Esto se traduce en ahorro de tiempo, eficiencia y fácil administración de la base de código. Las propiedades específicas de la programación funcional permiten que las implementaciones en estos lenguajes sean más seguras que las de otros lenguajes. A medida que avanza el desarrollo de la tecnología de IA, la evaluación de estos lenguajes para las necesidades de proyectos de desarrollo de sistemas a gran escala en IA será más frecuente.

Este artículo es parte de Detrás del código, el medio para desarrolladores, por desarrolladores. ¡Descubre más artículos y videos visitando Detrás del Código!

¿Quieres contribuir? ¡Publícate!

¡Síguenos en Twitter para estar atentos!

Ilustración de Victoria Roussel

Deja una respuesta

Tu dirección de correo electrónico no será publicada.