4. Exemple de mise en ligne (serveur) d'un agent neuromimétique
pour une utilisation musicale en temps-réel



    Cet exemple (cf. code source : som-exemple1.lisp) montre comment créer un réseau de neurones articiels et le mettre en ligne afin de le faire communiquer, en temps-réel, avec un programme d'analyse-synthèse audionumérique. Ce réseau de neurones est ici complété par son interface graphique OpenGL, chargée dans le programme Pd (la librairie gem est requise).

    Le chargement du code peut s'effectuer directement dans un terminal (Unix, linux) avec les programmes LISP openmcl (Macintosh) ou cmucl (PC). On peut aussi préférer, pour les tests et répétitions, l'exécuter dans l'environnement SLIME (Superior Lisp Interaction Mode for Emacs) :


    Chargement du code source neuromuse.lisp dans Emacs :

; SLIME 2005-06-01
CL-USER> (load "neuromuse.lisp")
Chargement de neuromuse1
Frederic Voisin, 2005
loading neuromuse...
maths...
MLP...
SOM...
ROSOM...
UDP...
adaptations x86 ...
ready !
#P"/home/fred/neuromuse/lisp/neuromuse1/neuromuse.lisp"

    Nous pouvons maintenant instancier un réseau de neurone de type carte auto-organisatrice (SOM, Self Organizing Map, Kohonen 1995). Dans cet exemple de test, il sera constitué d'une mémoire de 100 neurones et de 8 neurones pour l'encodage de données à traiter.  Un tel réseau est déjà capable d'apprendre un assez grand nombre de catégories cognitives (hauteurs, rythmes...). Plutôt que d'instancier un réseau constitué d'un (trés) grand nombre de neurones, il semble plus efficace, à l'usage, d'instancier de nombreux réseaux de neurones de plus petite taille : l'apprentissage, l'auto-organisation et les réponses du système bénéficient alors des propriétes dynamiques d'un système multi-agent (l'objet des répétitions étant donc de controler ces propriétés selon les principes de la partition musicale, cf. 6. Apprentissage, concert et installation).

Ici le code LISP instancie un reseau nomme, ici, SOM-1009 (le nom est généré par le programme, avec un surnom SOM), défini dans une topologie euclidienne à deux dimensions (carte carree), avec la fonction de voisinage par défaut, "voisins" :

CL-USER> (defvar SOM (make-instance 'som
               :name 'som
               :topology '(euclidian 2)
               :radius 2
               :neighbourhood 'voisins))
SOM
CL-USER> (init-som som 100 8)
<SOM SOM-1009>


    La topologie permet d'étendre ou de réduire la dimensionalité des catégories musicales qui devront être apprises par le réseau lors de l'apprentissage de la partition et des répétitions.  Pour les apprentissages de cycles, on instanciera, de la même manière, des réseaux de type ROSOM.
    Il s'agit ensuite de configurer les differents parametres d'interaction (proprietes) du réseau de neurones :
'l'attention",
le facteur d'apprentissage ,
et le rayon de propagation des l'information sur les neurones voisins :


CL-USER> (setf (latence som) .01)
0.01
CL-USER> (setf (learn-fact som) .05)
0.05
CL-USER> (setf (radius som) 2)
2

    Pour permettre au réseau SOM de communiquer par internet selon le protocole UDP, on établi la liste des ports UDP utilisé par les processus de communication que nous allons définir par la suite.
    Dans un premier temps, on peut commencer par définir 4 canaux minimaux pour les fonctions de communications d'un réseau SOM :
 - input (port 10001)
 - neurone "gagnant" (port 10011)
 - le vecteur de l'activation du neurone gagnant - output "pertinent" (port 10012)
 - l'erreur de chacun des neurones du reseau SOM (port 10013)

CL-USER> (setf (udplist som) '(10001 10011 10012 10013))

(10001 10011 10012 10013)

    Il s'agit maintenant de définir une fonction LISP permettant au réseau d'écouter les données arrivant sur le port UDP d'input et de renvoyer les résultats attendus sur les autres ports.
Nous définissons ici fonction "playlist"  constituée des programmes génériques udp2input, sendudp et learn définis dans le code source neuromuse.lisp.
    Afin de permettre au réseau une écoute permanente ("en boucle"), le programme sera de préférence récurrent; en s'appelant lui-même à chaque cycle (définissant la cadence, ou tempo, du système). Il peut aussi être adapté (dynamiquement) selon les besoins, selon la structure suivante :

CL-USER> (defun playlist (nn)
  (let (win)
    (udp2input nn (car (udplist nn)) 512)
    (setf win (winner nn))
    (sendudp (format nil "~F\;" (car (id win)))
         "127.0.0.1" (nth 1 (udplist nn)))
    (sendudp (list2string
          (output (nth (car (id win)) (net nn))))
          "127.0.0.1" (nth 2 (udplist nn)))
    (sendudp (list2string
          (mapcar #'(lambda (x) (distance x)) (net nn)))
         "127.0.0.1" (nth 3 (udplist nn)))
    (learn nn)
    (sleep (latence nn))
    (if (superdaemon som)
        (playlist nn) nil)))
PLAYLIST

    Comme on le voit, il est prudent de mettre une condition logique de controle permettant l'interruption de la boucle (mise hors-ligne); nous utilisons ici le slot "superdaemon" a cette fin. Cette première structure peut varier considérablement, des processus écrits en LISP, pouvant être ajoutés ou retirés de la boucle à tout moment sans interruption du réseau où sa mise hors-ligne : le réseau est capable d'évoluer dynamiquement (le code permet cependant une gestion des processus plus hiérarchisee en faisant appel a une liste de processus - daemons - gérés par un processus de niveau supérieur - superdaemon - pouvant être également plus où moins autonome.
    Ici, les adresses IP des ordinateurs avec lesquels communique ce réseau SOM sont indiquées dans le corps de la fonction.  Il est prévu que les IP constituent un paramètre internalisé dans chaque réseau synaptique, de même que la liste des ports UDP au moyen desquels il pourra être mis en relation avec les autres reseaux (agents) synaptiques.
    Le paramètre "attention" du réseau est ici utilisé pour forcer le réseau a "dormir" pendant un certain temps (selon les besoins : de 1 ms a plusieurs secondes, voir minutes). Cette variable interne au réseau peut par ailleurs  varier de manière plus ou moins autonome dans selon un processus LISP arbitraire, dépendant de la partition et/ou de l'état du réseau lui-même.

    La fonction "playlist" précédemment définie est placée dans un processus LISP qui sera executé en tache de fond (thread). Ce processus est lui-meme placé dans la liste des processus définissant le comportement du reseau SOM ("daemons") :

CL-USER> (setf (daemons som)
               (list
(process-preset (make-process "som-playlist")
                     #'playlist som)) )
(#<PROCESS som-playlist(8) [Reset] #x35524C6E>)
CL-USER> 


    Ce processus est dès lors prêt a être activé :

CL-USER> (setf (superdaemon son) t)
CL-USER> (process-enable (first (daemons som)))
CL-USER> (#<PROCESS som-playlist(8) [Active] #x35524C6E>)

    Le réseau SOM-1009  est maintenant "en ligne" et agit comme un serveur. A chaque vecteur de données reçu sur le port UDP 10001, il renvoie aux IP prédefinies la position du neurone "gagnant", son activation (output) et l'erreur de chacun des neurones, respectivement sur les ports UDP 10011, 10012, 10013. Le processus LISP (thread) s'exécute alors indéfiniment en tache de fond et rend la main du terminal à l'utilisateur, si necessaire, pour les réglages local des paramètres du réseau :

CL-USER> (inspect som)
[0]     <SOM SOM-1009>
[1]     Class: #<STANDARD-CLASS SOM>
[2]     Wrapper: #<CCL::CLASS-WRAPPER SOM #x35479AA6>
        Instance slots
[4]     NAME: SOM-1009
[5]     ID: NIL
[6]     SUPERDAEMON: T
[7]     DEAMONS: (#<PROCESS som-playlist(8) [Active] #x35524C6E>)
[8]     UDPLIST: (10001 10011 10012 10013)
[9]     NET: (NEURON NEURON-1010 NEURON-1011 NEURON-1012 NEURON-1013
...)
[10]    CREATION-DATE: 3338673755
[11]    MODIFICATION-DATE: NIL
[12]    EPOCH: 0
[13]    INPUT: NIL
[14]    OUTPUT: NIL
[15]    LATENCE: 0.01
...
CL-USER>


    Avant le premier que le premier vecteur ne soit recu depuis  programme d'analyse audio, l'input du réseau SOM est vide (nil). Ce facteur sera mis a jour a l'arrivé de chaque vecteur se présentant sur le port UDP 10001 du serveur LISP (cf. udp.lisp)  et activera le noyau neuromimétique dont l'activation produira la synthèse sonore, en temps réel (cf. 5. Connection et exploitation musicale en temps réel) ainsi que l'interace graphique de controle (voir dossier pd).

     Les propriétés des agents peuvent également être consultés et édités, localeement et dynamiquement, grace aux
différents "slots" définissant chaque sous-classe neuromuse (SOM, ROSOM, MLP...) de la classe "ann" :

    Les neurones du réseau sont accessibles au moyen de la commande net :

CL-USER> (net som)
(NEURON NEURON-1010 NEURON-1011 NEURON-1012 NEURON-1013 NEURON-1014 NEURON-1015 NEURON-1016 NEURON-1017 NEURON-1018 NEURON-1019 NEURON-1020 NEURON-1021 NEURON-1022 NEURON-1023 NEURON-1024 NEURON-1025 NEURON-1026 NEURON-1027 NEURON-1028 NEURON-1029 NEURON-1030 NEURON-1031 NEURON-1032 NEURON-1033 NEURON-1034 NEURON-1035 NEURON-1036 NEURON-1037 NEURON-1038 NEURON-1039 NEURON-1040 NEURON-1041 NEURON-1042 NEURON-1043 NEURON-1044 NEURON-1045 NEURON-1046 NEURON-1047 NEURON-1048 NEURON-1049 NEURON-1050 NEURON-1051 NEURON-1052 NEURON-1053 NEURON-1054 NEURON-1055 NEURON-1056 NEURON-1057 NEURON-1058 NEURON-1059 NEURON-1060 NEURON-1061 NEURON-1062 NEURON-1063 NEURON-1064 NEURON-1065 NEURON-1066 NEURON-1067 NEURON-1068 NEURON-1069 NEURON-1070 NEURON-1071 NEURON-1072 NEURON-1073 NEURON-1074 NEURON-1075 NEURON-1076 NEURON-1077 NEURON-1078 NEURON-1079 NEURON-1080 NEURON-1081 NEURON-1082 NEURON-1083 NEURON-1084 NEURON-1085 NEURON-1086 NEURON-1087 NEURON-1088 NEURON-1089 NEURON-1090 NEURON-1091 NEURON-1092 NEURON-1093 NEURON-1094 NEURON-1095 NEURON-1096 NEURON-1097 NEURON-1098 NEURON-1099 NEURON-1100 NEURON-1101 NEURON-1102 NEURON-1103 NEURON-1104 NEURON-1105 NEURON-1106 NEURON-1107 NEURON-1108)
CL-USER>

L'état de chacun des neurones et de ses connections synaptiques peut être consulté et modifié a tout moment. La commande suivante, par exemple, interroge l'état des connexions synaptiques du premier neurone du réseau SOM :

CL-USER> (net (first (net som)))
(((0 0) -0.06410415 0) ((1 0) -0.08305668 0) ((2 0) 0.06482069 0) ((3 0) -0.040510483 0) ((4 0) 0.04589649 0) ((5 0) -0.030582674 0) ((6 0) -0.07954394 0) ((7 0) 0.030791879 0))
CL-USER>

    Ces connexions évoluent au cours de l'apprentissage, à chaque arrivée de vecteur d'input sur le port UDP 10001.
    Cet agent neuromimétique peut dès lors communiquer avec un programme d'analyse-synthese audio (Pd) situé sur une autre ordinateur (ou localement), avec le serveur de streaming (icecast), mais gagnera aussi a communiquer avec d'autres reseaux neuromimétiques SOM, ROSOM ou MLP qui influenceront sont état interne selon leurs propres apprentissages, selon la meme procedure, afin de constituer un système multi-agents (SMA).
     La structure du SMA est définie par la liste des adresses IP de chacun des agents (réseaux) neuromimétiques, en même temps que par leurs mises en relation par une combinaison judicieuse de différents ports UDP d'input et d'output, développant ainsi un réseau synaptique de niveau supérieur. La liste des input et output, restreinte ici a l'utilisation de 4 ports UDP, sera étendue progressivement, selon les différents contrôles (musicaux), auto-asservissements du système, et l'evolution de l'architecture distribuée : température, facteur d'apprentissage, latence, attention, sensibilité, évolution des fonctions de transfert, voisinage, inhibition.... La souplesse du langage LISP, permettant notamment au code de se générer lui-même (métaprogrammation), rend confortable l'expérimentation à long terme sur plusieurs mois, en permettant au système d'évoluer sans être interrompu.

    L'arrêt du cycle LISP du réseau s'effectue, dans cet exemple, en réglant ici la variable
superdaemon a nil (nul), empéchant dans la fonction "playlist" de s'appeler elle-même (recursion) : le processus (daemons som) s'interrompt naturellement au prochain vecteur se présentant sur le port UDP 10001 si lesuperdaemon est absent (nil).

CL-USER> (setf (superdaemon som) nil)
NIL
CL-USER> (daemons som)
(#<PROCESS som-playlist(9) [Exhausted] #x3545E726>)
CL-USER>

Inversement, le processus superdaemon simplifié ici à la variable t (true) peut évoluer (être réécrit) sans interruption du système; par exemple :

(setf (superdamon som) (funcall #'nouvelle-fonction))

ou "nouvelle-fonction" doit au moins donner une valeur (non nil) lors de son évaluation.


    La charge du traitement des données UDP est raisonnable. Selon nos premiers tests, la charge du calcul neuronal permet a un processeur de type Pentium 4 d'héberger trois agents ou plus, selon leur taille (de l'ordre du milliers de "neurones", au total). Une cadence de l'ordre de 10 à 30 millisecondes, accessible aux processeurs standards récents, rend possible le
calcul dans le domaine symbolique, au niveau descriptif du controle de la synthese.
   
Plusieurs heures de  simulation ont été effectuées sur des processeurs de génération differentes (ppc G3 400 MHz, ppc G4 1.2 GHz, Pentium4 3GHz), sous linux et OSX, ce qui a permis de valider la stabilité du système connecté à son interface graphique et audionumérique, dans Pure Data, en mode local ou distribué. S'il est aujourd'hui developpe en vue de la plus grande souplesse topologique, le code neuromimetique pourra etre optimisé si necessaire (en langage python éventuellement), lorsque l'architecture de chacun des agents aura ete défintivement determinee, apres les dernières répétitions les semaines précédant le concert et l'initialisation de l'installation.



3. noyau synaptique
index
5. connection et exploitation musicale
en temps réel >


Frederic Voisin : Maquette du serveur multi-agent neuromimetique de Amplification / Synaptique