Lektion 7.1

Deklarative Programmierung und wissensbasierte Systeme

17 min Lesezeit

Du fragst ein System: „Wer sind die Großeltern von Clara?“ Du schreibst dafür keine Schleife über alle Personen. Du sagst, was du wissen willst, und das System findet die Antwort. Bisher hast du Programme geschrieben, die Schritt für Schritt vorgeben, wie etwas berechnet wird. Es geht auch anders: Man beschreibt, was gilt oder was gesucht ist, und überlässt den Lösungsweg dem System.

Zwei Wege führen dorthin. Bei der funktionalen Modellierung ist ein Programm eine Kombination von Funktionen. Bei der logischen Modellierung ist es eine Sammlung von Fakten und Regeln, aus denen das System Antworten herleitet. So entsteht ein wissensbasiertes System, zum Beispiel ein Stammbaum, der Verwandtschaftsfragen beantwortet.

Definition

Funktionale Modellierung

Ein funktionales Programm besteht aus Funktionen im mathematischen Sinn: Das Ergebnis hängt nur von den Argumenten ab, und keine Variable ändert im Lauf des Programms ihren Wert. Solche Funktionen heißen rein. Schleifen werden durch Rekursion ersetzt. Zwei Mittel prägen den Stil:

  • Kombination: Eine Funktion ruft andere Funktionen auf oder bekommt deren Ergebnis als Argument. So entstehen aus kleinen Funktionen größere.
  • Abstraktion: Ein Muster, das mehrfach vorkommt, wird zu einer Funktion mit Parameter. Auch eine Funktion kann Parameter sein (Funktion höherer Ordnung).

Wir schreiben die Funktionen in Python-Schreibweise, aber ohne Schleifen und ohne veränderte Variablen.

Beispiel

Funktionen kombinieren und abstrahieren

ZeileEinrückungAnweisung
10def quadrat(x):\texttt{def quadrat(x):}
21return x * x\texttt{return x * x}
30def summe_quadrate(a, b):\texttt{def summe\_quadrate(a, b):}
41return quadrat(a) + quadrat(b)\texttt{return quadrat(a) + quadrat(b)}
50def abbilden(f, liste):\texttt{def abbilden(f, liste):}
61return [] if liste == [] else [f(liste[0])] + abbilden(f, liste[1:])\texttt{return [] if liste == [] else [f(liste[0])] + abbilden(f, liste[1:])}

Was liefern summe_quadrate(3, 4)\texttt{summe\_quadrate(3, 4)} und abbilden(quadrat, [1, 2, 3])\texttt{abbilden(quadrat, [1, 2, 3])}?

Zeile 3 und 4 kombinieren: summe_quadrate(3, 4)\texttt{summe\_quadrate(3, 4)} ist quadrat(3) + quadrat(4)\texttt{quadrat(3) + quadrat(4)}, also 9+16=259 + 16 = 25.

Zeile 5 und 6 abstrahieren: abbilden\texttt{abbilden} wendet eine beliebige Funktion f\texttt{f} auf jedes Element an. Die Abbruchbedingung ist die leere Liste. Für [1, 2, 3]\texttt{[1, 2, 3]} entsteht [quadrat(1)]\texttt{[quadrat(1)]} gefolgt vom Ergebnis für [2, 3]\texttt{[2, 3]}, am Ende [1, 4, 9]\texttt{[1, 4, 9]}.

Definition

Regelbasierte und logische Modellierung

Hier beschreibst du ein Gebiet durch Wissen. Wir nutzen die Schreibweise der Sprache Prolog. Grundlage ist die Prädikatenlogik: Ein Prädikat ist eine Aussage über Dinge, etwa eltern(anna, ben)\texttt{eltern(anna, ben)} für „Anna ist Elternteil von Ben“. Die Zahl der Argumente heißt Stelligkeit, eltern\texttt{eltern} ist zweistellig. Namen mit kleinem Anfangsbuchstaben sind feste Dinge, Namen mit großem Anfangsbuchstaben sind Variablen. Aussagen mit Variablen gelten für alle Belegungen (Allquantor ∀\forall). Eine Anfrage mit Variablen fragt, ob es mindestens eine Belegung gibt (Existenzquantor ∃\exists).

Das Wissen steht in Klauseln, hier in zwei Arten:

  • Ein Fakt ist eine Klausel ohne Bedingung, etwa eltern(anna, ben).\texttt{eltern(anna, ben).}
  • Eine Regel kopf :- rumpf.\texttt{kopf :- rumpf.} liest man „kopf gilt, wenn rumpf gilt“. Im Rumpf bedeutet das Komma „und“. „Oder“ schreibst du als mehrere Regeln mit demselben Kopf.

Eine Anfrage beginnt mit ?-\texttt{?-} und fragt, ob eine Aussage aus dem Wissen folgt und für welche Werte der Variablen. Fakten und Regeln zusammen heißen Wissensbasis. Eine Regel entspricht einer Formel der Prädikatenlogik:

∀X,Y,Z:  eltern(X,Y)∧eltern(Y,Z)⇒grosseltern(X,Z)\forall X, Y, Z:\; \text{eltern}(X, Y) \land \text{eltern}(Y, Z) \Rightarrow \text{grosseltern}(X, Z)
Beispiel

Wissensbasis zum Stammbaum

ZeileEinrückungAnweisung
10eltern(anna, ben).\texttt{eltern(anna, ben).}
20eltern(anna, dora).\texttt{eltern(anna, dora).}
30eltern(ben, clara).\texttt{eltern(ben, clara).}
40eltern(dora, emil).\texttt{eltern(dora, emil).}
50eltern(dora, finn).\texttt{eltern(dora, finn).}
60grosseltern(X, Z) :- eltern(X, Y), eltern(Y, Z).\texttt{grosseltern(X, Z) :- eltern(X, Y), eltern(Y, Z).}
70vorfahre(X, Y) :- eltern(X, Y).\texttt{vorfahre(X, Y) :- eltern(X, Y).}
80vorfahre(X, Z) :- eltern(X, Y), vorfahre(Y, Z).\texttt{vorfahre(X, Z) :- eltern(X, Y), vorfahre(Y, Z).}

Welche Antworten liefert die Anfrage ?- grosseltern(anna, Z).\texttt{?- grosseltern(anna, Z).}?

Regel 6 verlangt ein Y\texttt{Y} mit eltern(anna, Y)\texttt{eltern(anna, Y)} und dann eltern(Y, Z)\texttt{eltern(Y, Z)}. Für Y = ben\texttt{Y = ben} gibt Zeile 3 die Antwort Z = clara\texttt{Z = clara}. Für Y = dora\texttt{Y = dora} geben Zeile 4 und 5 die Antworten Z = emil\texttt{Z = emil} und Z = finn\texttt{Z = finn}. Es gibt drei Antworten, in dieser Reihenfolge.

Die Regeln 7 und 8 definieren vorfahre\texttt{vorfahre} rekursiv: Regel 7 ist der Abbruch, Regel 8 der rekursive Schritt.

Definition

Inferenzmaschine, Tiefensuche und Backtracking

Die Inferenzmaschine leitet aus der Wissensbasis Antworten her. Sie arbeitet rückwärts vom Ziel:

  • Sie sucht von oben nach unten die erste Klausel, deren Kopf zum Ziel passt, und bindet dabei Variablen an Werte.
  • Bei einem Fakt ist das Ziel erfüllt. Bei einer Regel werden die Teilziele des Rumpfs von links nach rechts bearbeitet.
  • Findet sie für ein Teilziel keine Klausel, schlägt es fehl. Dann kehrt sie zum letzten Punkt zurück, an dem noch eine andere Klausel zur Wahl stand, löst die Bindungen wieder und probiert die nächste. Das ist Backtracking.

Alle Möglichkeiten bilden einen Baum, in dem jedes Ziel zu Teilzielen verzweigt. Die Inferenzmaschine durchläuft ihn per Tiefensuche: Sie folgt einem Pfad bis zum Erfolg oder Misserfolg und geht erst dann zurück. Das ist dasselbe Prinzip wie bei der Tiefensuche in Graphen und beim Backtracking in der Rekursion.

Beispiel

Eine Anfrage Schritt für Schritt beantworten

Die Anfrage ?- vorfahre(anna, emil).\texttt{?- vorfahre(anna, emil).} an die Wissensbasis oben. Wie sucht die Inferenzmaschine?

SchrittZielWas passiert
1vorfahre(anna, emil)\texttt{vorfahre(anna, emil)} mit Regel 7neues Ziel eltern(anna, emil)\texttt{eltern(anna, emil)}: kein Fakt passt, Misserfolg
2dasselbe Ziel mit Regel 8neue Ziele eltern(anna, Y)\texttt{eltern(anna, Y)} und vorfahre(Y, emil)\texttt{vorfahre(Y, emil)}
3eltern(anna, Y)\texttt{eltern(anna, Y)}erster Fakt: Y = ben\texttt{Y = ben}
4vorfahre(ben, emil)\texttt{vorfahre(ben, emil)}Regel 7 misslingt. Regel 8 gibt eltern(ben, clara)\texttt{eltern(ben, clara)}, dann vorfahre(clara, emil)\texttt{vorfahre(clara, emil)}
5vorfahre(clara, emil)\texttt{vorfahre(clara, emil)}Regel 7 misslingt, für Regel 8 gibt es keinen Fakt eltern(clara, _)\texttt{eltern(clara, \_)}: Misserfolg
6BacktrackingFür eltern(ben, _)\texttt{eltern(ben, \_)} gibt es keinen weiteren Fakt, also misslingt auch Schritt 4. Zurück zu Schritt 3: nächster Fakt, Y = dora\texttt{Y = dora}
7vorfahre(dora, emil)\texttt{vorfahre(dora, emil)}Regel 7: eltern(dora, emil)\texttt{eltern(dora, emil)} ist ein Fakt. Erfolg

Antwort: true\texttt{true}. Der Weg über Ben war eine Sackgasse, die Tiefensuche hat sie erst vollständig durchlaufen und dann die Alternative gewählt.

Definition

Wissensbasis modellieren und implementieren

Vorgehen: Du klärst, welche Dinge und Beziehungen vorkommen, und legst die Prädikate mit Stelligkeit fest. Gesicherte Tatsachen werden Fakten, allgemeine Zusammenhänge Regeln. Zuletzt testest du mit Anfragen, die gelingen, und mit solchen, die misslingen sollen.

Beispiel Schulbibliothek: Mitglieder dürfen verfügbare Bücher ausleihen. Fakten: mitglied(lea).\texttt{mitglied(lea).} mitglied(tom).\texttt{mitglied(tom).} verfuegbar(faust).\texttt{verfuegbar(faust).} verfuegbar(momo).\texttt{verfuegbar(momo).} Regel: darf_ausleihen(P, B) :- mitglied(P), verfuegbar(B).\texttt{darf\_ausleihen(P, B) :- mitglied(P), verfuegbar(B).} Die Anfrage ?- darf_ausleihen(lea, B).\texttt{?- darf\_ausleihen(lea, B).} liefert B = faust\texttt{B = faust} und B = momo\texttt{B = momo}. Die Anfrage ?- darf_ausleihen(max, B).\texttt{?- darf\_ausleihen(max, B).} liefert false\texttt{false}, weil mitglied(max)\texttt{mitglied(max)} nicht in der Wissensbasis steht. Was nicht herleitbar ist, gilt dem System als falsch (Annahme der geschlossenen Welt).

Definition

Imperative und deklarative Programmierung im Vergleich

Merkmalimperativdeklarativ
ProgrammBefehlsfolge, die den Zustand ändertFunktionen oder Fakten und Regeln
Ablauflegt die Programmiererin festbestimmt das System (Auswertung, Inferenzmaschine)
Stärkegenaue Kontrolle über Ablauf und Speicherkurz, nah an der Problembeschreibung, gut prüfbar
Schwächelänger, mehr Fehlerquellen durch ZuständeLaufzeit schwer vorhersagbar

SQL ist ebenfalls deklarativ: SELECT\texttt{SELECT} beschreibt das Ergebnis, nicht den Weg. Die Regel grosseltern\texttt{grosseltern} ist eine Zeile, imperativ bräuchtest du zwei verschachtelte Schleifen über alle Elternpaare.

Häufige Fehler

Die Reihenfolge der Klauseln ist egal

„Logisch ist es gleich, wo eine Regel steht. Also darf ich sie beliebig anordnen.“

Richtig ist

Logisch stimmt das, für die Inferenzmaschine nicht. Sie probiert Klauseln von oben nach unten und Teilziele von links nach rechts. Das ändert die Reihenfolge der Antworten und kann eine Endlosschleife auslösen: Die Regel vorfahre(X, Z) :- vorfahre(X, Y), eltern(Y, Z).\texttt{vorfahre(X, Z) :- vorfahre(X, Y), eltern(Y, Z).} ruft sich als erstes Teilziel selbst auf und kommt nie zu einem Fakt.

Häufige Fehler

false heißt: in der Wirklichkeit falsch

„Das System antwortet false, also stimmt die Aussage nicht.“

Richtig ist

false\texttt{false} heißt nur: Die Aussage ist aus der Wissensbasis nicht herleitbar. Fehlt ein Fakt, antwortet das System trotz wahrer Aussage mit false\texttt{false}.

Zusammenfassung

  • Deklarative Programme beschreiben, was gilt oder gesucht ist. Der Lösungsweg liegt beim System.

  • Funktionale Programme bestehen aus reinen Funktionen. Kombination baut größere aus kleineren, Abstraktion fasst Muster zu Funktionen zusammen.

  • Eine Wissensbasis besteht aus Klauseln: Fakten und Regeln der Form kopf :- rumpf. Prädikate mit Variablen stammen aus der Prädikatenlogik.

  • Die Inferenzmaschine arbeitet rückwärts vom Ziel per Tiefensuche. Schlägt ein Teilziel fehl, probiert sie per Backtracking die nächste Klausel.

  • Die Reihenfolge der Klauseln kann Antworten und Abbruch der Suche beeinflussen. false heißt nur: nicht herleitbar.

  • Wissensbasen entstehen in Schritten: Beziehungen finden, Prädikate festlegen, Fakten und Regeln schreiben, mit Anfragen testen.

Übungen

Aufgabe 1Numerisch

Mit den Funktionen aus dem Beispiel: Welchen Wert liefert summe_quadrate(5, 12)?

Aufgabe 2Multiple Choice

Welche Funktion ist rein im Sinne der funktionalen Modellierung?

Aufgabe 3Multiple Choice

Welches Ergebnis liefert die Anfrage ?- vorfahre(ben, finn). an die Wissensbasis des Stammbaums?

Aufgabe 4Freitext

Formuliere eine Regel geschwister(X, Y) für den Stammbaum. Erkläre, welche zusätzliche Bedingung nötig ist und warum.