477 lines
18 KiB
Plaintext
477 lines
18 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"collapsed": true
|
|
},
|
|
"source": [
|
|
"# Implementación de un Sistema Experto de Animales\n",
|
|
"\n",
|
|
"Un ejemplo de [Currículo de IA para Principiantes](http://github.com/microsoft/ai-for-beginners).\n",
|
|
"\n",
|
|
"En este ejemplo, implementaremos un sistema simple basado en conocimiento para determinar un animal basado en algunas características físicas. El sistema puede ser representado por el siguiente árbol AND-OR (esta es una parte del árbol completo, podemos agregar fácilmente algunas reglas más):\n",
|
|
"\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Nuestra propia shell de sistemas expertos con inferencia hacia atrás\n",
|
|
"\n",
|
|
"Intentemos definir un lenguaje simple para la representación del conocimiento basado en reglas de producción. Usaremos clases de Python como palabras clave para definir reglas. Básicamente habría 3 tipos de clases: \n",
|
|
"* `Ask` representa una pregunta que debe hacerse al usuario. Contiene el conjunto de posibles respuestas. \n",
|
|
"* `If` representa una regla, y es simplemente un azúcar sintáctico para almacenar el contenido de la regla \n",
|
|
"* `AND`/`OR` son clases para representar ramas AND/OR del árbol. Simplemente almacenan la lista de argumentos dentro. Para simplificar el código, toda la funcionalidad se define en la clase padre `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": [
|
|
"En nuestro sistema, la memoria de trabajo contendría la lista de **hechos** como **pares atributo-valor**. La base de conocimiento puede definirse como un gran diccionario que asigna acciones (nuevos hechos que deberían insertarse en la memoria de trabajo) a condiciones, expresadas como expresiones AND-OR. Además, algunos hechos pueden ser `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": [
|
|
"Para realizar la inferencia hacia atrás, definiremos la clase `Knowledgebase`. Esta contendrá:\n",
|
|
"* Memoria de trabajo `memory` - un diccionario que mapea atributos a valores\n",
|
|
"* Reglas de la base de conocimiento `rules` en el formato definido anteriormente\n",
|
|
"\n",
|
|
"Dos métodos principales son:\n",
|
|
"* `get` para obtener el valor de un atributo, realizando inferencia si es necesario. Por ejemplo, `get('color')` obtendría el valor de una casilla de color (preguntará si es necesario, y almacenará el valor para uso posterior en la memoria de trabajo). Si preguntamos `get('color:blue')`, preguntará por un color y luego devolverá un valor `y`/`n` dependiendo del color.\n",
|
|
"* `eval` realiza la inferencia real, es decir, recorre el árbol AND/OR, evalúa subobjetivos, etc.\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": [
|
|
"Ahora definamos nuestra base de conocimiento sobre animales y realicemos la consulta. Ten en cuenta que esta llamada te hará preguntas. Puedes responder escribiendo `y`/`n` para preguntas de sí o no, o especificando un número (0..N) para preguntas con respuestas de opción múltiple más largas.\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": [
|
|
"## Uso de Experta para Inferencia Hacia Adelante\n",
|
|
"\n",
|
|
"En el siguiente ejemplo, intentaremos implementar inferencia hacia adelante utilizando una de las bibliotecas para representación de conocimiento, [Experta](https://github.com/nilp0inter/experta). **Experta** es una biblioteca para crear sistemas de inferencia hacia adelante en Python, que está diseñada para ser similar al sistema clásico antiguo [CLIPS](http://www.clipsrules.net/index.html).\n",
|
|
"\n",
|
|
"También podríamos haber implementado encadenamiento hacia adelante nosotros mismos sin muchos problemas, pero las implementaciones ingenuas generalmente no son muy eficientes. Para una coincidencia más efectiva de reglas se utiliza un algoritmo especial [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": [
|
|
"Definiremos nuestro sistema como una clase que hereda de `KnowledgeEngine`. Cada regla se define mediante una función separada con la anotación `@Rule`, que especifica cuándo debe activarse la regla. Dentro de la regla, podemos agregar nuevos hechos usando la función `declare`, y agregar esos hechos hará que se llamen más reglas mediante el motor de inferencia hacia adelante.\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": [
|
|
"Una vez que hemos definido una base de conocimientos, llenamos nuestra memoria de trabajo con algunos hechos iniciales y luego llamamos al método `run()` para realizar la inferencia. Como resultado, se puede ver que se añaden nuevos hechos inferidos a la memoria de trabajo, incluyendo el hecho final sobre el animal (si configuramos todos los hechos iniciales correctamente).\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**Descargo de responsabilidad**:\nEste documento ha sido traducido utilizando el servicio de traducción automática [Co-op Translator](https://github.com/Azure/co-op-translator). Aunque nos esforzamos por lograr precisión, tenga en cuenta que las traducciones automáticas pueden contener errores o inexactitudes. El documento original en su idioma nativo debe considerarse la fuente autorizada. Para información crítica, se recomienda una traducción profesional realizada por humanos. No nos hacemos responsables de ningún malentendido o interpretación errónea derivada del uso de esta traducción.\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:20+00:00",
|
|
"source_file": "lessons/2-Symbolic/Animals.ipynb",
|
|
"language_code": "es"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |