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.
Frederic Voisin :
Maquette du
serveur multi-agent neuromimetique de Amplification
/ Synaptique