{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# 实现动物专家系统\n",
    "\n",
    "来自 [AI for Beginners Curriculum](http://github.com/microsoft/ai-for-beginners) 的示例。\n",
    "\n",
    "在此示例中，我们将实现一个简单的基于知识的系统，根据一些物理特征来确定动物。该系统可以用以下 AND-OR 树表示（这是整个树的一部分，我们可以很容易地添加更多规则）：\n",
    "\n",
    "![](../../../../../../translated_images/zh-CN/AND-OR-Tree.5592d2c70187f283.webp)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 我们自己的带有逆向推理的专家系统外壳\n",
    "\n",
    "让我们尝试定义一个基于产生规则的简单知识表示语言。我们将使用 Python 类作为关键字来定义规则。基本上将有三种类型的类：\n",
    "* `Ask` 表示需要向用户提出的问题。它包含可能的答案集合。\n",
    "* `If` 表示一个规则，它只是存储规则内容的语法糖\n",
    "* `AND`/`OR` 是表示树的 AND/OR 分支的类。它们只存储内部的参数列表。为简化代码，所有功能都定义在父类 `Content` 中。\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": [
    "在我们的系统中，工作记忆将包含作为**属性-值对**的**事实**列表。知识库可以定义为一个大型字典，将动作（应插入工作记忆的新事实）映射到以与或表达式表示的条件。此外，某些事实可以被`Ask`。\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": [
    "为了执行向后推理，我们将定义 `Knowledgebase` 类。它将包含：\n",
    "* 工作的 `memory` —— 一个将属性映射到值的字典\n",
    "* 知识库中的 `rules`，格式如上文所定义\n",
    "\n",
    "两个主要方法是：\n",
    "* `get` 用于获取属性的值，必要时执行推理。例如，`get('color')` 会获取颜色槽的值（如有必要会询问，并将值存储以备后续使用在工作内存中）。如果我们调用 `get('color:blue')`，它会询问颜色，然后根据颜色返回 `y`/`n` 值。\n",
    "* `eval` 执行实际推理，即遍历 AND/OR 树，评估子目标等。\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": [
    "现在让我们定义我们的动物知识库并进行咨询。请注意，此调用会向您提问。您可以通过输入 `y`/`n` 来回答是非题，或者通过指定数字（0..N）来回答具有多个选项的题目。\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": [
    "## 使用 Experta 进行正向推理\n",
    "\n",
    "在下一个示例中，我们将尝试使用用于知识表示的库之一 [Experta](https://github.com/nilp0inter/experta) 实现正向推理。**Experta** 是一个用于在 Python 中创建正向推理系统的库，其设计类似于经典的旧系统 [CLIPS](http://www.clipsrules.net/index.html)。\n",
    "\n",
    "我们本来也可以自己实现正向链式推理而不会有太大问题，但简单的实现通常效率不高。为了更有效地进行规则匹配，使用了一种特殊算法 [Rete](https://en.wikipedia.org/wiki/Rete_algorithm)。\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": [
    "我们将把系统定义为一个继承自 `KnowledgeEngine` 的类。每条规则由一个带有 `@Rule` 注解的单独函数定义，该注解指定规则何时触发。在规则内部，我们可以使用 `declare` 函数添加新的事实，添加这些事实将导致前向推理引擎调用更多规则。\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": [
    "一旦我们定义了知识库，我们就用一些初始事实来填充工作内存，然后调用 `run()` 方法来执行推理。结果你可以看到，新的推断事实被添加到工作内存中，包括关于动物的最终事实（如果我们正确设置了所有初始事实）。\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**免责声明**：  \n本文件已使用人工智能翻译服务[Co-op Translator](https://github.com/Azure/co-op-translator)进行翻译。尽管我们力求准确，但请注意，自动翻译可能存在错误或不准确之处。请以原始语言的原文件为权威来源。对于重要信息，建议采用专业人工翻译。我们不对因使用本翻译而产生的任何误解或误释承担责任。\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-15T11:43:18+00:00",
   "source_file": "lessons/2-Symbolic/Animals.ipynb",
   "language_code": "zh"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}