{ "cells": [ { "cell_type": "markdown", "id": "30c1b92ca8b95ae", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "# UD05 · Notebook 3 — Clasificación de animales\n", "En esta práctica implementaremos el ejemplo de un sistema experto visto en teoría, donde de acuerdo con las características dadas, el sistema experto debe adivinar qué animal es. En este caso, el sistema experto solo tendrá en cuenta los animales que se muestran en el esquema de teoría, es decir, los animales que se muestran en la siguiente imagen:\n", "\n", "![and or tree](assets/AND-OR-Tree.png)" ] }, { "cell_type": "markdown", "id": "ababb0f577ff3806", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "Utilizaremos el módulo `experta` de Python para implementar un sistema experto **encadenamiento hacia adelante**. Este módulo nos permitirá crear un sistema experto de una manera muy simple, ya que solo tendremos que definir las reglas y hechos que nuestro sistema experto utilizará.\n", "\n", "Si implementamos un motor de inferencia, tendremos problemas de rendimiento cuando el sistema experto tenga muchas reglas, ya que debemos verificar todas las reglas para cada hecho. Por el contrario, `experta` tiene una implementación muy optimizada (utilizando el algoritmo RETE) que nos permitirá crear sistemas expertos con muchas reglas y hechos.\n", "\n", "A continuación instalaremos e importaremos las librerías necesarias para la práctica." ] }, { "cell_type": "code", "execution_count": 1, "id": "7503431b09c94c72", "metadata": { "ExecuteTime": { "end_time": "2024-01-08T20:07:44.731016Z", "start_time": "2024-01-08T20:07:33.931326Z" }, "collapsed": false, "execution": { "iopub.execute_input": "2026-08-22T14:07:09.692637Z", "iopub.status.busy": "2026-08-22T14:07:09.692327Z", "iopub.status.idle": "2026-08-22T14:07:10.186553Z", "shell.execute_reply": "2026-08-22T14:07:10.185050Z" }, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: experta in ./venv-experta/lib/python3.14/site-packages (1.9.4)\r\n", "Requirement already satisfied: frozendict==1.2 in ./venv-experta/lib/python3.14/site-packages (from experta) (1.2)\r\n", "Requirement already satisfied: schema==0.6.7 in ./venv-experta/lib/python3.14/site-packages (from experta) (0.6.7)\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install experta\n", "\n", "# experta (2019) rompe en Python 3.10+ porque `collections.Mapping` se eliminó.\n", "# Parche de tres líneas antes de importar (ya documentado en la UD00):\n", "import collections, collections.abc\n", "if not hasattr(collections, 'Mapping'):\n", " collections.Mapping = collections.abc.Mapping\n", " collections.Iterable = collections.abc.Iterable\n", " collections.MutableMapping = collections.abc.MutableMapping\n", "\n", "from experta import *" ] }, { "cell_type": "markdown", "id": "510874823d8f68db", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "## Definición del sistema experto\n", "Definiremos nuestro sistema experto como una clase que herede de `KnowledgeEngine`. Cada regla se define mediante una función con la anotación `@Rule`, que especifica cuándo debe ejecutarse la regla. Dentro de la regla, podemos agregar nuevos hechos a través de la función `declare`, y agregar estos hechos causará más reglas que se ejecutan a través del motor de la inferencia.\n", "\n", "El contenido de `@Rule` es una expresión lógica que se evalúa a `True` o `False`. Esta expresión lógica puede contener **hechos**, operadores lógicos (`AND`, `OR`, `NOT`) y operadores de comparación (` == `,`!=`,`<`,`>`,`<=`, `>=`). Los hechos son objetos de la clase de `Fact`, que pueden tener atributos que se especifican como parámetros de la clase `Fact`. Por ejemplo, el hecho `Fact('mamífero', pelo=True)` tiene el atributo 'pelo' con valor 'True'.\n", "\n", "Adicionalmente, podemos usar la función `MATCH` para especificar que un atributo puede tener cualquier valor.Por ejemplo, `Fact('mamífero', pelo=MATCH.pelo)` es un hecho que tiene el atributo `pelo`para cualquier valor." ] }, { "cell_type": "code", "execution_count": 2, "id": "cdcf212140490b14", "metadata": { "ExecuteTime": { "end_time": "2024-01-08T20:07:44.776392Z", "start_time": "2024-01-08T20:07:44.767474Z" }, "collapsed": false, "execution": { "iopub.execute_input": "2026-08-22T14:07:10.189840Z", "iopub.status.busy": "2026-08-22T14:07:10.189596Z", "iopub.status.idle": "2026-08-22T14:07:10.201891Z", "shell.execute_reply": "2026-08-22T14:07:10.200899Z" }, "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "class Animales(KnowledgeEngine):\n", " # Reglas \n", " @Rule(OR(\n", " AND(Fact('dientes afilados'), Fact('uñas'), Fact('ojos mirando al frente')),\n", " Fact('carnivoro')))\n", " # Si el animal tiene dientes afilados, uñas y ojos mirando hacia adelante, o come carne, entonces es un carnivoro\n", " def carnivoro(self):\n", " # Agregamos el hecho de 'Carnivoro' al sistema experto\n", " self.declare(Fact('carnivoro'))\n", "\n", " @Rule(OR(Fact('pelo'), Fact('da leche')))\n", " # Si el animal tiene pelo o da leche, entonces es un mamífero\n", " def mamifero(self):\n", " self.declare(Fact('mamifero'))\n", "\n", " @Rule(Fact('mamifero'),\n", " OR(Fact('tiene pezuñas'), Fact('rumia')))\n", " # Si el animal es un mamífero y tiene pezuñas o rumia entonces es un ungulado\n", " def ungulado(self):\n", " self.declare('ungulado')\n", "\n", " @Rule(OR(Fact('plumas'), AND(Fact('vuela'), Fact('pone huevos'))))\n", " # Si el animal tiene plumas o vuela y pone huevos, entonces es un pájaro\n", " def pajaro(self):\n", " self.declare('pajaro')\n", "\n", " @Rule(Fact('mamifero'), Fact('carnivoro'),\n", " Fact(color='marron-rojizo'),\n", " Fact(pattern='manchas oscuras'))\n", " # Si el animal es un mamífero de color marron-rojizo con manchas oscuras, entonces es un mono\n", " def mono(self):\n", " self.declare(Fact(animal='mono'))\n", "\n", " @Rule(Fact('mamifero'), Fact('carnivoro'),\n", " Fact(color='marron-rojizo'),\n", " Fact(pattern='rayas oscuras'))\n", " #Si el animal es un mamífero marron-rojizo con rayas oscuras, entonces es un tigre\n", " def tigre(self):\n", " self.declare(Fact(animal='tigre'))\n", "\n", " @Rule(Fact('ungulado'),\n", " Fact('cuello largo'),\n", " Fact('largas piernas'),\n", " Fact(pattern='manchas oscuras'))\n", " # Si el animal tiene un cuello largo, ungulado, piernas largas y con manchas oscuras, entonces es una jirafa\n", " def jirafa(self):\n", " self.declare(Fact(animal='jirafa'))\n", "\n", " @Rule(Fact('ungulado'),\n", " Fact(pattern='rayas oscuras'))\n", " # Si el animal es ungulado y tiene rayas oscuras, entonces es una cebra\n", " def cebra(self):\n", " self.declare(Fact(animal='cebra'))\n", "\n", " @Rule(Fact('pajaro'),\n", " Fact('cuello largo'),\n", " Fact('no vuela'),\n", " Fact(color='blanco y negro'))\n", " # Si el animal es un pájaro, con cuello largo, no vuela, es de color blanco y negro, entonces es un avestruz\n", " def avestruz(self):\n", " self.declare(Fact(animal='avestruz'))\n", "\n", " @Rule(Fact('pajaro'),\n", " Fact('nada'),\n", " Fact('no vuela'),\n", " Fact(color='blanco y negre'))\n", " # Si el animal es un pájaro, nada, no vuela, es de color blanco y negro, entonces es un pingüino\n", " def pinguino(self):\n", " self.declare(Fact(animal='pinguino'))\n", "\n", " @Rule(Fact('pajaro'),\n", " Fact('vuela bien'))\n", " # Si el animal es un pájaro y vuela bien, entonces es un albatros\n", " def albatros(self):\n", " self.declare(Fact(animal='albatros'))\n", "\n", " @Rule(Fact(animal=MATCH.a))\n", " # Si està definit l'animal, llavors el mostrem\n", " def print_result(self, a):\n", " print(f\"El animal es un {a}\")\n", "\n", " def añadir_hechos(self, hechos):\n", " # Añadiremos los hechos pasados ​​como parámetro (hechos) al sistema experto\n", " # Usaremos esta función para inicializar el sistema experto.\n", " for f in hechos:\n", " self.declare(f)" ] }, { "cell_type": "markdown", "id": "1c8aeb29f1da8acd", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "# Pruebas del sistemas experto\n", "Una vez que hemos definida la base de conocimiento, inicializamos la memoria de trabajo con los hechos iniciales, y luego llamamos al método `run()` para llevar a cabo la inferencia. Podemos ver como resultado que se añaden nuevos hechos inferido a la memoria de trabajo, incluido el evento final sobre el animal (si hemos configurado todos los hechos iniciales correctamente).\n", "\n", "Veamos un ejemplo en el que inicializamos el sistema experto con los hechos `pelo`, `dientes afilados`, `uñas` i `ojos mirando al frente`. Estos hechos son suficientes para inferir que el animal es un `carnivoro`." ] }, { "cell_type": "code", "execution_count": 3, "id": "c1226046d1d09c7f", "metadata": { "ExecuteTime": { "end_time": "2024-01-08T20:07:44.848546Z", "start_time": "2024-01-08T20:07:44.788841Z" }, "collapsed": false, "execution": { "iopub.execute_input": "2026-08-22T14:07:10.205411Z", "iopub.status.busy": "2026-08-22T14:07:10.205110Z", "iopub.status.idle": "2026-08-22T14:07:10.223165Z", "shell.execute_reply": "2026-08-22T14:07:10.221932Z" }, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ "FactList([(0, InitialFact()),\n", " (1, Fact('pelo')),\n", " (2, Fact('dientes afilados')),\n", " (3, Fact('uñas')),\n", " (4, Fact('ojos mirando al frente')),\n", " (5, Fact('carnivoro')),\n", " (6, Fact('mamifero'))])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Creamos una instancia de nuestro sistema experto\n", "animales = Animales()\n", "\n", "# Restablecer el sistema experto. Requerido para volver al estado inicial.\n", "animales.reset()\n", "\n", "# Añadimos los hechos iniciales.\n", "animales.añadir_hechos(\n", " [Fact('pelo'), Fact('dientes afilados'), Fact('uñas'), Fact('ojos mirando al frente')]\n", ")\n", "\n", "# Ejecutar el sistema experto\n", "animales.run()\n", "# Mostramos la base de hechos hasta ahora.\n", "animales.facts" ] }, { "cell_type": "markdown", "id": "95376f4cf4d07f0a", "metadata": { "collapsed": false, "jupyter": { "outputs_hidden": false } }, "source": [ "Podemos ver cómo se ha inferido el hecho `carnivoro` de los hechos iniciales y cómo se ha inferido el hecho `mamífero` del hecho `pelo`.\n", "\n", "Agregaremos los hechos `Marrón-Rojizo` primero y `Manchas Oscuras` más tarde. Veremos cómo primero no se infiere ningún animal, y luego se infiere el hecho de los hechos `Mamífero`, `Carnivoro`, `Marrón-Rojizo` y `Manchas Oscuras`." ] }, { "cell_type": "code", "execution_count": 4, "id": "a81ddd8862942231", "metadata": { "ExecuteTime": { "end_time": "2024-01-08T20:07:44.850834Z", "start_time": "2024-01-08T20:07:44.822169Z" }, "collapsed": false, "execution": { "iopub.execute_input": "2026-08-22T14:07:10.225717Z", "iopub.status.busy": "2026-08-22T14:07:10.225484Z", "iopub.status.idle": "2026-08-22T14:07:10.231257Z", "shell.execute_reply": "2026-08-22T14:07:10.230136Z" }, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ "FactList([(0, InitialFact()),\n", " (1, Fact('pelo')),\n", " (2, Fact('dientes afilados')),\n", " (3, Fact('uñas')),\n", " (4, Fact('ojos mirando al frente')),\n", " (5, Fact('carnivoro')),\n", " (6, Fact('mamifero')),\n", " (7, Fact(color='marron-rojizo'))])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "animales.añadir_hechos(\n", " [Fact(color='marron-rojizo')]\n", ")\n", "animales.run()\n", "animales.facts" ] }, { "cell_type": "code", "execution_count": 5, "id": "bca6d6502b82bc8d", "metadata": { "ExecuteTime": { "end_time": "2024-01-08T20:08:02.441394Z", "start_time": "2024-01-08T20:08:02.417092Z" }, "collapsed": false, "execution": { "iopub.execute_input": "2026-08-22T14:07:10.234009Z", "iopub.status.busy": "2026-08-22T14:07:10.233764Z", "iopub.status.idle": "2026-08-22T14:07:10.239836Z", "shell.execute_reply": "2026-08-22T14:07:10.238729Z" }, "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "El animal es un mono\n" ] }, { "data": { "text/plain": [ "FactList([(0, InitialFact()),\n", " (1, Fact('pelo')),\n", " (2, Fact('dientes afilados')),\n", " (3, Fact('uñas')),\n", " (4, Fact('ojos mirando al frente')),\n", " (5, Fact('carnivoro')),\n", " (6, Fact('mamifero')),\n", " (7, Fact(color='marron-rojizo')),\n", " (8, Fact(pattern='manchas oscuras')),\n", " (9, Fact(animal='mono'))])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "animales.añadir_hechos(\n", " [Fact(pattern='manchas oscuras')]\n", ")\n", "animales.run()\n", "animales.facts" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "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.14.4" } }, "nbformat": 4, "nbformat_minor": 5 }