Sommaire
Qu’est‐ce qu’une ontologie formelle ?
Les ontologies formelles sont un moyen de modéliser des connaissances. Par certains côtés, les ontologies ressemblent beaucoup aux modèles objets : on y retrouve les notions de classes, de propriétés et d’instances (appelées individus).
Les ontologies ont deux finalités principales :
-
le raisonnement automatique : les ontologies définissent des concepts (telles que des classes) de manière logique et formelle. En utilisant un raisonneur, il est donc possible d’effectuer des déductions logiques. En particulier, le raisonneur peut « reclasser » les classes et les instances, c’est‐à‐dire calculer l’arbre d’héritage des classes et la (ou les) classe(s) de chaque instance, à partir de leurs propriétés ;
-
les données liées (linked data) : toutes les ontologies partagent le même espace de nommage. Elles permettent donc de lier entre elles toutes les données existantes. En particulier, la définition d’une classe n’est pas nécessairement contenue dans un seul fichier : une ontologie peut très bien compléter la définition d’une classe issue d’une autre ontologie.
Par rapport aux modèles objet habituels, les ontologies possèdent une expressivité supérieure : elles permettent d’exprimer des contraintes logiques sur les classes, en s’appuyant sur les logiques de description. On peut par exemple créer la classe des « licences » et la classe des « systèmes d’exploitation ». La classe des « licences libres » est une sous‐classe de la classe « licence » (héritage). Nous pouvons ensuite définir la classe des « systèmes d’exploitation libres » comme équivalente à « un systèmes d’exploitation qui a une licence libre ». Tout système d’exploitation ayant (au moins) une licence libre pourra alors être automatiquement reclassé comme « système d’exploitation libre ».
La grande majorité des ontologies utilisent le langage OWL (Web Ontology Language, actuellement en version 2.0). Ce langage peut s’enregistrer en plusieurs formats, le plus employé étant RDF/XML. RDF sérialise l’ontologie sous forme de triplets (sujet, prédicat, objet), par exemple (individu, type, classe) pour renseigner la classe d’un individu ou (individu, propriété, valeur) pour renseigner ses attributs. Les contraintes logiques mentionnées ci‐dessus sont décomposées en plusieurs triplets RDF.
De nombreux outils existent pour traiter les ontologies OWL. Le plus connu est l’éditeur Protégé, qui permet de créer et d’éditer une ontologie en OWL.
En Python, le principal module existant est RDFLIB. Mais RDFLIB présente deux défauts :
- RDFLIB fonctionne au niveau RDF mais pas au niveau OWL. Il permet donc de gérer des triplets et des ressources (c’est‐à‐dire des objets), mais il n’est pas adapté pour gérer les classes et les contraintes logiques. Il n’intègre pas non plus de raisonneur ;
- en pratique, les performances de RDFLIB ne permettent pas de manipuler de grosses ontologies (plusieurs centaines de mégaoctets ou plusieurs millions de triplets).
Owlready et la programmation orientée ontologie
Trois approches existent pour intégrer une ontologie formelle dans un programme :
Les API
Les API, comme OWLAPI en Java, permettent d’accéder aux ontologies à l’aide de classes correspondant aux éléments d’OWL. Par exemple, avec Java + OWLAPI, pour obtenir la propriété « prop » de l’objet « obj », qui est de type « float », on écrira :
OWLDataProperty prop = owlDataFactory.getOWLDataProperty(IRI.create("onto.owl#prop"));
float valeur = ((Float) obj.getPropertyValue(prop)).floatValue();
Les langages de requêtes
Les langages de requêtes s’inspirent de SQL et l’adaptent à RDF. Le plus courant est SPARQL. Si l’on reprend l’exemple précédent en SPARQL, cela donnera :
SELECT ?valeur WHERE { ?obj :prop ?valeur . }
Ensuite, il faut exécuter la requête dans le langage de programmation, par exemple en Python avec RDFLIB, nous aurons :
valeur = graph.query("SELECT ?valeur WHERE { ?obj :prop ?valeur . }")
La programmation orientée ontologie
La programmation orientée ontologie permet de manipuler les classes et les individus de l’ontologie comme s’il s’agissait de classes et d’instances du langage de programmation. Si l’on reprend l’exemple précédent avec Python + Owlready, il suffira d’écrire :
valeur = obj.prop
On comprend donc rapidement que cette troisième approche est de loin la plus facile à utiliser, c’est donc celle que j’ai choisie pour Owlready. Owlready est un module pour Python 3 sous licence LGPL v3+, qui permet la programmation orientée ontologie. La version 2 d’Owlready intègre :
- le raisonneur HermiT (N. B. : celui‐ci étant programmé en Java, il faut une machine virtuelle Java pour utiliser le raisonneur) ;
- un quadstore RDF optimisé utilisant SQLite3. Un quadstore est une base de triplets RDF, auquel on ajoute un quatrième élément qui permet d’identifier de quelle ontologie provient le triplet. Ce quadstore peut être stocké en mémoire ou bien dans un fichier. De plus, le quadstore est compatible avec RDFLIB ;
- des analyseurs pour les formats de fichiers RDF/XML, OWL/XML et NTriples.
Au final, Owlready cherche à obtenir le meilleur de trois mondes :
- la programmation orienté objet, pour l’encapsulation (c’est‐à‐dire la capacité à rassembler les données et les traitements associés : les méthodes) ;
- les ontologies formelles, pour l’expressivité (les contraintes logiques et les capacités de raisonnement automatique associées) ;
- les bases de données relationnelles, pour les performances (la capacité de stockage et la rapidité d’accès).
L’architecture, la syntaxe et les algorithmes utilisés dans Owlready ont été publiés dans un article récent de la revue Artificial Intelligence In Medicine, que l’on peut trouver sur mon site perso (je reviendrai plus bas sur la délicate question du libre accès aux articles scientifiques).
Notons qu’Owlready peut aussi être utilisé en lieu et place d’un ORM (Object Relational Mapper). Un ORM est une surcouche objet à une base de données (généralement SQL) et permet la persistance des objets, comme par exemple SQLAlchemy ou SQLObject en Python. Les tests montrent qu’Owlready conduit à un niveau de performance équivalent voire supérieur.
Exemple avec Owlready
Nous allons reprendre l’exemple précédent sur les licences libres et les systèmes d’exploitation, et le créer avec Owlready. La première ligne importe le module, la seconde crée une ontologie, la troisième (bloc with
) indique que tout ce qui sera créé dans ce bloc (classes, propriétés, individus, etc.) sera défini dans l’ontologie « onto ». Ensuite nous définissons les classes, en héritant de Thing, qui est la classe la plus générale en OWL.
from owlready2 import *
onto = get_ontology("http://test.org/onto.owl")
with onto:
class Licence(Thing): pass
class LicenceLibre(Licence): pass
class LicenceProprietaire(Licence): pass
licence_proprio = LicenceProprietaire("licence_proprio")
gpl = LicenceLibre("gpl")
lgpl = LicenceLibre("lgpl")
class SystemeDExploitation(Thing): pass
class a_pour_licence(ObjectProperty):
domain = [SystemeDExploitation]
range = [Licence]
gnu_linux = SystemeDExploitation("gnu_linux")
gnu_linux.a_pour_licence = [gpl]
windows = SystemeDExploitation("windows")
windows.a_pour_licence = [licence_proprio]
class SystemeDExploitationLibre(Thing):
equivalent_to = [ SystemeDExploitation & a_pour_licence.some(LicenceLibre) ]
La propriété a_pour_licence est créée comme une classe fille d’ObjectProperty et nous définissons son domaine et son range. Le range correspond au « type » de la propriété, c’est‐à‐dire au type de valeur qu’elle peut prendre. Le domaine correspond à la classe qui possède cette propriété : contrairement aux modèles objet habituels, les propriétés ne sont pas définies pour une classe donnée mais indépendamment. Cela permet à une ontologie d’ajouter des propriétés aux classes définies dans une autre ontologie.
Enfin, l’exemple crée la classe SystemeDExploitationLibre, qui est définie comme équivalente à SystemeDExploitation et « a_pour_licence SOME LicenceLibre » (au moins une licence libre, donc).
Nous pouvons ensuite exécuter le raisonneur et afficher le résultat :
sync_reasoner()
print(gnu_linux.__class__)
# => onto.SystemeDExploitationLibre
Nous constatons que l’individu « gnu_linux » a été reclassé.
Et si nous voulons faire le même raisonnement pour les systèmes d’exploitation non libres ? C’est plus compliqué ! Nous pouvons créer la classe des « systèmes d’exploitation non libres » ainsi (notez le « Not » par rapport à tout à l’heure) :
with onto:
class SystemeDExploitationNonLibre(Thing):
equivalent_to = [ SystemeDExploitation & Not(a_pour_licence.some(LicenceLibre)) ]
Mais si vous exécutez le raisonneur, vous constaterez que Windows n’est pas reclassé en SystemeDExploitationNonLibre ! En effet, les raisonneurs fonctionnent selon l’assomption du monde ouvert : tout ce qui n’est pas défini est considéré comme possible. Nous avons défini que Windows avait une licence propriétaire, cependant nous n’avons pas dit que Windows n’avait pas d’autres licences (oui, certains logiciels ont plusieurs licences). Le raisonneur a donc considéré qu’il n’était pas impossible que Windows possède une autre licence, et que celle‐ci soit libre.
Nous devons donc indiquer que Windows possède seulement pour licence la licence propriétaire, ce qui peut se faire en ajoutant une contrainte OWL :
windows.is_a.append( a_pour_licence.only(OneOf([licence_proprio])) )
Ou plus simplement, avec Owlready, en utilisant la fonction close_world()
qui crée automatiquement les contraintes nécessaires pour considérer un individu ou une classe en « monde fermé » (c’est‐à‐dire pour asserter que tout est connu à leur sujet) :
close_world(windows)
close_world(gnu_linux)
Enfin, nous devons également définir que les classes LicenceLibre et LicencePropriétaire sont disjointes, c’est‐à‐dire qu’il n’existe pas de classe fille héritant des deux (une licence ne peut pas être à la fois libre et propriétaire). Cela se fait ainsi :
AllDisjoint([LicenceLibre, LicenceProprietaire])
Nous pouvons ensuite exécuter de nouveau le raisonneur et afficher le résultat :
sync_reasoner()
print(windows.__class__)
# => onto.SystemeDExploitationNonLibre
Voilà, c’était un exemple simple de raisonnement logique, avec quelques pièges classiques.
À propos de l’accès libre aux articles scientifiques
La question du libre accès à la connaissance étant à la base du logiciel libre, j’aimerais revenir sur l’accès aux articles scientifiques. Les revues scientifiques font intervenir plusieurs acteurs : les auteurs des articles, les relecteurs (reviewers, chargés de corriger et d’évaluer l’article), l’éditeur (publisher, qui édite la revue) et les lecteurs. Historiquement, les auteurs et les relecteurs sont bénévoles (ce sont souvent des chercheurs payés par l’état), et le lecteur paie l’éditeur pour accéder à l’article (en général, il s’agit d’un abonnement institutionnel : un laboratoire ou une université paie un éditeur pour que ses chercheurs aient accès à telle ou telle revue). Donc, pas d’accès libre.
Il y a une vingtaine d’années sont apparues des revues en accès libre, avec un modèle différent : ce sont les auteurs qui paient l’éditeur, et le lecteur accède gratuitement à la revue (en ligne). Ce modèle a notamment été lancé par PLOS, avec un certain succès. La qualité des revues se mesure notamment à leur facteur d’impact, c’est‐à‐dire le nombre de fois où les articles publiés sont cités dans les deux ans qui suivent la publication. L’idée derrière les revues en accès libre est la suivante : les articles étant librement accessibles, ils seront davantage lus et donc plus cités.
Mais ce n’est pas si simple : le nombre de citations ne suffit pas à faire la qualité… Dans une revue où l’auteur paie, l’éditeur a tout intérêt à faire en sorte qu’un maximum d’articles soit accepté, même si certains sont de mauvaise qualité, puisqu’il est payé à chaque article accepté. En fait, l’éditeur n’a qu’à dire « oui » et il est payé… On voit donc exploser le nombre de revues et d’éditeurs de ce type, avec un niveau de qualité souvent très faible. Et du coup les chercheurs sont régulièrement sollicités (pour ne pas dire « spammés ») par ce type d’éditeurs…
Les éditeurs « classiques » ont auss réagi par rapport au libre accès. Toutes sortes « d’exceptions » ont été mises en place. Par exemple pour la revue Artificial Intelligence In Medicine (édité par Elsevier) où j’ai publié :
- les auteurs peuvent payer pour avoir leur article en libre accès (N. B. : hors de prix, mais certains projets de recherche, notamment européen, exigent que ce soit le cas) ;
- un lien est fourni aux auteurs qui permet de télécharger gratuitement l’article pendant 50 jours, et ce lien peut être diffusé à volonté ;
- après six mois d’embargo, une « version auteur » (même contenu que le vrai article, mais sans la mise en forme de l’éditeur) peut être mise à disposition librement sur des serveurs comme HAL (Hyper‐Archives en Ligne) ;
- les auteurs ont le droit de mettre une « version auteur » sur leur site personnel dès la parution de l’article, sous licence Creative Commons Attribution Non‐Commercial No Derivatives ».
Nous ne sommes donc pas loin d’un accès libre (surtout grâce au dernier point), à condition que les auteurs fassent l’effort de produire et mettre en ligne cette « version auteur ».