477 lines
18 KiB
Plaintext
477 lines
18 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {
|
||
"collapsed": true
|
||
},
|
||
"source": [
|
||
"# Implementierung eines Expertensystems für Tiere\n",
|
||
"\n",
|
||
"Ein Beispiel aus dem [AI for Beginners Curriculum](http://github.com/microsoft/ai-for-beginners).\n",
|
||
"\n",
|
||
"In diesem Beispiel werden wir ein einfaches wissensbasiertes System implementieren, um ein Tier anhand einiger körperlicher Merkmale zu bestimmen. Das System kann durch den folgenden AND-OR-Baum dargestellt werden (dies ist ein Teil des gesamten Baums, wir können problemlos weitere Regeln hinzufügen):\n",
|
||
"\n",
|
||
"\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Unser eigenes Expertensystem-Shell mit Rückwärtsverkettung\n",
|
||
"\n",
|
||
"Lassen Sie uns versuchen, eine einfache Sprache für die Wissensrepräsentation basierend auf Produktionsregeln zu definieren. Wir verwenden Python-Klassen als Schlüsselwörter, um Regeln zu definieren. Es gibt im Wesentlichen 3 Arten von Klassen:\n",
|
||
"* `Ask` repräsentiert eine Frage, die dem Benutzer gestellt werden muss. Es enthält die Menge möglicher Antworten.\n",
|
||
"* `If` repräsentiert eine Regel und ist nur ein syntaktischer Zucker, um den Inhalt der Regel zu speichern.\n",
|
||
"* `AND`/`OR` sind Klassen, um AND/OR-Verzweigungen des Baums darzustellen. Sie speichern nur die Liste der Argumente darin. Zur Vereinfachung des Codes ist die gesamte Funktionalität in der Oberklasse `Content` definiert.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"class Ask():\n",
|
||
" def __init__(self,choices=['y','n']):\n",
|
||
" self.choices = choices\n",
|
||
" def ask(self):\n",
|
||
" if max([len(x) for x in self.choices])>1:\n",
|
||
" for i,x in enumerate(self.choices):\n",
|
||
" print(\"{0}. {1}\".format(i,x),flush=True)\n",
|
||
" x = int(input())\n",
|
||
" return self.choices[x]\n",
|
||
" else:\n",
|
||
" print(\"/\".join(self.choices),flush=True)\n",
|
||
" return input()\n",
|
||
"\n",
|
||
"class Content():\n",
|
||
" def __init__(self,x):\n",
|
||
" self.x=x\n",
|
||
" \n",
|
||
"class If(Content):\n",
|
||
" pass\n",
|
||
"\n",
|
||
"class AND(Content):\n",
|
||
" pass\n",
|
||
"\n",
|
||
"class OR(Content):\n",
|
||
" pass"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"In unserem System würde das Arbeitsgedächtnis die Liste der **Fakten** als **Attribut-Wert-Paare** enthalten. Die Wissensbasis kann als ein großes Wörterbuch definiert werden, das Aktionen (neue Fakten, die in das Arbeitsgedächtnis eingefügt werden sollen) auf Bedingungen abbildet, die als UND-ODER-Ausdrücke dargestellt sind. Außerdem können einige Fakten `Ask`-ed werden.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"rules = {\n",
|
||
" 'default': Ask(['y','n']),\n",
|
||
" 'color' : Ask(['red-brown','black and white','other']),\n",
|
||
" 'pattern' : Ask(['dark stripes','dark spots']),\n",
|
||
" 'mammal': If(OR(['hair','gives milk'])),\n",
|
||
" 'carnivor': If(OR([AND(['sharp teeth','claws','forward-looking eyes']),'eats meat'])),\n",
|
||
" 'ungulate': If(['mammal',OR(['has hooves','chews cud'])]),\n",
|
||
" 'bird': If(OR(['feathers',AND(['flies','lies eggs'])])),\n",
|
||
" 'animal:monkey' : If(['mammal','carnivor','color:red-brown','pattern:dark spots']),\n",
|
||
" 'animal:tiger' : If(['mammal','carnivor','color:red-brown','pattern:dark stripes']),\n",
|
||
" 'animal:giraffe' : If(['ungulate','long neck','long legs','pattern:dark spots']),\n",
|
||
" 'animal:zebra' : If(['ungulate','pattern:dark stripes']),\n",
|
||
" 'animal:ostrich' : If(['bird','long nech','color:black and white','cannot fly']),\n",
|
||
" 'animal:pinguin' : If(['bird','swims','color:black and white','cannot fly']),\n",
|
||
" 'animal:albatross' : If(['bird','flies well'])\n",
|
||
"}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Um die Rückwärtsinferenz durchzuführen, definieren wir die Klasse `Knowledgebase`. Sie wird enthalten:\n",
|
||
"* Arbeitsspeicher `memory` – ein Wörterbuch, das Attribute auf Werte abbildet\n",
|
||
"* Knowledgebase-`rules` im oben definierten Format\n",
|
||
"\n",
|
||
"Zwei Hauptmethoden sind:\n",
|
||
"* `get`, um den Wert eines Attributs zu erhalten, wobei bei Bedarf eine Inferenz durchgeführt wird. Zum Beispiel würde `get('color')` den Wert eines Farb-Slots abfragen (es wird bei Bedarf gefragt und der Wert für die spätere Verwendung im Arbeitsspeicher gespeichert). Wenn wir `get('color:blue')` abfragen, wird nach einer Farbe gefragt und dann abhängig von der Farbe ein `y`/`n`-Wert zurückgegeben.\n",
|
||
"* `eval` führt die eigentliche Inferenz durch, d.h. durchläuft den AND/OR-Baum, bewertet Teilziele usw.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"class KnowledgeBase():\n",
|
||
" def __init__(self,rules):\n",
|
||
" self.rules = rules\n",
|
||
" self.memory = {}\n",
|
||
" \n",
|
||
" def get(self,name):\n",
|
||
" if ':' in name:\n",
|
||
" k,v = name.split(':')\n",
|
||
" vv = self.get(k)\n",
|
||
" return 'y' if v==vv else 'n'\n",
|
||
" if name in self.memory.keys():\n",
|
||
" return self.memory[name]\n",
|
||
" for fld in self.rules.keys():\n",
|
||
" if fld==name or fld.startswith(name+\":\"):\n",
|
||
" # print(\" + proving {}\".format(fld))\n",
|
||
" value = 'y' if fld==name else fld.split(':')[1]\n",
|
||
" res = self.eval(self.rules[fld],field=name)\n",
|
||
" if res!='y' and res!='n' and value=='y':\n",
|
||
" self.memory[name] = res\n",
|
||
" return res\n",
|
||
" if res=='y':\n",
|
||
" self.memory[name] = value\n",
|
||
" return value\n",
|
||
" # field is not found, using default\n",
|
||
" res = self.eval(self.rules['default'],field=name)\n",
|
||
" self.memory[name]=res\n",
|
||
" return res\n",
|
||
" \n",
|
||
" def eval(self,expr,field=None):\n",
|
||
" # print(\" + eval {}\".format(expr))\n",
|
||
" if isinstance(expr,Ask):\n",
|
||
" print(field)\n",
|
||
" return expr.ask()\n",
|
||
" elif isinstance(expr,If):\n",
|
||
" return self.eval(expr.x)\n",
|
||
" elif isinstance(expr,AND) or isinstance(expr,list):\n",
|
||
" expr = expr.x if isinstance(expr,AND) else expr\n",
|
||
" for x in expr:\n",
|
||
" if self.eval(x)=='n':\n",
|
||
" return 'n'\n",
|
||
" return 'y'\n",
|
||
" elif isinstance(expr,OR):\n",
|
||
" for x in expr.x:\n",
|
||
" if self.eval(x)=='y':\n",
|
||
" return 'y'\n",
|
||
" return 'n'\n",
|
||
" elif isinstance(expr,str):\n",
|
||
" return self.get(expr)\n",
|
||
" else:\n",
|
||
" print(\"Unknown expr: {}\".format(expr))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Definieren wir nun unsere Tierwissensdatenbank und führen die Beratung durch. Beachten Sie, dass Ihnen bei diesem Aufruf Fragen gestellt werden. Sie können mit `y`/`n` für Ja-Nein-Fragen antworten oder eine Zahl (0..N) für Fragen mit längeren Mehrfachauswahlantworten angeben.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"hair\n",
|
||
"y/n\n",
|
||
"sharp teeth\n",
|
||
"y/n\n",
|
||
"claws\n",
|
||
"y/n\n",
|
||
"forward-looking eyes\n",
|
||
"y/n\n",
|
||
"color\n",
|
||
"0. red-brown\n",
|
||
"1. black and white\n",
|
||
"2. other\n",
|
||
"has hooves\n",
|
||
"y/n\n",
|
||
"long neck\n",
|
||
"y/n\n",
|
||
"long legs\n",
|
||
"y/n\n",
|
||
"pattern\n",
|
||
"0. dark stripes\n",
|
||
"1. dark spots\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"'giraffe'"
|
||
]
|
||
},
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"kb = KnowledgeBase(rules)\n",
|
||
"kb.get('animal')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Verwendung von Experta für Vorwärts-Schlussfolgerungen\n",
|
||
"\n",
|
||
"Im nächsten Beispiel werden wir versuchen, Vorwärts-Schlussfolgerungen mit einer der Bibliotheken für Wissensrepräsentation, [Experta](https://github.com/nilp0inter/experta), zu implementieren. **Experta** ist eine Bibliothek zur Erstellung von Vorwärts-Schlusssystemen in Python, die so gestaltet ist, dass sie dem klassischen alten System [CLIPS](http://www.clipsrules.net/index.html) ähnelt.\n",
|
||
"\n",
|
||
"Wir hätten die Vorwärtsverkettung auch selbst ohne große Probleme implementieren können, aber naive Implementierungen sind normalerweise nicht sehr effizient. Für ein effektiveres Regelmatching wird ein spezieller Algorithmus namens [Rete](https://en.wikipedia.org/wiki/Rete_algorithm) verwendet.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Collecting git+https://github.com/nilp0inter/experta\n",
|
||
" Cloning https://github.com/nilp0inter/experta to /tmp/pip-req-build-7qurtwk3\n",
|
||
" Running command git clone --filter=blob:none --quiet https://github.com/nilp0inter/experta /tmp/pip-req-build-7qurtwk3\n",
|
||
" Resolved https://github.com/nilp0inter/experta to commit c6d5834b123861f5ae09e7d07027dc98bec58741\n",
|
||
" Installing build dependencies ... \u001b[?25ldone\n",
|
||
"\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n",
|
||
"\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n",
|
||
"\u001b[?25hRequirement already satisfied: frozendict~=2.4.6 in /opt/conda/envs/ai4beg/lib/python3.12/site-packages (from experta==1.9.5.dev1) (2.4.7)\n",
|
||
"Collecting schema~=0.6.7 (from experta==1.9.5.dev1)\n",
|
||
" Downloading schema-0.6.8-py2.py3-none-any.whl.metadata (14 kB)\n",
|
||
"Downloading schema-0.6.8-py2.py3-none-any.whl (14 kB)\n",
|
||
"Building wheels for collected packages: experta\n",
|
||
" Building wheel for experta (pyproject.toml) ... \u001b[?25ldone\n",
|
||
"\u001b[?25h Created wheel for experta: filename=experta-1.9.5.dev1-py3-none-any.whl size=34804 sha256=888c459512a5e713f4b674caa9a0f96cfdf07ec0d6eb56cc318ce0653d218014\n",
|
||
" Stored in directory: /tmp/pip-ephem-wheel-cache-1eeii9zy/wheels/3d/e8/bb/22d7956359603fa8dd679aa09f5b8efb3f29991c3986fdc787\n",
|
||
"Successfully built experta\n",
|
||
"Installing collected packages: schema, experta\n",
|
||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2/2\u001b[0m [experta]\n",
|
||
"\u001b[1A\u001b[2KSuccessfully installed experta-1.9.5.dev1 schema-0.6.8\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import sys\n",
|
||
"!{sys.executable} -m pip install git+https://github.com/nilp0inter/experta"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 13,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"from experta import *\n",
|
||
"#import experta"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Wir definieren unser System als eine Klasse, die von `KnowledgeEngine` erbt. Jede Regel wird durch eine separate Funktion mit der `@Rule`-Annotation definiert, die angibt, wann die Regel ausgelöst werden soll. Innerhalb der Regel können wir mit der Funktion `declare` neue Fakten hinzufügen, und das Hinzufügen dieser Fakten führt dazu, dass weitere Regeln durch die vorwärtsgerichtete Inferenzmaschine aufgerufen werden.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [],
|
||
"source": [
|
||
"class Animals(KnowledgeEngine):\n",
|
||
" @Rule(OR(\n",
|
||
" AND(Fact('sharp teeth'),Fact('claws'),Fact('forward looking eyes')),\n",
|
||
" Fact('eats meat')))\n",
|
||
" def cornivor(self):\n",
|
||
" self.declare(Fact('carnivor'))\n",
|
||
" \n",
|
||
" @Rule(OR(Fact('hair'),Fact('gives milk')))\n",
|
||
" def mammal(self):\n",
|
||
" self.declare(Fact('mammal'))\n",
|
||
"\n",
|
||
" @Rule(Fact('mammal'),\n",
|
||
" OR(Fact('has hooves'),Fact('chews cud')))\n",
|
||
" def hooves(self):\n",
|
||
" self.declare('ungulate')\n",
|
||
" \n",
|
||
" @Rule(OR(Fact('feathers'),AND(Fact('flies'),Fact('lays eggs'))))\n",
|
||
" def bird(self):\n",
|
||
" self.declare('bird')\n",
|
||
" \n",
|
||
" @Rule(Fact('mammal'),Fact('carnivor'),\n",
|
||
" Fact(color='red-brown'),\n",
|
||
" Fact(pattern='dark spots'))\n",
|
||
" def monkey(self):\n",
|
||
" self.declare(Fact(animal='monkey'))\n",
|
||
"\n",
|
||
" @Rule(Fact('mammal'),Fact('carnivor'),\n",
|
||
" Fact(color='red-brown'),\n",
|
||
" Fact(pattern='dark stripes'))\n",
|
||
" def tiger(self):\n",
|
||
" self.declare(Fact(animal='tiger'))\n",
|
||
"\n",
|
||
" @Rule(Fact('ungulate'),\n",
|
||
" Fact('long neck'),\n",
|
||
" Fact('long legs'),\n",
|
||
" Fact(pattern='dark spots'))\n",
|
||
" def giraffe(self):\n",
|
||
" self.declare(Fact(animal='giraffe'))\n",
|
||
"\n",
|
||
" @Rule(Fact('ungulate'),\n",
|
||
" Fact(pattern='dark stripes'))\n",
|
||
" def zebra(self):\n",
|
||
" self.declare(Fact(animal='zebra'))\n",
|
||
"\n",
|
||
" @Rule(Fact('bird'),\n",
|
||
" Fact('long neck'),\n",
|
||
" Fact('cannot fly'),\n",
|
||
" Fact(color='black and white'))\n",
|
||
" def straus(self):\n",
|
||
" self.declare(Fact(animal='ostrich'))\n",
|
||
"\n",
|
||
" @Rule(Fact('bird'),\n",
|
||
" Fact('swims'),\n",
|
||
" Fact('cannot fly'),\n",
|
||
" Fact(color='black and white'))\n",
|
||
" def pinguin(self):\n",
|
||
" self.declare(Fact(animal='pinguin'))\n",
|
||
"\n",
|
||
" @Rule(Fact('bird'),\n",
|
||
" Fact('flies well'))\n",
|
||
" def albatros(self):\n",
|
||
" self.declare(Fact(animal='albatross'))\n",
|
||
" \n",
|
||
" @Rule(Fact(animal=MATCH.a))\n",
|
||
" def print_result(self,a):\n",
|
||
" print('Animal is {}'.format(a))\n",
|
||
" \n",
|
||
" def factz(self,l):\n",
|
||
" for x in l:\n",
|
||
" self.declare(x)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Sobald wir eine Wissensbasis definiert haben, füllen wir unser Arbeitsgedächtnis mit einigen Anfangsfakten und rufen dann die `run()`-Methode auf, um die Inferenz durchzuführen. Sie können als Ergebnis sehen, dass neue abgeleitete Fakten dem Arbeitsgedächtnis hinzugefügt werden, einschließlich der endgültigen Tatsache über das Tier (wenn wir alle anfänglichen Fakten korrekt eingerichtet haben).\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"metadata": {
|
||
"trusted": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Animal is tiger\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"FactList([(0, InitialFact()),\n",
|
||
" (1, Fact(color='red-brown')),\n",
|
||
" (2, Fact(pattern='dark stripes')),\n",
|
||
" (3, Fact('sharp teeth')),\n",
|
||
" (4, Fact('claws')),\n",
|
||
" (5, Fact('forward looking eyes')),\n",
|
||
" (6, Fact('gives milk')),\n",
|
||
" (7, Fact('mammal')),\n",
|
||
" (8, Fact('carnivor')),\n",
|
||
" (9, Fact(animal='tiger'))])"
|
||
]
|
||
},
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"ex1 = Animals()\n",
|
||
"ex1.reset()\n",
|
||
"ex1.factz([\n",
|
||
" Fact(color='red-brown'),\n",
|
||
" Fact(pattern='dark stripes'),\n",
|
||
" Fact('sharp teeth'),\n",
|
||
" Fact('claws'),\n",
|
||
" Fact('forward looking eyes'),\n",
|
||
" Fact('gives milk')])\n",
|
||
"ex1.run()\n",
|
||
"ex1.facts"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": []
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**Haftungsausschluss**: \nDieses Dokument wurde mit dem KI-Übersetzungsdienst [Co-op Translator](https://github.com/Azure/co-op-translator) übersetzt. Obwohl wir uns um Genauigkeit bemühen, beachten Sie bitte, dass automatisierte Übersetzungen Fehler oder Ungenauigkeiten enthalten können. Das Originaldokument in seiner Ursprungssprache gilt als maßgebliche Quelle. Für wichtige Informationen wird eine professionelle menschliche Übersetzung empfohlen. Wir übernehmen keine Haftung für Missverständnisse oder Fehlinterpretationen, die aus der Nutzung dieser Übersetzung entstehen.\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3.7.4 64-bit (conda)",
|
||
"metadata": {
|
||
"interpreter": {
|
||
"hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
|
||
}
|
||
},
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.11.2"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "8ef43db4b9182239fd150a76bd494fdb",
|
||
"translation_date": "2026-01-15T10:38:45+00:00",
|
||
"source_file": "lessons/2-Symbolic/Animals.ipynb",
|
||
"language_code": "de"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |