From 26f9edb997f016064fd4ea29a0d0d9c3baaa3070 Mon Sep 17 00:00:00 2001 From: trieungochai Date: Sun, 14 Jan 2024 22:57:00 +0700 Subject: [PATCH 1/3] remove the existing code --- lessons/2-Symbolic/Animals.ipynb | 465 ------------------------------- 1 file changed, 465 deletions(-) delete mode 100644 lessons/2-Symbolic/Animals.ipynb diff --git a/lessons/2-Symbolic/Animals.ipynb b/lessons/2-Symbolic/Animals.ipynb deleted file mode 100644 index 57e73bb3..00000000 --- a/lessons/2-Symbolic/Animals.ipynb +++ /dev/null @@ -1,465 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "collapsed": true - }, - "source": [ - "# Implementing an Animal Expert System\n", - "\n", - "An example from [AI for Beginners Curriculum](http://github.com/microsoft/ai-for-beginners).\n", - "\n", - "In this sample, we will implement a simple knowledge-based system to determine an animal based on some physical characteristics. The system can be represented by the following AND-OR tree (this is a part of the whole tree, we can easily add some more rules):\n", - "\n", - "![](images/AND-OR-Tree.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Our own expert systems shell with backward inference\n", - "\n", - "Let's try to define a simple language for knowledge representation based on production rules. We will use Python classes as keywords to define rules. There would be essentially 3 types of classes:\n", - "* `Ask` represents a question that needs to be asked to the user. It contains the set of possible answers.\n", - "* `If` represents a rule, and it is just a syntactic sugar to store the content of the rule\n", - "* `AND`/`OR` are classes to represent AND/OR branches of the tree. They just store the list of arguments inside. To simplify code, all functionality is defined in the parent class `Content`" - ] - }, - { - "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 our system, working memory would contain the list of **facts** as **attribute-value pairs**. The knowledgebase can be defined as one big dictionary that maps actions (new facts that should be inserted into working memory) to conditions, expressed as AND-OR expressions. Also, some facts can be `Ask`-ed." - ] - }, - { - "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": [ - "To perform the backward inference, we will define `Knowledgebase` class. It will contain:\n", - "* Working `memory` - a dictionary that maps attributes to values\n", - "* Knowledgebase `rules` in the format as defined above\n", - "\n", - "Two main methods are:\n", - "* `get` to obtain the value of an attribute, performing inference if necessary. For example, `get('color')` would get the value of a color slot (it will ask if necessary, and store the value for later usage in the working memory). If we ask `get('color:blue')`, it will ask for a color, and then return `y`/`n` value depending on the color.\n", - "* `eval` performs the actual inference, i.e. traverses AND/OR tree, evaluates sub-goals, etc." - ] - }, - { - "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": [ - "Now let's define our animal knowledgebase and perform the consultation. Note that this call will ask you questions. You can answer by typing `y`/`n` for yes-no questions, or by specifying number (0..N) for questions with longer multiple-choice answers." - ] - }, - { - "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": [ - "## Using PyKnow for Forward Inference\n", - "\n", - "In the next example, we will try to implement forward inference using one of the libraries for knowledge representation, [PyKnow](https://github.com/buguroo/pyknow/). **PyKnow** is a library for creating forward inference systems in Python, which is designed to be similar to classical old system [CLIPS](http://www.clipsrules.net/index.html). \n", - "\n", - "We could have also implemented forward chaining ourselves without many problems, but naive implementations are usually not very efficient. For more effective rule matching a special algorithm [Rete](https://en.wikipedia.org/wiki/Rete_algorithm) is used." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "trusted": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Collecting git+https://github.com/buguroo/pyknow/\n", - " Cloning https://github.com/buguroo/pyknow/ to /tmp/pip-req-build-3cqeulyl\n", - " Running command git clone --filter=blob:none --quiet https://github.com/buguroo/pyknow/ /tmp/pip-req-build-3cqeulyl\n", - " Resolved https://github.com/buguroo/pyknow/ to commit 48818336f2e9a126f1964f2d8dc22d37ff800fe8\n", - " Preparing metadata (setup.py) ... \u001b[?25ldone\n", - "\u001b[?25hCollecting frozendict==1.2\n", - " Using cached frozendict-1.2.tar.gz (2.6 kB)\n", - " Preparing metadata (setup.py) ... \u001b[?25ldone\n", - "\u001b[?25hCollecting schema==0.6.7\n", - " Using cached schema-0.6.7-py2.py3-none-any.whl (14 kB)\n", - "Building wheels for collected packages: pyknow, frozendict\n", - " Building wheel for pyknow (setup.py) ... \u001b[?25ldone\n", - "\u001b[?25h Created wheel for pyknow: filename=pyknow-1.7.0-py3-none-any.whl size=34228 sha256=b7de5b09292c4007667c72f69b98d5a1b5f7324ff15f9dd8e077c3d5f7aade42\n", - " Stored in directory: /tmp/pip-ephem-wheel-cache-k7jpave7/wheels/81/1a/d3/f6c15dbe1955598a37755215f2a10449e7418500d7bd4b9508\n", - " Building wheel for frozendict (setup.py) ... \u001b[?25ldone\n", - "\u001b[?25h Created wheel for frozendict: filename=frozendict-1.2-py3-none-any.whl size=3148 sha256=2863d55c240d2409cddf05ccfe600591f8478681549fc97555c47c90dc6bb160\n", - " Stored in directory: /home/rg/.cache/pip/wheels/49/ac/f8/cb8120244e710bdb479c86198b03c7b08c3c2d3d2bf448fd6e\n", - "Successfully built pyknow frozendict\n", - "Installing collected packages: schema, frozendict, pyknow\n", - "Successfully installed frozendict-1.2 pyknow-1.7.0 schema-0.6.7\n" - ] - } - ], - "source": [ - "import sys\n", - "!{sys.executable} -m pip install git+https://github.com/buguroo/pyknow/" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "trusted": true - }, - "outputs": [], - "source": [ - "from pyknow import *\n", - "#import pyknow" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will define our system as a class that subclasses `KnowledgeEngine`. Each rule is defined by a separate function with `@Rule` annotation, which specifies when the rule should fire. Inside the rule, we can add new facts using `declare` function, and adding those facts will result in some more rules being called by forward inference engine. " - ] - }, - { - "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": [ - "Once we have defined a knowledgebase, we populate our working memory with some initial facts, and then call `run()` method to perform the inference. You can see as a result that new inferred facts are added to the working memory, including the final fact about the animal (if we set up all the initial facts correctly)." - ] - }, - { - "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": [] - } - ], - "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" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 889dfdac22e339f38a6b2b5c4d832916034a7e19 Mon Sep 17 00:00:00 2001 From: trieungochai Date: Mon, 15 Jan 2024 00:03:14 +0700 Subject: [PATCH 2/3] docs: add intro --- lessons/2-Symbolic/Animals.ipynb | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 lessons/2-Symbolic/Animals.ipynb diff --git a/lessons/2-Symbolic/Animals.ipynb b/lessons/2-Symbolic/Animals.ipynb new file mode 100644 index 00000000..a26e9577 --- /dev/null +++ b/lessons/2-Symbolic/Animals.ipynb @@ -0,0 +1,33 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Implementing an Animal Expert System\n", + "In this sample, we will implement a simple knowledge-based system to determine an animal based on some physical characteristics.\n", + "The system can be represented by the following AND-OR tree (this is a part of the whole tree, we can easily add some more rules).\n", + "\n", + "![](images/AND-OR-Tree.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 486c6b0e5196e9ea1d126b18a7cacb0ff28920ba Mon Sep 17 00:00:00 2001 From: trieungochai Date: Mon, 15 Jan 2024 00:12:34 +0700 Subject: [PATCH 3/3] docs: update intro --- lessons/2-Symbolic/Animals.ipynb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lessons/2-Symbolic/Animals.ipynb b/lessons/2-Symbolic/Animals.ipynb index a26e9577..166eb31d 100644 --- a/lessons/2-Symbolic/Animals.ipynb +++ b/lessons/2-Symbolic/Animals.ipynb @@ -2,10 +2,14 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": true + }, "source": [ "# Implementing an Animal Expert System\n", + "\n", "In this sample, we will implement a simple knowledge-based system to determine an animal based on some physical characteristics.\n", + "\n", "The system can be represented by the following AND-OR tree (this is a part of the whole tree, we can easily add some more rules).\n", "\n", "![](images/AND-OR-Tree.png)" @@ -29,5 +33,5 @@ } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 2 }