introduction
Cet article décrit une nouvelle version d'un programme d'apprentissage automatique basé sur la théorie des treillis. Le principal avantage de cette version est une interface pour les programmeurs Python aux algorithmes efficaces programmés en C ++.
Aujourd'hui, les procédures d'apprentissage automatique (telles que les réseaux de neurones convolutifs, les forêts aléatoires et les machines vectorielles de support) ont atteint un niveau très élevé, surpassant les humains en reconnaissance vocale, vidéo et d'image. Cependant, ils ne peuvent pas fournir d'arguments pour prouver l'exactitude de leurs conclusions.
D'autre part, les approches symboliques de l'apprentissage automatique (programmation logique inductive, couverture d'apprentissage utilisant la programmation par nombres entiers) ont une complexité de calcul prouvée très élevée et sont pratiquement inapplicables à des échantillons, même de taille relativement petite.
L'approche décrite ici utilise des algorithmes probabilistes pour éviter ces problèmes. La méthode d'apprentissage automatique ICF utilise les techniques de la théorie algébrique moderne du réseau (analyse des concepts formels) et de la théorie des probabilités (en particulier les chaînes de Markov). Mais maintenant, vous n'avez pas besoin de connaissances en mathématiques avancées pour utiliser et créer des programmes à l'aide du système ICF. L'auteur a créé une bibliothèque (vkf.cp38-win32.pyd sous Windows ou vkf.cpython-38-x86_64-linux-gnu.so sous Linux) pour donner accès au programme via une interface que les programmeurs Python peuvent comprendre.
Il y a une approche parentale par rapport à l'approche décrite - inventée au début des années 80 du XXe siècle, docteur en sciences techniques. prof. VK. Méthode Finn JSM pour la génération automatique d'hypothèses. Il a maintenant évolué, comme le dit son créateur, en une méthode de soutien automatisé de la recherche scientifique. Il permet d'utiliser des méthodes de logique d'argumentation, de vérifier la stabilité des hypothèses trouvées lors de l'extension de l'ensemble d'apprentissage, de combiner les résultats de prédictions en utilisant diverses stratégies de raisonnement JSM.
Malheureusement, l'auteur de cette note et ses collègues ont découvert et étudié certaines lacunes théoriques de la méthode JSM:
- Dans le pire des cas, le nombre de similitudes générées peut être exponentiellement grand par rapport à la taille des données d'entrée (échantillon d'apprentissage).
- (NP-).
- .
- «» , .
- .
Les recherches de l'auteur sont résumées dans le chapitre 2 de son travail de thèse . Le dernier point a été découvert récemment par l'auteur, mais, à son avis, met fin à l'approche de l'échantillon en expansion.
Enfin, la communauté JSM n'offre pas d'accès au code source de ses systèmes. De plus, les langages de programmation utilisés (Fort et C #) ne permettront pas leur utilisation par le grand public. La seule version C ++ gratuite du solveur JSM connue de l'auteur (par T.A. Volkovarobofreak) contient une erreur gênante qui conduit parfois à une interruption anormale des calculs.
Au départ, l'auteur prévoyait de partager l'encodeur développé pour le système de la méthode VKF avec la communauté JSM. Par conséquent, il a mis tous les algorithmes applicables simultanément aux systèmes JSM et VKF dans une bibliothèque séparée (vkfencoder.cp38-win32.pyd sous Windows ou vkfencoder.cpython-38-x86_64-linux-gnu.so sous Linux) ... Malheureusement, ces algorithmes se sont révélés non revendiqués par la communauté JSM. La bibliothèque VKF implémente ces algorithmes (par exemple, la classe vkf.FCA), mais en s'appuyant sur le remplissage de tables non pas à partir de fichiers, mais directement via l'interface Web. Ici, nous utiliserons la bibliothèque vkfencoder.
1 Procédure d'utilisation de la bibliothèque pour les entités discrètes
Supposons que le lecteur sache avoir installé le serveur MariaDB + MariaDB Connector / C (par défaut, nous utilisons l'adresse IP 127.0.0.1:3306 et l'utilisateur 'root' avec le mot de passe 'toor'). Commençons par installer les bibliothèques vkfencoder et vkf dans l'environnement virtuel de démonstration et en créant une base de données MariaDB vide nommée 'mushroom'.
krrguest@amd2700vii:~/src$ python3 -m venv demo
krrguest@amd2700vii:~/src$ source demo/bin/activate
(demo) krrguest@amd2700vii:~/src$ cd vkfencoder
(demo) krrguest@amd2700vii:~/src/vkfencoder$ python3 ./setup.py build
(demo) krrguest@amd2700vii:~/src/vkfencoder$ python3 ./setup.py install
(demo) krrguest@amd2700vii:~/src/vkfencoder$ cd ../vkf
(demo) krrguest@amd2700vii:~/src/vkf$ python3 ./setup.py build
(demo) krrguest@amd2700vii:~/src/vkf$ python3 ./setup.py install
(demo) krrguest@amd2700vii:~/src/vkf$ cd ../demo
(demo) krrguest@amd2700vii:~/src/demo$ mysql -u root -p
MariaDB [(none)]> CREATE DATABASE IF NOT EXISTS mushroom;
MariaDB [(none)]> exit;
À la suite du travail, la base de données 'champignon'
apparaîtra, le fichier vkfencoder.cpython-38 apparaîtra dans le dossier ~ / src / demo / lib / python3.8 / site-packages / vkfencoder-1.0.3-py3.8-linux-x86_64.egg / -x86_64-linux-gnu.so, et le
fichier vkf.cpython apparaîtra dans le dossier ~ / src / demo / lib / python3.8 / site-packages / vkf-2.0.1-py3.8-linux-x86_64.egg / 38-x86_64-linux-gnu.so
Lancez Python3 et exécutez une expérience CCF sur le tableau Mushrooms. On suppose qu'il y a 3 fichiers dans le dossier ~ / src / demo / files / (mushrooms.xml, MUSHROOMS.train, MUSHROOMS.rest). La structure de ces fichiers sera décrite dans la section suivante. Le premier fichier 'mushrooms.xml' définit la structure du demi-réseau inférieur sur les valeurs de chaque attribut décrivant les champignons. Les deuxième et troisième fichiers sont le fichier 'agaricus-lepiota.data' divisé par un générateur de nombres aléatoires environ en deux - le livre numérisé "Identifier of North American Mushrooms" publié à New York en 1981. Les noms suivants sont "encoder", "lattices", " trains 'et' tests 'sont les noms des tables de la base de données' mushroom 'pour, respectivement, les codages des valeurs de caractéristiques par sous-chaînes de bits, les relations de couverture pour les diagrammes en demi-treillis sur ces valeurs,exemples de formation et de test.
(demo) krrguest@amd2700vii:~/src/demo$ Python3
>>> import vkfencoder
>>> xml = vkfencoder.XMLImport('./files/mushrooms.xml', 'encoder', 'lattices', 'mushroom', '127.0.0.1', 'root', 'toor')
>>> trn = vkfencoder.DataImport('./files/MUSHROOMS.train', 'e', 'encoder', 'trains', 'mushroom', '127.0.0.1', 'root', 'toor')
>>> tst = vkfencoder.DataImport('./files/MUSHROOMS.rest', 'e', 'encoder', 'tests', 'mushroom', '127.0.0.1', 'root', 'toor')
Le «e» dans les deux dernières lignes correspond à la comestibilité du champignon (c'est ainsi que le trait cible est encodé dans ce tableau).
Il est important de noter qu'il existe une classe vkfencoder.XMLExport qui vous permet d'enregistrer les informations de deux tables 'encoder' et 'lattices' dans un fichier xml, qui, après avoir apporté des modifications, peut être traité à nouveau par la classe vkfencoder.XMLImport.
Passons maintenant à l'expérience VKF réelle: nous connectons les encodeurs, chargeons les hypothèses précédemment calculées (le cas échéant), calculons le nombre supplémentaire (100) d'hypothèses dans le nombre spécifié (4) de flux et les sauvegardons dans la table 'vkfhyps'.
>>> enc = vkf.Encoder('encoder', 'mushroom', '127.0.0.1', 'root', 'toor')
>>> ind = vkf.Induction()
>>> ind.load_discrete_hypotheses(enc, 'trains', 'vkfhyps', 'mushroom', '127.0.0.1', 'root', 'toor')
>>> ind.add_hypotheses(100, 4)
>>> ind.save_discrete_hypotheses(enc, 'vkfhyps', 'mushroom', '127.0.0.1', 'root', 'toor')
Vous pouvez obtenir une liste Python de toutes les paires non triviales (feature_name, feature_value) pour l'hypothèse CCF numérotée ndx
>>> ind.show_discrete_hypothesis(enc, ndx)
L'une des hypothèses pour prendre une décision sur la comestibilité du champignon a la forme
[('gill_attachment', 'free'), ('gill_spacing', 'close'), ('gill_size', 'broad'), ('stalk_shape', 'enlarging'), ('stalk_surface_below_ring', 'scaly'), ('veil_type', 'partial'), ('veil_color', 'white'), ('ring_number', 'one'), ('ring_type', 'pendant')]
En raison de la nature probabiliste des algorithmes de la méthode CCF, elle peut ne pas être générée, mais l'auteur a prouvé qu'avec un nombre suffisamment grand d'hypothèses CCF générées, une hypothèse très similaire se posera, qui prédira presque aussi bien la propriété cible pour des cas de test importants. Les détails sont dans le chapitre 4 de la thèse de l' auteur.
Enfin, nous passons à la prédiction. Nous créons un échantillon de test pour évaluer la qualité des hypothèses générées et en même temps prédire la propriété cible de ses éléments
>>> tes = vkf.TestSample(enc, ind, 'tests', 'mushroom', '127.0.0.1', 'root', 'toor')
>>> tes.correct_positive_cases()
>>> tes.correct_negative_cases()
>>> exit()
2 Description de la structure des données
2.1 Structures en treillis sur des entités discrètes
La classe vkfencoder.XMLImport analyse un fichier XML décrivant l'ordre entre les valeurs de fonctionnalité, crée et remplit deux tables 'encoder' (convertissant chaque valeur en une chaîne de bits) et 'lattices' (stockage des relations entre les valeurs d'une fonctionnalité).
La structure du fichier d'entrée doit être comme
<?xml version="1.0"?>
<document name="mushrooms_db">
<attribute name="cap_color">
<vertices>
<node string="null" char='_'></node>
<node string="brown" char='n'></node>
<node string="buff" char='b'></node>
<node string="cinnamon" char='c'></node>
<node string="gray" char='g'></node>
<node string="green" char='r'></node>
<node string="pink" char='p'></node>
<node string="purple" char='u'></node>
<node string="red" char='e'></node>
<node string="white" char='w'></node>
<node string="yellow" char='y'></node>
</vertices>
<edges>
<arc source="brown" target="null"></arc>
<arc source="buff" target="brown"></arc>
<arc source="buff" target="yellow"></arc>
<arc source="cinnamon" target="brown"></arc>
<arc source="cinnamon" target="red"></arc>
<arc source="gray" target="null"></arc>
<arc source="green" target="null"></arc>
<arc source="pink" target="red"></arc>
<arc source="pink" target="white"></arc>
<arc source="purple" target="red"></arc>
<arc source="red" target="null"></arc>
<arc source="white" target="null"></arc>
<arc source="yellow" target="null"></arc>
</edges>
</attribute>
</document>
L'exemple ci-dessus représente l'ordre entre les valeurs du troisième trait 'cap_color' pour le tableau Mushrooms du référentiel de données d'apprentissage automatique (Université de Californie à Irvine). Chaque champ «attribut» représente une structure en treillis sur les valeurs de l'attribut correspondant. Dans notre exemple, le groupe correspond à l'attribut discret 'cap_color'. La liste de toutes les valeurs caractéristiques forme un sous-groupe. Nous avons ajouté une valeur pour indiquer les similitudes insignifiantes (manquantes). Le reste des valeurs correspond à la description dans le fichier d'accompagnement «agaricus-lepiota.names».
L'ordre entre eux est représenté par le contenu du sous-groupe. Chaque élément décrit une relation plus spécifique / plus générale entre les paires de valeurs caractéristiques. Par exemple ,, signifie qu'un chapeau de champignon rose est plus spécifique qu'un chapeau rouge.
L'auteur a prouvé le théorème sur l'exactitude de la représentation générée par le constructeur de la classe vkfencoder.XMLImport et stockée dans la table 'encoder'. Son algorithme et la preuve du théorème de correction utilisent une branche moderne de la théorie du réseau connue sous le nom d'analyse de concept formel. Pour plus de détails, le lecteur est à nouveau renvoyé au travail de thèse de l' auteur (chapitre 1).
2.2 Exemple de structure pour les entités discrètes
Tout d'abord, il faut noter que le lecteur peut implémenter son propre chargeur d'entraînement et de cas de test dans la base de données ou utiliser la classe vkfencoder.DataImport disponible dans la bibliothèque. Dans le second cas, le lecteur doit tenir compte du fait que l'élément cible doit être en première position et se composer d'un seul caractère (par exemple, «+» / «-», «1» / «0» ou «e» / «p»).
L'exemple d'apprentissage doit être un fichier CSV (avec des valeurs séparées par des virgules) décrivant des exemples d'entraînement et des exemples de compteur (exemples qui n'ont pas la propriété target).
La structure du fichier d'entrée doit être comme
e,f,f,g,t,n,f,c,b,w,t,b,s,s,p,w,p,w,o,p,k,v,d
p,x,s,p,f,c,f,c,n,u,e,b,s,s,w,w,p,w,o,p,n,s,d
p,f,y,g,f,f,f,c,b,p,e,b,k,k,b,n,p,w,o,l,h,v,g
p,x,y,y,f,f,f,c,b,p,e,b,k,k,n,n,p,w,o,l,h,v,p
e,x,y,b,t,n,f,c,b,e,e,?,s,s,e,w,p,w,t,e,w,c,w
p,f,f,g,f,f,f,c,b,g,e,b,k,k,n,p,p,w,o,l,h,y,g
p,x,f,g,f,f,f,c,b,p,e,b,k,k,p,n,p,w,o,l,h,y,g
p,x,f,y,f,f,f,c,b,h,e,b,k,k,n,b,p,w,o,l,h,y,g
p,f,f,y,f,f,f,c,b,h,e,b,k,k,p,p,p,w,o,l,h,y,g
p,x,y,g,f,f,f,c,b,h,e,b,k,k,p,p,p,w,o,l,h,v,d
p,x,f,g,f,c,f,c,n,u,e,b,s,s,w,w,p,w,o,p,n,v,d
p,x,y,g,f,f,f,c,b,h,e,b,k,k,p,b,p,w,o,l,h,v,g
e,f,y,g,t,n,f,c,b,n,t,b,s,s,p,g,p,w,o,p,k,y,d
e,f,f,e,t,n,f,c,b,p,t,b,s,s,p,p,p,w,o,p,k,v,d
p,f,y,g,f,f,f,c,b,p,e,b,k,k,p,b,p,w,o,l,h,y,p
Chaque ligne décrit ici un exemple de formation. La première position contient «e» ou «p» en fonction de la comestibilité / toxicité du champignon décrit. Les autres positions (séparées par des virgules) contiennent des lettres (noms courts) ou des chaînes (noms complets des valeurs de l'attribut correspondant). Par exemple, la quatrième position correspond à l'attribut "cup_color", où "g", "p", "y", "b" et "e" représentent "gris", "rose", "jaune", "sable" et "rouge" , respectivement. Le point d'interrogation au milieu du tableau ci-dessus indique une valeur manquante. Cependant, les valeurs des caractéristiques restantes (non cibles) peuvent être des chaînes. Il est important de noter que lors de l'enregistrement dans la base de données, les espaces dans leurs noms seront remplacés par des caractères «_».
Le fichier avec les cas de test a la même forme, seul le système n'est pas informé du signe réel de l'exemple, et sa prédiction lui est comparée pour calculer la qualité des hypothèses générées et leur pouvoir prédictif.
2.3 Exemple de structure pour les caractéristiques continues
Dans ce cas, des options sont à nouveau possibles: le lecteur peut implémenter son propre entraîneur et chargeur de cas de test dans la base de données ou utiliser la classe vkfencoder.DataLoad disponible dans la bibliothèque. Dans le second cas, le lecteur doit tenir compte du fait que l'entité cible doit être en première position et se composer d'un nombre naturel (par exemple, 0, 7, 15).
L'exemple d'apprentissage doit être un fichier CSV (avec des valeurs séparées par une sorte de séparateur) décrivant des exemples d'entraînement et des exemples de compteur (exemples qui n'ont pas la propriété target).
La structure du fichier d'entrée doit être comme
"quality";"fixed acidity";"volatile acidity";"citric acid";"residual sugar";"chlorides";"free sulfur dioxide";"total sulfur dioxide";"density";"pH";"sulphates";"alcohol"
5;7.4;0.7;0;1.9;0.076;11;34;0.9978;3.51;0.56;9.4
5;7.8;0.88;0;2.6;0.098;25;67;0.9968;3.2;0.68;9.8
5;7.8;0.76;0.04;2.3;0.092;15;54;0.997;3.26;0.65;9.8
6;11.2;0.28;0.56;1.9;0.075;17;60;0.998;3.16;0.58;9.8
5;7.4;0.7;0;1.9;0.076;11;34;0.9978;3.51;0.56;9.4
5;7.4;0.66;0;1.8;0.075;13;40;0.9978;3.51;0.56;9.4
5;7.9;0.6;0.06;1.6;0.069;15;59;0.9964;3.3;0.46;9.4
7;7.3;0.65;0;1.2;0.065;15;21;0.9946;3.39;0.47;10
7;7.8;0.58;0.02;2;0.073;9;18;0.9968;3.36;0.57;9.5
Dans notre cas, ce sont les premières lignes du fichier 'vv_red.csv' généré à partir du fichier 'winequality-red.csv' de vins rouges portugais du tableau Wine Quality du référentiel de données UCI pour tester les procédures d'apprentissage automatique en réorganisant la dernière colonne au tout début. Il est important de noter que lors de l'enregistrement dans la base de données, les espaces dans leurs noms seront remplacés par des caractères «_».
3 Expérience CCF sur des exemples avec des fonctionnalités continues
Comme précédemment écrit, nous démontrerons le travail sur le tableau Wine Quality à partir du référentiel UCI. Commençons par créer une base de données vide sous MariaDB nommée 'red_wine'.
(demo) krrguest@amd2700vii:~/src/demo$ mysql -u root -p
MariaDB [(none)]> CREATE DATABASE IF NOT EXISTS red_wine;
MariaDB [(none)]> exit;
À la suite du travail, la base de données 'red_wine' apparaîtra.
Nous lançons Python3 et menons une expérience VKF sur le tableau Wine Quality. On suppose qu'il existe un fichier vv_red.csv dans le dossier ~ / src / demo / files /. La structure de ces fichiers a été décrite dans le paragraphe précédent. Les noms suivants 'verges', 'complex', 'trains' et 'tests' sont les noms des tables de la base de données 'red_wine' pour, respectivement, les seuils (à la fois pour les entités initiales et pour les entités calculées par régression), les coefficients de régressions logistiques significatives entre paires de fonctionnalités, de formation et de cas de test.
(demo) krrguest@amd2700vii:~/src/demo$ Python3
>>> import vkfencoder
>>> nam = vkfencoder.DataLoad('./files/vv_red.csv', 7, 'verges', 'trains', 'red_wine', ';', '127.0.0.1', 'root', 'toor')
Le deuxième argument définit le seuil (7 dans notre cas), au-dessus duquel l'exemple est déclaré positif. Argument ';' correspond au délimiteur (la valeur par défaut est ',').
Comme indiqué dans la note précédente de l'auteur , la procédure est différente du cas des traits discrets. Tout d'abord, nous calculons les régressions logistiques à l'aide de la classe vkf.Join, dont les coefficients sont stockés dans la table «complexe».
>>> join = vkf.Join('trains', 'red_wine', '127.0.0.1', 'root', 'toor')
>>> join.compute_save('complex', 'red_wine', '127.0.0.1', 'root', 'toor')
Maintenant, en utilisant la théorie de l'information, nous calculons les seuils à l'aide de la classe vkf.Generator, qui, avec le maximum et le minimum, sont stockés dans la table des «verges».
>>> gen = vkf.Generator('complex', 'trains', 'verges', 'red_wine', 7, '127.0.0.1', 'root', 'toor')
Le cinquième argument définit le nombre de seuils sur les entités d'origine. Par défaut (et avec une valeur de 0), il est calculé comme le logarithme du nombre d'entités. Les régressions recherchent un seul seuil.
Passons maintenant à l'expérience VKF réelle: nous connectons les encodeurs, chargeons les hypothèses précédemment calculées (le cas échéant), calculons le nombre supplémentaire (300) d'hypothèses dans le nombre spécifié (8) de flux et les sauvegardons dans la table 'vkfhyps'.
>>> qual = vkf.Qualifier('verges', 'red_wine', '127.0.0.1', 'root', 'toor')
>>> beg = vkf.Beget('complex', 'red_wine', '127.0.0.1', 'root', 'toor')
>>> ind = vkf.Induction()
>>> ind.load_continuous_hypotheses(qual, beg, 'trains', 'vkfhyps', 'red_wine', '127.0.0.1', 'root', 'toor')
>>> ind.add_hypotheses(300, 8)
>>> ind.save_continuous_hypotheses(qual, 'vkfhyps', 'red_wine', '127.0.0.1', 'root', 'toor')
Vous pouvez obtenir une liste Python de triplets (feature_name, (lower_bound, upper_bound)) pour l'hypothèse CCF numérotée ndx
>>> ind.show_continuous_hypothesis(enc, ndx)
Enfin, nous passons à la prédiction. Nous créons un échantillon de test pour évaluer la qualité des hypothèses générées et en même temps prédire la propriété cible de ses éléments
>>> tes = vkf.TestSample(qual, ind, beg, 'trains', 'red_wine', '127.0.0.1', 'root', 'toor')
>>> tes.correct_positive_cases()
>>> tes.correct_negative_cases()
>>> exit()
Comme nous n'avons qu'un seul fichier, nous avons utilisé ici l'ensemble d'entraînement comme cas de test.
4. Remarque sur le téléchargement de fichiers
J'ai utilisé la bibliothèque aiofiles pour télécharger des fichiers (tels que «mushrooms.xml») sur un serveur Web. Voici un morceau de code qui pourrait vous être utile
import aiofiles
import os
import vkfencoder
async def write_file(path, body):
async with aiofiles.open(path, 'wb') as file_write:
await file_write.write(body)
file_write.close()
async def xml_upload(request):
#determine names of tables
encoder_table_name = request.form.get('encoder_table_name')
lattices_table_name = request.form.get('lattices_table_name')
#Create upload folder if doesn't exist
if not os.path.exists(Settings.UPLOAD_DIR):
os.makedirs(Settings.UPLOAD_DIR)
#Ensure a file was sent
upload_file = request.files.get('file_name')
if not upload_file:
return response.redirect("/?error=no_file")
#write the file to disk and redirect back to main
short_name = upload_file.name.split('/')[-1]
full_path = f"{Settings.UPLOAD_DIR}/{short_name}"
try:
await write_file(full_path, upload_file.body)
except Exception as exc:
return response.redirect('/?error='+ str(exc))
return response.redirect('/ask_data')
Conclusion
La bibliothèque vkf.cpython-38-x86_64-linux-gnu.so contient de nombreuses classes et algorithmes cachés. Ils utilisent des concepts mathématiques avancés tels que les opérations fermées par un, l'évaluation paresseuse, les chaînes de Markov appariées, les états transitoires de la chaîne de Markov, la métrique de la variation totale, etc.
En pratique, des expériences avec des tableaux du Data Repository for Machine Learning (Université de Californie à Irvine ) a montré l'applicabilité réelle du programme C ++ «Méthode VKF» aux données de taille moyenne (le tableau Adult contient 32560 entraînements et 16280 cas de test).
Le système «Méthode VKF» a surpassé (en termes de précision de prédiction) le système CLIP3 (Integer Programming Coverage Learning v.3) sur un tableau SPECT, où CLIP3 est un programme créé par les auteurs du tableau SPECT. Sur le tableau Mushrooms (divisé aléatoirement en échantillons d'apprentissage et d'essai), le système de la méthode VKF a montré une précision de 100% (à la fois en ce qui concerne le critère de toxicité des champignons et le critère de comestibilité). Le programme a également été appliqué à la matrice Adult pour générer plus d'un million d'hypothèses (pas de pépins).
Les codes sources de la bibliothèque vkf CPython sont pré-modérés sur savannah.nongnu.org. Les codes de la bibliothèque auxiliaire vkfencoder peuvent être obtenus auprès de Bitbucket .
L'auteur tient à remercier ses collègues et étudiants (D.A. Anokhin, E.D.Baranova, I.V. Nikulin, E.Yu.Sidorova, L.A. Yakimova) pour leur soutien, leurs discussions utiles et leurs recherches conjointes. Cependant, comme d'habitude, l'auteur est seul responsable de toutes les lacunes existantes.