Thématiques principales

dimanche 21 janvier 2018

IA : Les systèmes experts, Drools

Cet article est le premier d’une longue série sur l’intelligence artificielle. Je n’ai pas introduit cette dernière mais je n’y manquerai pas dans un article futur. Je passe donc sur cela pour m’intéresser directement à un axe complet de l’IA que sont les systèmes experts [1] [2].

Les systèmes experts sont ce que l’on appelle dans le domaine de la modélisation, le modèle des comportements d’un expert du domaine. Son rôle est de procéder de la même manière que l’expert afin d'en reproduire l’analyse.

Un système expert va donc consolider la connaissance de l’expert du domaine pour procéder à des traitements conformes à ceux prescrits par ce même expert. Deux concepts clefs des systèmes experts sont identifiables alors:

  • une base de connaissances
  • des règles d'inférences

Selon ces concepts, un système expert croise alors les connaissances avec les règles dans un moteur d'inférence qui peut alors produire soit des nouvelles connaissances, soit de nouvelles règles ou les deux.





Historiquement portés par des langages comme le Lisp [3] ou le Prolog [4], les systèmes experts ont eu leurs heures de gloire dans des domaines d’applications variées, comme dans le milieu médical avec Mycin [5], [6], [9].

Aujourd’hui, dans le domaines de l’intelligence artificielle, les systèmes experts ont moins la cote et sont plutôt employés dans l’aide décisionnelle.

Le cas de Drools

Drools [7] est système expert ou Business Rules Management System (BRMS). Il utilise un moteur de règles [8] permettant de déclencher des actions sur la base d'un ensemble de règles si des données les valident ou non.

Un système expert tel que Drools  trouve son intérêt dans les systèmes logiciels ou il est nécessaire de réaliser de nombreux tests sur des données entrantes afin d’en produire d'autres déduites des premières apportant plus de valeur pour par exemple une prise de décision.

Une règle se définit tout simplement par la formule :


IF [test] THEN [actions]


Cette définition est un peu abusive car la règle proprement dite est en réalité le champs [test] alors que la partie [actions] est appelée activation.

Plus dans le détail, un moteur de règles fonctionne sur une base de règles contenant un ensemble des règles associées à des contraintes d'exploitation formant ce que l’on appelle une session (existe sous deux types de formes, comme les EJB : statefull et stateless). En parallèle, des données que l'on appelle des faits sont injectées dans le moteur d’inférence qui par l'utilisation de pattern matching va chercher la ou les règles qui doivent être appliquées et par voie de conséquence appliquer la ou les activations associées. Ces dernières forment alors ce qui est appelé agenda (ensemble des activations prêtes a être exécutées). Grâce à cette approche, le système expert permet l’application du principe de découplage des flux de données et des flux de contrôles [10] souvent essentiel à la conception d’un logiciel.

Le moteur de règles permet donc de traiter les données entrantes pour en construire de nouvelles qu’il va être possible de réinjecter en tant que fait dans le moteur d'inférence. De la même façon, la gestion de la base de connaissances de règles peut être rendu dynamique si l’on prévoit des mécanismes d’injection de nouvelles règles déduites sur la bases des faits traités. Ainsi, les règles peuvent évoluer au fil de l'exploitation du système logiciel de façon à ce que celui-ci, s'adapte sans rupture de service a des nouveaux besoins.

Ainsi, par exemple, si un logiciel doit gérer un site en ligne, un moteur de règles pourra s’injecter selon le calendrier et les périodes de solde de nouvelles conditions promotionnelles à appliquer sur les produits celle-ci adaptées a, par exemple, la fidélité du client.

Par cet exemple très simple, on peut percevoir les possibilités d'un outil comme Drools car en plus de d’ingérer des règles a appliquer, Drools se charge d'évoluer par lui même.

Afin de nous permettre une meilleur compréhension de l’outil, traitons d’un cas simple d’utilisation de Drools

Exemple

L’exemple que nous allons utiliser est un pseudo chatbot qui va poser des questions à l’utilisateur et agir en conséquence en fonction de si celui ci est majeur ou non.

Comme scénario, notre bot va demander le nom de l’utilisateur puis son age, pour enfin conclure si la discussion peut être poursuivie ou non.

Dans un premier temps, il faut mettre en place l’environnement de travail. Il faut utiliser le Singleton (un design pattern dont nous reparlerons) KieServices qui va nous permettre d’acceder aux différentes API de Drools. A partir du KieServices, il faut produire une factory sur des ressources adaptées aux types d’IO qui seront employées ainsi qu’un fileSystem dans lequel seront déposées les règles que nous allons définir et utiliser.


KieServices droolsServices = KieServices.Factory.get();
KieResources resources = kieServices.getResources();
KieFileSystem fileSystem = kieServices.newKieFileSystem();


Les règles seront exécutés par le moteur d'inférence drools mais pour cela, elles doivent déposées dans le fileSystem puis être compilées par le moteur Drools. Pour cela on va donner le fileSystem dans lequel on aura préalablement déclaré le fichier contenant les règles a un builder obtenu à partir du KieServices.


Resource resource=kieResources.newClassPathResource("rules/Regle.drl");
kieFileSystem.write(resource);
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
          
if (kb.getResults().hasMessages(org.kie.api.builder.Message.Level.ERROR)) {
 throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}


On analyse le résultat et on passe ensuite à la création de l’environnement d'exécution des règles et de l’insertion des faits. Pour cela on crée un ReleaseId par défaut (on reviendra sur ceci dans un autre article) que l’on va utiliser pour créer un conteneur KieContainer qui nous permettra d’obtenir une session d'exécution


ReleaseId ridOriginal = kieServices.newReleaseId("org.default", "artifact", "1.0.0-SNAPSHOT");
KieContainer kieContainer = kieServices.newKieContainer(ridOriginal);
KieSession kSession= kieContainer.newKieSession();


Grâce à la session on va enfin pouvoir exécuter des règles sur les facts. A noter que nous avons fait le choix de l’utilisation d’une session StateFull, permettant de maintenir les facts dans le moteur (ces derniers peuvent évoluer lors de l’exécution contrairement aux StateLess auquel cas on aurait utilisé la méthode newStatelessKieSession du KieContainer)

Enfin avant de passer aux règles et a la formalisation des facts sur lesquels vont s’appliquer les règles, il nous faut finalement choisir un modèle opérationnel pour l’exécution des règles. Ne prenons pas peur, nous avons simplement a choisir, si d’un coté nous voulons exécuter les règles en continue ou si nous souhaitons choisir quand appliquer celles-ci.

Nous avons la possibilités d’appeler la méthode fireAllRules mais il faudra la rappeler nous même a chaque changement des facts:


kSession.fireAllRules();


Ou de laisser drools exécuter les règles en boucle


this.kSession.fireUntilHalt();


La première approche que nous ne choisirons pas, permet d’avoir une exécution réactive des règles. Si aucuns facts ne se présente, alors inutile de vouloir faire évoluer le modèle. Nous utiliserons donc la second solution mais celle-ci est bloquante (voir [7]). Il va nous falloir placer cet appel dans un thread séparé donc


public class DroolsThread implements Runnable {
 private KieSession kSession;
 public DroolsThread(KieSession kSession)
 {
  this.kSession=kSession;
 }
 @Override
 public void run() {
  this.kSession.fireUntilHalt();
 }
}


Et nous construiront le Thread:


Thread t=new Thread(new DroolsThread(kSession));
t.start();


Nous avons l’environnement mais il nous faut des règles à mettre dedans et une structure objet sur laquelle ces règles vont s’appliquer : en un mot les facts.

Nous avons dit que l’on souhaitait vérifier l'âge d’un utilisateur après lui avoir demander son nom. Très bien, construisons la classe Profil contenant ces informations.


public class Profil {
 private String nom;
 private Integer age;
 public Profil() {}
 public Profil(String nom)
 {
  super();
  this.nom = nom;
 }
 public Integer getAge()
 {
  return age;
 }
 public void setAge(Integer age)
  {
  this.age = age;
 }
 public String getNom()
 {
  return nom;
 }
 public void setNom(String nom)
 {
  this.nom = nom;
 }
}


A partir de cette classe on va pouvoir construire un objet profil représentant l’interlocuteur du bot sur lequel nos règles vont travailler. Cependant, il nous manque quelque chose, un moyen de traiter les messages de l’utilisateur. On va donc construire la classe Context qui va consolider ce que dira l’utilisateur, les réponses que notre bot donnera et élaborera au fur et à mesure son profil.


public class Context {

 private String message;
 private String reponse;
 private Profil profilCourant;
 
 public Context() {}
 public Context(String message)
 {
  super();
  this.message = message;
 }
 public String getMessage()
 {
  return message;
 }
 public void setMessage(String message)
 {
  this.message = message;
 }
 public Profil getProfilCourant()
 {
  return profilCourant;
 }
 public void setProfilCourant(Profil profilCourant)
 {
  this.profilCourant = profilCourant;
 }
 public String getReponse()
 {
  return reponse;
 }
 public void setReponse(String reponse) {
  this.reponse = reponse;
 }
}


Bien on imagine donc maintenant comment va se tenir la conversation:
Au démarrage, c’est a dire à la connexion de l’utilisateur, le bot dira “Bonjour, je suis un bot” histoire d’engager la conversation. Ensuite du coup on va pouvoir utiliser le Context pour traiter l’interaction homme/machine. Pour cela, on va insérer dans la session le contexte créé donnant le code général suivant:


KieSession kSession=kieContainer.newKieSession();
System.out.println("Bonjour, je suis un bot");
Context context=new Context();
kSession.insert(context);
context.readMessage();
Thread t=new Thread(new DroolsThread(kSession));
t.start();


On voit ici que l’on utilise sur le context une méthode readMessage. Effectivement, la classe présenté préalablement ne comptait pas de comportements particuliers pour plus de simplicité. Cependant, il va bien falloir gérer les IO avec l’utilisateur. Ici nous n'utilisons que la console. Ainsi on va ajouter les méthodes readMessage et pushReponse dans la classe Context.


public void readMessage()
{
 BufferedReader in  = new BufferedReader(new InputStreamReader(System.in));
 try {
  this.setMessage(in.readLine());
  System.out.println(this.getMessage());
 } catch (IOException e) {
  e.printStackTrace();
 }
}
public void pushReponse(String question)
{
 this.setReponse(question);
 System.out.println(this.getReponse());
}


Il reste maintenant a définir une règle permettant au bot d’attendre une réponse du genre “bonjour”. (Ok je ne suis pas très original mais cela permettra au bot de commencer a demander le nom puis l’age de l’utilisateur pour enfin conclure)

Bien on va donc définir cette regles dans le fichier dédié déclaré comme une ressource (rules/Regle.drl)

Le fichier de règles.

Ce fichier est comme un fichier java, on va donc le déclarer dans un package qui peut etre identique a nos classes Context et Profil (de façon à accéder aux classes sans devoir faire d’import). Sinon il faudra ajouter les imports adéquats. De la même façon pour toutes classes spécifiques il faudra faire un import pour pouvoir en utiliser les fonctionnalités (instanciation, appel de méthodes, etc..)

Voici donc son contenu avec la première règle, les suivantes seront à ajouter à la suite.


package org.tc.osgi.bundle.drools.rules

import org.tc.osgi.bundle.drools.module.activator.*;
rule "Demande_Nom"
    when
        $m: Context( message contains "jour" )
    then
     System.out.println("r1");
 modify( $m ) { setMessage("") };
 modify( $m ) { pushReponse("Quel est votre nom?") };
 modify( $m ) { readMessage()};
end


Le principe des règles est simple cependant si on l’assimile souvent a un “if/then”, ce qui est globalement le cas, en fait il faudrait plutôt le voir comme une requette SQL

Ainsi la règle précédente dans le jargon  “if/then” va définir une variable $m (avec un $ par convention) tel que si sa variable message d’un contexte contient la chaîne “jour”. Alors on imprime “r1”, on vide la variable message du contexte, on répond à l’utilisateur par une question et on se met en attente d’une reponse. L’utilisation du mot clef modify permet de modifier un fact directement dans l’environnement de traitement des regles, et drools comprend ainsi que ce fact reste a utiliser pour une prochaine evaluation des faits (sinon elle est mise de côté et il ne se passera plus rien)
Dans une traduction sql, on verrait cela plutôt sous la forme “pour tous” ou “quand”  les contextes dont la variable message contient jour, il faut, modifier les variables comme ceci …

La différences semblent légère mais il est plus facile d’imaginer le processus de traitement des règles comme d’un processus de requêtes sur une BD constituée par les facts courant car une requête traite d’un ensemble alors qu’un if/then nous amène à penser que l’on ne manipule qu’un élément (ce qui est faux, bien qu’ici effectivement nous n’avons qu’un contexte)

Maintenant que le bot a demandé le nom de l’utilisateur, il faut imaginer sa réponse. Pour faire simple on va considérer que l’utilisateur est quelqu’un de cool qui explicite ses réponses en faisant des phrases. Ainsi comme pour la règle précédente on va essayer de matcher la réponse reelle avec un possible pattern de réponse (une regex quoi)


rule "Recupere_nom"
    when
     $m: Context( message matches ".*nom est .*"  )
    then
     System.out.println("r2");
 Profil p=new Profil($m.match(".*est (.*)"));
 modify( $m ) { setProfilCourant( p ) };
 modify( $m ) { pushReponse("Enchanté "+ p.getNom()+ ".Quel est votre age?") };
 modify( $m ) { setMessage("") };
 insert(p);
 modify( $m ) { readMessage()};
end


Ainsi cette règle sera activée pour tous les contextes dont le message sera constitué de “mon nom est”. A noter que cela ne fait que permettre l’activation de la règle avec un contexte pour lequel on va maintenant pouvoir construire un Profil… maintenant que nous avons le nom. (La méthode match du contexte est aussi une méthode de Contexte permettant de faire du pattern matching….) Ce profil est intégrée au contexte et dans la session comme d’un nouveau fact.

On a le nom, faisons de meme avec l’age:


rule "Recupere_age"
    when
     $m: Context( message matches ".*age est .*")
     $p : Profil( nom == $m.getProfilCourant().getNom() )
    then
     System.out.println("r3");
 modify( $p ) { setAge(Integer.parseInt($m.match(".*est (.*)"))) };
 modify( $m ) { pushReponse("Attendez je regarde si ca va") };
 modify( $m ) { setMessage("") };
 modify( $m ) { readMessage()};
end


Ici rien de neuf par rapport a la règle précédente. Il ne reste donc maintenant plus qu’a traiter le cas ou l’utilisateur est majeur ou non. Et la on peut utiliser le contenu du profil pour le faire en testant directement dans la partie test (ou le critère de sélection) de la règle:


rule "trop_jeune"
    when
     $m : Context( reponse matches "Attendez je regarde si ca va", profilCourant.getAge() < 18  )
    then
     System.out.println("r4");
     System.out.println("ah non, Vous etes trop jeune "+$m.getProfilCourant().getNom()+ ", aurevoir");
     modify( $m ) { setMessage("") };
     modify( $m ) { readMessage()}; end
rule "c est bon"
    when
     $m : Context( reponse matches "Attendez je regarde si ca va", profilCourant.getAge() >= 18  )
    then
     System.out.println("r5");
    System.out.println("Ok ca va " + $m.getProfilCourant().getNom());
     modify( $m ) { setMessage("") };
     modify( $m ) { readMessage()};
     end


Une exécution de ceci produira alors la conversation suivante:


b>Bonjour, je suis un bot
t>Bonjour
r1
b>Quel est votre nom?
t>Mon nom est Thomas
r2
b>Enchanté Thomas.Quel est votre age?
t>Mon age est 37
r3
b>Attendez je regarde si ca va
t>
r5
b>Ok ca va Thomas


Voila, nous avons fait un tour (rapide) de Drools. Bien sur il reste beaucoup de fonctionnalités à explorer, comme l’utilisation de l’agenda, l'intégration a Spring ou ses interactions avec le serveur d’application JBoss. A l’occasion de nouveaux articles, nous y reviendrons.

References:

[1] https://fr.wikipedia.org/wiki/Syst%C3%A8me_expert
[2] http://villemin.gerard.free.fr/Wwwgvmm/Logique/IAexpert.htm
[3] http://lisp-lang.org/
[4] http://www.swi-prolog.org/
[5] https://en.wikibooks.org/wiki/Expert_Systems/MYCIN
[6] https://expertsystem101.weebly.com/mycin.html
[7] https://www.drools.org/
[8] https://blog.xebia.fr/2010/01/08/drools-et-les-moteurs-de-regles/
[9] https://fr.slideshare.net/vini89/mycin
[10] https://www.tutorialspoint.com/drools/drools_introduction.htm

Aucun commentaire:

Enregistrer un commentaire