SW-6-RDFS

Navigation : << 5-LinkedData | Index | 7-OWL >>

RDFS : Schema et Inference pour RDF

Objectifs d’apprentissage

A la fin de ce notebook, vous saurez : 1. Comprendre le vocabulaire RDFS et son rôle dans le Web Sémantique 2. Construire des hiérarchies de classes et de proprietes avec rdfs:subClassOf et rdfs:subPropertyOf 3. Utiliser rdfs:domain et rdfs:range pour contraindre les proprietes 4. Appliquer l’inference RDFS pour deduire de nouveaux triplets 5. Interroger des données enrichies par le raisonnement RDFS

Prerequis

  • .NET SDK 9.0+ et .NET Interactive
  • Notebooks SW-1 a SW-4 (bases RDF, graphes, SPARQL)

Duree estimee : 40 minutes


Installation et imports

Chargeons dotNetRDF et les espaces de noms necessaires pour travailler avec RDFS.

#r "nuget: dotNetRDF, 3.2.1"

using System;
using System.Linq;
using System.Collections.Generic;
using VDS.RDF;
using VDS.RDF.Parsing;
using VDS.RDF.Writing;
using VDS.RDF.Ontology;
using VDS.RDF.Query;
using VDS.RDF.Query.Datasets;

Console.WriteLine("dotNetRDF charge avec succes.");
Installed Packages
  • dotNetRDF, 3.2.1
dotNetRDF charge avec succes.

1. Qu’est-ce que RDFS ?

RDF Schema (RDFS) est une extension de RDF qui fournit un vocabulaire pour decrire la structure des données RDF. Tandis que RDF permet d’exprimer des faits sous forme de triplets (sujet, predicat, objet), RDFS ajoute la capacite de définir :

RDF Schema (RDFS) est standardise par le W3C dans RDF Schema 1.1 (Brickley & Guha, W3C Recommendation 2014). C’est une extension de RDF qui fournit un vocabulaire pour decrire la structure des données RDF – classes, hiérarchies, domaines/portees de proprietes et règles d’inference.

  • Des classes et des hiérarchies de classes
  • Des proprietes avec domaine et portee
  • Des règles d’inference implicites

RDFS dans la pile du Web Sémantique

flowchart TD
    OWL["OWL (SW-7) - Logique descriptive"] --> RDFS["RDFS (SW-6) - Vocabulaires, hierarchies - NOUS SOMMES ICI"]
    RDFS --> SPARQL["SPARQL (SW-4/5) - Interrogation"]
    SPARQL --> RDF["RDF (SW-1/2/3) - Triplets, graphes"]
    RDF --> URI["URI / IRI - Identifiants"]
    style RDFS font-weight:bold

RDF seul vs RDFS

Capacite RDF seul RDF + RDFS
Exprimer des faits (triplets) Oui Oui
Définir des classes Non rdfs:Class
Hiérarchies de classes Non rdfs:subClassOf
Hiérarchies de proprietes Non rdfs:subPropertyOf
Contraindre domaine/portee Non rdfs:domain, rdfs:range
Inference automatique Non Oui (règles RDFS)
Documentation Limite rdfs:label, rdfs:comment

Vocabulaire RDFS complet

Le namespace RDFS est http://www.w3.org/2000/01/rdf-schema#. Voici les termes essentiels :

Terme RDFS Type Description
rdfs:Class Classe Définit une classe (un ensemble de ressources)
rdfs:subClassOf Propriete A est une sous-classe de B (A \(\subseteq\) B)
rdfs:subPropertyOf Propriete P1 est une sous-propriete de P2
rdfs:domain Propriete Le sujet d’un triplet avec P est de type C
rdfs:range Propriete L’objet d’un triplet avec P est de type C
rdfs:label Propriete Etiquette lisible par un humain
rdfs:comment Propriete Description textuelle
rdfs:Resource Classe Superclasse de toutes les ressources
rdfs:Literal Classe Classe des valeurs litterales
rdfs:Datatype Classe Classe des types de données
rdfs:seeAlso Propriete Lien vers une ressource connexe
rdfs:isDefinedBy Propriete Lien vers la definition de la ressource

2. Construire un schema RDFS en code

Construisons une taxonomie animale en C# avec dotNetRDF. Nous allons créer la hiérarchie suivante :

  Animal
  ├── Mammal (Mammifere)
  │   ├── Dog (Chien)
  │   └── Cat (Chat)
  └── Bird (Oiseau)
      └── Parrot (Perroquet)

La même taxonomie, rendue sous forme de graphe : chaque flèche rdfs:subClassOf pointe de la sous-classe vers sa super-classe (sens de lecture « est une sorte de »).

graph BT
    Dog["ex:Dog · Chien"]
    Cat["ex:Cat · Chat"]
    Parrot["ex:Parrot · Perroquet"]
    Mammal["ex:Mammal · Mammifère"]
    Bird["ex:Bird · Oiseau"]
    Animal["ex:Animal"]
    Dog -->|rdfs:subClassOf| Mammal
    Cat -->|rdfs:subClassOf| Mammal
    Parrot -->|rdfs:subClassOf| Bird
    Mammal -->|rdfs:subClassOf| Animal
    Bird -->|rdfs:subClassOf| Animal
    classDef root fill:#cfe2ff,stroke:#084298,color:#052c65
    classDef mid fill:#d1e7dd,stroke:#0f5132,color:#052e16
    classDef leaf fill:#fff3cd,stroke:#b8860b,color:#5c4400
    class Animal root
    class Mammal,Bird mid
    class Dog,Cat,Parrot leaf

Lecture. Les 5 relations rdfs:subClassOf (Dog/Cat < Mammal, Parrot < Bird, Mammal/Bird < Animal) forment un arbre d’héritage. RDFS garantit que cette relation est transitive : Dog ⊑ Mammal et Mammal ⊑ Animal impliquent Dog ⊑ Animal. C’est cette transitivité que le raisonneur exploite pour typer les instances par toutes leurs classes ancêtres (diagramme suivant).

// Creer un graphe et definir les namespaces
IGraph schema = new Graph();
schema.NamespaceMap.AddNamespace("ex", new Uri("http://example.org/animals#"));
schema.NamespaceMap.AddNamespace("rdf", new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
schema.NamespaceMap.AddNamespace("rdfs", new Uri("http://www.w3.org/2000/01/rdf-schema#"));
schema.NamespaceMap.AddNamespace("xsd", new Uri("http://www.w3.org/2001/XMLSchema#"));

// Noeuds predicats RDFS
IUriNode rdfType = schema.CreateUriNode("rdf:type");
IUriNode rdfsClass = schema.CreateUriNode("rdfs:Class");
IUriNode subClassOf = schema.CreateUriNode("rdfs:subClassOf");
IUriNode rdfsLabel = schema.CreateUriNode("rdfs:label");
IUriNode rdfsComment = schema.CreateUriNode("rdfs:comment");
IUriNode rdfsDomain = schema.CreateUriNode("rdfs:domain");
IUriNode rdfsRange = schema.CreateUriNode("rdfs:range");
IUriNode rdfProperty = schema.CreateUriNode("rdf:Property");

// --- Definition des classes ---
IUriNode animal = schema.CreateUriNode("ex:Animal");
IUriNode mammal = schema.CreateUriNode("ex:Mammal");
IUriNode bird = schema.CreateUriNode("ex:Bird");
IUriNode dog = schema.CreateUriNode("ex:Dog");
IUriNode cat = schema.CreateUriNode("ex:Cat");
IUriNode parrot = schema.CreateUriNode("ex:Parrot");

// Declarer les classes
schema.Assert(new Triple(animal, rdfType, rdfsClass));
schema.Assert(new Triple(mammal, rdfType, rdfsClass));
schema.Assert(new Triple(bird, rdfType, rdfsClass));
schema.Assert(new Triple(dog, rdfType, rdfsClass));
schema.Assert(new Triple(cat, rdfType, rdfsClass));
schema.Assert(new Triple(parrot, rdfType, rdfsClass));

// Hierarchie de classes (rdfs:subClassOf)
schema.Assert(new Triple(mammal, subClassOf, animal));
schema.Assert(new Triple(bird, subClassOf, animal));
schema.Assert(new Triple(dog, subClassOf, mammal));
schema.Assert(new Triple(cat, subClassOf, mammal));
schema.Assert(new Triple(parrot, subClassOf, bird));

// Etiquettes (rdfs:label)
schema.Assert(new Triple(animal, rdfsLabel, schema.CreateLiteralNode("Animal", "fr")));
schema.Assert(new Triple(mammal, rdfsLabel, schema.CreateLiteralNode("Mammifere", "fr")));
schema.Assert(new Triple(bird, rdfsLabel, schema.CreateLiteralNode("Oiseau", "fr")));
schema.Assert(new Triple(dog, rdfsLabel, schema.CreateLiteralNode("Chien", "fr")));
schema.Assert(new Triple(cat, rdfsLabel, schema.CreateLiteralNode("Chat", "fr")));
schema.Assert(new Triple(parrot, rdfsLabel, schema.CreateLiteralNode("Perroquet", "fr")));

// Commentaire sur la classe racine
schema.Assert(new Triple(animal, rdfsComment, 
    schema.CreateLiteralNode("Classe racine de tous les animaux", "fr")));

Console.WriteLine($"Schema RDFS cree : {schema.Triples.Count} triplets");
Console.WriteLine();

// Afficher la hierarchie
Console.WriteLine("Hierarchie de classes :");
foreach (Triple t in schema.GetTriplesWithPredicate(subClassOf))
{
    string child = ((IUriNode)t.Subject).Uri.Fragment.TrimStart('#');
    string parent = ((IUriNode)t.Object).Uri.Fragment.TrimStart('#');
    Console.WriteLine($"  {child} rdfs:subClassOf {parent}");
}
Schema RDFS cree : 18 triplets

Hierarchie de classes :
  Mammal rdfs:subClassOf Animal
  Bird rdfs:subClassOf Animal
  Dog rdfs:subClassOf Mammal
  Cat rdfs:subClassOf Mammal
  Parrot rdfs:subClassOf Bird

Interpretation : Construction du schema

Nous avons créé un schema RDFS complet avec : - 6 classes organisees en hiérarchie - 5 relations rdfs:subClassOf definissant l’arbre taxonomique - 6 etiquettes rdfs:label en francais

Points cles : 1. Chaque classe est declaree comme instance de rdfs:Class via rdf:type 2. rdfs:subClassOf est transitif : Dog < Mammal < Animal, donc Dog < Animal 3. Les etiquettes multilingues utilisent les tags de langue ("fr", "en", etc.)

Ajout de proprietes avec domaine et portee

Definissons des proprietes pour notre taxonomie, avec rdfs:domain (type du sujet) et rdfs:range (type de l’objet/valeur).

// Definir des proprietes avec domaine et portee
IUriNode nameProp = schema.CreateUriNode("ex:name");
IUriNode ageProp = schema.CreateUriNode("ex:age");
IUriNode soundProp = schema.CreateUriNode("ex:sound");
IUriNode canFlyProp = schema.CreateUriNode("ex:canFly");

// ex:name - propriete avec domaine Animal, portee xsd:string
schema.Assert(new Triple(nameProp, rdfType, rdfProperty));
schema.Assert(new Triple(nameProp, rdfsDomain, animal));
schema.Assert(new Triple(nameProp, rdfsRange, schema.CreateUriNode("xsd:string")));
schema.Assert(new Triple(nameProp, rdfsLabel, schema.CreateLiteralNode("nom", "fr")));

// ex:age - propriete avec domaine Animal, portee xsd:integer
schema.Assert(new Triple(ageProp, rdfType, rdfProperty));
schema.Assert(new Triple(ageProp, rdfsDomain, animal));
schema.Assert(new Triple(ageProp, rdfsRange, schema.CreateUriNode("xsd:integer")));
schema.Assert(new Triple(ageProp, rdfsLabel, schema.CreateLiteralNode("age", "fr")));

// ex:sound - propriete avec domaine Animal, portee xsd:string
schema.Assert(new Triple(soundProp, rdfType, rdfProperty));
schema.Assert(new Triple(soundProp, rdfsDomain, animal));
schema.Assert(new Triple(soundProp, rdfsRange, schema.CreateUriNode("xsd:string")));
schema.Assert(new Triple(soundProp, rdfsLabel, schema.CreateLiteralNode("cri", "fr")));

// ex:canFly - propriete avec domaine Animal, portee xsd:boolean
schema.Assert(new Triple(canFlyProp, rdfType, rdfProperty));
schema.Assert(new Triple(canFlyProp, rdfsDomain, animal));
schema.Assert(new Triple(canFlyProp, rdfsRange, schema.CreateUriNode("xsd:boolean")));
schema.Assert(new Triple(canFlyProp, rdfsLabel, schema.CreateLiteralNode("peut voler", "fr")));

Console.WriteLine($"Schema enrichi : {schema.Triples.Count} triplets");
Console.WriteLine();

// Afficher les proprietes et leurs contraintes
Console.WriteLine("Proprietes definies :");
foreach (Triple t in schema.GetTriplesWithPredicateObject(rdfType, rdfProperty))
{
    string propName = ((IUriNode)t.Subject).Uri.Fragment.TrimStart('#');
    
    // Chercher le domaine
    var domainTriples = schema.GetTriplesWithSubjectPredicate(t.Subject, rdfsDomain);
    string domain = domainTriples.Any() ? 
        ((IUriNode)domainTriples.First().Object).Uri.Fragment.TrimStart('#') : "?";
    
    // Chercher la portee
    var rangeTriples = schema.GetTriplesWithSubjectPredicate(t.Subject, rdfsRange);
    string range = rangeTriples.Any() ? 
        ((IUriNode)rangeTriples.First().Object).Uri.Fragment.TrimStart('#') : "?";
    
    Console.WriteLine($"  ex:{propName}  domain={domain}  range={range}");
}
Schema enrichi : 34 triplets

Proprietes definies :
  ex:name  domain=Animal  range=string
  ex:age  domain=Animal  range=integer
  ex:sound  domain=Animal  range=string
  ex:canFly  domain=Animal  range=boolean

Interpretation : Domaine et portee

Propriete Domaine Portee Signification
ex:name ex:Animal xsd:string Le nom est une chaîne, applicable a tout Animal
ex:age ex:Animal xsd:integer L’age est un entier
ex:sound ex:Animal xsd:string Le cri est une chaîne
ex:canFly ex:Animal xsd:boolean Capacite de vol, vrai/faux

Consequence de rdfs:domain : si on ecrit ex:rex ex:name "Rex", alors un raisonneur RDFS peut inferer que ex:rex rdf:type ex:Animal (car ex:name a pour domaine ex:Animal).

Attention : rdfs:domain et rdfs:range ne sont pas des contraintes de validation (comme en SQL). Ce sont des règles d’inference. Si le domaine est viole, RDFS ne signale pas d’erreur – il infere un type supplementaire.


3. Charger et explorer animals.ttl

Le fichier data/animals.ttl contient la hiérarchie complete avec des instances. Chargeons-le et explorons sa structure.

// Charger le fichier animals.ttl
IGraph animals = new Graph();
TurtleParser ttlParser = new TurtleParser();
ttlParser.Load(animals, "data/animals.ttl");

Console.WriteLine($"Graphe charge : {animals.Triples.Count} triplets");
Console.WriteLine($"Namespaces : {string.Join(", ", animals.NamespaceMap.Prefixes)}");
Console.WriteLine();

// Lister toutes les classes
IUriNode rdfTypeNode = animals.CreateUriNode(new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"));
IUriNode rdfsClassNode = animals.CreateUriNode(new Uri("http://www.w3.org/2000/01/rdf-schema#Class"));

Console.WriteLine("Classes definies :");
foreach (Triple t in animals.GetTriplesWithPredicateObject(rdfTypeNode, rdfsClassNode))
{
    string className = ((IUriNode)t.Subject).Uri.Fragment.TrimStart('#');
    Console.WriteLine($"  - {className}");
}
Graphe charge : 51 triplets
Namespaces : rdf, rdfs, xsd, ex

Classes definies :
  - Animal
  - Mammal
  - Bird
  - Dog
  - Cat
  - Parrot

Explorons maintenant la hiérarchie de classes et les instances.

// Explorer la hierarchie de classes
IUriNode subClassOfNode = animals.CreateUriNode(new Uri("http://www.w3.org/2000/01/rdf-schema#subClassOf"));

Console.WriteLine("Hierarchie de classes (rdfs:subClassOf) :");
foreach (Triple t in animals.GetTriplesWithPredicate(subClassOfNode))
{
    string child = ((IUriNode)t.Subject).Uri.Fragment.TrimStart('#');
    string parent = ((IUriNode)t.Object).Uri.Fragment.TrimStart('#');
    Console.WriteLine($"  {child} --> {parent}");
}

Console.WriteLine();

// Lister les instances par classe
Console.WriteLine("Instances par classe :");
var classes = new[] { "Dog", "Cat", "Parrot" };
foreach (string cls in classes)
{
    IUriNode classNode = animals.CreateUriNode(new Uri($"http://example.org/animals#{cls}"));
    var instances = animals.GetTriplesWithPredicateObject(rdfTypeNode, classNode);
    Console.WriteLine($"  {cls} :");
    foreach (Triple t in instances)
    {
        string name = ((IUriNode)t.Subject).Uri.Fragment.TrimStart('#');
        Console.WriteLine($"    - {name}");
    }
}
Hierarchie de classes (rdfs:subClassOf) :
  Mammal --> Animal
  Bird --> Animal
  Dog --> Mammal
  Cat --> Mammal
  Parrot --> Bird

Instances par classe :
  Dog :
    - rex
    - buddy
  Cat :
    - minou
  Parrot :
    - coco

Interpretation : Structure du fichier animals.ttl

Le fichier Turtle contient :

Élément Quantite Exemples
Classes 6 Animal, Mammal, Bird, Dog, Cat, Parrot
Proprietes 4 name, age, sound, canFly
Instances 4 rex (Dog), minou (Cat), coco (Parrot), buddy (Dog)
Relations subClassOf 5 Dog < Mammal < Animal, etc.

Observation importante : Les instances sont typees avec leur classe la plus spécifique (ex: ex:rex a ex:Dog), mais pas avec les classes parentes. Un raisonneur RDFS deduira que Rex est aussi un Mammal et un Animal.


4. Inference RDFS

L’inference RDFS permet de deduire automatiquement de nouveaux triplets a partir des règles RDFS. Le mécanisme principal est la transitivite de rdfs:subClassOf.

Les règles d’inference RDFS presentees ci-dessous (rdfs2, rdfs3, rdfs5, rdfs9, rdfs11) sont les règles d’entailment formellement specifiees dans RDF 1.1 Semantics (Hayes & Patel-Schneider, W3C Recommendation 2014). Elles definissent quels triplets peuvent etre deduits a partir d’un graphe RDFS.

Règles d’inference RDFS essentielles

Règle Premisses Conclusion
rdfs9 (heritage de type) ?C rdfs:subClassOf ?D et ?X rdf:type ?C ?X rdf:type ?D
rdfs11 (transitivite) ?A rdfs:subClassOf ?B et ?B rdfs:subClassOf ?C ?A rdfs:subClassOf ?C
rdfs5 (sous-propriete) ?P rdfs:subPropertyOf ?Q et ?X ?P ?Y ?X ?Q ?Y
rdfs2 (domaine) ?P rdfs:domain ?C et ?X ?P ?Y ?X rdf:type ?C
rdfs3 (portee) ?P rdfs:range ?C et ?X ?P ?Y ?Y rdf:type ?C

Exemple concret

Avec nos données :

ex:Dog rdfs:subClassOf ex:Mammal .
ex:Mammal rdfs:subClassOf ex:Animal .
ex:rex rdf:type ex:Dog .

L’inference RDFS deduit :

ex:rex rdf:type ex:Mammal .   # Par rdfs9 (Dog < Mammal)
ex:rex rdf:type ex:Animal .   # Par rdfs9 (Mammal < Animal)
ex:Dog rdfs:subClassOf ex:Animal .  # Par rdfs11 (transitivite)

Demonstration : inference manuelle

Implementons d’abord l’inference manuellement pour bien comprendre le mécanisme, puis nous utiliserons le raisonneur integre.

// Inference manuelle : heritage de type par rdfs:subClassOf
// Recree un graphe simple pour la demonstration

Graph data = new Graph();
data.NamespaceMap.AddNamespace("ex", new Uri("http://example.org/animals#"));
data.NamespaceMap.AddNamespace("rdf", new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
data.NamespaceMap.AddNamespace("rdfs", new Uri("http://www.w3.org/2000/01/rdf-schema#"));

IUriNode rdfT = data.CreateUriNode("rdf:type");
IUriNode subC = data.CreateUriNode("rdfs:subClassOf");

// Schema
IUriNode animalN = data.CreateUriNode("ex:Animal");
IUriNode mammalN = data.CreateUriNode("ex:Mammal");
IUriNode dogN = data.CreateUriNode("ex:Dog");

data.Assert(new Triple(dogN, subC, mammalN));
data.Assert(new Triple(mammalN, subC, animalN));

// Instance
IUriNode rex = data.CreateUriNode("ex:rex");
data.Assert(new Triple(rex, rdfT, dogN));

Console.WriteLine("=== AVANT inference ===");
Console.WriteLine($"Nombre de triplets : {data.Triples.Count}");
Console.WriteLine("Types de rex :");
foreach (Triple t in data.GetTriplesWithSubjectPredicate(rex, rdfT))
{
    Console.WriteLine($"  rex rdf:type {((IUriNode)t.Object).Uri.Fragment.TrimStart('#')}");
}

Console.WriteLine();

// --- Inference manuelle : regle rdfs9 ---
// Pour chaque "?X rdf:type ?C" et "?C rdfs:subClassOf ?D", ajouter "?X rdf:type ?D"
var newTriples = new List<Triple>();
bool changed = true;
int iteration = 0;

while (changed)
{
    changed = false;
    iteration++;
    var typeTriples = data.GetTriplesWithPredicate(rdfT).ToList();
    
    foreach (Triple typeTriple in typeTriples)
    {
        // typeTriple = (?X rdf:type ?C)
        var superClasses = data.GetTriplesWithSubjectPredicate(typeTriple.Object, subC);
        foreach (Triple scTriple in superClasses)
        {
            // scTriple = (?C rdfs:subClassOf ?D)
            Triple inferred = new Triple(typeTriple.Subject, rdfT, scTriple.Object);
            if (!data.ContainsTriple(inferred))
            {
                newTriples.Add(inferred);
                changed = true;
            }
        }
    }
    
    foreach (Triple t in newTriples)
    {
        data.Assert(t);
    }
    
    if (newTriples.Count > 0)
    {
        Console.WriteLine($"Iteration {iteration} : {newTriples.Count} triplet(s) infere(s)");
        foreach (Triple t in newTriples)
        {
            string s = ((IUriNode)t.Subject).Uri.Fragment.TrimStart('#');
            string o = ((IUriNode)t.Object).Uri.Fragment.TrimStart('#');
            Console.WriteLine($"  + {s} rdf:type {o}");
        }
    }
    newTriples.Clear();
}

Console.WriteLine();
Console.WriteLine("=== APRES inference ===");
Console.WriteLine($"Nombre de triplets : {data.Triples.Count}");
Console.WriteLine("Types de rex :");
foreach (Triple t in data.GetTriplesWithSubjectPredicate(rex, rdfT))
{
    Console.WriteLine($"  rex rdf:type {((IUriNode)t.Object).Uri.Fragment.TrimStart('#')}");
}
=== AVANT inference ===
Nombre de triplets : 3
Types de rex :
  rex rdf:type Dog

Iteration 1 : 1 triplet(s) infere(s)
  + rex rdf:type Mammal
Iteration 2 : 1 triplet(s) infere(s)
  + rex rdf:type Animal

=== APRES inference ===
Nombre de triplets : 5
Types de rex :
  rex rdf:type Dog
  rex rdf:type Mammal
  rex rdf:type Animal

Interpretation : Inference manuelle

Sortie obtenue : A partir de 3 triplets initiaux, l’inference a deduit 2 triplets supplementaires.

Itération Triplet infere Règle appliquee
1 rex rdf:type Mammal rdfs9 : rex est Dog, Dog < Mammal
2 rex rdf:type Animal rdfs9 : rex est Mammal, Mammal < Animal

Points cles : 1. L’algorithme est un point fixe : on itere jusqu’a ce qu’aucun nouveau triplet ne soit infere 2. Chaque itération propage les types d’un niveau dans la hiérarchie 3. Le nombre d’itérations depend de la profondeur de la hiérarchie de classes

En pratique, on utilise les raisonneurs integres plutot que cette implementation manuelle. Mais comprendre le mécanisme est essentiel.

La règle d’inférence rdfs9 (héritage de type par rdfs:subClassOf), rendue sous forme de graphe pour l’instance ex:rex. En trait plein : les 3 triplets assertés ; en tireté ambre : les 2 triplets inférés par transitivité.

graph BT
    rex["ex:rex<br/>(instance)"]
    Dog["ex:Dog"]
    Mammal["ex:Mammal"]
    Animal["ex:Animal"]
    rex -->|"rdf:type (asserté)"| Dog
    Dog -->|"rdfs:subClassOf"| Mammal
    Mammal -->|"rdfs:subClassOf"| Animal
    rex -.->|"rdf:type (inféré)"| Mammal
    rex -.->|"rdf:type (inféré)"| Animal
    classDef inst fill:#cfe2ff,stroke:#084298,color:#052c65
    classDef cls fill:#d1e7dd,stroke:#0f5132,color:#052e16
    class rex inst
    class Dog,Mammal,Animal cls
    linkStyle 3,4 stroke:#b8860b,stroke-width:2px

Lecture. Partant de 3 triplets (rex rdf:type Dog, Dog ⊑ Mammal, Mammal ⊑ Animal), la règle rdfs9 — « si ?x rdf:type ?C et ?C rdfs:subClassOf ?D, alors ?x rdf:type ?D » — déduit 2 triplets supplémentaires : rex rdf:type Mammal puis, en réappliquant la règle, rex rdf:type Animal. Le point-fixe est atteint quand plus aucun nouveau triplet n’apparaît. C’est exactement la sortie de la cellule d’inférence manuelle ci-dessus (3 initiaux → 2 inférés) et ce que le raisonneur RDFS intégré automatise dans la section suivante.

Utilisation du raisonneur RDFS de dotNetRDF

dotNetRDF fournit un StaticRdfsReasoner qui applique l’ensemble des règles RDFS.

using VDS.RDF.Query.Inference;

// Charger animals.ttl dans un nouveau graphe
Graph animalGraph = new Graph();
TurtleParser parser = new TurtleParser();
parser.Load(animalGraph, "data/animals.ttl");

int tripletsBefore = animalGraph.Triples.Count;
Console.WriteLine($"Avant inference : {tripletsBefore} triplets");

// Verifier les types de rex avant inference
IUriNode rexNode = animalGraph.CreateUriNode(new Uri("http://example.org/animals#rex"));
IUriNode typeNode = animalGraph.CreateUriNode(new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"));

Console.WriteLine("Types de rex avant inference :");
foreach (Triple t in animalGraph.GetTriplesWithSubjectPredicate(rexNode, typeNode))
{
    Console.WriteLine($"  - {t.Object}");
}

Console.WriteLine();

// Appliquer l'inference RDFS
StaticRdfsReasoner reasoner = new StaticRdfsReasoner();
reasoner.Initialise(animalGraph); // Le schema est dans le meme graphe
reasoner.Apply(animalGraph);

int tripletsAfter = animalGraph.Triples.Count;
Console.WriteLine($"Apres inference : {tripletsAfter} triplets (+{tripletsAfter - tripletsBefore} inferes)");

Console.WriteLine("Types de rex apres inference :");
foreach (Triple t in animalGraph.GetTriplesWithSubjectPredicate(rexNode, typeNode))
{
    Console.WriteLine($"  - {t.Object}");
}

Console.WriteLine();

// Verifier aussi pour coco (Parrot)
IUriNode cocoNode = animalGraph.CreateUriNode(new Uri("http://example.org/animals#coco"));
Console.WriteLine("Types de coco apres inference :");
foreach (Triple t in animalGraph.GetTriplesWithSubjectPredicate(cocoNode, typeNode))
{
    Console.WriteLine($"  - {t.Object}");
}
Avant inference : 51 triplets
Types de rex avant inference :
  - http://example.org/animals#Dog

Apres inference : 59 triplets (+8 inferes)
Types de rex apres inference :
  - http://example.org/animals#Dog
  - http://example.org/animals#Mammal
  - http://example.org/animals#Animal

Types de coco apres inference :
  - http://example.org/animals#Parrot
  - http://example.org/animals#Bird
  - http://example.org/animals#Animal

Interpretation : Raisonneur RDFS

Le StaticRdfsReasoner a enrichi le graphe automatiquement :

Instance Types explicites Types inferes
rex Dog Mammal, Animal
minou Cat Mammal, Animal
coco Parrot Bird, Animal
buddy Dog Mammal, Animal

Comparaison : - Sans inference : seul le type le plus spécifique est present - Avec inference : tous les super-types sont materialises dans le graphe

Note technique : StaticRdfsReasoner materialise les triplets inferes directement dans le graphe. C’est une approche de materialisation (par opposition au raisonnement a la requête).


5. Interroger les données enrichies par l’inference

Maintenant que le graphe a ete enrichi par le raisonneur RDFS, utilisons SPARQL pour interroger les données inferees.

// Requete SPARQL : trouver tous les animaux (grace a l'inference)
string query1 = @"
PREFIX ex: <http://example.org/animals#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT ?animal ?name ?type
WHERE {
    ?animal rdf:type ex:Animal .
    ?animal ex:name ?name .
    ?animal rdf:type ?type .
    FILTER(?type != ex:Animal)
}
ORDER BY ?name
";

var dataset = new InMemoryDataset(animalGraph);
var queryProcessor = new LeviathanQueryProcessor(dataset);
var sparqlParser = new SparqlQueryParser();

SparqlQuery q1 = sparqlParser.ParseFromString(query1);
SparqlResultSet results1 = (SparqlResultSet)queryProcessor.ProcessQuery(q1);

Console.WriteLine("Tous les animaux (grace a l'inference rdf:type Animal) :");
Console.WriteLine("---------------------------------------------------");
foreach (SparqlResult r in results1)
{
    string name = r["name"].ToString();
    string type = ((IUriNode)r["type"]).Uri.Fragment.TrimStart('#');
    Console.WriteLine($"  {name} (type: {type})");
}
Tous les animaux (grace a l'inference rdf:type Animal) :
---------------------------------------------------
  Buddy^^http://www.w3.org/2001/XMLSchema#string (type: Dog)
  Buddy^^http://www.w3.org/2001/XMLSchema#string (type: Mammal)
  Coco^^http://www.w3.org/2001/XMLSchema#string (type: Parrot)
  Coco^^http://www.w3.org/2001/XMLSchema#string (type: Bird)
  Minou^^http://www.w3.org/2001/XMLSchema#string (type: Cat)
  Minou^^http://www.w3.org/2001/XMLSchema#string (type: Mammal)
  Rex^^http://www.w3.org/2001/XMLSchema#string (type: Dog)
  Rex^^http://www.w3.org/2001/XMLSchema#string (type: Mammal)

La requête ?animal rdf:type ex:Animal retourne tous les animaux, y compris ceux qui sont seulement types comme Dog, Cat ou Parrot. C’est la puissance de l’inference RDFS.

Comparons avec une requête qui exploite la hiérarchie.

// Requete 2 : trouver tous les mammiferes
string query2 = @"
PREFIX ex: <http://example.org/animals#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT ?animal ?name ?sound
WHERE {
    ?animal rdf:type ex:Mammal .
    ?animal ex:name ?name .
    OPTIONAL { ?animal ex:sound ?sound . }
}
ORDER BY ?name
";

SparqlQuery q2 = sparqlParser.ParseFromString(query2);
SparqlResultSet results2 = (SparqlResultSet)queryProcessor.ProcessQuery(q2);

Console.WriteLine("Tous les mammiferes (Dogs + Cats, grace a l'inference) :");
Console.WriteLine("-------------------------------------------------------");
foreach (SparqlResult r in results2)
{
    string name = r["name"].ToString();
    string sound = r.HasBoundValue("sound") ? r["sound"].ToString() : "(inconnu)";
    Console.WriteLine($"  {name} : {sound}");
}

Console.WriteLine();

// Requete 3 : compter les instances par classe
string query3 = @"
PREFIX ex: <http://example.org/animals#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?class (COUNT(?instance) AS ?count)
WHERE {
    ?class rdf:type rdfs:Class .
    ?instance rdf:type ?class .
}
GROUP BY ?class
ORDER BY DESC(?count)
";

SparqlQuery q3 = sparqlParser.ParseFromString(query3);
SparqlResultSet results3 = (SparqlResultSet)queryProcessor.ProcessQuery(q3);

Console.WriteLine("Nombre d'instances par classe (avec inference) :");
Console.WriteLine("------------------------------------------------");
foreach (SparqlResult r in results3)
{
    string cls = ((IUriNode)r["class"]).Uri.Fragment.TrimStart('#');
    string count = ((ILiteralNode)r["count"]).Value;
    Console.WriteLine($"  {cls} : {count} instance(s)");
}
Tous les mammiferes (Dogs + Cats, grace a l'inference) :
-------------------------------------------------------
  Buddy^^http://www.w3.org/2001/XMLSchema#string : Woof woof^^http://www.w3.org/2001/XMLSchema#string
  Minou^^http://www.w3.org/2001/XMLSchema#string : Miaou^^http://www.w3.org/2001/XMLSchema#string
  Rex^^http://www.w3.org/2001/XMLSchema#string : Woof^^http://www.w3.org/2001/XMLSchema#string

Nombre d'instances par classe (avec inference) :
------------------------------------------------
  Animal : 4 instance(s)
  Mammal : 3 instance(s)
  Dog : 2 instance(s)
  Bird : 1 instance(s)
  Cat : 1 instance(s)
  Parrot : 1 instance(s)

Interpretation : Requêtes sur données inferees

Classe Instances directes Instances inferees Total
Animal 0 4 (rex, minou, coco, buddy) 4
Mammal 0 3 (rex, minou, buddy) 3
Bird 0 1 (coco) 1
Dog 2 (rex, buddy) 0 2
Cat 1 (minou) 0 1
Parrot 1 (coco) 0 1

Points cles : 1. La classe Animal n’a aucune instance directe mais 4 par inference 2. Les requêtes SPARQL fonctionnent sur le graphe materialise, incluant les triplets inferes 3. C’est un avantage majeur de RDFS : les données sont flexibles mais interrogeables a différents niveaux d’abstraction


Exercices

Exercice 1 : Créer un vocabulaire RDFS pour une bibliotheque

Créez un schema RDFS pour un domaine de bibliotheque avec : - Classes : Publication, Book, Article, Author, Publisher - Hiérarchie : Book et Article sont des sous-classes de Publication - Proprietes : title (domain: Publication, range: string), writtenBy (domain: Publication, range: Author), publishedBy (domain: Book, range: Publisher), year (domain: Publication, range: integer) - Instances : au moins 2 livres et 1 article avec leurs auteurs - Appliquez le raisonneur RDFS et verifiez que les types sont bien inferes

Exercice : Requête SPARQL avec inference RDFS

Les sections précédentes ont montre le raisonneur RDFS et les requêtes SPARQL. Utilisez le raisonneur pour trouver toutes les instances d’une superclasse.

Indices :

  • Chargez animals.ttl, appliquez rdfsReasoner.Initialise(g) puis executez SPARQL
  • ?animal rdf:type ex:Animal avec inference retourne aussi les chiens et chats
// Exercice : Requete SPARQL avec inference RDFS
// TODO etudiant : chargez animals.ttl et trouvez toutes les instances d'une superclasse
//
// Etape 1 : charger le fichier animals.ttl dans un graphe
// Etape 2 : initialiser le raisonneur RDFS et l'appliquer au graphe
// Etape 3 : ecrire une requete SPARQL pour trouver toutes les instances d'ex:Animal
// Etape 4 : afficher les resultats (y compris les instances inferees par rdfs:subClassOf)

Console.WriteLine("Exercice a completer");
Exercice a completer
// Exercice 1 : Vocabulaire RDFS pour une bibliotheque
// Completez le code ci-dessous

Graph library = new Graph();
library.NamespaceMap.AddNamespace("lib", new Uri("http://example.org/library#"));
library.NamespaceMap.AddNamespace("rdf", new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
library.NamespaceMap.AddNamespace("rdfs", new Uri("http://www.w3.org/2000/01/rdf-schema#"));
library.NamespaceMap.AddNamespace("xsd", new Uri("http://www.w3.org/2001/XMLSchema#"));

// TODO: Definir les classes (Publication, Book, Article, Author, Publisher)
// TODO: Definir la hierarchie (Book < Publication, Article < Publication)
// TODO: Definir les proprietes (title, writtenBy, publishedBy, year)
// TODO: Creer des instances
// TODO: Appliquer le raisonneur RDFS
// TODO: Verifier les types inferes

Console.WriteLine("Exercice 1 : a completer");
Exercice 1 : a completer

Exercice 2 : Chaîne d’inference par sous-classes

Créez une hiérarchie de classes plus profonde pour tester la transitivite : - LivingThing > Animal > Vertebrate > Mammal > Primate > Human - Créez une instance alice rdf:type Human - Appliquez l’inference et verifiez que alice est aussi de type LivingThing, Animal, Vertebrate, Mammal et Primate - Comptez le nombre de triplets avant et après inference

// Exercice 2 : Chaine d'inference profonde
// Completez le code ci-dessous

Graph taxonomyGraph = new Graph();
taxonomyGraph.NamespaceMap.AddNamespace("bio", new Uri("http://example.org/biology#"));
taxonomyGraph.NamespaceMap.AddNamespace("rdf", new Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
taxonomyGraph.NamespaceMap.AddNamespace("rdfs", new Uri("http://www.w3.org/2000/01/rdf-schema#"));

// TODO: Creer la hierarchie LivingThing > Animal > Vertebrate > Mammal > Primate > Human
// TODO: Creer une instance alice de type Human
// TODO: Afficher les types de alice AVANT inference
// TODO: Appliquer le raisonneur RDFS
// TODO: Afficher les types de alice APRES inference
// TODO: Comparer le nombre de triplets avant/apres

Console.WriteLine("Exercice 2 : a completer");
Exercice 2 : a completer

Resume

Vocabulaire RDFS essentiel

Terme Utilisation Exemple
rdfs:Class Declarer une classe ex:Dog rdf:type rdfs:Class
rdfs:subClassOf Hiérarchie de classes ex:Dog rdfs:subClassOf ex:Mammal
rdfs:subPropertyOf Hiérarchie de proprietes ex:father rdfs:subPropertyOf ex:parent
rdfs:domain Type du sujet ex:name rdfs:domain ex:Animal
rdfs:range Type de l’objet/valeur ex:name rdfs:range xsd:string
rdfs:label Etiquette lisible ex:Dog rdfs:label "Chien"@fr
rdfs:comment Description ex:Dog rdfs:comment "Un canide domestique"

Ce que nous avons appris

  1. RDFS enrichit RDF avec un vocabulaire pour decrire la structure des données
  2. Les hiérarchies de classes (rdfs:subClassOf) permettent l’heritage de type
  3. L’inference RDFS deduit automatiquement des triplets implicites
  4. Le raisonneur StaticRdfsReasoner de dotNetRDF materialise les triplets inferes
  5. SPARQL sur données inferees permet d’interroger a différents niveaux d’abstraction

Limites de RDFS

RDFS est volontairement simple. Il ne permet pas de : - Définir des classes disjointes (Dog et Cat ne peuvent pas etre la même instance) - Exprimer des cardinalites (une personne a exactement 2 parents) - Declarer des proprietes inverses (teaches / taughtBy) - Exprimer des classes par union/intersection

Pour ces fonctionnalites, il faut passer a OWL (notebook suivant).

References

  • RDF Schema 1.1 – Brickley & Guha, W3C Recommendation (2014). w3.org/TR/rdf-schema
  • RDF 1.1 Semantics – Hayes & Patel-Schneider, W3C Recommendation (2014). Specifie formellement les règles d’entailment rdfs2/3/5/9/11. w3.org/TR/rdf11-mt
  • RDF 1.1 Concepts and Abstract Syntax – Cyganiak, Wood & Lanthaler, W3C Recommendation (2014). Modèle de triplet sur lequel RDFS s’appuie.

Navigation : << 5-LinkedData | Index | 7-OWL >>

Retour au sommet