La beauté des Langages fonctionnels dans l’apprentissage en profondeur – Clojure et Haskell

L’apprentissage en profondeur est un sous-ensemble de méthodes d’apprentissage automatique basées sur des réseaux de neurones artificiels. Ceux-ci s’inspirent du traitement de l’information et des nœuds de communication distribués dans des systèmes biologiques tels que le cerveau. En apprentissage profond, chaque niveau apprend à transformer les données d’entrée en une représentation légèrement plus abstraite et composite. Par exemple, dans un système de reconnaissance faciale, les pixels peuvent être une couche du système, tandis que les bords peuvent en être une autre, les yeux peuvent en être une autre et le visage peut en être un autre. La complexité des méthodes d’apprentissage en profondeur rend l’utilisation de paquets existants populaire dans la communauté de la programmation. TensorFlow et PyTorch en Python sont populaires, tout comme le package Keras en R. Cependant, dans la production de systèmes d’apprentissage en profondeur, les performances et la sécurité sont deux problèmes qui poussent les entreprises à choisir des langages de programmation fonctionnels tels que Clojure et Haskell à la place.

image

Les difficultés des implémentations d’apprentissage profond

Lors de la mise en production de systèmes d’apprentissage profond, les réseaux de neurones peuvent contenir un million de paramètres. Les données peuvent rapidement exploser pour entraîner ces paramètres. Cette explosion de données nécessite des performances qui ne peuvent être atteintes que par un langage de programmation efficace avec des capacités de simultanéité et de parallélisme sûres. En raison de la complexité des réseaux de neurones, avec des données transmises de couche en couche, la simplicité et la cohérence de la manière dont le langage de programmation gère ces données sont importantes. La sécurité, dans ce cas, signifie la capacité de préserver l’état des données d’origine de manière cohérente, tandis que la simplicité signifie pouvoir lire et maintenir facilement la base de code tout en maximisant les performances.

Pourquoi la programmation fonctionnelle est plus adaptée à l’apprentissage profond

Pour tenter de résoudre certaines des difficultés qui peuvent survenir lors de la mise en œuvre de l’apprentissage profond, les programmeurs constatent que les langages de programmation fonctionnels peuvent fournir des solutions.

En informatique, la programmation fonctionnelle est un paradigme de programmation qui traite le calcul comme l’évaluation de fonctions mathématiques et évite les changements d’état et les données mutables. C’est un modèle de programmation plus proche de la pensée mathématique.

Les modèles d’apprentissage profond sont essentiellement des modèles mathématiques. Par exemple, les réseaux de neurones artificiels comprennent des nœuds connectés, chacun effectuant des opérations mathématiques simples. En utilisant un langage de programmation fonctionnel, les programmeurs sont capables de décrire ces opérations mathématiques dans un langage plus proche des opérations elles-mêmes. La façon explicite dont ces programmes sont écrits facilite beaucoup la lecture et la maintenance de la base de code.

Dans le même temps, la nature compositionnelle des algorithmes d’apprentissage profond signifie que, à chaque couche du travail neuronal, les couches ou les fonctions ont tendance à s’enchaîner pour effectuer des tâches. Ceci peut être facilement mis en œuvre en utilisant le chaînage fonctionnel d’un langage de programmation fonctionnel.

image

De plus, en apprentissage profond, lorsque des fonctions sont appliquées aux données, les données ne changent pas. Les nouvelles valeurs peuvent être sorties séquentiellement sur la ligne, mais les données elles-mêmes restent cohérentes. La fonction d’immuabilité d’un langage de programmation fonctionnel permettra au programmeur de créer un nouvel ensemble de données chaque fois que de nouvelles valeurs sont générées sans modifier l’ensemble de données immuables d’origine. Cela facilite le maintien de la cohérence des données dans tout le réseau neuronal.

Enfin, le grand nombre de paramètres et de données de formation impliqués dans la mise en œuvre du deep learning signifie que le parallélisme et la concurrence sont les clés de la création de systèmes de deep learning au niveau de la production. Le parallélisme signifie exécuter des threads sur différents PROCESSEURS pour accélérer le processus d’apprentissage. La concurrence signifie la capacité de gérer les threads pour éviter les conflits. La programmation fonctionnelle permet la simultanéité et le parallélisme sans frais. Cela signifie que, de par sa nature, la programmation fonctionnelle, où la fonction pure est sans état, produira toujours la même sortie pour une entrée particulière, conduira à la possibilité d’isoler n’importe quelle fonction et de l’exécuter quand vous le souhaitez. Cela rend la concurrence et le parallélisme beaucoup plus faciles à gérer. Vous n’avez pas à faire face à des problèmes tels que les blocages et les conditions de course. Différents threads accédant à différents PROCESSEURS pourront s’exécuter indépendamment sans aucune prétention.

Clojure

Avec la popularité croissante de la programmation fonctionnelle dans le deep learning et avec les packages robustes disponibles pour le deep learning, Clojure est désormais favorisé par des entreprises telles que Walmart et Facebook. C’est un langage de programmation fonctionnel dynamique de haut niveau basé sur le langage de programmation LISP et il a des compilateurs qui permettent de s’exécuter à la fois sur l’environnement d’exécution Java et .NET.

La puissance de la programmation simultanée dans Clojure

Clojure ne remplace pas le système de threads Java, il fonctionne plutôt avec. Étant donné que les structures de données principales sont immuables, elles peuvent être facilement partagées entre les threads. Dans le même temps, des changements d’état dans le programme sont possibles, mais Clojure fournit des mécanismes pour s’assurer que les états restent cohérents. Si des conflits se produisent entre 2 transactions essayant de modifier la même référence, l’une d’entre elles se retirera. Il n’y a pas besoin de verrouillage explicite.

(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)

Source

Le parallélisme dans Clojure est bon marché

En apprentissage profond, les modèles doivent s’entraîner sur de grandes quantités de données. Le parallélisme implique l’exécution de plusieurs threads sur différents PROCESSEURS. Un parallélisme bon marché se traduira par des améliorations significatives des performances. L’utilisation de la partition en conjonction avec la carte peut atteindre un parallélisme moins coûteux.

(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))))

Source

Les fonctions de chaînage dans Clojure signifient clarté

Dans Clojure, il existe de nombreuses fonctions pour très peu de types de données. Les fonctions peuvent également être passées comme arguments à d’autres fonctions, ce qui rend possible le chaînage des fonctions dans l’apprentissage profond. Avec une implémentation plus proche du modèle mathématique réel, le code Clojure peut être simple à lire et à maintenir.

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

Source

L’identité et l’état dans Clojure assurent la sécurité

Dans Clojure, l’identité de chaque modèle a un état à tout moment. Cet état est une vraie valeur qui ne change jamais. Si l’identité semble changer, c’est parce qu’elle est associée à un état différent. Les nouvelles valeurs sont des fonctions de l’ancienne. À l’intérieur de chaque couche du réseau de neurones, l’état des données d’origine est toujours préservé. Chaque ensemble de données avec de nouvelles valeurs qui sont des sorties de fonctions peut fonctionner indépendamment. Cela signifie que des actions peuvent être effectuées sur ces ensembles de données en toute sécurité ou sans tenir compte de la contention. Nous pouvons à tout moment revenir à l’état d’origine des données. Par conséquent, la cohérence, dans ce cas, signifie la sécurité.

Bibliothèques et limitations

Historiquement, la bibliothèque d’apprentissage automatique cortex contient tout ce dont vous avez besoin pour implémenter des algorithmes d’apprentissage automatique dans Clojure. Avec la popularité croissante récente du framework open source MXNet pour le deep learning, il est plus facile d’implémenter le deep learning à l’aide de l’API MXNet-Clojure.

Bien qu’il existe maintenant différentes API et bibliothèques d’apprentissage automatique disponibles pour Clojure, il existe toujours une courbe d’apprentissage abrupte pour devenir fluide. Les messages d’erreur peuvent être cryptés et les entreprises devront être prêtes à investir dès le départ pour l’utiliser pour étendre leurs systèmes d’apprentissage automatique. Au fur et à mesure que de plus en plus d’exemples de systèmes prêts à la production sont écrits en Clojure, le langage gagnera en popularité au cours des années à venir, mais seulement si le nombre et la taille des bibliothèques accompagnant l’utilisation de Clojure augmentent de manière constante.

Haskell

Haskell est un langage fonctionnel typé statiquement avec inférence de type et évaluation paresseuse. Il est basé sur la sémantique du langage de programmation Miranda et considéré comme plus expressif, plus rapide et plus sûr pour la mise en œuvre de l’apprentissage automatique.

La sécurité de type dans Haskell offre sécurité et flexibilité

La sécurité de type définit les contraintes sur les types de valeurs qu’une variable peut contenir. Cela aidera à prévenir les opérations illégales, à améliorer la sécurité de la mémoire et à réduire les erreurs logiques. Une évaluation paresseuse signifie que Haskell retardera l’évaluation d’une expression jusqu’à ce que sa valeur soit nécessaire. Cela évite également les évaluations répétées, ce qui économisera du temps de fonctionnement. En même temps, l’évaluation paresseuse permet de définir des structures de données infinies. Cela donne à un programmeur des possibilités mathématiques illimitées.

Un code explicite simple dans Haskell fournit des implémentations claires

L’un des plus grands avantages de Haskell est qu’il peut décrire des algorithmes dans des constructions mathématiques très explicites. Vous pouvez représenter un modèle en quelques lignes de code. Vous pouvez également lire le code de la même manière que vous pouvez lire une équation mathématique. Cela peut être très puissant dans des algorithmes complexes tels que les algorithmes d’apprentissage profond dans l’apprentissage automatique. Par exemple, l’implémentation ci-dessous d’une seule couche d’un réseau de neurones à flux direct montre à quel point le code peut être lisible.

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)

Source

Le parallélisme multicœur dans Haskell fournit des performances

Dans le deep learning, les réseaux de neurones typiques contiendront un million de paramètres qui définissent le modèle. De plus, une grande quantité de données est nécessaire pour apprendre ces paramètres, ce qui, sur le plan informatique, prend beaucoup de temps. Sur une seule machine, l’utilisation de plusieurs cœurs pour partager la mémoire et le processus en parallèle peut être très puissante lorsqu’il s’agit de mettre en œuvre l’apprentissage en profondeur. Dans Haskell, cependant, la mise en œuvre du parallélisme multicœur est facile.

Bibliothèques et limitations

La bibliothèque HLearn de Haskell contient des implémentations d’algorithmes d’apprentissage automatique, tandis que la liaison tensor-flow pour Haskell peut être utilisée pour l’apprentissage en profondeur. Parallèle et concurrent, quant à eux, sont utilisés pour le parallélisme et la concurrence.

Bien qu’il existe des bibliothèques d’apprentissage automatique développées dans Haskell, des implémentations de base devront encore être effectuées pour les implémentations Haskell prêtes à la production. Alors que les bibliothèques publiques disponibles pour des tâches spécifiques d’apprentissage profond et d’apprentissage automatique sont limitées, l’utilisation de Haskell en IA sera également limitée. Des entreprises telles que Aetion Technologies et Credit Suisse Global Modeling and Analytics Group utilisent Haskell dans leurs implémentations — voici une liste complète des organisations qui l’utilisent.

Conclusion

Les modèles d’apprentissage profond sont des modèles mathématiques complexes qui nécessitent une superposition spécifique de fonctions. Les langages de programmation fonctionnels tels que Clojure et Haskell peuvent souvent représenter la complexité avec un code plus propre et plus proche des mathématiques du modèle. Cela permet de gagner du temps, de gagner en efficacité et de gérer facilement la base de code. Les propriétés spécifiques de la programmation fonctionnelle permettent aux implémentations dans ces langages d’être plus sûres que celles des autres langages. Au fur et à mesure que le développement de la technologie de l’IA progresse, l’évaluation de ces langages pour les besoins de projets de développement de systèmes à grande échelle en IA deviendra plus répandue.

Cet article fait partie de Behind the Code, les médias pour les développeurs, par les développeurs. Découvrez plus d’articles et de vidéos en visitant Behind the Code!

Vous voulez contribuer? Soyez publié!

Suivez-nous sur Twitter pour rester à l’écoute !

Illustration de Victoria Roussel

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.