3-Star to 5-Star: Schnitzel vom jungen Rind#

Diese Seite: Demo, wie man von einer nicht normalisierten CSV-Datei (LOD 3-Star) zu einem ontologiebasierten RDF-Graphern (LOD 5-Star) und wieder zurück zu einem Tabellen-View kommt – unter Einbeziehung von OWL2-RL Inferencing, in diesem Fall einer ontologiebasierten Klassifizierung von Instanzen.

Tabelle einlesen#

In diesem Fall liegt die Tabelle als CSV-Datei vor. Aber genau genommen interessiert uns nicht, in welchem konkreten Format (CSV, Excel, JSON, etc) unsere Tabelle tatsächlich vorliegt: Solange wir unsere Daten mit Pandas einlesen und in ein Pandas DataFrame überführen können, sind wir zufrieden.

import pandas as pd
example = "Schnitzel_vom_Jungrind"
df = pd.read_csv(f"../sheets/{example}.csv", 
                            dtype=str, delimiter= ';')
df.fillna(value="", inplace=True)
df
Schnitzel hat_Herkunft Tierart hat_Alter Altersgruppe
0 Schnitzel_1 Tier_1 Rind 210_Tage acht_Monate
1 Schnitzel_2 Tier_2 Rind 400_Tage neun_bis_dreißig_Monate
2 Schnitzel_3 Tier_3 Schwein 220_Tage acht_Monate

Diskussion: Wir sehen, dass die Tabelle nicht normalisiert ist.

  • Offensichtlich bezieht sich die Spalte hat_Alter auf das Tier_1 (aus Spalte hat_Herkunft), von dem das Schnitzel_1 stammt, und nicht auf das Schnitzel_1 (Spalte Schnitzel) selbst.

  • Unklar ist, ob sich die Spalte Altersgruppe auf die Spalte hat_Herkunft oder die Spalte hat_Alter bezieht.

(ABBILDUNG möglich: Tabellen-Bezüge mit Pfeilen visualisieren)

GenDIfS Mindmap einlesen, compilieren#

import sys
sys.path.append('../py/')
from gd06 import GenDifS_Map
backup_mindmap = False
update_mindmap = False

# Mindmap und Sheet einlesen
o = GenDifS_Map(f"../mm/{example}.mm", # Mindmap
       sheet_df = df,         # das oben eingelesene DataFrame
       pattern = ['ALL', 'owl', 'a-box', 'owl-classify'],
       verbose = 1)

# Lexikalische Analyse, Parser, OWL-Code erzeugen
o.compile()
__init__: GenDifS 0.63 (2023-08-01, 2023-09-08)
lexer: Lexer: found #1 start nodes: ['TAXONOMY']
codegen: codegen: generated 4 entries in `ttl_records`
get_code: Collected 37 code lines, pattern: ['ALL', 'owl', 'a-box', 'owl-classify', 'ALL']
rdflib_parse: rdflib: 125 triples.

Zur Kontrolle: Beim Compilieren wurde die Mindmap so interpretiert:

o.display_markdown()
  • TAXONOMY

    • Schnitzel COL

      • BY hat_Herkunft SOME Tier

        • Schweineschnitzel

          • SOME Schwein

        • Kalbschnitzel

          • SOME Kalb

      • REL hat_Herkunft COL

    • Rind

      • BY hat_Alter SOME Alter

        • Kalb

          • SOME acht_Monate

        • Jungtier

          • SOME neun_bis_dreißig_Monate

    • Tier COL hat_Herkunft

      • REL a COL Tierart

      • REL hat_Alter COL

    • Alter COL hat_Alter

      • REL a COL Altersgruppe

Die Mindmap kann mit jedem Mindmap-Tool editiert werden, das das (einfache XML-) Format *.mm unterstützt. Wir verwenden freeplane. Als Feednack an Mindmap-Nutzer kann die interpretierte Mindmap wieder in zurückgeschrieben werden:

if update_mindmap:
    update_mindmap_filename = f"../mm/{example}.mm"
    o.mindmap.write(update_mindmap_filename,pretty_print=True)
    print(f"updated mindmap {update_mindmap_filename}")

ttl Code ohne Inferencing#

In Rahmen dieses Protototypen erzeugt der Compiler Turtle (ttl-) Code direkt als String (print(o.ttl_code)):

print(o.ttl_code)

# bei Bedarf im Dateisystem speichern
with open(f"../ttl/{example}_mm.ttl", "w") as file:
    file.write(o.ttl_code)
# __init__
# ALL: 
@prefix ex: <http://example.net/namespace/ex#> .
@prefix cpt: <http://example.net/namespace/cpt#> .
@prefix sheet: <http://example.net/namespace/sheet#> .
@prefix : <http://example.net/namespace/default#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix gendifs: <http://jbusse.de/gendifs#> .


# __init__
# ALL: 
[ rdf:type owl:Ontology ] .



# SUBTAXON.TAXONOMY.a
# owl: declare class *Schnitzel* being a subclass of topConcept
:Schnitzel
   a owl:Class ;
   rdfs:subClassOf :topConcept .


# A-Box.SUBTAXON.TAXONOMY.d
# a-box: 
sheet:Schnitzel_1 
  a :Schnitzel .
sheet:Schnitzel_2 
  a :Schnitzel .
sheet:Schnitzel_3 
  a :Schnitzel .


# BY.a
# owl: declare *None* as a object property.
:None
   rdf:type owl:ObjectProperty .


# BY.b
# owl: declare *Tier* as a owl:Class .
:Tier
   a owl:Class .


# SUBTAXON.BY.a
# owl: declare class *Schweineschnitzel* being a subclass of Schnitzel
:Schweineschnitzel
   a owl:Class ;
   rdfs:subClassOf :Schnitzel .


# SOME.b
# owl: declare *Schwein* as a owl:Class
:Schwein
   a owl:Class ;
   rdfs:subClassOf :Tier .


# SOME.g
# owl-classify: 
:SOME_1_533_660_747_restriction
   a owl:Class ;
   rdfs:label "BY hat_Herkunft SOME Schwein" ;
   rdfs:subClassOf :gendifs_restrictions ;
   owl:equivalentClass [ a owl:Restriction ;
      owl:onProperty :hat_Herkunft ;
      owl:someValuesFrom :Schwein ] .



# SOME.h
# owl-classify: intersection of the superiors 
:SOME_1_533_660_747_intersection
   a owl:Class ;
   rdfs:label "(BY hat_Herkunft SOME Schwein) INTERSECT Schnitzel" ;
   rdfs:subClassOf :Schweineschnitzel , :gendifs_intersections ;
   owl:equivalentClass [ a owl:Class ;
      owl:intersectionOf (
          :SOME_1_533_660_747_restriction
          :Schnitzel ) ] .



# SUBTAXON.BY.a
# owl: declare class *Kalbschnitzel* being a subclass of Schnitzel
:Kalbschnitzel
   a owl:Class ;
   rdfs:subClassOf :Schnitzel .


# SOME.b
# owl: declare *Kalb* as a owl:Class
:Kalb
   a owl:Class ;
   rdfs:subClassOf :Tier .


# SOME.g
# owl-classify: 
:SOME_816_993_019_restriction
   a owl:Class ;
   rdfs:label "BY hat_Herkunft SOME Kalb" ;
   rdfs:subClassOf :gendifs_restrictions ;
   owl:equivalentClass [ a owl:Restriction ;
      owl:onProperty :hat_Herkunft ;
      owl:someValuesFrom :Kalb ] .



# SOME.h
# owl-classify: intersection of the superiors 
:SOME_816_993_019_intersection
   a owl:Class ;
   rdfs:label "(BY hat_Herkunft SOME Kalb) INTERSECT Schnitzel" ;
   rdfs:subClassOf :Kalbschnitzel , :gendifs_intersections ;
   owl:equivalentClass [ a owl:Class ;
      owl:intersectionOf (
          :SOME_816_993_019_restriction
          :Schnitzel ) ] .



# REL.a
# owl: declare *hat_Herkunft* as a object property.
:hat_Herkunft
   rdf:type owl:ObjectProperty ;
   rdf:label "hat_Herkunft" .


# A-Box.REL.c
# a-box: 
sheet:Schnitzel_1 :hat_Herkunft sheet:Tier_1 .
sheet:Schnitzel_2 :hat_Herkunft sheet:Tier_2 .
sheet:Schnitzel_3 :hat_Herkunft sheet:Tier_3 .


# SUBTAXON.TAXONOMY.a
# owl: declare class *Rind* being a subclass of topConcept
:Rind
   a owl:Class ;
   rdfs:subClassOf :topConcept .


# BY.a
# owl: declare *None* as a object property.
:None
   rdf:type owl:ObjectProperty .


# BY.b
# owl: declare *Alter* as a owl:Class .
:Alter
   a owl:Class .


# SUBTAXON.BY.a
# owl: declare class *Kalb* being a subclass of Rind
:Kalb
   a owl:Class ;
   rdfs:subClassOf :Rind .


# SOME.b
# owl: declare *acht_Monate* as a owl:Class
:acht_Monate
   a owl:Class ;
   rdfs:subClassOf :Alter .


# SOME.g
# owl-classify: 
:SOME_1_957_776_890_restriction
   a owl:Class ;
   rdfs:label "BY hat_Alter SOME acht_Monate" ;
   rdfs:subClassOf :gendifs_restrictions ;
   owl:equivalentClass [ a owl:Restriction ;
      owl:onProperty :hat_Alter ;
      owl:someValuesFrom :acht_Monate ] .



# SOME.h
# owl-classify: intersection of the superiors 
:SOME_1_957_776_890_intersection
   a owl:Class ;
   rdfs:label "(BY hat_Alter SOME acht_Monate) INTERSECT Rind" ;
   rdfs:subClassOf :Kalb , :gendifs_intersections ;
   owl:equivalentClass [ a owl:Class ;
      owl:intersectionOf (
          :SOME_1_957_776_890_restriction
          :Rind ) ] .



# SUBTAXON.BY.a
# owl: declare class *Jungtier* being a subclass of Rind
:Jungtier
   a owl:Class ;
   rdfs:subClassOf :Rind .


# SOME.b
# owl: declare *neun_bis_dreißig_Monate* as a owl:Class
:neun_bis_dreißig_Monate
   a owl:Class ;
   rdfs:subClassOf :Alter .


# SOME.g
# owl-classify: 
:SOME_1_926_796_333_restriction
   a owl:Class ;
   rdfs:label "BY hat_Alter SOME neun_bis_dreißig_Monate" ;
   rdfs:subClassOf :gendifs_restrictions ;
   owl:equivalentClass [ a owl:Restriction ;
      owl:onProperty :hat_Alter ;
      owl:someValuesFrom :neun_bis_dreißig_Monate ] .



# SOME.h
# owl-classify: intersection of the superiors 
:SOME_1_926_796_333_intersection
   a owl:Class ;
   rdfs:label "(BY hat_Alter SOME neun_bis_dreißig_Monate) INTERSECT Rind" ;
   rdfs:subClassOf :Jungtier , :gendifs_intersections ;
   owl:equivalentClass [ a owl:Class ;
      owl:intersectionOf (
          :SOME_1_926_796_333_restriction
          :Rind ) ] .



# SUBTAXON.TAXONOMY.a
# owl: declare class *Tier* being a subclass of topConcept
:Tier
   a owl:Class ;
   rdfs:subClassOf :topConcept .


# A-Box.SUBTAXON.TAXONOMY.d
# a-box: 
sheet:Tier_1 
  a :Tier .
sheet:Tier_2 
  a :Tier .
sheet:Tier_3 
  a :Tier .


# REL.a
# owl: declare *a* as a object property.
:a
   rdf:type owl:ObjectProperty ;
   rdf:label "Tierart" .


# A-Box.REL.c
# a-box: 
sheet:Tier_1 rdf:type :Rind .
sheet:Tier_2 rdf:type :Rind .
sheet:Tier_3 rdf:type :Schwein .


# REL.a
# owl: declare *hat_Alter* as a object property.
:hat_Alter
   rdf:type owl:ObjectProperty ;
   rdf:label "hat_Alter" .


# A-Box.REL.c
# a-box: 
sheet:Tier_1 :hat_Alter sheet:210_Tage .
sheet:Tier_2 :hat_Alter sheet:400_Tage .
sheet:Tier_3 :hat_Alter sheet:220_Tage .


# SUBTAXON.TAXONOMY.a
# owl: declare class *Alter* being a subclass of topConcept
:Alter
   a owl:Class ;
   rdfs:subClassOf :topConcept .


# A-Box.SUBTAXON.TAXONOMY.d
# a-box: 
sheet:210_Tage 
  a :Alter .
sheet:400_Tage 
  a :Alter .
sheet:220_Tage 
  a :Alter .


# REL.a
# owl: declare *a* as a object property.
:a
   rdf:type owl:ObjectProperty ;
   rdf:label "Altersgruppe" .


# A-Box.REL.c
# a-box: 
sheet:210_Tage rdf:type :acht_Monate .
sheet:400_Tage rdf:type :neun_bis_dreißig_Monate .
sheet:220_Tage rdf:type :acht_Monate .

Wir lesen diesen Code mit der Standard-Bibliothek librdf ein und erhalten den RDF-Graphen o.rdflib_graph. Wenn man diesen Graphen wieder im Format ttl serislisiert sieht man, dass die Tripel anders zusammengefasst sind und alles insgesamt „aufgeräumter“ aussieht.

Die Methode o.select_molecules() dient dazu, in der ttl-Fassung eines Graphen nach Text-Absätzen zu suchen, in denen einer von mehreren Strings vorkommt. Zweck ist die gezielte Inspektion eines Graphen nach bestimmten Klassen oder Instanzen.

Schnitzel_1 ohne Inferencing#

Alles über Schnitzel_1 und/oder Tier_1 im Graphen o.rdflib_graph:

print(o.select_molecules(o.rdflib_graph,
  ["sheet:Schnitzel_1", "sheet:Tier_1", "sheet:210_Tage" ]))
sheet:Schnitzel_1 a :Schnitzel ;
    :hat_Herkunft sheet:Tier_1 .

sheet:210_Tage a :Alter,
        :acht_Monate .

sheet:Tier_1 a :Rind,
        :Tier ;
    :hat_Alter sheet:210_Tage .

Vergleicht man diesen ttl-Code mit der CVS-Tabelle oben sieht man, wie die entsprechenden Informationen zu Schnitzel_1 nach ttl übersetzt wurden: Der vollständige RDF-Graph o.rdflib_graph enthält außer der RDF-Fassung unserer Tabelle – der sog. Assertion-Box, A-Box – auch die entsprechenden teilweise axiomatisierten Klassen, die sog. Terminology-Box, T-Box. Damit liegt unsere Tabelle als vollwertiges LOD 5-Star Datenmodell vor.

Wir sehen:

  • Schnitzel_1 stammt von Tier_1

  • Tier 1

    • wurde als Rind deklariert (aber nicht als Kalb!)

    • ist 210_Tage alt

Aufgrund dieser Informationen kann ein Mensch ableiten, dass Schnitzel_1 ein Kalbsschnitzel ist – aber unmittelbar aus den Daten ablesen kann man das nicht!

Schnitzel_1 mit Inferencing#

Liegen Daten einmal als RDF-Graph vor, lassen sich bei Vorhandensein einer geeigneten Ontologie (hier in Form einer T-Box) dann auch formal-logische Schlussfolgerungen ableiten. Die Methode o.owlrl() stößt den Reasoner owlrl an, der das OWL-Profil OWL2-RL implementiert.

o.owlrl()
print(o.select_molecules(o.owlrl_graph,
            ["sheet:Schnitzel_1", "sheet:Tier_1"]))
owlrl: owlrl: 560 triples.
sheet:Schnitzel_1 a _:nfb894ff7a439482ab7aaeba0f090bc20b6,
        _:nfb894ff7a439482ab7aaeba0f090bc20b7,
        :Kalbschnitzel,
        :SOME_816_993_019_intersection,
        :SOME_816_993_019_restriction,
        :Schnitzel,
        :gendifs_intersections,
        :gendifs_restrictions,
        :topConcept,
        owl:Thing ;
    :hat_Herkunft sheet:Tier_1 ;
    owl:sameAs sheet:Schnitzel_1 .

sheet:Tier_1 a _:nfb894ff7a439482ab7aaeba0f090bc20b10,
        _:nfb894ff7a439482ab7aaeba0f090bc20b11,
        :Kalb,
        :Rind,
        :SOME_1_957_776_890_intersection,
        :SOME_1_957_776_890_restriction,
        :Tier,
        :gendifs_intersections,
        :gendifs_restrictions,
        :topConcept,
        owl:Thing ;
    :hat_Alter sheet:210_Tage ;
    owl:sameAs sheet:Tier_1 .

Wir sehen hier, dass durch das Inferencing korrekt klassifiziert wurde:

  • Tier_1 aufgrund seines Alters als Kalb

  • Schnitzel_1 aufgrund seiner Herkunft als Kalbschnitzel.

Ist das KI? JA, und zwar eindeutig gemäß EU Entwurf AI Act, Anhang I: Techniken und Konzepte der Künstlichen Intellizenz gemäß Artikel 3 Absatz 1:

b) Logik- und wissensgestützte Konzepte, einschließlich Wissensrepräsentation, induktiver (logischer) Programmierung, Wissensgrundlagen, Inferenz- und Deduktionsmaschinen, (symbolischer) Schlussfolgerungs- und Expertensysteme;

SPARQL#

Der RDF-Graph lässt sich mit SPARQ abfragen.

Q = """
PREFIX ex: <http://example.net/namespace/ex#> 
PREFIX sheet: <http://example.net/namespace/sheet#> 
PREFIX : <http://example.net/namespace/default#> 
PREFIX owl: <http://www.w3.org/2002/07/owl#> 
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> 
PREFIX xml: <http://www.w3.org/XML/1998/namespace> 
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> 


SELECT ?a?b ?c
WHERE
  { ?a 
      a :Kalbschnitzel .}
"""

qres = o.owlrl_graph.query(Q)
for p,q,r in qres:
    print(p,q,r)
http://example.net/namespace/sheet#Schnitzel_1 None None