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 Spaltehat_Herkunft
), von dem das Schnitzel_1 stammt, und nicht auf das Schnitzel_1 (SpalteSchnitzel
) selbst.Unklar ist, ob sich die Spalte
Altersgruppe
auf die Spaltehat_Herkunft
oder die Spaltehat_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 _:nec116390e49643ff9143a6b996dd8925b6,
_:nec116390e49643ff9143a6b996dd8925b7,
: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 _:nec116390e49643ff9143a6b996dd8925b10,
_:nec116390e49643ff9143a6b996dd8925b11,
: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